本文原作者:汪毅雄,经授权后发布。
导语:本文用了从数学层面和代码层面,再结合一些通俗易懂的例子,详细地描述了回归主要涉及的原理和知识,希望对于机器学习的初学者或者有兴趣研究模型具体实现的同学带来一点帮助。
接上篇文章 机器学习之数据清洗与特征提取 我们知道了,机器学习中重要的一步是数据的分析处理。其简单的流程图如下:
上篇文章中我们了解了数据的预处理方式,今天我们聊聊,已经有了多维特征的数据,怎么得出我们想要的结论。这部分的内容无疑是机器学习的核心,这里我们会涉及怎么建模,建模后怎么求解模型等一系列问题。
方法论
说这些问题之前,我们先聊聊建模时候的一些方法论。和工作一样,一些方法论的建立能很大程度上提高我们的效率。在我们建模计算中,合理地假设总是必要的。
- 合理性: 假设是在常理上是正确的。比如说,我们要统计一个人的体重的区间。我们的取值范围要是取为0 - 1000,显示是没必要的。我们可以假设为30 - 150kg,显然绝大多数人都在这个范围内,这样就能一定程度上减少我们的计算量,这在大数据处理是很有意义的。
- 简化性:举个例子,相信大家都见过这句话 “研表究明,汉字的序顺并不定一能影阅响读,比如当你看完这句话后,才发这现里的字全是都乱的”,这一定程度上反应了简化性的意义。我们说一句话的时候,其实词与词之间是有相关联的,但是实际处理的时候,我们可以酌情把部分当成独立来处理。这样做的结果就是在某个模型中 ”研表究明 = 研究表明“,至少在人眼中它还是过关的,哈哈。
- 发散性:还是举上面的例子,在一个”蒙蔽人眼模型“中”研表究明 = 研究表明“这个是成立,但事实上,我们对这个模型做的部分独立的假设缩得到的这个结论,并不一定只能用于这个模型。换句话结果的适用性往往会比你想象的更广。
什么是回归
这个相信大家都比较了解了,这里简单的提一下这个名次的意义。
看最开始那张大图,我们所有想做的事情都是围绕得出一个”结论“。而这个”结论“从数学上来说有不同的形态。
一种形态是比如说,AlphaGo下棋的时候,在19*19的棋盘上,它最多有361种选择,我们对每个选择计算它赢的概率,这种有361种离散的结果的过程,我们把它叫分类。
而对于链家房子的价格,我们会根据房子大小、是否学区等因素,得出一个房屋的总价。这个总价的可能性在数学上来说可能是连续的。我们把这个过程叫做回归。
一句话就是:结论是有限的离散个数叫分类,反之就是回归。当然只是一种叫法而已!
线性回归
还是以预测房屋价格例子来引入,我们知道每个房子有很多个特征。那么我们可以开始第一个假设,如果排除其他任何因素,房子的价格会和房子的面积呈线性关系,房子的价格会和学区的好坏呈线性关系。怎么量化学区的好坏呢,再打个广告参考上一篇 机器学习之数据清洗与特征提取 ,可以使用one-hot编码等实现 。考虑这个两个因素,我们认为房屋的总价(y)和面积(x1)、学区好坏(x2)有这么一个关系:
寻找目标函数
先忽略什么是目标函数。对于上面的结论我们显然可以延展,对于n维的特征,如果用线性模型来求解的话,有:
而对于某个特征 i,其实际值与理论值肯定存在误差
此时,我们做第二个假设,我们假设每个特征值的误差都满足:独立、同分布。那么y可以写成:
由于我们假设了误差满足独立同分布,因此由 中心极限定理 可知误差之和必定能满足一个均值为0,误差为
的高斯分布。因此,对于误差,其概率密度函数都可以写出:
把误差代入上述概率密度函数中,其结果就变成,在指定x下y关于
的函数。也就是这样:
这样对于m个样本我们可以求它的似然函数L,经过一系列化简后得到
求似然函数的最大值,前一项为固定值,因此我们提取后一项,得到一个关于
的函数,我们把这个函数叫做损失函数或者目标函数,因此我们只需要求目标函数的最小值即可。目标函数会贯穿整个机器学习的几乎所有模型中,几乎所有模型都在找它的目标函数,有了这个目标函数就可以对
进行求解了。
目标函数求解
方法一: 最小二乘法
先把目标函数写成矩阵的形式,由于
是我们的预测值,因此在线性回归中它就等于
,这样目标函数就可以写为:
对目标函数求梯度(导数),求得
的偏导为:
令梯度等于0,这时候就可以求出
的显示解。
方法二:大名鼎鼎的”梯度下降算法“
对于无显示解的函数,我们通常会采用此法。所谓梯度下降算法,就是使函数沿着负梯度方向,随着步长不断的迭代下降,直到不能下降为止。这样算法,肯定能使得某个函数能取到局部最小值(不一定为全局最小)。因此,跟随我们对梯度下降算法的认识,我们肯定需要几个步骤:1、初始化变量,2、找到梯度方向,3、选择步长,4、比较最近两次函数值决定是否继续下降。
对于线性回归,我们要求目标函数中的
,只需要不断下降
,找到使目标函数最小的
。因此算法可以简单描述成这样,其中
代表步长:
对于线性回归,我们对目标函数求梯度就可以得到:
好了有了这些算法,我们可以兴致勃勃的写代码实现看看效果。由于python sklearn库中已经包含了线性回归的模型,我们直接调用。这里我们采取9个纬度的数据,看看其效果:
图代码见后 ”无校正线性回归代码“
可以发现在2阶以后准确性就已经很高了,但是在8阶的时候曲线很明显发现了震荡。我们使用模型的目的是为了让模型来预测未知的数据,虽然8阶的时候精度是100%的,但是带来的显然震荡的效果并非我们想要的。.为了避免这种震荡,我们需要采取一些限制了。怎么限制呢?下面慢慢说来。
先看图中给出的各阶的系数结果如下:
可以看到,8阶的时候为了保证100%拟合,模型被迫调大参数来配合模型的精度。这好比我们想要模型把一个数精确到0.01,被迫地采用了10000 - 9999.99来拟合,而不是用0 - 0这样毫无震荡的来估计。所以我们要解决的问题,就变成了避免这种过大的参数问题。
怎么解决呢?一个最简单最简单的方式就是限定一下
的大小。怎么限定呢?
如果直接把
的平方和做加法,使它小于我们一个我们给定的数,这种限定方法的线性回归,我们把它叫做:Ridge 回归
而如果直接把
的绝对值和做加法,使它小于我们一个我们给定的数,这种限定方法的线性回归,我们把它叫做:LASSO 回归
这种做法我们可以从一个图像来解释,以2维的Ridge来举例子:
如图我们的
可能在等值线的原点,但是它过大,已经超出另外限定圆的范围了,因此我们以
做圆心,逐渐扩散到最先触碰到限定圆的那个点,以那么点来代替
值。
对于2维的LASSO也是类似:
由上引出的另一种方式,即是以上两中限定方式以一定的比例结合的回归,我们叫Elastic Net回归
其表示方式为:
对于以上几种升级版的线性回归,代码跑出来的效果如下:
可以看到在经过限定后,曲线就几乎不再有震荡的效果了。
但是这里有引入一个问题,
的值该如何去取呢,这个值不和任何样本、结论关联,所以是无解了。实际的做法可能是假设一个范围,然后在里面等范围地取一些值,然手从一系列值中选取效果最好的作为最后的
。
到这里,大部分和线性回归相关的知识就已经完成了。
不过,文章未完待续。。。
附:无校正线性回归代码:
代码语言:javascript复制#!/usr/bin/python
# -*- coding:utf-8 -*-
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.exceptions import ConvergenceWarning
import matplotlib as mpl
import matplotlib.pyplot as plt
import warnings
# @author yixiongwang 2017.8.13
if __name__ == "__main__":
warnings.filterwarnings(action='ignore', category=ConvergenceWarning)
np.random.seed(0)
np.set_printoptions(linewidth=300)
# 获取10个随机点x y坐标
N = 9
x = np.linspace(0, 7, N) np.random.randn(N)
x = np.sort(x)
y = x**2 - 4*x - 3 np.random.randn(N)
# 声明是列向量
x.shape = -1, 1
y.shape = -1, 1
# 模型采用线性回归,且是多项式的形式
model = Pipeline([
('poly', PolynomialFeatures()),
('linear', LinearRegression(fit_intercept=False))])
mpl.rcParams['font.sans-serif'] = ['simHei']
mpl.rcParams['axes.unicode_minus'] = False
np.set_printoptions(suppress=True)
# N个点,最多只需N-1阶就能100%拟合,把阶数留下来画图用
d_pool = np.arange(1, N, 1)
m = d_pool.size
# 为每一阶的团分配不同的颜色,画图用
clrs = []
for c in np.linspace(11119999, 255, m, dtype=int):
clrs.append('#x' % c)
line_width = np.linspace(5, 2, m)
title = '线性回归'
# 画这9个点
plt.plot(x, y, 'ro', ms=10, zorder=N)
# 对每一阶画出它的预测值曲线
for i, d in enumerate(d_pool):
model.set_params(poly__degree=d)
model.fit(x, y.ravel())
# 获取多项式的系数并输出
lin = model.get_params('linear')['linear']
output = '%s:%d阶,系数为:' % (title, d)
print(output, lin.coef_.ravel())
# 把x坐标分成100分,分别得到其预测值,并相连绘制成曲线
x_hat = np.linspace(x.min(), x.max(), num=100)
x_hat.shape = -1, 1
# 模型的预测值
y_hat = model.predict(x_hat)
# 模型的准确度
s = model.score(x, y)
label = '%d阶,$R^2$=%.3f' % (d, s)
# 画这一阶的曲线
plt.plot(x_hat, y_hat, color=clrs[i], lw=line_width[i], alpha=0.75, label=label)
plt.legend(loc='upper left')
plt.grid(True)
plt.xlabel('X', fontsize=16)
plt.ylabel('Y', fontsize=16)
plt.suptitle(title, fontsize=22)
plt.show()