講透機器學習中的梯度下降

本文始發於個人公衆號:TechFlow,原創不易,求個關注


在之前的文章當中,我們一起推導了線性迴歸的公式,今天我們繼續來學習上次沒有結束的內容。

上次我們推導完了公式的時候,曾經說過由於有許多的問題,比如最主要的複雜度問題。隨着樣本和特徵數量的增大,通過公式求解的時間會急劇增大,並且如果特徵爲空,還會出現公式無法計算的情況。所以和直接公式求解相比,實際當中更傾向於使用另外一種方法來代替,它就是今天這篇文章的主角——梯度下降法。

梯度下降法可以說是機器學習和深度學習當中最重要的方法,可以說是沒有之一。尤其是在深度學習當中,幾乎清一色所有的神經網絡都是使用梯度下降法來訓練的。那麼,梯度下降法究竟是一種什麼樣的方法呢,讓我們先從梯度的定義開始。


梯度的定義


我們先來看看維基百科當中的定義:梯度(gradient)是一種關於多元導數的概括。平常的一元(單變量)函數的導數是標量值函數,而多元函數的梯度是向量值函數。多元可微函數f{\displaystyle f}在點P{\displaystyle P}上的梯度,是以f{\displaystyle f}P{\displaystyle P}上的偏導數爲分量的向量。

這句話很精煉,但是不一定容易理解,我們一點一點來看。我們之前高中學過導數,但是高中時候計算的求導往往針對的是一元函數。也就是說只有一個變量x,求導的結果是一個具體的值,它是一個標量。而多元函數在某個點求導的結果是一個向量,n元函數的求導的結果分量就是n,導數的每個分量是對應的變量在該點的偏導數。這個偏導數組成的向量,就是這個函數在該點的梯度。

那麼,根據上面的定義,我們可以明確兩點,首先梯度是一個向量,它既有方向,也有大小。


梯度的解釋


維基百科當中還列舉了兩個關於梯度的例子,幫助我們更好的理解。

第一個例子是最經典的山坡模型,假設我們當下站在一個凹凸不平的山坡上,我們想要以最快的速度下山,那麼我們應該該從什麼方向出發呢?很簡單,我們應該計算一下腳下點的梯度,梯度的方向告訴我們下山最快的方向,梯度的大小代表這點的坡度。

第二個例子是房間溫度模型,假設我們對房間建立座標系,那麼房間裏的每一個點都可以表示成(x,y,z){\displaystyle (x,y,z)},該點的溫度是ϕ(x,y,z)\phi(x,y,z)。如果假設房間的溫度不隨時間變化,那麼房間裏每個點的梯度表示溫度變熱最快的方向,梯度的大小代表溫度變化的速率。

通過這兩個例子,應該很容易理解梯度的方向和大小這兩個概念。


舉例


假設f是一個定義在三維空間裏的函數,那麼,f在某一點的梯度,可以寫成:

f=(fx,fy,fz)=fxi+fyj+fzk\nabla f=(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z})=\frac{\partial f}{\partial x}i+\frac{\partial f}{\partial y}j + \frac{\partial f}{\partial z}k

這裏的i,j,ki, j, k都是標準單位向量,表示座標軸x,y,zx, y, z的方向。

我們舉個例子:

f=3x2+4ysinzf=3x^2+4y-sinz

套入剛纔的梯度公式,可以得到:

f=6xi+4jcoszk\nabla f = 6x\cdot i + 4\cdot j - \cos z\cdot k

如果我們知道x,y,zx, y, z的座標,代入其中,就可以知道對應的梯度了。


梯度下降法


理解了梯度的概念之後,再來看梯度下降法其實就是一張圖的事。請看下面這張圖。

這裏的黑色的曲線表示我們損失函數的函數曲線,我們要做的,就是找到這個最佳的參數x,使得損失函數的值最小。損失函數的值達到最小,也就說明了模型的效果達到了極限,這也就是我們預期的。

