1. 程式人生 > 其它 >神經網路之優化演算法

神經網路之優化演算法

優化概述

下面概述一下常見的優化演算法,優化演算法的核心是梯度下降,不同優化演算法改進的地方在於梯度的方向和大小。可以將優化演算法粗分為兩大類,一類是改變方向的 Momentum,一類是改變學習率即梯度大小的 adagrad,最常用的 Adam 結合了這兩類的優點。

  • SGD:梯度下降。這裡有幾個概念需要辨析:GD 使用全部的樣本累積梯度,用累積的梯度更新權值;SGD 每次用一個樣本,計算梯度,更新權值;mini-batch SGD,使用一個 batch 的樣本累積梯度,更新權值,一般 SGD 的實現用的就是一個 batch 來更新權值,而不是一個樣本。
  • Momentum:儲存一個稱之為動量的變數,不斷累加梯度到動量上,最後動量更新權值。我們可以將梯度視為力,如果每一步都朝著同個方向前進,那麼累加的力就不會抵消,還會因為累加的效果可以在合力方向前進得更快,如果梯度某個方向上發生了震盪,即力的方向經常發生改變,那麼力會不斷抵消,因為在累加,所以這一步可能和上一步的力發生了抵消,在震盪的方向上將改變的少。可以閱讀 [5] 相關章節,用了一個小球的例子,挺形象的。
  • Nesterov momentum:和 Momentum 差不多,區別在於,累加的梯度計算。Momentum 使用的梯度是當前的梯度,而 Nesterov 使用的是“假想下一步位置的梯度” ,先用動量更新權值,然後計算新的位置,在新的位置上計算梯度,最後使用這個梯度去更新原來的動量,再用更新後的動量更新權值。[1]
  • adagrad:動態調整學習率,累加梯度的平方和,再用這個值計算學習率,累加的梯度越大,學習率就越小,因此 adagrad 的問題是,學習到後面,學習率越來越小,直接學不動了。
  • RMSProp:改進了 adagrad 的問題,RMSProp 不再是簡簡單單直接累加梯度,而是加權求和,權值之和為 1,設定舊的累積梯度為 \(\beta\)
    ,新的梯度為 \(1 - \beta\)。 [2]
  • adadelta:同樣是為了改進 adagrad 的問題,[3] 給出了詳細的公式,adadelta 在 RMSProp 的基礎上,改進了學習率的計算,這個方法甚至不需要手動設定學習率,在 pytorch 的實現中,學習率是可選的,預設是 1。
  • Adam:最常用的優化器了,結合了 Momentum 和 adagrad 的思想,計算動量和累加梯度的平方和,接著就是一頓算,根據動量算方向和大小,根據累加梯度的平方和算學習率。
  • Yogi:改進 Adam 的問題,在累加梯度的平方時,這個梯度可能很大(blows up),使到 Adam 不能收斂(even in convex setting),結合公式看一下就知道為什麼了,因為這個梯度大,學習率也大,後面看公式就知道了。Yogi 改進的點在於累加梯度的平方和那個公式。連結 [4] 的公式很詳細。動量和累加梯度有一個初始值,Yogi 還建議了使用一個 batch 來設定初始值。

關於學習率的討論

學習率太大,不能收斂;學習率太小,學得慢或者陷入區域性最小。學習率衰減,如果學習率一直都保持那麼大,會在最小值周圍震盪,而到達不了最小值。

初始化

權值初始化。[5] 這本書總結了一個最佳實踐:

當啟用函式為 sigmoid 或者 tanh 等 S 型曲線函式時,權重初始化使用 Xaiver 初始值。
當啟用函式使用 ReLU 時,權重初始化使用 He 初始值;

如果不用這兩種方法,還可以使用正態分佈來初始化。不能使用一個常量來初始化所有的權值,因為如果使用了常量,那麼每一層節點的輸出都是一樣的,最後一層每個節點輸出也一樣。這導致了梯度也是一樣的,之後每次更新權值也一樣,這樣之後,不管怎麼更新,整個神經網路權值還是一個量。

為什麼要進行 Xaiver 初始化、He 初始化?一個原因是解決梯度消失的問題,另一個是表達力受限的問題,很多節點都輸出相同的值,那麼可以由一個神經元表達同樣的事情。Xaiver 和 He 初始化,本質還是正態分佈,只不過使用了不同的標準差。Xaiver 的標準差取決於前一層的節點數,He 還要再乘個 2。[6] 的討論之處,權值初始化的問題,Batch Normalization 已經很好地解決了。

正態分佈初始化的問題

下面簡要分析一下正態分佈初始化存在的問題,具體看 [5] 的 P178 ~ P182,[5] 將 “正態分佈” 的資料在一個 “正態分佈初始化” 的權值的 5 層 MLP 網路上進行了前向傳播。

  • 標準差為 1,輸出偏向 0 或 1,因為使用的是 Sigmoid,在輸出偏向 0 或 1 時,梯度很小,想象一下 Sigmoid 的影象,如果輸出接近 0 或 1,曲線是不是很平緩呢。梯度在反向傳播的過程中,經過連乘,就沒有了,即存在梯度消失的問題。
  • 標準差為 0.01,輸出全部都集中在 0.5 附近,輸出太集中,存在的問題是表達力受限,因為多個節點輸出都是相同的值。

Xaiver 初始化存在的問題

Xaiver 在使用 ReLU 作為啟用函式的場景下,存在一些問題。輸出值偏向 0,在反向傳播的時候,梯度也會偏向 0,隨著層的加深,會出現梯度消失的問題。

Q: 為什麼輸出值偏小,在反向傳播的時候,梯度也會偏向 0 呢?

我們可以假設有這樣一個網路,輸入 x,中間有兩層。

