1. 程式人生 > >資料探勘經典演算法:線性迴歸、區域性加權迴歸、嶺迴歸、逐步線性迴歸 sklearn實現

資料探勘經典演算法:線性迴歸、區域性加權迴歸、嶺迴歸、逐步線性迴歸 sklearn實現

這裡記錄一下關於迴歸方面的知識包括(線性迴歸、區域性加權迴歸、嶺迴歸、逐步線性迴歸)等基礎思想和程式碼實現。以及sklearn的實現方法。(資料來自機器學習實戰第八章)

迴歸:

    分類的目標變數是標稱型資料,而回歸可以對連續型資料做預測,同樣也是尋找一條最佳的擬合線。

    迴歸的目的是預測數值型的目標值,最直接的辦法是根據輸入資料寫出一個目標值的計算公式,即一個線性方程:y=kx+bz類似的函式,這就是所謂的迴歸方程,其中k、b稱作迴歸係數,求這些迴歸係數的過程就是迴歸。一旦有了這些迴歸係數,給定輸入,做預測就很容易了。具體的做法使用迴歸係數(每個特徵一個對應的係數)乘以輸入(特徵)值,再將結果全部加在一起,就得到了預測值。當然還有非線性迴歸,該模型不認同上面的公式。

線性迴歸:

    如何從一堆資料中求迴歸方程呢?假定輸入資料為x,迴歸係數為向量w,那麼預測結果 y = x^{T}w,現在手裡有資料x、y,我們通過最小誤差來找w,即預測y與真實y的差值,為防止簡單的差值求和正負抵消,一般採用平方誤差。

    平方誤差可以寫做:\sum_{i=1}^{m}(y_{i}-x_{i}^{T}w)^{2}         使用矩陣表示(y-Xw)^{T}(y-Xw),再對w求導得X^{T}(Y-Xw),令w=0得到最終方程:

        \varpi =(X^{T}X)^{-1}X^{T}y                       將資料帶進公式運算即可

其中\varpi表示當前可以估計出w的最優解,由於(X^{T}X)^{-1}需要對矩陣求逆,所以必須判斷矩陣是否能逆,可以使用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是一個矩陣,用來給每個點賦予權重):

    \varpi =(X^{T}WX)^{-1}X^{T}Wy

程式碼(同樣的樣例資料):

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不是滿秩矩陣求逆有問題,簡單來說,就是在矩陣X^{T}X上加入一個\lambda I從而使得矩陣非奇異,進而能對X^{T}X+\lambda I求逆。現在也用來在估計中加入偏差,得到更好的估計,引入懲罰項\lambda限制了所有w的和,減少不重要的引數項,統計學中也叫“縮減”。

最終迴歸公式:

    \varpi =(X^{T}X+\lambda I)^{-1}X^{T}y

程式碼:

樣例資料(使用作者提供的資料,最後一列為目標變數):

# 嶺迴歸 返回迴歸係數
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()

(按列繪製每個特徵隨λ變化得線條)該圖繪出了每個迴歸係數與log(\lambda )的關係,在最左邊,即λ最小時,可以得到所有係數的原始值(與線性迴歸一致);在右邊,係數全部縮減成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_)

Lasso

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_)

這裡僅僅簡單介紹,以上給出了官網地址,具體請參考。結合具體資料,使用合適的模型,調整最佳引數,才能得到更好的效果。

參考書籍:《機器學習實戰》