我們一開始的時候顯然是不知道最佳的x是多少的(廢話,知道了還求啥),所以我們假設一開始的時候在一個隨機的位置。就假設是圖中的x1x_1的位置。接着我們對x1x_1求梯度。我們之前說了,梯度就是該點下降最陡峭的方向,梯度的大小就是它的陡峭程度。我們既然知道了梯度的方向之後,其實就很簡單了,我們要做的就是朝着梯度下降,也就是最陡峭的方向向前走一小步。

我們假設,x1x_1處的梯度是s1s_1,那麼我們根據s1s_1通過迭代的方法優化損失函數。說起來有些空洞,我寫出來就明白了。

x2=x1+ηs1x3=x2+ηs2xn=xn1+ηsn1 \begin{aligned} x_2 &= x_1 + \eta \cdot s_1 \\ x_3 &= x_2 + \eta \cdot s_2 \\ \vdots \\ x_n &= x_{n-1} + \eta \cdot s_{n-1} \end{aligned}

從上面這個公式可以看出來,這是一個迭代公式。也就是說我們通過不停地迭代,來優化參數。理論上來說,這樣的迭代是沒有窮盡的,我們需要手動終止迭代。什麼時候可以停止呢?我們可以判斷每一次迭代的梯度,當梯度已經小到逼近於0的時候,就說明模型的訓練已經收斂了,這個時候可以停止訓練了。

這裏的η\eta是一個固定的參數,稱爲學習率,它表示梯度對於迭代的影響程度。學習率越大,說明梯度對於參數變化的影響越大。如果學習率越小,自然每一次迭代參數的變化也就越小,說明到收斂需要的迭代次數也就越多,也可以單純理解成,收斂需要的時間也就越長。

那麼是不是學習率越大越好呢?顯然也不是的。因爲如果學習率過大,很有可能會導致在迭代的過程當中錯過最優點。就好像油門踩猛了,一下子就過頭了,於是可能會出現永遠也無法收斂的情況。比如我們可以參考下面這張圖:

從這張圖上可以看到,變量一直在最值附近震盪,永遠也達不成收斂狀態。

如果學習率設置得小一些是不是就沒事了?也不是,如果設置的學習率過小,除了會導致迭代的次數非常龐大以至於訓練花費的時間過久之外,還有可能由於小數的部分過大,導致超出了浮點數精度的範圍,以至於出現非法值Nan這種情況出現。同樣,我們可以參考一下下圖:

這張圖畫的是學習率過小,導致一直在迭代,遲遲不能收斂的情況。

從上面這兩張圖,我們可以看得出來,在機器學習領域學習率的設置非常重要。一個好的參數不僅可以縮短模型訓練的時間,也可以使模型的效果更好。但是設置學習率業內雖然有種種方法,但是不同的問題場景,不同的模型的學習率設置方法都略有差別,也正因此,很多人才會調侃自己是調參工程師。

我們來看一下一個合適的學習率的迭代曲線是什麼樣的。

到這裏還沒有結束,好的學習率並不能解決所有的問題。在有些問題有些模型當中,很有可能最優解本身就是無法達到的,即使用非常科學的方法,設置非常好的參數。我們再來看一張圖:

這張圖有不止一個極值點,如果我們一開始的時候,參數落在了區間的左側,那麼很快模型就會收斂到一個極值,但是它並不是全局最優解,只是一個局部最優解。這時候無論我們如何設置學習率,都不可能找到右側的那個全局最優解。同樣,如果我們一開始參數落在了區間右側,那裏的曲線非常平坦,使得每次迭代的梯度都非常小,非常接近0.那麼雖然最終可以到達全局最優解,但是需要經過漫長的迭代過程。

所以,模型訓練、梯度下降雖然方法簡單,但是真實的使用場景也是非常複雜的。我們不可以掉以輕心,不過好在,對於線性迴歸的最小二乘法來說,損失函數是一個凸函數,意味着它一定有全局最優解,並且只有一個。隨着我們的迭代,一定可以達到收斂。


