Python - 梯度下降法實現線性迴歸

1. 背景

文章的背景取自An Introduction to Gradient Descent and Linear Regression,本文想在該文章的基礎上,完整地描述線性迴歸算法。部分數據和圖片取自該文章。沒有太多時間摳細節,所以難免有什麼缺漏錯誤之處,望指正。

線性迴歸的目標很簡單,就是用一條線,來擬合這些點,並且使得點集與擬合函數間的誤差最小。如果這個函數曲線是一條直線,那就被稱爲線性迴歸,如果曲線是一條二次曲線,就被稱爲二次迴歸。數據來自於GradientDescentExample中的data.csv文件,共100個數據點,如下圖所示:

圖片名稱

我們的目標是用一條直線來擬合這些點。既然是二維,那麼y=b+mxy=b+mx這個公式相信對於中國學生都很熟悉。其中bb是直線在y軸的截距(y-intercept),mm是直線的斜率(slope)。尋找最佳擬合直線的過程,其實就是尋找最佳的bbmm的過程。爲了尋找最佳的擬合直線,這裏首先要定義,什麼樣的直線纔是最佳的直線。我們定義誤差(cost function):

 Error(b,m)=1N1N((b+mxi)yi)2誤差函數 Error(b,m)=1N∑1N((b+mxi)−yi)2

計算損失函數的python代碼如下:

# y = b + mx
def compute_error_for_line_given_points(b, m, points):
    totalError = sum((((b + m * point[0]) - point[1]) ** 2 for point in points))
    return totalError / float(len(points))
  • 1
  • 2
  • 3
  • 4

現在問題被轉化爲,尋找參數bbmm,使得誤差函數Error(b,m)Error(b,m)有最小值。在這裏,xixiyiyi都被視爲已知值。從下圖看,最小二乘法所做的是通過數學推導直接計算得到最低點;而梯度下降法所做的是從圖中的任意一點開始,逐步找到圖的最低點。

這裏寫圖片描述

2. 多元線性迴歸模型

從機器學習的角度來說,以上的數據只有一個feature,所以用一元線性迴歸模型即可。這裏我們將一元線性模型的結論一般化,即推廣到多元線性迴歸模型。這部分內部參考了機器學習中的數學(1)-迴歸(regression)、梯度下降(gradient descent)。假設有x1x1x2x2......xnxnnn個feature,θθxx的係數,則

 hθ(x)=θ0+θ1x1+...+θnxn=θTxx0=1擬合函數 hθ(x)=θ0+θ1x1+...+θnxn=θTx,其中x0=1
 J(θ)=12i=1m(hθ(x(i))y(i))2mm誤差函數 J(θ)=12∑i=1m(hθ(x(i))−y(i))2,m代表有m組樣本

更一般地,我們可以得到廣義線性迴歸。ϕ(x)ϕ(x)可以換成不同的函數,從而得到的擬合函數就不一定是一條直線了。

 hθ(x)=θTx=θ0+i=1nθiϕi(xi)廣義線性函數 hθ(x)=θTx=θ0+∑i=1nθiϕi(xi)

2.1 誤差函數的進一步思考

這裏有一個有意思的東西,就是誤差函數爲什麼要寫成這樣的形式。首先是誤差函數最前面的係數1212,這個參數其實對結果並沒有什麼影響,這裏之所以取1212,是爲了抵消求偏導過程中得到的22。可以實驗,把Error(b,m)Error(b,m)最前面的1N1N修改或者刪除並不會改變最終的擬合結果。那麼爲什麼要使用平方誤差呢?考慮以下公式:

y(i)=θTx(i)+ε(i)y(i)=θTx(i)+ε(i)

假定誤差ε(i)(1im)ε(i)(1⩽i⩽m)是獨立同分布的,由中心極限定理可得,ε(i)ε(i)服從均值爲00,方差爲σ2σ2的正態分佈(均值若不爲0,可以歸約到θ0θ0上)。進一步的推導來自從@鄒博_機器學習的機器學習課件。

圖片名稱 
圖片名稱

所以求maxL(θ)maxL(θ)的過程,就變成了求minJ(θ)minJ(θ)的過程,從理論上解釋了誤差函數J(θ)J(θ)的由來。

3 最小二乘法求誤差函數最優解

最小二乘法(normal equation)相信大家都很熟悉,這裏簡單進行解釋並提供python實現。首先,我們進一步把J(θ)J(θ)寫成矩陣的形式。XXmmnn列的矩陣(代表mm個樣本,每個樣本有nn個feature),θθYYmm11列的矩陣。所以 

 J(θ)=12i=1m(hθ(x(i))y(i))2=12(XθY)T(XθY) J(θ)=12∑i=1m(hθ(x(i))−y(i))2=12(Xθ−Y)T(Xθ−Y)

