1. 程式人生 > >斯坦福cs231n學習筆記(11)------神經網路訓練細節(梯度下降演算法大總結/SGD/Momentum/AdaGrad/RMSProp/Adam/牛頓法)

斯坦福cs231n學習筆記(11)------神經網路訓練細節(梯度下降演算法大總結/SGD/Momentum/AdaGrad/RMSProp/Adam/牛頓法)

神經網路訓練細節系列筆記:

通過學習,我們知道,因為訓練神經網路有個過程:
<1>Sample 獲得一批資料;
<2>Forward 通過計算圖前向傳播,獲得loss;
<3>Backprop 反向傳播計算梯度,這個梯度能告訴我們如何去調整權重,最終能夠更好的分類圖片;
<4>Update 用計算出的梯度去更新引數

用一段偽碼錶述上述文字:

while True:
     data_batch = dataset.sample_data_batch()
     loss = network.forward(data_batch
) dx = network.backward() x += - learninng_rate * dx

當我們談論引數更新時,指的是上述偽碼的最後一行,這一篇部落格我們所要討論的就是如何把這一行的更新迭代做的高階一點:

x += - learninng_rate * dx

梯度下降演算法是深度學習中使用非常廣泛的優化演算法,也是眾多機器學習演算法中最常用的優化方法。幾乎當前每一個先進的(state-of-the-art)機器學習庫或者深度學習庫都會包括梯度下降演算法的不同變種實現。但是,它們就像一個黑盒優化器,很難得到它們優缺點的實際解釋。這篇部落格旨在提供梯度下降演算法中的不同變種的介紹。

首先介紹梯度下降演算法的三種框架,然後介紹它們存在的問題和挑戰,接著介紹如何進行改進來解決存在的問題即演算法的優化。

一、梯度下降演算法三大框架

  • 批量梯度下降(Batch gradient descent)

旨在每次使用全量的訓練集樣本來更新模型引數,即:θ=θ−η⋅∇θJ(θ)
偽碼如下:

for i in range(nb_epochs):
    params_grad = evaluate_gradient(loss_function,data,params)
    params = params - learning_rate * params_grad 

nb_epochs是使用者輸入的最大迭代次數。使用全量的訓練集樣本計算損失函式loss_function的梯度params_grad,然後使用學習速率learning_rate朝著梯度相反方向去更新模型的每一個引數params。

批量梯度下降每次學習都使用整個訓練集,因此其優點在於每次更新都會朝著正確的方向進行,最後能夠保證收斂於極值點(凸函式收斂於全域性極值點,非凸函式可能會收斂於區域性極值點,屬於凸理論的問題了),但是其缺點在於每次學習時間過長,並且如果訓練集很大以至於需要消耗大量的記憶體,並且全量梯度下降不能進行線上模型引數更新。

  • 隨機梯度下降(Stochastic gradient descent)

旨在每次從訓練集中隨機選擇一個樣本來進行學習,即:θ=θ−η⋅∇θJ(θ;xi;yi)
偽碼如下:

for i in range(nb_epochs):
     np.random.shuffle(data)
     for example in data:
        params_grad = evaluate_gradient(loss_functon,example,params)
        params = params - learning_rate * params_grad

批量梯度下降演算法每次都會使用全部訓練樣本,因此這些計算是冗餘的,因為每次都使用完全相同的樣本集。而隨機梯度下降演算法每次只隨機選擇一個樣本來更新模型引數,因此每次的學習是非常快速的,並且可以進行線上更新。

我們可以對不同的引數方案和他們如何快速優化有一個直觀的認識:

特別觀察一下SGD,紅色的那條線,從圖中可以看出SGD實際上是所有方法中最慢的一個,所以在實際中很少應用它,我們可以使用更好的方案。
那我們來分析一下SGD的問題,是什麼原因導致它的的速度這麼慢?
舉個栗子:

我們可以從水平方向上可以看出,它的梯度很是非常小的,因為處於在一個比較淺的水平裡;但是垂直有很大的速率,因為它是一個非常陡峭的函式,所以出現了這種狀況:

在這種情況下使用SGD,在水平方向上進行比較緩慢,而在垂直方向上進展的很快,所以產生了很大的上下震盪。
* 小批量梯度下降(Mini-batch gradient descent)

旨在綜合了 batch 梯度下降與 stochastic 梯度下降,在每次更新速度與更新次數中間取得一個平衡,其每次更新從訓練集中隨機選擇 m,m

for i in range(nb_epochs):
     np.random.shuffle(data)
     for batch in get_batches(data,batch_size=50):
         params_grad = evaluate_gradient(loss_function,batch,params)
         params = params - learning_rate * params_grad 

