線性回歸的一個問題是有可能出現欠擬合現象,因為它求的是具有最小均方誤差的無偏估計。顯而易見,如果模型欠擬合將不能取得最好的預測效果。所以有些方法允許在估計中引入一些偏差,從而降低預測的均方誤差。
其中的一個方法是局部加權線性回歸(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 * ws
def 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的情況,就會發現大多數據點的權重都接近零。如果避免這些計算將可以減少程序運行時間,從而緩解因計算量增加帶來的問題。
到此為止,我們已經介紹了找出最佳擬合直線的兩種方法,下面用這些技術來預測鮑魚的年齡。