局部加權線性迴歸算法原理分析和代碼實戰
前言
前面我們介紹了最基礎的線性迴歸算法,也提到了它容易出現欠擬合的現象。那麼,本文的局部加權線性迴歸將解決這個欠擬合問題。我覺得這個思路具有很好的遷移性,咱們一起好好看看。
項目源碼已上傳至GitHubb上,有需要的自取:項目地址
如果項目對你有些許幫助,不要忘記給個小⭐⭐
算法原理分析
1. 什麼叫局部加權?
使用“近朱者赤,近墨者黑”來形容這個算法再合適不過了。在你的社交圈子裏,離你距離越近,對你的影響就越大,離你距離越遠,對你的影響越小。我們以每一個數據爲一個社交中心,對於存在於該數據周圍的所有數據按照距離賦予權值,這裏我們使用高斯函數進行權值計算。
2. 什麼是高斯核函數?
也稱徑向基函數,簡稱RBF,定義爲空間中任意一點xi到x之間歐氏距離的單調函數。當兩個點距離很近時,這個值越大,當兩個點距離很遠時,距離很小。我們就使用這個函數來對數據賦予權值。
數據集普遍都具有很多屬性,所以一般的高斯核函數如下:
σ表示屬性值的方差
由於我們本次使用的數據集只有一個屬性,並且我們需要自己控制擬合程度,所以,我們對於上面的式子做出了一點改進:
這個w權值是一個方陣,其形狀大小有數據量決定。我們假設有m條數據量,那麼w權值的形狀大小爲(mxm)的方陣。
此方陣對角線存放權值,其餘全是0,這樣弄的原因是爲了計算表示的方便,我們看下面的式子:
接下來我以數據點x[0]= [1.0, 0.42781]爲數據中心點,對整個數據集計算權值,生成的圖像如下:
從圖像我們可以看到,接近於0.4281的地方都比較高,都比較接近於1,離這個點越遠,其值就越小。
3. 加入權值後的線性迴歸公式
下面公式是在上一篇博客的公式推導基礎上,進行改進的。如果沒有看過,建議先去大致看一眼
- 最小均方差公式:
- 化簡形式
- 對F進行求導,得出α的表達式
局部加權線性迴歸代碼實戰
- 加載數據
首先我們先看一下文本數據的屬性有哪些
圖中最座標紅色方塊是數據的常數1,用來求解常量,對應着就是我們前面公式的b。
中間黃色的是我們的屬性值α1.
最右邊白色的是我們數據的值,也就是y值。
我們來看一下數據的分佈情況:
def LoadData(filename):
dataMat = []
labelMat = []
with open(filename) as f:
numFeat = len(f.readline().split('\t'))-1#這裏會導致忽略第一個數據
f.seek(0)
for line in f.readlines():
lineArr = []
curLine = line.strip().split('\t')
for i in range(numFeat):
lineArr.append(float(curLine[i]))
dataMat.append(lineArr)
labelMat.append(float(curLine[-1]))
return dataMat,labelMat
- 首先從文件中加載進來數據
- 獲取數據的屬性個數numFeat
- 將數據的屬性值和y值分別放入到dataMat列表和labelMat列表中
2. 進行局部加權迴歸
def lwlr(testPoint,xArr,yArr,k=0.1):
xMat = np.mat(xArr);yMat = np.mat(yArr).T#
m = np.shape(xMat)[0]#數據個數
#爲什麼創建方陣,是爲了實現給每個數據增添不同的權值
weights = np.mat(np.eye(m))#初始化一個階數等於m的方陣,其對角線的值爲1,其餘值均爲0
for j in range(m):
diffMat = testPoint-xMat[j,:]
weights[j,j] = np.exp(diffMat*diffMat.T/(-2.0*k**2)) #e的指數形式
xTx = xMat.T*(weights*xMat)
#print("weights",weights)
if np.linalg.det(xTx)==0.0:#通過計算行列式的值來判是否可逆
print("this matrix is singular,cannot do inverse")
return
ws = xTx.I*(xMat.T*(weights*yMat))
return testPoint*ws
- 這裏testPoint 是中心點數據,將所有數據對求解w權值。
- 我們創建一個大小爲m,對角線爲1的方陣weights
- 通過一個循環,來計算testPoint 對數據中各個點的高斯函數計算
- 通過上面咱們推導的公式,進行求解α(代碼中是ws)
- 由於公式中,我們需要用到行列式的逆,所以,我們需要判斷行列式的值是否等於0,以此來判定是否可逆。
- 最後可以求出testPoint 的預測值
3. 循環求出所有數據點的預測值
def lwlrTest(testArr,xArr,yArr,k=1.0):
m = np.shape(testArr)[0]
yHat = np.zeros(m)
for i in range(m):
yHat[i] = lwlr(testArr[i],xArr,yArr,k)
return yHat
- 這裏我們使用循環,對每個數據進行循環獲取其預測值,並將預測值存入到yHat數組中。
4. 圖形的顯示
def lwlshow(xArr,yMat,yHat):
fig = plt.figure()
ax = fig.add_subplot(111)
xCopy = np.array(xArr.copy())
srtInd = xCopy[:,1].argsort(0)
print("xSort",xCopy[srtInd])
ax.plot(xCopy[srtInd][:,1],yHat[srtInd])
yMat = np.array(yMat)
ax.scatter(xCopy[srtInd][:,1].tolist(),yMat[srtInd],s=10,alpha=0.7)
plt.show()
- 這裏我們首先對數據進行排序
- 然後將預測值用直線連接,將原數據點使用點的形式顯示
這裏我們取k=0.01,顯示的圖像如上圖。
接下來,我們分別測試,當k=1、0.1、0.01、0.005的圖像分別是什麼樣的。
從圖中我們能夠看到,隨着k的逐漸減小,原本擬合的直線逐漸的變形,慢慢的與數據開始擬合。
k=0.01的時候,擬合程度 剛剛好。當k=0.005的時候,就出現了過擬合。
總結
通過局部加權線性迴歸算法,我們基本解決了一般線性迴歸的欠擬合問題。當然這裏k的取值還是需要我們去多次嘗試,找到一個合適的值。
最後我想說一點的時,這個加權的思想可以用在很多地方,我也在反覆的想這個事情,希望之後學到新的算法時,能夠將這個思想添加進去,以達到更好的效果。