\[a_1 = ReLU(W_1 x + b_1) \]\[y = W_2 a_1 + b_2 \]

我們可以計算 \(W_1\) 的梯度,下面的公式定性地理解,本渣渣意識到矩陣並不能隨便用鏈式法則,不過這裡為了定性分析,還是可以的,你也可以將 \(W_1\) 視為某一個具體的權值變數,而不是矩陣。

\[\frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial a_1} \frac{\partial ReLU(W_1 x + b_1)}{\partial (W_1 x + b_1)} \frac{\partial (W_1 x + b_1)}{\partial W_1} \]

其中最後一項微分可以得到 \(x\),即上一層的輸出。如果上一層的輸出小了,那麼梯度也會小,即偏向 0。疊多幾層,梯度就消失了。

優化的公式

當前階段,本渣渣只是知其然,不知所以然。天知道這些公式背後隱藏著多少的道理,為什麼要用動量,為什麼要累積梯度的平方?不過,雖然不知道為什麼,但是至少要求自己知道怎麼算。

這裡先宣告幾個符號:

  • \(W\),表示網路的權值。
  • \(g_t\),表示第 t 步的梯度,一般公式是 \(\frac{\partial L}{\partial W}\),如果是 mini-batch,那麼就是一個累積的梯度。
  • \(v_t\),表示第 t 步的動量,一般是梯度的累積。更新公式為 $v_t = \alpha v_{t-1} - \eta g_t $
  • \(h_t\),表示第 t 步的梯度平方和的累積。一般的更新公式為,\(h_t = h_{t-1} + g_t^2\)

SGD

梯度下降,權值朝著梯度的反方向去。

\[W \leftarrow W - \eta g_t \]

Momentum

\[v_t = \alpha v_{t-1} - \eta g_t \]\[W \leftarrow W + v_t \]

Nesterov momentum

先用上一步的動量,更新權值。

\[\hat{W} \leftarrow W + v_t \]

使用 \(\hat{W}\) 進行一次前向傳播,重新計算梯度 \(g_t\)

更新動量:

\[v_t = \alpha v_{t-1} - \eta g_t \]

再更新權值:

\[W \leftarrow W + v_t \]

adagrad

看到了下面公式,就知道為什麼 adagrad 學久了就學不動了吧,梯度累積的越多,越學不動。

\[h_t \leftarrow h_{t-1} + g_t^2 \]\[W \leftarrow W - \eta \frac{1}{\sqrt{h}} g_t \]

RMSProp

改進梯度累積的公式。

\[h_t \leftarrow \beta h_{t-1} + (1 - \beta) g_t^2 \]\[W \leftarrow W - \eta \frac{1}{\sqrt{h}} g_t \]

adadelta

改進了梯度的計算,注意到 adadelta 中的 delta 了吧,下面的公式真有 \(\Delta\)

\[h_t \leftarrow \beta h_{t-1} + (1 - \beta) g_t^2 \]\[g_t' \leftarrow \frac{\sqrt{\Delta W_{t-1} + \epsilon}}{\sqrt{h_t + \epsilon}} \odot g_t \]\[W \leftarrow W - \eta \frac{1}{\sqrt{h}} g_t \]\[\Delta W_{t} \leftarrow \rho \Delta W_{t-1} + (1 - \rho) g_t'^2 \]

Adam

結合了 Momentum 和 adagrad 的思想

\[v_t \leftarrow \beta_1 v_{t-1} - (1 - \beta_1) g_t \]\[h_t \leftarrow \beta_2 h_{t-1} - (1 - \beta_2) g_t^2 \]\[\hat{v_t} \leftarrow \frac{v_t}{1 - \beta_1^t} \]\[\hat{h_t} \leftarrow \frac{h_t}{1 - \beta_2^t} \]\[g_t'\leftarrow \frac{\eta \hat{v_t}}{\sqrt{\hat{h_t}} + \epsilon} \]\[W \leftarrow W - g_t' \]

Yogi

在累加梯度的平方時,這個梯度可能很大(blows up),使到 Adam 不能收斂(even in convex setting),因為這個梯度大,學習率也變大,結合上面的公式看看。

於是有人提出了下面的公式進行改進

\[h_t \leftarrow h_{t-1} - (1 - \beta_2) (g_t^2 - h_{t-1}) \]

[4] 提到了上述改進的問題:

Whenever \(g_t^2\) has high variance or updates are sparse, \(h_t\) might forget past values too quickly.

翻譯翻譯,當 \(g_t^2\) 方差較大的時候,\(h_t\) 前面的值可能很快就被忘記了,意思就是 \(h_t\) 前面的值在 \(h_t\) 中所佔的比重減少了。為了解決這個問題,Yogi 提出瞭如下的公式。

\[h_t \leftarrow h_{t-1} - (1 - \beta_2) g_t^2 \odot (g_t^2 - s_{t-1}) \]

總結

如上總結了常用的優化演算法,本渣渣知其然,不知所以然,背後有許多為什麼等待著探索。當前階段,搞清楚每一種優化演算法的優缺點,如何計算即可。此外,權值的初始化是一個比較重要的細節,需要稍加留意,在訓練太慢的時候,可以檢查一下。

參考連結

[1] https://zhuanlan.zhihu.com/p/73264637
[2] https://towardsdatascience.com/understanding-rmsprop-faster-neural-network-learning-62e116fcf29a
[3] https://d2l.ai/chapter_optimization/adadelta.html
[4] https://d2l.ai/chapter_optimization/adam.html
[5] 深度學習入門:齋藤康毅
[6] https://www.quora.com/Does-Xavier-initialization-work-well-when-the-activation-function-is-a-ReLu