代碼實戰


Talk is cheap, show me the code.

光說不練假把式,既然我們已經學習到了梯度下降的精髓,也該親身用代碼體驗一下了。我們還是用之前線性迴歸的問題。如果有遺忘的同學可以點擊下方的鏈接回顧一下之前的內容:

一文講透線性迴歸模型

還是和之前一樣,我們先生成一批點:

import numpy as np
X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

這是根據函數y=3x+4y = 3x + 4隨機出來的,我們接下來就要通過梯度下降的方法來做線性迴歸。首先,我們來推導一下梯度公式:

在使用梯度下降算法的時候,我們其實計算當前θ\theta下的梯度。這個量反應的是當我們的θ\theta發生變化的時候,整個的損失函數MSE(mean square error 均方差)會變化多少。而梯度,可以通過對變量求偏導得到。寫成:θjMSE(θ)\frac {\partial}{\partial \theta_j}MSE(\theta)

我們單獨計算θj\theta_j的損失函數偏導,寫成:θjMSE(θ)\frac {\partial}{\partial \theta_j}MSE(\theta),帶入之前的損失函數公式,計算化簡可以得到:

θjMSE(θ)=1mi=1m(θTxiyi)xj\frac {\partial}{\partial \theta_j}MSE(\theta)=\frac {1}{m}\sum_{i=1}^m(\theta^T \cdot x_i-y_i)x_j

這只是θj\theta_j的偏導數,我們可以把向量θ\theta中每一個變量的偏導數合在一起計算。標記爲:

θMSE(θ)=(θ0MSE(θ)θ1MSE(θ)θnMSE(θ))=1mXT(Xθy)\nabla_\theta MSE(\theta)=\begin{pmatrix} \frac {\partial}{\partial \theta_0}MSE(\theta) \\ \frac {\partial}{\partial \theta_1}MSE(\theta) \\ \vdots\\ \frac {\partial}{\partial \theta_n}MSE(\theta) \\ \end{pmatrix}=\frac {1}{m}X^T\cdot(X \cdot\theta-y)

我們不難看出,在這個公式當中,我們涉及了全量的訓練樣本X。因此這種方法被稱爲批量梯度下降。因此,當我們的訓練樣本非常大的時候,會使得我們的算法非常緩慢。但是使用梯度下降算法,和特徵的數量成正比,當特徵數量很大的時候,梯度下降要比方程直接求解快得多。

需要注意一點,我們推導得到的梯度是向上的方向。我們要下降,所以需要加一個負號,最後再乘上學習率,得到的公式如下:

θnextstep=θηθMSE(θ)\theta^{next step}=\theta-\eta\nabla_\theta MSE(\theta)

根據公式,寫出代碼就不復雜了:

eta = 0.1 # 學習率
n_iterations = 1000 # 迭代次數
m = 100

theta = np.random.randn(2,1) # 隨機初始值
X = np.c_[np.ones(100).T, X]
for iteration in range(n_iterations):
    gradients = 1/m * X.T.dot(X.dot(theta) - y) # 根據梯度公式迭代
    theta = theta - eta * gradients

我們調用一下這段代碼,來查看一下結果:

和我們設置的參數非常接近,效果算是很不錯了。如果我們調整學習率和迭代次數,最後的效果可能會更好。

觀察一下代碼可以發現,我們在實現梯度下降的時候,用到了全部的樣本。顯然,隨着樣本的數量增大,梯度下降會變得非常慢。爲了解決這個問題,專家們後續推出了許多優化的方法。不過由於篇幅的限制,我們會在下一篇文章當中和大家分享,感興趣的同學可以小小地期待一下。

梯度下降非常重要,可以說是機器學習領域至關重要的基礎之一,希望大家都能學會。

今天的文章就到這裏,如果覺得有所收穫,請順手點個關注或者轉發吧,你們的支持是我最大的動力。

發佈了51 篇原創文章 · 獲贊 0 · 訪問量 3465
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章