接上一篇文章 線性迴歸——最小二乘法(公式推導和非調包實現)
前言:
不知不覺已經做了幾期的線性迴歸的算法博文了,寫博文就是這樣雖說自己經理解了,但是講出來別人卻不一定能夠聽懂,因爲自己寫博文肯定是跟着自己的習慣和思路走的,對於讀者來說就並不那麼好懂.如果你看過博主往期的算法講解應該會發現博主在證明原理講解時內容都普遍囉嗦,本來可以一筆帶過的地方,可還是會花篇幅去講解.可能是有強迫症吧,我覺得既然講一個東西就不能將得似懂非懂,反而誤人子弟,能夠多一點就多一點吧,不爲別人也爲自己將來回過頭來複習提供便利.博主在上期的文章中講到了線性迴歸中的最小二乘法的推導和計算,今天就來說一下梯度下降法吧,有人可能會納悶,既然都有了最小二乘法可以計算線性迴歸問題了,爲什麼還有多來一種梯度下降法呢?其實你觀察最小二乘法的公式你就清楚了,最小二乘法只適用於訓練數據滿秩的情況下才能計算,什麼情況是滿秩呢?就是你的每行數據中的特徵值個數要小於你所有的樣本個數.既然是這樣那麼就來看看梯度下降法是個什麼吧:
一、什麼是梯度下降?
- 很多初學者一聽到這個詞就望而生畏,覺得這是一個很高大上的詞,覺得很高大上.其實它也只是名字被蒙上了一層面紗,遠沒有大家想得那麼高大上,打個很通俗的比方就是樓梯跟滑滑梯的區別是什麼,不就是樓梯是一步一步的移動嗎,而滑滑梯則是光滑的持續的運動軌跡,在上面是一瞬即逝的.在打個比方你在一座山上,現在你需要找到一條路下到山底,你現在是不知道下山的路的,山上有很多的岔路口,該怎麼辦呢?其實解決辦法很簡單,既然是下山那麼你在高度H是必定要減少的,不妨你走一步然後測量一下當前這一步是讓你的高度下降了還是上升了.如果是下降了就跟着這個方向走,如果不是則換個方向再測量反覆如此,你就到達山下了.這是一個典型的貪心問題,用局部解求出全解的過程.
- 我們再從現實回到書面函數圖像上來.
這是一個開口向上的二次函數 f(x)=x2 的函數圖像,我們把它比作成一座山,它的最低點就是山底,圖中還有一個紅點,他表示我們人現在所處的位置,如果用梯度下降的方法,讓人下山,那麼它的軌跡可以表示成下圖:
圖淺顯易懂,一眼便能看出’下山’軌跡,但是問題來了在算法上我們是如何實現的呢?
導數大家都應該清楚吧,導數反應的就是函數中某點的變化率,而且這個變化率是正增長的變化率,換句話說就是某個點它最對應的f(x)的增長那個’增髒速度速度’,給他加個負號反轉過來便是我們的’下山’方向.在’山路’(函數)中我們麼一步就可以用下面的方式來進行表示:
xj=xj−1−αf′(x)
其中你發現多另一個 α 參數,這個參數的作用是代表我們每一步的跨度,專業術語也叫學習率,之所以要有這個參數是因爲,我們要讓下上的時間儘量短(也就是一步走大一點),但是也不能太大,如果太大會發生什麼呢?請看下圖你就明白了:
可以看到在這種函數圖像下面,我們如果一步邁大了,那麼直接就跳過了最小值的地方,來到了另一個極小值的區域,顯然不是我們想要的,所以 α 不能太小也不能太大,恰到好處最好
搞懂了原理我們再來根據公式來計算一遍:
我們這裏讓起點爲 x0=−4,α=0.4
f(x)=x2
f′(x)=2x
(1):
x1=x0−αf′(x0)=−4−0.4×(2×−4)=−0.8
(2):
x2=x1−αf′(x1)=−0.8−0.4×(2×−0.8)=−0.16
(3)
x3=x2−αf′(x2)=−0.16−0.4×(2×−0.16)=−0.032
可以看到只經過了三步迭代就已經接近最低點的位置了,可見效率還是挺高.
有了上面的知識你就算入門梯度下降了,你會發現上面只提到了一個自變量的情況,但在實際開發當中數據往往是多維的,具有多個特徵值,那麼多維的數據該怎麼處理呢,上面我們只是提到了導數,其實導數還有一個親兄弟叫做偏導數,多維度的數據我們往往採用偏導數的方式來解決,如果我們有一個函數是這樣的:
f(θ)=θ12+θ22
通過作圖工具我們將圖像畫出來是這樣的:
很明顯函數的最低點在(0,0,0)處,但我們要怎麼來求?先別急計算的原理都是大同小異,在計算之前先了解這樣一個概念
在只有一個自變量的函數中我們對其求導,因爲只有一個自變量所以它的方向就要麼向前,要麼向後我們就用一個標量Δ
來表示它的變化,Δ爲負表示向後,Δ爲正表示向前.
現在到了有多個自變量的函數中,我們就需要對所有的自變量求一次偏導,然後讓這些求得的偏導組成一個列表,我們通常稱這個列表爲一個向量,向量是具有方向的而標量則沒有.求出來的向量是這樣的
⟨δθ1δ(f(θ)),δθ1δ(f(θ)),⋯,δθnδ(f(θ))⟩
我們不妨來對f(θ)=θ12+θ22,這個曲面方程來計算一下它的梯度下降過程:
還以一樣先確定取初始位置,起點就設爲<θ01,θ02>=<4,4>,α=0.4
再分別把偏導函數求出來:
δθ1δ(f(θ))=2θ1,δθ2δ(f(θ))=2θ2
然後開始迭代:
(1)
<θ11,θ12>=⟨θ01−αδθ1δ(f(θ01)),θ02−αδθ2δ(f(θ02))⟩=<4−0.4×(2×4),4−0.4×(2×4)>=<0.8,0.8>
(2)
<θ21,θ22>=⟨θ11−αδθ1δ(f(θ11)),θ12−αδθ2δ(f(θ12))⟩=<0.8−0.4×(2×0.8),0.8−0.4×(2×0.8)>=<0.16,0.16>
(3)
<θ31,θ32>=⟨θ21−αδθ1δ(f(θ21)),θ22−αδθ2δ(f(θ22))⟩=<0.16−0.4×(2×0.16),0.16−0.4×(2×0.16)>=<0.032,0.032>
……,……
(j)
<θj1,θj2>=⟨θ(j−1)1−αδθ1δ(f(θ(j−1)1)),θ(j−1)2−αδθ2δ(f(θ(j−1)2))⟩
由於篇幅原因這裏就列出了三次迭代過程,我們來看看將整個過程在途中繪製出來是怎樣的
根據圖像還是很直觀可以看到梯度下降法的下降軌跡.
二、線性迴歸梯度下降法公式推導
- 最小二乘法公式:
J(θ)=21i=1∑n(yi−θTxi)2
公式的證明與推導在上一篇博文中已經講解,這裏就不闡述公式的由來了,如有疑問可以翻看博文查看由來.爲什麼要在這裏提及最小二乘法的公式呢?這是因爲我們這裏要引入一個新的式子,均方誤差或者也可以叫它帶價函數(J(θ)):
⎩⎪⎨⎪⎧J(θ)=2n1i=1∑n(hθ(xi)−yi)2hθ(xi)=θTxi
可以到這個公式和上面的最小二乘法的公式去就在與前面對整個計算結果除以了一下樣本個數,就相當於是求了一下誤差的平均值.
讓我們來對這個函數求偏導數:
δ(θj)δ(J(θ))=n−1i=1∑n[(hθ(xi)−yi)xij]
#################################### 明白求導過程的小夥伴可以跳過 ####################################
到這裏的小夥伴肯定對求導之後爲什麼會是這樣,不妨聽博主推導一遍:
在推導之前你要先明白下面三個個性質:
性質一:(複合函數求導公式)
u=x2,f′(u)=f′(u)u′
性質二:(求偏導時忽略其他自變量)
f(θ)=θ12+2θ2+θ32θ1δ(f(θ))=2θ1,θ2δ(f(θ))=2,θ1δ(f(θ))=θ3
性質三:(導數的加法分配率)
f(x)=f1(x)+f2(x),f′(x)=f1′(x)+f2′(x)
明白了這三個性質,我們現在將J(θ)展開:
J(θ)=2n(hθ(x1)−y1)2+⋯+(hθ(xn)−yn)2
開始對J(θ)求θ0的偏導數
δθ0δ(J(θ))=2n−2(hθ(x1)−y1)hθ′(x1)−⋯−2(hθ(xn)−yn)hθ′(x1)
把所有的 2 與分母約掉:
δθ0δ(J(θ))=n−(hθ(x1)−y1)δθ0δ(hθ(x1))−⋯−(hθ(xn)−yn)δθ0δ(hθ(xn))
現在我們來計算δθ0δ(hθ(xn)),先展開再根據性質二可得:
δθ0δ(hθ(xn))=δθ0δ(θ0xn0+θ0xn1+⋯+θ0xnm)=xn0
我們再來回到原來的式子中將δθ0δ(hθ(xn))替換掉就有:
δθ0δ(J(θ))=n−(hθ(x1)−y1)x10−⋯−(hθ(xn)−yn)xn0
最後將式子合起來表示:
δ(θ0)δ(J(θ))=n−1i=1∑n[(hθ(xi)−yi)xi0]
############################################# END ################################################
我們求得了偏導函數,現在就可以利用前面所介紹的梯度下降法則了,那麼就有公式:
θj=θ(j−1)−αn1i=1∑n[(hθ(xi)−yi)xi0]
這個公式中的 n 代表樣本數,α代表學習率也就是步長,如果我們每次都取全部樣本來計算顯然算出來的參數是最準確的,但是時間上肯定也是最大的 n 如果取 1 ,我們隨機取一個樣本來計算參數,這樣是最快,但是準確就不能保證,所以通常n可以取一個常數表示抽取樣本中的一部分數據來測試,這樣準確率不會太低,時間開銷也不會太大.
三、代碼實現
- 爲了方便計算我們把上面的公式換成矩陣來表式
θ=θ−αn1XT(Xθ−Y)
至於爲什麼可以變成這樣,也可以參考博主前面一篇文章中的推理方法,自行推理這裏也不多講了。
- 定義必要的函數:
def error_func(theta,train_x,train_y):
data_len = train_x.size
temp = np.dot(train_x,theta) - train_y
result = np.dot(temp.T,temp)/2*data_len
return result
def gradient_func(theta,train_x,train_y):
data_len = train_x.size
temp = np.dot(train_x,theta) - train_y
result = np.dot(train_x.T,temp)/data_len
return result
def gradient_descent(x,y,a,valve):
features_len = x.shape[1]
theta = np.ones(features_len).reshape(features_len,1)
data_len = x.shape[0]
while True:
gradient = gradient_func(theta,x,y)
theta = theta - gradient*a
if np.all(np.absolute(gradient) < valve):
return theta
def predict(model,x_data):
return np.dot(np.matrix(x_data),model)
from sklearn.datasets import load_boston
boston_data = load_boston().data
X = boston_data[:,:-1]
ones_x = np.ones(X.shape[0])
X = np.column_stack((ones_x,X))
Y = boston_data[:,-1:]
t = gradient_descent(X,Y,0.00004,0.1)
p_y = predict(t,X)
p_y = np.sort(np.array(p_y)[:,0])
plt.scatter(np.arange(0,506),np.sort(boston_data[:,-1]),marker="x",c="r",label="label_data")
plt.plot(np.arange(0,506),p_y,label="predict_data")
plt.legend()