7.1 用回归预测房价
让我们从一个简单的问题开始——预测波士顿的房价。
我们使用的是一个公开的数据集。它涉及一些人口统计信息和地理属性,例如犯罪率或师生比例。我们的目标是预测特定区域内的房价均值。和以往一样,我们有一些训练数据,其中答案是已知的。
我们使用scikit-learn里的函数来读取数据。这个数据集是scikit-learn的一个内置数据集,所以读取非常容易:
from sklearn.datasets import load_boston boston = load_boston()
boston 对象是一个合成对象,它包含有若干属性。我们对其中的boston.data 和boston.target 比较感兴趣。
我们将从一维回归开始,尝试根据平均住宿房间数这个属性来对价格进行回归。这个数据存储在位置5(你可以查询boston.DESCR 和boston.feature_names 获得数据的详细信息):
from matplotlib import pyplot as plt plt.scatter(boston.data[:,5], boston.target, color='r')
boston.target 属性里包含有房屋的平均价格(我们的目标变量)。我们可以使用标准最小二乘回归,你第一次见到它可能是在高等学校里。
import numpy as np
我们引入NumPy,这是我们所需要的基础程序包。我们将会使用np.linalg 子模块中的函数,来进行基础线性代数操作:
x = boston.data[:,5] x = np.array([[v] for v in x])
这看起来有些奇怪,但我们希望x 是二维的:第一维是不同的样本,第二维是属性。在我们的例子中,我们只有一个属性:平均住宿房间数,所以第二维就是1。
y = boston.target slope,_,_,_ = np.linalg.lstsq(x,y)
最后,我们用最小二乘回归得到回归的斜率。np.linalg.lstsg 函数还返回了一些关于数据拟合程度的内部信息,我们此时先忽略这些。
前图显示了所有的数据(点),以及我们的拟合(实线)。它看起来并不是太好。事实上,采用这个一维的模型,是因为我们知道房屋价格(House Price)一定是RM 变量(房屋的数目)的倍数。
这意味着,一套两居室的平均价格是一居室价格的两倍,而三居室价格将会是一居室的三倍。我们知道这是一个错误的假设(甚至连近似真实都算不上)。
一个比较通用的方法是在前面这个式子中加入偏移项,使得价格等于RM 的倍数再加上一个偏移。可以把这个偏移量看做一个零居室房子的基础价格。实现这个方法的技巧,就是在x 的每个元素上面加1。
x = boston.data[:,5] x = np.array([[v,1] for v in x]) # 我们使用[v,1]而不是[v] y = boston.target (slope,bias),_,_,_ = np.linalg.lstsq(x,y)
在下图中,我们看到,拟合的曲线在视觉上看起来好多了(尽管一小部分离群点可能会对结果造成一些不成比例的影响)。
在理想情况下,我们希望量化衡量曲线拟合的效果。要实现这个目标,我们需要看看预测值和真实值之间的接近程度。为此,我们看一下np.linalg.lstsq 函数返回值中的第2项:
(slope,bias),total_error,_,_ = np.linalg.lstsq(x,y) rmse = np.sqrt(total_error[0]/len(x))
np.linal.lstsq 函数返回了总体平方误差。对每一个数据元素,它都会计算误差(拟合的线和真实值之间的差距),并进行平方,然后返回所有平方误差的总和。由于衡量平均误差更易于理解,所以我们把它除以数据的个数。最后,我们计算平方根,并打印出均方根误差 (Root Mean Squared Error,RMSE)。对最初这个无偏移的回归,我们得到了7.6的误差,而如果加上偏移项,将会提升至6.6。这表明,我们可以预期的价格和真实价格之间最多相差13 000美元。
提示 均方根误差和预测
均方根误差与标准差,是近似相对应的。由于大多数数据与它的均值之间的偏移量最多是两个标准差,所以我们可以将RMSE乘以2,得到一个较为粗略的置信区间。这只有在误差是正态分布的情况下才完全成立,但即使是非正态分布,也可以认为这是近似正确的。
7.1.1 多维回归
到目前为止,只有一个变量参与了预测,那就是每套房子的房间数。我们现在将要使用多维回归来对所有数据进行模型拟合,尝试基于多个输入来预测一个输出(平均房价)。
代码看起来和之前的很像:
x = boston.data # 我们仍然要添加一个偏移项,但现在必须使用np.concatenate # 它会将两个数组/列表合并起来,因为我们 # 在v里面有几个输入变量 x = np.array([np.concatenate(v,[1]) for v in boston.data]) y = boston.target s,total_error,_,_ = np.linalg.lstsq(x,y)
现在,均方根误差只有4.7了!这比以前好了很多,说明额外的变量确实有所帮助。遗憾的是,结果不太容易展示出来,因为这是一个14维的回归。
7.1.2 回归里的交叉验证
记得在初次介绍分类的时候,我们强调了交叉验证对于衡量预测质量的重要性。在回归里面,我们不会一直这么做。事实上,之前只讨论了训练误差。如果你因此就满怀信心地推断模型的泛化能力,那是不可取的。由于普通最小二乘法是一个非常简单的模型,它通常不会犯很严重的错误(过拟合程度比较轻)。然而,我们仍需要用scikit-learn对它进行验证。我们还将使用线性回归的类,因为在本章后面它们很容易被替换成更高级的方法。
from sklearn.linear_model import LinearRegression
LinearREgression 类实现了OLS回归,如下:
lr = LinearRegression(fit_intercept=True)
我们将fit_intercept 参数设为True ,用来加入偏移项。这跟我们以前做的一样,但用了一个更为方便的接口:
lr.fit(x,y) p = map(lr.predict, x)
分类的学习和预测过程如下所示:
e = p-y total_error = np.sum(e*e) # 平方和 rmse_train = np.sqrt(total_error/len(p)) print('RMSE on training: {}'.format(rmse_train))
我们在计算训练集均方根误差的时候使用了一个不同的过程。当然,结果和之前是一样的:4.6。(进行这些检查是有益处的,可以确保我们所做的是正确的。)
现在,我们将使用KFold 类来构建一个10折交叉验证循环,并测试线性回归的泛化能力:
from sklearn.cross_validation import Kfold kf = KFold(len(x), n_folds=10) err = 0 for train,test in kf: lr.fit(x[train],y[train]) p = map(lr.predict, x[test]) e = p-y[test] err += np.sum(e*e) rmse_10cv = np.sqrt(err/len(x)) print('RMSE on 10-fold CV: {}'.format(rmse_10cv))
通过交叉验证,我们得到了一个保守的估计(这是说,实际误差要更大):5.6。在这个例子里,它是对价格预测泛化能力的一个更好的估计。
普通最小二乘法的学习时间非常短,给出的是一个简单模型,并且在预测阶段非常迅速。由于这些原因,这个模型通常会是在回归问题中所使用的第一个模型。现在我们去看一看更多的高级方法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论