相對於隨機梯度下降,Mini-batch梯度下降降低了收斂波動性,即降低了引數更新的方差,使得更新更加穩定。相對於全量梯度下降,其提高了每次學習的速度。並且其不用擔心記憶體瓶頸從而可以利用矩陣運算進行高效計算。一般而言每次更新隨機選擇[50,256]個樣本進行學習,但是也要根據具體問題而選擇,實踐中可以進行多次試驗,選擇一個更新速度與更次次數都較適合的樣本數。mini-batch梯度下降可以保證收斂性,常用於神經網路中。

二、Challenges

雖然梯度下降演算法效果很好,並廣泛使用,但是也存在著問題和挑戰需要解決:
1. 選擇一個合理的學習速率很難。如果學習速率過小,則會導致收斂速度很慢;如果學習速率過大,那麼就會阻礙收斂,即在極值點附近會震盪。
學習速率調整(又稱學習速率排程,Learning rate schedules),在每次更新過程中,改變學習速率,如退火。一般使用某種事先設定的策略或者在每次迭代中衰減一個較小的閾值。無論哪種調整方法,都需要事先進行固定設定,這便無法自適應每次學習的資料集特點。

2.模型所有的引數每次更新都是使用相同的學習速率。如果資料特徵是稀疏的或者每個特徵有著不同的取值統計特徵與空間,那麼便不能在每次更新中每個引數使用相同的學習速率,那些很少出現的特徵應該使用一個相對較大的學習速率。

3.對於非凸目標函式,容易陷入那些次優的區域性極值點中,如在神經網路中。那麼如何避免呢。而更嚴重的問題不是區域性極值點,而是鞍點。

三、梯度下降優化演算法

  • Momentum

如果把要優化的目標函式看成山谷的話,可以把要優化的引數看成滾下山的石頭,引數隨機化為一個隨機數可以看做在山谷的某個位置以0速度開始往下滾。目標函式的梯度可以看做給石頭施加的力,由力學定律知:F=m∗a,所以梯度與石頭下滾的加速度成正比。因而,梯度直接影響速度,速度的累加得到石頭的位置,對這個物理過程進行建模,可以得到引數更新過程為

 # Momentum update
  v = momentum * v - learning_rate * dx # integrate velocity
  x += v # integrate position

程式碼中v指代速度,其計算過程中有一個超引數momentum,稱為動量(momentum)。雖然名字為動量,其物理意義更接近於摩擦,其可以降低速度值,降低了系統的動能,防止石頭在山谷的最底部不能停止情況的發生。如果沒有momentum * v,那麼小球就永遠都不會停下了,會在平面上滾動,不會有能量的損失,損失函式就很難最小化。動量的取值範圍通常為[0.5, 0.9, 0.95, 0.99],一種常見的做法是在迭代開始時將其設為0.5,在一定的迭代次數(epoch)後,將其值更新為0.99。

加上動量項就像從山頂滾下一個球,球往下滾的時候累積了前面的動量(動量不斷增加),因此速度變得越來越快,直到到達終點。同理,在更新模型引數時,對於那些當前的梯度方向與上一次梯度方向相同的引數,那麼進行加強,即這些方向上更快了;對於那些當前的梯度方向與上一次梯度方向不同的引數,那麼進行削減,即這些方向上減慢了。即在陡峭的方向上削弱這些動盪,在一致的淺的方向激勵此過程。因此可以獲得更快的收斂速度與減少振盪。

  • Nesterov Momentum Update(又稱之為Nesterov Accelerated Gradient-NAG)

回頭看一下原始的Momentum:

v = momentum * v - learning_rate * dx


圖中的momentum step(綠色的變數)代表程式碼中的動能項(momentum * v),gradient step (紅色的變數)代表梯度也就是損失函式減少的方向( - learning_rate * dx),藍色的線就是他們倆的向量和。

Nesterov Momentum要比Momentum的效果會好一些,我們先不管現輸入是什麼,所以在我們還沒有計算出gradient step(紅色的變數)情況下,我們已經建立了momentum step(綠色的變數),並得到了未知的梯度。也就是說,Nesterov Momentum想讓我們來預測結果–gradient step,也就是計算在momentum step綠色箭頭這一點的梯度確定gradient step,這樣得到了和之前細微不同的更新結果,理論上,這一方法有著更好的收斂效果,在實際上確實要比Momentum好得多。

通俗的解釋一下,由於從山頂往下滾的球會盲目地選擇斜,Nesterov Momentum是在遇到傾斜向上之前應該減慢速度。

Nesterov Momentum和Momentum的差別在這裡:

和之前Momentum不同的是,Nesterov Momentum在計算的梯度的時候,加上了μ*Vt-1,這種方式預估了下一次引數所在的位置,選擇了和之前計算梯度不同的位置,稱之為預測位置(lookahead position),所以Nesterov Momentum的效果會更好。

  • Adagrad

Adagrad也是一種基於梯度的優化演算法。

