3.4 使用回归模型预测连续的结果
现在,让我们把注意力转向回归问题。我相信你现在已经熟记了,回归是对连续结果的预测,而不是对离散的类标签的预测。
3.4.1 理解线性回归
最简单的回归模型称之为线性回归。线性回归隐含的思想是用特征的一个线性组合来描述一个目标变量(例如,波士顿房价——我们在第1章中学习过的各种数据集)。
为了简单起见,让我们只关注两个特征。假设我们想用两个特征预测(今天的股票价格和昨天的股票价格)明天的股票价格。我们将今天的股票价格作为第一个特征f1,昨天的股票价格作为第二个特征f2。然后,线性回归的目标是学习两个权重系数:w1和w2,这样我们就可以预测明天的股票价格,如下所示:
ŷ=w1f1+w2f2
(3.1)
这里,ŷ是明天的真实股票价格y的预测。
注意
只有一个特征变量的特例称为简单线性回归(simple linear regression)。
根据更多过去的股票价格样本,我们可以很容易扩展特征。如果我们有M个特征值,而不是两个特征值的话,我们可以把方程(3.1)扩展到M个乘积的和,这样每个特征都有一个权重系数。我们可以把方程结果写成这样:
我们先从几何角度看一下方程(3.2)。在只有一个特征f1的情况下,方程(3.2)中的ŷ将变成ŷ = w1f1,这实际上是一条直线。在有两个特征的情况下,ŷ = w1f1+ w2 f2可以描述特征空间中的一个平面,如图3-7所示。
图3-7 两个特征构成特征空间中的一个平面
注意
在N维空间中,这就是一个超平面。如果一个空间是N维的,那么它的超平面有N–1维。
如图3-7所示,所有这些直线和平面都相交于原点。但是如果我们想要估计的真实的y值没有经过原点会怎么样?
要从原点抵消ŷ,习惯上是添加另一个不依赖于任何特征值的权重系数,因此它就充当一个偏置项。在一维情况下,将该项当作ŷ截距。在实践中,这通常是通过设置f0 = 1实现的,这样可以将w0作为偏置项:
这里,我们可以保持f0 = 1。
最后,线性回归的目标是学习一组权重系数,这些权重系数使得预测尽可能准确地逼近真实值。与我们在分类器中显式地获取一个模型的准确率不同,回归中的评分函数通常采用所谓代价函数(或者损失函数)的形式。
如在3.2节中所述,我们有许多评分函数可以用于度量回归模型的性能。最常用的代价函数可能就是均方误差了,它通过比较预测值ŷi与目标输出值yi,然后取平均,计算每个数据点i的一个误差(yi–ŷi)2:
这里,N是数据点数。
因此,回归问题成为一个优化问题——我们的任务是找到使代价函数最小的权重设置。
注意
这通常是通过一个迭代算法(逐个数据点应用迭代算法)来实现的,因此可以逐步降低代价函数。我们会在第9章中更深入地讨论这类算法。
理论已经讲得足够多了——让我们来编码吧!
3.4.2 OpenCV中的线性回归
在对实际数据集尝试线性回归之前,先让我们来了解一下如何使用cv2.fitLine函数用一条直线拟合二维或三维点集:
1)让我们从生成一些点开始。通过向直线y = 5x + 5上的点添加噪声来生成这些点:
2)使用下列代码,我们还可以可视化这些点:
结果如图3-8所示,直线是真实的函数。
图3-8 生成数据点的可视化图
3)接下来,我们将这数据些点拆分成训练集和测试集。这里,我们根据70:30的比例拆分数据,70%的数据点用于训练,30%的数据点用于测试:
4)现在,让我们借助cv2.fitLine用一条线拟合这个二维点集。该函数取下列参数:
- points:这是一条直线必须拟合的点集。
- distType:这是M-估计所使用的距离。
- param:这是数值参数(C),用于某些类型的距离。我们将其保持为0,这样就可以选择一个最优值。
- reps:这是原点到直线的距离准确率。0.01是reps的一个不错的默认值。
- aeps:这是角度的准确率。0.01是aeps的一个不错的默认值。
注意
更多信息参见documentation。
5)让我们来看看使用不同的距离类型选项会得到什么样的结果?
6)我们还将使用scikit-learn的LinearRegression拟合训练点,然后使用predict函数来预测这些点的y值:
7)我们使用reshape(–1, 1)和reshape(1, –1),将NumPy数组转换成一个列向量,然后回到一个行向量:
上面这段冗长的代码实现的唯一目标是创建一个图,它可以用来比较使用不同距离测量得到的结果。
得到的结果如图3-9所示。
图3-9 使用不同距离度量得到的比较结果
我们可以清楚地看到,scikit-learn的LinearRegression模型比OpenCV的fitLine函数执行结果更好。现在,让我们使用scikit-learn的API来预测波士顿的房价。
3.4.3 使用线性回归预测波士顿房价
为了更好地理解线性回归,我们想构建一个简单模型,可应用于一个最著名的机器学习数据集:波士顿房价数据集(Boston housing prices dataset)。这里的目标是利用犯罪率、房产税率、到就业中心的距离和高速公路可达性等信息预测20世纪70年代波士顿一些社区的房价。
1. 加载数据集
我们再次感谢scikit-learn,让我们能够轻松访问数据集。就像我们之前做的那样,首先导入所有必要的模块:
然后,加载数据集只需一行程序:
正如在前面的命令中讨论过的那样,boston对象的结构与iris对象相同。在'DESCR'中我们可以获取关于数据集的更多信息,在'data'中找到所有的数据,在'feature_names'中找到所有的特征名称,在'filename'中找到波士顿CSV数据集的物理位置,并在'target'中找到所有的目标值:
数据集共包含506个数据点,每个数据点有13个特征:
当然,我们只有一个目标值,那就是房价:
2. 训练模型
现在,让我们创建一个LinearRegression模型,然后我们将在该训练集上进行训练:
在上面的命令中,我们希望将数据拆分成训练集和测试集。我们可以根据自己认为合适的方式自由地拆分数据,但是通常预留10%到30%的数据用于测试是最好的。这里,我们选择10%,使用test_size参数:
在scikit-learn中,train函数命名为fit,但是在其他方面,它的行为与在OpenCV中是完全一样的:
通过比较真实房价y_train和我们的预测linreg.predict(X_train),我们可以看看预测的均方误差:
linreg对象的score方法返回决定系数(R平方):
3. 测试模型
为了测试模型的泛化性能,我们计算测试数据的均方误差:
我们注意到,测试集的均方误差略低于训练集的均方误差。这很不错,因为我们主要关心的是测试误差。但是,从这些数据中,我们真的很难理解这个模型到底有多好。也许绘制数据图会更好:
生成的图如图3-10所示。
图3-10 生成的测试数据的预测结果
这更有意义!这里,我们看到所有测试样本的ground truth房价用红色表示(图中浅色曲线),predicted房价用蓝色表示(图中深色曲线)。如果你问我的话,我觉得挺接近的。可是,值得注意的是,对于非常高或者非常低的房价,比如数据点12、18、42的峰值,该模型往往偏离得较远。我们通过计算R平方来形式化数据的方差:
x轴是ground truth价格y_test,y轴是predicted价格y_pred。我们还绘制了一条对角线作为参考(用黑色虚线'k--'),这我们很快就能看到。可是我们还希望在文本框中显示R2得分和均方误差:
这将生成图3-11,这是绘制模型拟合的一种专业方式。
图3-11 模型拟合结果
如果我们的模型是完美的,那么所有的数据点都应该位于虚线对角线上,因为y_pred总是等于y_true。对角线上的偏差表明模型存在一定的误差,或者数据中存在一些模型无法解释的偏差。实际上,R2表明我们能够解释76%的数据分散,均方误差是14.996。这些是我们可以用来比较线性回归模型和一些更复杂的模型的性能指标。
3.4.4 Lasso回归和岭回归的应用
机器学习中一个常见的问题是,一个算法在训练集上可能工作得很好,但是在应用到未知数据时,就会产生很多错误。你可以明白这是有问题的,通常因为我们最感兴趣的是模型对新数据的泛化能力。一些算法(如决策树)比其他算法更容易受到这种现象的影响,但是即使是线性回归也可能会受到影响。
注意
这种现象也被称为过拟合(overfitting),在第5章和第11章中,我们将对过拟合进行详细的讨论。
降低过拟合的一种常见技术是正则化(regularization),该技术涉及向代价函数中添加另一个与所有特征值无关的约束。常用的两个正则化项如下所述:
- L1正则项:这将向评分函数中添加一项,该项与所有绝对权值的和成正比。或者说,这是基于权值向量的L1范数(也称为直角距离、蛇距,或者曼哈顿距离)。因为曼哈顿街道的网格布局,L1范数类似于一个度量纽约出租车司机从A点到B点的距离。由此得到的算法使这个距离最小化,也称为Lasso回归。
- L2正则项:这将向评分函数中添加一项,该项与所有权重平方值的和成正比。或者说,这是基于权值向量的L2范数(也称之为欧氏距离,Euclidean distance)。因为L2范数涉及一个平方运算,因此它对权重向量中的强异常值的惩罚要比L1范数严很多。由此得到的算法也称为岭回归。
这个过程与前面的过程完全相同,只是我们替换了初始化命令,并加载了Lasso或者RidgeRegression对象。具体来说,我们需要替换以下命令:
对于Lasso回归算法,我们可以将上面这行代码修改为:
对于ridge回归算法,我们可以将上面这行代码修改为:
建议你用波士顿数据集代替传统的线性回归测试这两种算法。泛化误差(In [25])是如何变化的呢?预测图(In [27])是如何变化的呢?你认为性能上有改进吗?