資料探勘經典演算法:線性迴歸、區域性加權迴歸、嶺迴歸、逐步線性迴歸 sklearn實現
這裡記錄一下關於迴歸方面的知識包括(線性迴歸、區域性加權迴歸、嶺迴歸、逐步線性迴歸)等基礎思想和程式碼實現。以及sklearn的實現方法。(資料來自機器學習實戰第八章)
迴歸:
分類的目標變數是標稱型資料,而回歸可以對連續型資料做預測,同樣也是尋找一條最佳的擬合線。
迴歸的目的是預測數值型的目標值,最直接的辦法是根據輸入資料寫出一個目標值的計算公式,即一個線性方程:y=kx+bz類似的函式,這就是所謂的迴歸方程,其中k、b稱作迴歸係數,求這些迴歸係數的過程就是迴歸。一旦有了這些迴歸係數,給定輸入,做預測就很容易了。具體的做法使用迴歸係數(每個特徵一個對應的係數)乘以輸入(特徵)值,再將結果全部加在一起,就得到了預測值。當然還有非線性迴歸,該模型不認同上面的公式。
線性迴歸:
如何從一堆資料中求迴歸方程呢?假定輸入資料為x,迴歸係數為向量w,那麼預測結果 ,現在手裡有資料x、y,我們通過最小誤差來找w,即預測y與真實y的差值,為防止簡單的差值求和正負抵消,一般採用平方誤差。
平方誤差可以寫做: 使用矩陣表示,再對w求導得,令w=0得到最終方程:
將資料帶進公式運算即可
其中表示當前可以估計出w的最優解,由於需要對矩陣求逆,所以必須判斷矩陣是否能逆,可以使用Numpy的矩陣方法來運算。(”普通最小二乘法“)
程式碼:
樣例資料(使用作者提供的資料,最後一列為目標變數):
from numpy import * import matplotlib.pyplot as plt # 載入資料 返回資料和目標值 def loadDataSet(fileName): numFeat = len(open(fileName).readline().split('\t')) - 1 dataMat = []; labelMat = [] fr = open(fileName) for line in fr.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 # 利用公式計算迴歸係數 def standRegres(xArr,yArr): xMat = mat(xArr); yMat = mat(yArr).T xTx = xMat.T*xMat # 公式步驟 if linalg.det(xTx) == 0.0: print("行列式為0,奇異矩陣,不能做逆") return ws = xTx.I * (xMat.T*yMat) #解線性方程組 # ws = linalg.solve(xTx,xMat.T*yMat) # 也可以使用函式來計算 線性方程組 return ws # 繪製資料點和擬合線(線性迴歸) xArr,yArr = loadDataSet('ex0.txt') ws = standRegres(xArr,yArr) xMat = mat(xArr) yMat = mat(yArr) yHat = xMat*ws print(yHat.T) # 檢視預測值 # 繪製圖形,觀察擬合線 fig = plt.figure() ax = fig.add_subplot(111) # !很方便的小知識,使用mat將序列轉換為二維陣列。使用flatten可以再轉換為摺疊的一維陣列,矩陣.A = getA(),使用A[0]得到ndarray陣列 ax.scatter(xMat[:,1].flatten().A[0],yMat.T[:,0].flatten().A[0]) xCopy = xMat.copy() xCopy.sort(0) # 如果直線上的點次序混亂,繪圖將出現問題 yHat = xCopy*ws ax.plot(xCopy[:,1],yHat,color='red') plt.show()
普通線性迴歸很簡單,只需使用上述的公式帶入資料即可會的迴歸係數,獲得預測值,然後在繪圖檢視擬合線與原資料。
這裡有個問題就是,有可能不同的資料獲得的擬合線相同,如何判斷模型的好壞?使用np.corrcoer求預測值與真實值的相關度,相關度越高,則模型相對較好。
# 求該模型的好壞,使用corrcoer求預測值和真實值的相關度
yHat = xMat*ws
k = corrcoef(yHat.T,yMat)
print(k)
對角線為yMat與自己匹配為1,yHat與yMat相關係數為0.986。
區域性加權線性迴歸:
由上面繪製的圖形可以看出,線性迴歸可能出現欠擬合的問題(求的是最小均方誤差的無偏估計),所以可以引入一些偏差,降低預測的均方誤差。
原理:
給待預測點附近的每個點都賦予一定的權重,然後與上面一樣基於最小均方誤差進行普通的線性迴歸。
問題:
與KNN一樣每次預測都需要事先選取對應子集(遍歷資料集),時間複雜度提高。
同SVM一樣採用核來對附近的點賦予權重,比較常用的核為高斯核,越近的點權重越大:
最終公式為(W是一個矩陣,用來給每個點賦予權重):
程式碼(同樣的樣例資料):
from numpy import *
import matplotlib.pyplot as plt
# 載入資料 返回資料和目標值
def loadDataSet(fileName):
numFeat = len(open(fileName).readline().split('\t')) - 1
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.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
# 利用公式計算迴歸係數
def standRegres(xArr,yArr):
xMat = mat(xArr); yMat = mat(yArr).T
xTx = xMat.T*xMat # 公式步驟
if linalg.det(xTx) == 0.0:
print("行列式為0,奇異矩陣,不能做逆")
return
ws = xTx.I * (xMat.T*yMat) #解線性方程組
# ws = linalg.solve(xTx,xMat.T*yMat) # 也可以使用函式來計算 線性方程組
return ws
# 區域性加權線性迴歸 返回該條樣本預測值
def lwlr(testPoint,xArr,yArr,k=1.0):
xMat = mat(xArr); yMat = mat(yArr).T
m = shape(xMat)[0]
weights = mat(eye((m))) # 建立為單位矩陣,再mat轉換資料格式 因為後面是與原資料矩陣運算,所以這裡是為了後面運算且不帶來其他影響
for j in range(m): # 利用高斯公式建立權重W 遍歷所有資料,給它們一個權重
diffMat = testPoint - xMat[j,:] # 高斯核公式1
weights[j,j] = exp(diffMat*diffMat.T/(-2.0*k**2)) # 高斯核公式2 矩陣*矩陣.T 轉行向量為一個值 權重值以指數級衰減
xTx = xMat.T * (weights * xMat) # 求迴歸係數公式1
if linalg.det(xTx) == 0.0: # 判斷是否有逆矩陣
print("行列式為0,奇異矩陣,不能做逆")
return
ws = xTx.I * (xMat.T * (weights * yMat)) # 求迴歸係數公式2
return testPoint * ws
# 迴圈所有點求出所有的預測值
def lwlrTest(testArr,xArr,yArr,k=1.0): # 傳入的k值決定了樣本的權重,1和原來一樣一條直線,0.01擬合程度不錯,0.003納入太多噪聲點過擬合了
m = shape(testArr)[0]
yHat = zeros(m)
for i in range(m):
yHat[i] = lwlr(testArr[i],xArr,yArr,k) # 返回該條樣本的預測目標值
return yHat
xArr,yArr = loadDataSet('ex0.txt')
# 求所有預測值
yHat = lwlrTest(xArr,xArr,yArr,0.01)
print(yHat)
# 繪製資料點和擬合線(區域性加權線性迴歸)
xMat = mat(xArr)
srtInd = xMat[:,1].argsort(0) # 畫擬合線 需要獲得所有橫座標從小到大的下標
xSort = xMat[srtInd][:,0,:] # 獲得排序後的資料
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(xSort[:,1],yHat[srtInd],color='red')
ax.scatter(xMat[:,1].flatten().A[0],mat(yArr).T.flatten().A[0])
plt.show()
這裡有個問題,就是平滑引數k值的選擇,1時等權重,欠擬合;0.01效果很好;0.003納入太多噪聲點,過擬合了。
區域性加權線性迴歸的計算量較大(遍歷資料),當k=0.01時,很多資料點的權重接近0,如果可以避免這些計算,則可以減少程式執行時間。
嶺迴歸(鮑魚年齡預測):
嶺迴歸屬於一種縮減方法,當特徵>樣本數量時X不是滿秩矩陣求逆有問題,簡單來說,就是在矩陣上加入一個從而使得矩陣非奇異,進而能對+求逆。現在也用來在估計中加入偏差,得到更好的估計,引入懲罰項限制了所有w的和,減少不重要的引數項,統計學中也叫“縮減”。
最終迴歸公式:
程式碼:
樣例資料(使用作者提供的資料,最後一列為目標變數):
# 嶺迴歸 返回迴歸係數
def ridgeRegres(xMat, yMat, lam=0.2): #lambda
xTx = xMat.T * xMat
denom = xTx + eye(shape(xMat)[1]) * lam # 這幾行為嶺迴歸的公式
if linalg.det(denom) == 0.0: # 依舊需要判斷存在逆矩陣
print("行列式為0,奇異矩陣,不能做逆")
return
ws = denom.I * (xMat.T * yMat)
return ws # 返回每個特徵的迴歸係數 8個
# 迴圈30次,獲得不同大小λ下的迴歸係數
def ridgeTest(xArr, yArr):
xMat = mat(xArr) # 資料需要做標準化處理,最後得到xMat、yMat
yMat = mat(yArr).T
yMean = mean(yMat,0) # 該函式第二個引數(壓縮)=0表示對各列求平均值得到1*n的矩陣,=1表示對給行求平均值m*1矩陣
yMat = yMat - yMean
xMeans = mean(xMat, 0) # 對資料集做同樣的操作
xVar = var(xMat, 0) # 每一列 var方差,第二個引數=0表示求樣本的無偏估計值(除以N-1),=1求方差(除以N) cov協方差
xMat = (xMat - xMeans) / xVar
numTestPts = 30
wMat = zeros((numTestPts, shape(xMat)[1]))
for i in range(numTestPts): # λ值改變
ws = ridgeRegres(xMat, yMat, exp(i - 10)) # 行列格式一樣但處理了的資料集 行列格式一樣但處理了的目標值 e的i-10次方
wMat[i, :] = ws.T # 將第i次每個特徵的迴歸係數向量按行儲存到30次測試的第i行
return wMat
abX,abY = loadDataSet('abalone.txt')
ridgeWeights = ridgeTest(abX,abY) # 返回 此處30次改變λ值後,得到的30行迴歸係數
fig = plt.figure() # 為了看到縮減(懲罰項)的效果而畫圖
ax = fig.add_subplot(111) # 及迴歸係數和懲罰項的關係
ax.plot(ridgeWeights) # 每列
plt.show()
(按列繪製每個特徵隨λ變化得線條)該圖繪出了每個迴歸係數與的關係,在最左邊,即λ最小時,可以得到所有係數的原始值(與線性迴歸一致);在右邊,係數全部縮減成0;中間部分的某值可以取得最好得預測效果,為了定量得找到最佳引數值,需要進行交叉驗證。另外需要判斷哪些變數對資料預測最具影響力。(λ一般選擇當所有線條趨於穩定得時候)
前向逐步迴歸:
逐步前向迴歸可以得到與lasso差不多得效果,但更簡單,它屬於一種貪心演算法,即每一步都儘可能減少誤差,一開始,所有得權重都設定為1,然後每一步所做的決策時對某個權重增加或減少一個很小的值。
程式碼:
樣例資料與上面相同
# 使用該函式來計算 真實值和預測值 的平方誤差和
def rssError(yArr,yHatArr):
return ((yArr-yHatArr)**2).sum()
# 這裡將標準化構建成一個函式
def regularize(xMat):
inMat = xMat.copy()
inMeans = mean(inMat,0) #計算平均數,然後減去它
inVar = var(inMat,0)
inMat = (inMat-inMeans)/inVar
return inMat
# 前向逐步迴歸 不斷地在新獲得的權重上更新
def stageWise(xArr,yArr,eps=0.01,numIt=100):
xMat = mat(xArr); yMat=mat(yArr).T
yMean = mean(yMat,0)
yMat = yMat - yMean
xMat = regularize(xMat) # 呼叫函式標準化資料 在嶺迴歸中同樣的處理但直接在函式中
m,n = shape(xMat)
ws = zeros((n,1)); wsTest = ws.copy(); wsMax = ws.copy()
for i in range(numIt): # 不斷更新
print(ws.T) # 打印出來便於觀察每次權重的變化
lowestError = inf # 初始化最大誤差
for j in range(n): # 迴圈每個特徵
for sign in [-1,1]: # 增大或減小
wsTest = ws.copy()
wsTest[j] += eps*sign # eps每次迭代的步長
yTest = xMat*wsTest
rssE = rssError(yMat.A,yTest.A) # 預測值和真實值的誤差
if rssE < lowestError:
lowestError = rssE
wsMax = wsTest # 更新wsMax,ws、wsMax、wsTest三者之間相互copy來保證每次結果的保留和更改
ws = wsMax.copy() # 當所有特徵迴圈完了,找到錯誤最小的wsMax賦值給新的ws
#returnMat[i,:]=ws.T
#return returnMat
xArr,yArr = loadDataSet('abalone.txt')
stageWise(xArr,yArr,0.01,300) # 做300次的更新
每次迴圈都將每個特徵的增大或減小情況運算,比較最小誤差,記錄當前最小誤差的特徵是增加還是減少的情況,得到本次迴圈的迴歸係數。
最後我們再與最開始的“最小二乘法”進行比較,看看兩者的差別:
# 與最小二乘法比較
xMat = mat(xArr)
yMat = mat(yArr).T
xMat = regularize(xMat)
yM = mean(yMat,0)
yMat = yMat - yM
weights = standRegres(xMat,yMat.T)
print(weights.T)
兩次結果兩者之間還是有較大差別的。
應用縮減方法(如逐步線性迴歸或嶺迴歸)時,模型也就增加了偏差,與此同時卻減小了模型的方差。
作者總結:
sklearn實現:
由於篇幅已經較多了,所以這裡只是簡單提一下sklearn的實現方法,sklearn的實現還是有很多引數需要注意的,所以最好還是根據官方提供的資料來運用。
from sklearn.linear_model import Ridge
clf = Ridge(alpha=.5)
X = [[0,0],[0,0],[1,1]]
y = [0,.1,1]
clf.fit(X,y)
print(clf.coef_)
print(clf.intercept_)
from sklearn import linear_model
clf = linear_model.Lasso(alpha=0.1)
clf.fit([[0,0], [1, 1], [2, 2]], [0, 1, 2])
print(clf.coef_)
print(clf.intercept_)
這裡僅僅簡單介紹,以上給出了官網地址,具體請參考。結合具體資料,使用合適的模型,調整最佳引數,才能得到更好的效果。
參考書籍:《機器學習實戰》