梯度下降法原理以及代碼實現
本篇博客承接本人上一篇關於逐步回歸算法的引申,本篇將開始整理梯度下降算法的相關知識。梯度下降,gradient descent(之后將簡稱GD),是一種通過迭代找最優的方式一步步找到損失函數最小值的算法,基本算法思路可總結為如下幾點:
(1) 隨機設置一個初始值
(2) 計算損失函數的梯度
(3) 設置步長,步長的長短將會決定梯度下降的速度和准確度,之后會詳細展開
(4) 將初值減去步長乘以梯度,更新初值,然后將這一過程不斷迭代
1. 原理講解——二維平面情形
還是老規矩,先從二維平面場景開始,由易到難,步步深入,先上圖:

為了方便思考,我們求解的是對上述函數圖像的任意一個點
來說,沿着哪個方向走能夠使得該函數的y上升幅度最大,那么逆向思維,該方向一旦求得,該方向的反方向不就是使得y下降速度最快的方向了嗎?好,沿着這個思路繼續,由於這個方向是我們要求的,不妨設該方向與x軸的夾角為
,現在我們沿着這個方向走
的距離,將該向量正交分解可知,它只有在水平方向上的分量才會提供使得函數值發生變化的效果,豎直方向上分量不產生任何效果,於是可得以下函數值變化量函數:

顯然,當
為0,也就是水平正方向時,可使得該函數值上升最快,也就是說水平負方向可使得該函數值下降最快,至此,二維平面上的使得函數值下降最快的方向我們就找到了:

2. 原理講解——三維空間情形
現將該問題拓展至三維空間上,由之前的證明可知,對於函數
來說,某向量在y軸上的分量不會提供使得函數值發生變化的效果,那么將此結論擴展,對於函數
來說,某向量在z軸上的分量也不會提供使得函數值發生變化的效果,因此,我們可以這樣說,該問題的最終解必落在xoy平面內,而與z軸無關!現正式開始證明
設
為xoy平面上的一個向量,該向量與x軸的夾角為
,則它在x軸和y軸上的分量為:

x軸和y軸的變化共同引起了z軸方向上的變化,由此可得:

利用向量的思想,將上式表示成兩個向量內積的結果,可得:

第一個向量其實就是向量
,而第二個向量就是函數
在某點上的梯度
,因此上式最終就可轉換成:

設上式中兩個向量的夾角為α,根據向量內積公式,又可表示成:

該式中,當α為0時達到最大值,也就是說,與梯度相同的方向是使得函數值上升最快的方向,換句話來說,與梯度相反的方向是使得函數值下降最快的方向!!!
3. 原理講解——高維空間情形
結合上一篇博客中提到的高維空間下的SSE的計算,我們直接引用之前的結論:

若采用梯度下降法求解最小值,只需沿着上式的方向進行試探即可
4. 代碼實現
目前常見的求解梯度下降法的方式有三種,BGD,Batch Gradient Descent,批量梯度下降法,SGD,Stochastic Gradient Descent,隨機梯度下降法,以及MBGD,Mini-Batch Gradient Descent,小批量梯度下降法。由於最為普通的批量梯度下降法是每次迭代都是對於全量的數據來說的,若初值選的不巧就比較容易陷入一個局部(local)最優而不是全局(global)最優的解中去,因此出現了一個更為優化的算法來解決這個問題,那就是隨機梯度下降法,在SGD中,每個循環只隨機選取一個樣本進行迭代,因此對於BGD來說,訓練模型的速度明顯快上了許多,但由於每次僅僅選用一個樣本,樣本量不夠,因此不能夠很快收斂到最優解,值得一提的是,由於該算法的隨機性,在面對非凸函數時,能很好地避免陷入局部最優的情況,因此比BGD效果更好;然后這兩種算法都比較極端,要么每次選擇全部,要么每次只選一個,有沒有一種折中的算法呢?答案是肯定的,那就是MBGD,小批量梯度下降法,每次選擇的樣本量是在總體中進行一次抽樣,一般以比例的方式抽取最佳。
4.1 關於步長大小的選擇
雖說在實際寫代碼的時候,步長是我們自己設定的,但對於步長究竟選擇多少合適,主要有兩點考量。
1. 步長過小會導致一步步迭代到最優值的過程會非常長,不過結果會較為精確,需要注意的是,倘若迭代次數不夠,就會導致最終結果往往不是最優解
2. 步長過長雖然可以使得迭代過程縮短,但是對於精度丟失會比較大,並且最終解很有可能會在最有點兩側來回跳動,導致始終接近不了最優解
4.2 三種算法代碼實現
我們使用python將這三種算法寫進一個包中,功能是使用梯度下降法來糾結回歸方程SSE的最小值,具體代碼如下所示:
import numpy as np import pandas as pd class regression: def __init__(self, data, intercept = True): self.X = np.mat(data.iloc[:,:-1].values) self.Y = np.mat(data.iloc[:,-1].values).T self.data = data self.intercept = intercept def BGDfit(self, lam, iternum): #This function aims to use batch gradient descent algorithm to get the minimum value theta = np.mat(np.zeros((self.X.shape[1], 1))) for _ in range(iternum): grad = 2 / self.X.shape[0] * (self.X.T * (self.X * theta - self.Y)) theta -= lam * grad return np.ravel(theta) def SGDfit(self, lam, iternum): #This function aims to use stochastic gradient descent algorithm to get the minimum value theta = np.mat(np.zeros((self.X.shape[1], 1))) for _ in range(iternum): rnd_num = np.random.randint(self.X.shape[0]) grad = 2 / self.X.shape[0] * (self.X[rnd_num].T * (self.X[rnd_num] * theta - self.Y[rnd_num])) theta -= lam * grad return np.ravel(theta) def MBGDfit(self, lam, iternum): #This function aims to use mini-batch gradient descent algorithm to get the minimum value theta = np.mat(np.zeros((self.X.shape[1], 1))) temp_X = pd.DataFrame(self.X) for _ in range(iternum): X_index = temp_X.sample(frac = 0.1, replace = False).index X_mini = self.X[X_index, :] Y_mini = self.Y[X_index] grad = 2 / X_mini.shape[0] * (X_mini.T * (X_mini * theta - Y_mini)) theta -= lam * grad return np.ravel(theta) def evaluate(self, coef): #This function aims to calculate SSE and R2 in order to evaluate whether the model is accurate or not Y_pred = self.X * np.mat(coef).T SSE = np.power(np.ravel(self.Y) - np.ravel(Y_pred), 2).sum() SST = np.power(np.ravel(self.Y) - np.ravel(self.Y).mean(), 2).sum() R2 = 1 - SSE / SST return SSE, R2