cache += dx**2
x += - learning_rate * dx / (np.sqrt(cache) + le-7)

相比於SGD,增加了個附加變數—-cache來放縮梯度,並且是不停的增加這一附加變數。這裡的變數cache是一個聯合向量,和主向量是一樣大的,因為cache在每一維度計算其相應梯度的平方和,我們將這些cache構造起來,然後逐項用這一函式去除以cache的平方,使得對每個引數自適應不同的學習速率,對稀疏特徵,得到大的學習更新,對非稀疏特徵,得到較小的學習更新,因此該優化演算法適合處理稀疏特徵資料。

So,舉個栗子 :

Adagrad在垂直方向上的梯度會加到cache中,然後相應的會除以越來越大的數,所以在垂直方向上會得到越來越小的更新。當我們在垂直方向上看到許多大的梯度,Adagrad就會衰減學習速率,使垂直方向的更新步長越來越小。在水平方向上的梯度是很小的,所以分母會變小,相比於垂直方向,水平方向更新更快。這就是對每個引數自適應不同的學習速率,針對不同梯度方向的補償措施。

但是,此時,我們思考一個問題,如果我們訓練整個神經網路,更新過程中步長大小會發生什麼變化?
A:在Adagrad演算法中,不斷有正數加到分母的cache變數中,步長就會逐漸衰減到0,最後完全停止學習。

但是在神經網路中,我們需要整個網路不斷變化從而修正資料,這才是正確的思考方式。它需要持續的活力來不斷更新資料,而不是衰退到停止。所以,針對這一問題,Geoff Hinton對Adagrad做了個小小的改變,也被稱之為RMSProp演算法。

  • RMSProp

RMSProp是由Adagrad演化過來的:

RMSProp主要的思想是:cache不再是每一維度計算平方和,而是變成一個“洩露”的變數,利用衰減率(decay_rate)這個超引數,通常將decay_rate設定為0.99,接著計算引入衰減率發生“洩露”的平方和。所以我們仍然保持了在梯度較大或較小方向上,對於更新步長的補償效果,但是不會再發生更新停止的情況。

  • Adam

Adam也是一種不同引數自適應不同學習速率方法,它是Adagrad演算法和Momentum演算法的結合體,可稱之為“極品”,哈哈。正如你所見:

# Adam
m = beta1 * m + (1-beta1) * dx
v  = beta2 * v  + (1-beta2) * (dx**2)
x += - learning_rate * m / (np.sqrt(v)) + le-7)

m與v分別是梯度的帶權平均和帶權有偏方差,初始為0向量,Adam的作者發現他們傾向於0向量(接近於0向量),特別是在衰減因子(衰減率)β1,β2接近於1時。為了改進這個問題,對m與v進行偏差修正(bias-corrected),偏差修正取決於時間步長t:

# Adam
m = beta1 * m + (1-beta1) * dx
v  = beta2 * v  + (1-beta2) * (dx**2)
m /= 1-beta1**t
v /= 1-beta2**t
x += - learning_rate * m / (np.sqrt(v)) + le-7)
  • 牛頓法

牛頓法是二階收斂,梯度下降是一階收斂,所以牛頓法就更快。如果更通俗地說的話,比如你想找一條最短的路徑走到一個盆地的最底部,梯度下降法每次只從你當前所處位置選一個坡度最大的方向走一步,牛頓法在選擇方向時,不僅會考慮坡度是否夠大,還會考慮你走了一步之後,坡度是否會變得更大。所以,可以說牛頓法比梯度下降法看得更遠一點,能更快地走到最底部。

根據wiki上的解釋,從幾何上說,牛頓法就是用一個二次曲面去擬合你當前所處位置的局部曲面,而梯度下降法是用一個平面去擬合當前的局部曲面,通常情況下,二次曲面的擬合會比平面更好,所以牛頓法選擇的下降路徑會更符合真實的最優下降路徑。

紅色的牛頓法的迭代路徑,綠色的是梯度下降法的迭代路徑。

迭代公式:

我們可以通過公式可以看出二階收斂的優勢,沒有學習速率,沒有超引數,只要知道方向和曲率,就能達到近似的最低值,牛頓法的具體推導過程在此不再贅述。但在實際應用中,牛頓法不是很受歡迎,在迭代公式中的H是指Hessian矩陣,比如你有1億的資料,Hessian矩陣是1億行和1億列,然後進行逆運算,Good Luck!這是不可能發生的。

還有一些二階的梯度下降演算法,我們不準備使用它們,在此一併簡單介紹:

  • BGFS

BGFS演算法不用對Hessian矩陣進行逆運算,通過秩為1的矩陣連續更新得到一個近似的Hessian矩陣,但是依然要儲存Hessian矩陣的值,所以對大型神經網路仍然不適用。