這裏寫圖片描述

所以θθ的最優解爲:θ=(XTX)1XTYθ=(XTX)−1XTY

當然這裏可能遇到一些問題,比如XX必須可逆,比如求逆運算時間開銷較大。具體解決方案待補充。

3.1 python實現最小二乘法

這裏的代碼僅僅針對背景裏的這個問題。部分參考了迴歸方法及其python實現

# 通過最小二乘法直接得到最優係數,返回計算出來的係數b, m
def least_square_regress(points):
    x_mat = np.mat(np.array([np.ones([len(points)]), points[:, 0]]).T)  # 轉爲100行2列的矩陣,2列其實只有一個feature,其中x0恆爲1
    y_mat = points[:, 1].reshape(len(points), 1)  # 轉爲100行1列的矩陣
    xT_x = x_mat.T * x_mat
    if np.linalg.det(xT_x) == 0.0:
        print('this matrix is singular,cannot inverse')  # 奇異矩陣,不存在逆矩陣
        return
    coefficient_mat = xT_x.I * (x_mat.T * y_mat)
    return coefficient_mat[0, 0], coefficient_mat[1, 0] # 即係數b和m
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

程序執行結果如下: 
b = 7.99102098227, m = 1.32243102276, error = 110.257383466, 相關係數 = 0.773728499888

擬合結果如下圖:

圖片名稱

4. 梯度下降法求誤差函數最優解

有了最小二乘法以後,我們已經可以對數據點進行擬合。但由於最小二乘法需要計算XX的逆矩陣,計算量很大,因此特徵個數多時計算會很慢,只適用於特徵個數小於100000時使用;當特徵數量大於100000時適合使用梯度下降法。最小二乘法與梯度下降法的區別見最小二乘法和梯度下降法有哪些區別?

4.1. 梯度

首先,我們簡單回顧一下微積分中梯度的概念。這裏參考了方向導數與梯度,具體的證明請務必看一下這份材料,很短很簡單的。

討論函數z=f(x,y)z=f(x,y)在某一點PP沿某一方向的變化率問題。設函數z=f(x,y)z=f(x,y)在點P(x,y)P(x,y)的某一鄰域U(P)U(P)內有定義,自點PP引射線ll到點P(x+Δx,y+Δy)P′(x+Δx,y+Δy)PU(P)P′∈U(P),如下圖所示。

這裏寫圖片描述

定義函數z=f(x,y)z=f(x,y)在點PP沿方向ll的方向導數爲:

fl=limρ0f(x+Δx,y+Δy)f(x,y)ρρ=(Δx)2+(Δy)2∂f∂l=limρ→0f(x+Δx,y+Δy)−f(x,y)ρ,其中ρ=(Δx)2+(Δy)2

方向導數可以理解爲,函數z=f(x,y)z=f(x,y)沿某個方向變化的速率。可以類比一下函數y=kx+by=kx+b的斜率k=dydxk=dydx。斜率越大,函數yy增長得越快。那麼現在問題來了,函數z=f(x,y)z=f(x,y)在點PP沿哪個方向增加的速度最快?而這個方向就是梯度的方向

gradf(x,y)=fxi+fyjgradf(x,y)=∂f∂xi→+∂f∂yj→

從幾何角度來理解,函數z=f(x,y)z=f(x,y)表示一個曲面,曲面被平面z=cz=c截得的曲線在xoyxoy平面上投影如下圖,這個投影也就是我們所謂的等高線。

這裏寫圖片描述

函數z=f(x,y)z=f(x,y)在點P(x,y)P(x,y)處的梯度方向與點PP的等高線f(x,y)=cf(x,y)=c在這點的法向量的方向相同,且從數值較低的等高線指向數值較高的等高線。

4.2 梯度方向計算

理解了梯度的概念之後,我們重新回到1. 背景中提到的例子。1. 背景提到,梯度下降法所做的是從圖中的任意一點開始,逐步找到圖的最低點。那麼現在問題來了,從任意一點開始,bbmm可以往任意方向”走”,如何可以保證我們走的方向一定是使誤差函數Error(b,m)Error(b,m)減小且減小最快的方向呢?回憶4.1. 梯度中提到的結論,梯度的方向是函數上升最快的方向,那麼函數下降最快的方向,也就是梯度的反方向。有了這個思路,我們首先計算梯度方向,

