线性回归的一个问题是有可能出现欠拟合现象,因为它求的是具有最小均方误差的无偏估计。显而易见,如果模型欠拟合将不能取得最好的预测效果。所以有些方法允许在估计中引入一些偏差,从而降低预测的均方误差。
其中的一个方法是局部加权线性回归(Locally Weighted Linear Regression,为LWLR)。在该算法中,我们给待预测点附近的每个点赋予一定的权重;然后与8.1节类似,在这个子集上基于最小均方差来进行普通的回归。与kNN一样,这种算法每次预测均需要事先选取出对应的数据子集。该算法解出回归系数w
的形式如下:
其中w
是一个矩阵,用来给每个数据点赋予权重。
LWLR使用“核”(与支持向量机中的核类似)来对附近的点赋予更高的权重1 。核的类型可以自由选择,最常用的核就是高斯核,高斯核对应的权重如下:
1. 读者要注意区分这里的权重W和回归系数w;与kNN一样,该加权模型认为样本点距离越近,越可能符合同一个线性模型。——译者注
这样就构建了一个只含对角元素的权重矩阵w
,并且点x
与x(i)
越近,w(i,i)
将会越大。上述公式包含一个需要用户指定的参数k
,它决定了对附近的点赋予多大的权重,这也是使用LWLR时唯一需要考虑的参数,在图8-4中可以看到参数k
与权重的关系。
图8-4 每个点的权重图(假定我们正预测的点是x = 0.5
),最上面的图是原始数据集,第二个图显示了当k = 0.5
时,大部分的数据都用于训练回归模型;而最下面的图显示当k = 0.01
时,仅有很少的局部点被用于训练回归模型
下面看看模型的效果,打开文本编辑器,将程序清单8-2的代码添加到文件regression.py
中。
程序清单8-2 局部加权线性回归函数
def lwlr(testPoint,xArr,yArr,k=1.0): xMat = mat(xArr); yMat = mat(yArr).T m = shape(xMat)[0] weights = mat(eye((m))) #❶ 创建对角矩阵 for j in range(m): #❷ 权重值大小以指数级衰减 diffMat = testPoint - xMat[j,:] weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2)) xTx = xMat.T * (weights * xMat) if linalg.det(xTx) == 0.0: print /"This matrix is singular, cannot do inverse/" return ws = xTx.I * (xMat.T * (weights * yMat)) return testPoint * wsdef lwlrTest(testArr,xArr,yArr,k=1.0): m = shape(testArr)[0] yHat = zeros(m) for i in range(m): yHat[i] = lwlr(testArr[i],xArr,yArr,k) return yHat
程序清单8-2中代码的作用是,给定x
空间中的任意一点,计算出对应的预测值yHat
。函数lwlr
的开头与程序清单8-1类似,读入数据并创建所需矩阵,之后创建对角权重矩阵weights
❶。权重矩阵是一个方阵,阶数等于样本点个数。也就是说,该矩阵为每个样本点初始化了一个权重。接着,算法将遍历数据集,计算每个样本点对应的权重值:随着样本点与待预测点距离的递增,权重将以指数级衰减❷。输入参数k
控制衰减的速度。与之前的函数standRegress
一样,在权重矩阵计算完毕后,就可以得到对回归系数ws
的一个估计。
程序清单8-2中的另一个函数是lwlrTest
,用于为数据集中每个点调用lwlr
,这有助于求解k
的大小。
下面看看实际效果,将程序清单8-2的代码加入到regression.py
中并保存,然后在Python提示符下输入如下命令:
>>> reload(regression)<module /'regression/' from /'regression.py/'>
如果需要重新载入数据集,则输入:
>>> xArr,yArr=regression.loadDataSet(/'ex0.txt/')
可以对单点进行估计:
>>> yArr[0]3.1765129999999999>>> regression.lwlr(xArr[0],xArr,yArr,1.0)matrix([[ 3.12204471]])>>> regression.lwlr(xArr[0],xArr,yArr,0.001)matrix([[ 3.20175729]])
为了得到数据集里所有点的估计,可以调用lwlrTest
函数:
>>> yHat = regression.lwlrTest(xArr, xArr, yArr,0.003)
下面绘出这些估计值和原始值,看看yHat
的拟合效果。所用的绘图函数需要将数据点按序排列,首先对 xArr
排序:
xMat=mat(xArr)>>> srtInd = xMat[:,1].argsort(0)>>> xSort=xMat[srtInd][:,0,:]
然后用Matplotlib绘图:
>>> fig = plt.figure>>> ax = fig.add_subplot(111)>>> ax.plot(xSort[:,1],yHat[srtInd])[<matplotlib.lines.Line2D object at 0x03639550>]>>> ax.scatter(xMat[:,1].flatten.A[0], mat(yArr).T.flatten.A[0] , s=2,c=/'red/')<matplotlib.collections.PathCollection object at 0x03859110>>>> plt.show
可以观察到如图8-5所示的效果。图8-5给出了k
在三种不同取值下的结果图。当k = 1.0
时权重很大,如同将所有的数据视为等权重,得出的最佳拟合直线与标准的回归一致。使用k = 0.01
得到了非常好的效果,抓住了数据的潜在模式。下图使用k = 0.003
纳入了太多的噪声点,拟合的直线与数据点过于贴近。所以,图8-5中的最下图是过拟合的一个例子,而最上图则是欠拟合的一个例子。下一节将对过拟合和欠拟合进行量化分析。
图8-5 使用3种不同的平滑值绘出的局部加权线性回归结果。上图中的平滑参数k = 1.0
,中图k = 0.01
,下图k = 0.003
。可以看到,k = 1.0
时的模型效果与最小二乘法差不多,k = 0.01
时该模型可以挖出数据的潜在规律,而k = 0.003
时则考虑了太多的噪音,进而导致了过拟合现象
局部加权线性回归也存在一个问题,即增加了计算量,因为它对每个点做预测时都必须使用整个数据集。从图8-5可以看出,*k *= 0.01时可以得到很好的估计,但是同时看一下图8-4中k = -0.01的情况,就会发现大多数据点的权重都接近零。如果避免这些计算将可以减少程序运行时间,从而缓解因计算量增加带来的问题。
到此为止,我们已经介绍了找出最佳拟合直线的两种方法,下面用这些技术来预测鲍鱼的年龄。