Error(b,m)m=i=1Nxi((b+mxi)yi)∂Error(b,m)∂m=∑i=1Nxi((b+mxi)−yi)
Error(b,m)b=i=1N((b+mxi)yi)x01∂Error(b,m)∂b=∑i=1N((b+mxi)−yi),x0恆爲1

有了這兩個結果,我們就可以開始使用梯度下降法來尋找誤差函數Error(b,m)Error(b,m)的最低點。我們從任意的點(b,m)(b,m)開始,逐步地沿梯度的負方向改變bbmm的值。每一次改變,Error(b,m)Error(b,m)都會得到更小的值,反覆進行該操作,逐步逼近Error(b,m)Error(b,m)的最低點。

回到更一般的情況,對於每一個向量θθ的每一維分量θiθi,我們都可以求出梯度的方向,也就是錯誤函數J(θ)J(θ)下降最快的方向: 

θjJ(θ)=θj12i=1m(hθ(x(i))y(i))2=i=1m(hθ(x(i))y(i))x(i)j∂∂θjJ(θ)=∂∂θj12∑i=1m(hθ(x(i))−y(i))2=∑i=1m(hθ(x(i))−y(i))xj(i)

4.3 批量梯度下降法

從上面的公式中,我們進一步得到特徵的參數θjθj的迭代式。因爲這個迭代式需要把m個樣本全部帶入計算,所以我們稱之爲批量梯度下降

θj=θjαJ(θ)θj=θjαi=1m(hθ(x(i))y(i))x(i)jθj′=θj−α∂J(θ)∂θj=θj−α∑i=1m(hθ(x(i))−y(i))xj(i)

針對此例,梯度下降法一次迭代過程的python代碼如下:

def step_gradient(b_current, m_current, points, learningRate):
    b_gradient = 0
    m_gradient = 0
    N = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        m_gradient += (2 / N) * x * ((b_current + m_current * x) - y)
        b_gradient += (2 / N) * ((b_current + m_current * x) - y)
    new_b = b_current - (learningRate * b_gradient)  # 沿梯度負方向
    new_m = m_current - (learningRate * m_gradient)  # 沿梯度負方向
    return [new_b, new_m]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

其中learningRate是學習速率,它決定了逼近最低點的速率。可以想到的是,如果learningRate太大,則可能導致我們不斷地最低點附近來回震盪; 而learningRate太小,則會導致逼近的速度太慢。An Introduction to Gradient Descent and Linear Regression提供了完整的實現代碼GradientDescentExample

這裏多插入一句,如何在python中生成GIF動圖。配置的過程參考了使用Matplotlib和Imagemagick實現算法可視化與GIF導出。需要安裝ImageMagick,使用到的python庫是Wand: a ctypes-based simple ImageMagick binding for Python。然後修改C:\Python27\Lib\site-packages\matplotlib__init__.py文件,在

# this is the instance used by the matplotlib classes
rcParams = rc_params()
  • 1
  • 2

後面加上:

# fix a bug by ZZR
rcParams['animation.convert_path'] = 'C:\Program Files\ImageMagick-6.9.2-Q16\convert.exe'
  • 1
  • 2

即可在python中調用ImageMagick。如何畫動圖參見Matplotlib動畫指南,不再贅述。

learningRate=0.0001,迭代100輪的結果如下圖:

After {100} iterations b = 0.0350749705923, m = 1.47880271753, error = 112.647056643, 相關係數 = 0.773728499888 
After {1000} iterations b = 0.0889365199374, m = 1.47774408519, error = 112.614810116, 相關係數 = 0.773728499888 
After {1w} iterations b = 0.607898599705, m = 1.46754404363, error = 112.315334271, 相關係數 = 0.773728499888 
After {10w} iterations b = 4.24798444022, m = 1.39599926553, error = 110.786319297, 相關係數 = 0.773728499888

這裏寫圖片描述 
這裏寫圖片描述

4.4 隨機梯度下降法

批量梯度下降法每次迭代都要用到訓練集的所有數據,計算量很大,針對這種不足,引入了隨機梯度下降法。隨機梯度下降法每次迭代只使用單個樣本,迭代公式如下:

θj=θjα(hθ(x(i))y(i))x(i)jθj′=θj−α(hθ(x(i))−y(i))xj(i)

可以看出,隨機梯度下降法是減小單個樣本的錯誤函數,每次迭代不一定都是向着全局最優方向,但大方向是朝着全局最優的。

這裏還有一些重要的細節沒有提及,比如如何確實learningRate,如果判斷何時遞歸可以結束等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章