從 SGD 到 Adam —— 深度學習優化算法

文章轉載於:從 SGD 到 Adam —— 深度學習優化算法概覽(一)梯度下降優化算法綜述

1. 引言

最優化問題是計算數學中最爲重要的研究方向之一。而在深度學習領域,優化算法的選擇也是一個模型的重中之重。即使在數據集和模型架構完全相同的情況下,採用不同的優化算法,也很可能導致截然不同的訓練效果。

梯度下降是目前神經網絡中使用最爲廣泛的優化算法之一。爲了彌補樸素梯度下降的種種缺陷,研究者們發明了一系列變種算法,優化算法經歷了 SGD -> SGDM -> NAG -> AdaGrad -> AdaDelta -> Adam -> NAdam 這樣的發展歷程。從最初的 SGD (隨機梯度下降) 逐步演進到 NAdam。然而,許多學術界最爲前沿的文章中,都並沒有一味使用 Adam/NAdam 等公認“好用”的自適應算法,很多甚至還選擇了最爲初級的 SGD 或者 SGD with Momentum 等。

本文旨在梳理深度學習優化算法的發展歷程,並在一個更加概括的框架之下,對優化算法做出分析和對比。

2. Gradient Descent

梯度下降是指,在給定待優化的模型參數 θRd\theta \in \mathbb{R}^d 和目標函數 J(θ)J(\theta) 後,算法通過沿梯度 θJ(θ)\nabla_\theta J(\theta)的相反方向更新 θ\theta 來最小化 J(θ)J(\theta) 。學習率 η\eta 決定了每一時刻的更新步長。對於每個epoch tt ,我們可以用下述步驟描述梯度下降的流程:

(1) 計算目標函數關於參數的梯度
gt=θJ(θ) g_t = \nabla_\theta J(\theta)
(2) 根據歷史梯度計算一階和二階動量
mt=ϕ(g1,g2, ,gt) m_t = \phi(g_1, g_2, \cdots, g_t)

vt=ψ(g1,g2, ,gt) v_t = \psi(g_1, g_2, \cdots, g_t)

(3) 計算當前時刻的下降梯度(步長) mtvt+ϵ\frac{m_t}{\sqrt{v_t + \epsilon}} ,並根據下降梯度更新模型參數
θt+1=θtmtvt+ϵ \theta_{t+1} = \theta_t - \frac{m_t}{\sqrt{v_t + \epsilon}}
其中, ϵ\epsilon 爲平滑項,防止分母爲零,通常取 1e81e-8

注意:本文提到的步長,就是下降梯度。

3. Gradient Descent 和其算法變種

根據以上框架,我們來分析和比較梯度下降的各變種算法。

4. Vanilla SGD

樸素 SGD (Stochastic Gradient Descent) 最爲簡單,沒有動量的概念,即
mt=ηgt m_t = \eta g_t

vt=I2 v_t = I^2

ϵ=0 \epsilon = 0

這時,更新步驟就是最簡單的
θi+1=θtηgt \theta_{i+1}= \theta_t - \eta g_t
缺點:

  • 收斂速度慢,而且可能會在溝壑的兩邊持續震盪
  • 容易停留在一個局部最優點
  • 如何合理的選擇學習率也是 SGD 的一大難點

5. SGD with Momentum

爲了抑制SGD的震盪,SGD-M認爲梯度下降過程可以加入慣性(動量) Momentum[3],加速 SGD 在正確方向的下降並抑制震盪。就好像下坡的時候,如果發現是陡坡,那就利用慣性跑的快一些。其在SGD的基礎上,引入了一階動量,然後進行更新:
mt=γmt1+ηgtθt+1=θtmt m_t = \gamma m_{t-1} + \eta g_t \\ \theta_{t+1}= \theta_t - m_t
即在原步長(SGD中是ηgt\eta g_t) 之上,增加了與上一時刻動量相關的一項 γmt1\gamma m_{t-1},目的是結合上一時刻步長(下降梯度), 其中 mt1m_{t-1} 是上一時刻的動量,γ\gamma 是動量因子。也就是說,tt 時刻的下降方向,不僅由當前點的梯度方向決定,而且由此前累積的下降方向決定,γ\gamma 通常取 0.9 左右。這就意味着下降方向主要是此前累積的下降方向,並略微偏向當前時刻的下降方向。這使得參數中那些梯度方向變化不大的維度可以加速更新,並減少梯度方向變化較大的維度上的更新幅度。由此產生了加速收斂和減小震盪的效果。

在這裏插入圖片描述 在這裏插入圖片描述
圖 1(a): SGD 圖 1(b): SGD with momentum

從圖 1 中可以看出,引入動量有效的加速了梯度下降收斂過程。

6. Nesterov Accelerated Gradient

SGD 還有一個問題是困在局部最優的溝壑裏面震盪。想象一下你走到一個盆地,四周都是略高的小山,你覺得沒有下坡的方向,那就只能待在這裏了。可是如果你爬上高地,就會發現外面的世界還很廣闊。因此,我們不能停留在當前位置去觀察未來的方向,而要向前一步、多看一步、看遠一些。

圖 2: Nesterov update

NAG全稱Nesterov Accelerated Gradient,則是在SGD、SGD-M的基礎上的進一步改進,算法能夠在目標函數有增高趨勢之前,減緩更新速率。我們知道在時刻 tt 的主要下降方向是由累積動量決定的,自己的梯度方向說了也不算,那與其看當前梯度方向,不如先看看如果跟着累積動量走了一步後,那個時候再怎麼走。因此,NAG在步驟1,不計算當前位置的梯度方向,而是計算如果按照累積動量走了一步,那個時候的下降方向
(1)gt=θJ(θγmt1) g_t = \nabla_\theta J(\theta - \gamma m_{t-1}) \tag{1}
參考圖2理解:最初,SGD-M先計算當前時刻的梯度(短藍向量)和累積動量 (長藍向量)進行參數更新。改進的方法NAG,先利用累積動量計算出下一時刻的 θ\theta 的近似位置 θγmt1\theta - \gamma m_{t-1}(棕向量),並根據該未來位置計算梯度(紅向量)公式(1),然後使用和 SGD-M 中相同的方式計算當前時刻的動量項(下降梯度),進而得到完整的參數更新(綠向量),公式(2)即將該未來位置的梯度與累積動量計算當前時刻的動量項(下降梯度):
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \begin{split} …

更新參數:
(3)θt+1=θtmt \theta_{t+1} = \theta_t - m_t \tag{3}

這種計算梯度的方式可以使算法更好的「預測未來」,提前調整更新速率。

注意:
累積動量指的是上一時刻的動量乘上動量因子: γmt1\gamma m_{t-1}
當前時刻的動量項指的是: mt{m_t}
(上面的圖只是爲了助於理解,其中累積動量

7. Adagrad

SGD、SGD-M 和 NAG 均是以相同的學習率去更新 θ\theta 的各個分量 θi\theta_i。而深度學習模型中往往涉及大量的參數,不同參數的更新頻率往往有所區別。對於更新不頻繁的參數(典型例子:更新 word embedding 中的低頻詞),我們希望單次步長更大,多學習一些知識;對於更新頻繁的參數,我們則希望步長較小,使得學習到的參數更穩定,不至於被單個樣本影響太多。

Adagrad在 tt 時刻對每一個參數 θi\theta_i 使用了不同的學習率,我們首先介紹 Adagrad 對每一個參數的更新,然後我們對其向量化。爲了簡潔,令 gt,ig_{t,i} 爲在 tt 時刻目標函數關於參數 θiθ_i 的梯度:
gt,i=θJ(θi) g_{t, i} = \nabla_\theta J(\theta_{i})
tt 時刻,對每個參數 θiθ_i 的更新過程變爲:
θt+1,i=θt,iηgt,i \theta_{t+1, i} = \theta_{t, i} - \eta g_{t, i}
對於上述的更新規則,在 tt 時刻,基於對 θiθ_i 計算過的歷史梯度,Adagrad修正了對每一個參數 θiθ_i 的學習率:
θt+1,i=θt,iηvt,ii+ϵgt,i \theta_{t+1, i}=\theta_{t, i}-\frac{\eta}{\sqrt{v_{t, i i}+\epsilon}} \cdot g_{t, i}
其中, vtRd×dv_t \in \mathbb{R}^{d\times d} 是對角矩陣,其元素 vt,iiv_{t, ii} 爲參數 第 ii從初始時刻到 tt 時刻的梯度平方和。即通過引入二階動量來調整每一個參數的學習率,等效爲 η/vt+ϵ\eta / \sqrt{v_t + \epsilon}
vt=diag(i=1tgi,12,i=1tgi,22, ,i=1tgi,d2) v_t = \text{diag}(\sum_{i=1}^t g_{i,1}^2, \sum_{i=1}^t g_{i,2}^2, \cdots, \sum_{i=1}^t g_{i,d}^2)
由於 vtv_t 的對角線上包含了關於所有參數 θθ 的歷史梯度的平方和,現在,我們可以通過 vtv_tgtg_t 之間的元素向量乘法⊙向量化上述的操作:
θt+1=θtηvt+ϵgt \theta_{t+1}=\theta_{t}-\frac{\eta}{\sqrt{v_{t}+\epsilon}} \odot g_{t}
Adagrad算法的一個主要優點是無需手動調整學習率。在大多數的應用場景中,通常採用常數0.01。通過引入二階動量,對於此前頻繁更新過的參數,其二階動量的對應分量較大,學習率就較小。這一方法在稀疏數據的場景下表現很好。但也存在一些問題因爲 vt\sqrt{v_t} 是單調遞增的,會使得學習率單調遞減至0,可能會使得訓練過程提前結束,即便後續還有數據也無法學到必要的知識。

8. AdaDelta

AdadeltaAdagrad 的一種擴展算法,以處理Adagrad學習速率單調遞減的問題。考慮在計算二階動量時,不是計算所有的歷史梯度平方,而只關注最近某一時間窗口內的下降梯度,Adadelta將計算計算曆史梯度的窗口大小限制爲一個固定值 ww

Adadelta 中,無需存儲先前的 ww 個平方梯度,而是將梯度的平方遞歸地表示成所有歷史梯度平方的均值。在 tt 時刻的均值 E[g2]tE[g^2]_t 只取決於先前的均值和當前的梯度(分量 γγ 類似於動量項):
E[g2]t=γE[g2]t1+(1γ)gt2 E\left[g^{2}\right]_{t}=\gamma E\left[g^{2}\right]_{t-1}+(1-\gamma) g_{t}^{2}
我們將 γγ 設置成與動量項相似的值,即0.9左右。爲了簡單起見,我們利用參數更新向量 ΔθtΔθ_t 重新表示SGD的更新過程:

Δθt=ηgt,iθt+1=θt+Δθt \begin{array}{c}{\Delta \theta_{t}=-\eta \cdot g_{t, i}} \\ {\theta_{t+1}=\theta_{t}+\Delta \theta_{t}}\end{array}
我們先前得到的 Adagrad 參數更新向量變爲:
Δθt=ηvt+ϵgt \Delta \theta_{t}=-\frac{\eta}{\sqrt{v_{t}+\epsilon}} \odot g_{t}
現在,我們簡單將對角矩陣 vtv_t 替換成歷史梯度的均值 E[g2]tE[g^2]_t
Δθt=ηE[g2]t+ϵgt \Delta \theta_{t}=-\frac{\eta}{\sqrt{E\left[g^{2}\right]_{t}+\epsilon}} g_{t}
由於分母僅僅是梯度的均方根(root mean squared,RMS)誤差,我們可以簡寫爲:
Δθt=ηRMS[g]tgt \Delta \theta_{t}=-\frac{\eta}{R M S[g]_{t}} g_{t}
作者指出上述更新公式中的每個部分(與SGD,SDG-M 或者Adagrad)並不一致,即更新規則中必須與參數具有相同的假設單位。爲了實現這個要求,作者首次定義了另一個指數衰減均值,這次不是梯度平方,而是參數的平方的更新:
E[Δθ2]t=γE[Δθ2]t1+(1γ)Δθt2 E\left[\Delta \theta^{2}\right]_{t}=\gamma E\left[\Delta \theta^{2}\right]_{t-1}+(1-\gamma) \Delta \theta_{t}^{2}
因此,參數更新的均方根誤差爲:
RMS[Δθ]t=E[Δθ2]t+ϵ R M S[\Delta \theta]_{t}=\sqrt{E\left[\Delta \theta^{2}\right]_{t}+\epsilon}
由於 RMS[Δθ]tRMS[Δθ]_t 是未知的,我們利用參數的均方根誤差來近似更新。利用 RMS[Δθ]t1RMS[Δθ]_{t−1} 替換先前的更新規則中的學習率 ηη,最終得到 Adadelta 的更新規則:
Δθt=RMS[Δθ]t1RMS[g]tgt \Delta \theta_{t}=-\frac{R M S[\Delta \theta]_{t-1}}{R M S[g]_{t}} g_{t}

θt+1=θt+Δθt \theta_{t+1}=\theta_{t}+\Delta \theta_{t}

使用 Adadelta 算法,我們甚至都無需設置默認的學習率,因爲更新規則中已經移除了學習率。

9. RMSprop

RMSprop 是一個未被髮表的自適應學習率的算法,該算法由Geoff Hinton在其Coursera課堂的課程6e中提出。

RMSprop和Adadelta在相同的時間裏被獨立的提出,都起源於對Adagrad的極速遞減的學習率問題的求解。實際上,RMSprop 是先前我們得到的 Adadelta 的第一個更新向量的特例:
E[g2]t=0.9E[g2]t1+0.1gt2θt+1=θtηE[g2]t+ϵgt \begin{array}{l}{E\left[g^{2}\right]_{t}=0.9 E\left[g^{2}\right]_{t-1}+0.1 g_{t}^{2}} \\ {\theta_{t+1}=\theta_{t}-\frac{\eta}{\sqrt{E\left[g^{2}\right]_{t}+\epsilon}} g_{t}}\end{array}
同樣,RMSprop 將學習率分解成一個平方梯度的指數衰減的平均。Hinton建議將 γγ 設置爲0.9,對於學習率 ηη,一個好的固定值爲0.001。

10. Adam

Adam 可以認爲是 RMSprop 和 Momentum 的結合。和 RMSprop 對二階動量使用指數移動平均類似,Adam 中對一階動量也是用指數移動平均計算。
mt=η[β1mt1+(1β1)gt] m_t = \eta[ \beta_1 m_{t-1} + (1 - \beta_1)g_t ]
vt=β2vt1+(1β2)diag(gt2) v_t = \beta_2 v_{t-1} + (1-\beta_2) \cdot \text{diag}(g_t^2)

其中,初值

m0=0v0=0 m_0 = 0 \\ v_0 = 0
注意到,在迭代初始階段,mtm_tvtv_t 有一個向初值的偏移(過多的偏向了 0)。因此,可以對一階和二階動量做偏置校正 (bias correction),
m^t=mt1β1t \hat{m}_t = \frac{m_t}{1-\beta_1^t}

v^t=vt1β2t \hat{v}_t = \frac{v_t}{1-\beta_2^t}
再進行更新,

θt+1=θt1v^t+ϵm^t \theta_{t+1} = \theta_t - \frac{1}{\sqrt{\hat{v}_t} + \epsilon } \hat{m}_t
可以保證迭代較爲平穩。

11. NAdam

這裏還沒有太看懂,先把上面的優化算法完全理解了再說!

NAdam 在 Adam 之上融合了 NAG 的思想。

首先回顧 NAG 的公式,

gt=θJ(θtγmt1) g_t = \nabla_\theta J(\theta_t - \gamma m_{t-1})

mt=γmt1+ηgt m_t = \gamma m_{t-1} + \eta g_t

θt+1=θtmt \theta_{t+1} = \theta_t - m_t

NAG 的核心在於,計算梯度時使用了「未來位置」θtγmt1\theta_t - \gamma m_{t-1}。NAdam 中提出了一種公式變形的思路,大意可以這樣理解:只要能在梯度計算中考慮到「未來因素」,即能達到 Nesterov 的效果;既然如此,那麼在計算梯度時,可以仍然使用原始公式 gt=θJ(θt)g_t = \nabla_\theta J(\theta_t) ,但在前一次迭代計算 θt\theta_t 時,就使用了未來時刻的動量,即 θt=θt1mt\theta_t = \theta_{t-1} - m_t ,那麼理論上所達到的效果是類似的。

這時,公式修改爲,

gt=θJ(θt) g_t = \nabla_\theta J(\theta_t)

mt=γmt1+ηgt m_t = \gamma m_{t-1} + \eta g_t

mˉt=γmt+ηgt \bar{m}_t = \gamma m_t + \eta g_t

θt+1=θtmˉt \theta_{t+1} = \theta_t - \bar{m}_t

理論上,下一刻的動量爲 mt+1=γmt+ηgt+1m_{t+1} = \gamma m_t + \eta g_{t+1},在假定連續兩次的梯度變化不大的情況下,即 gt+1gtg_{t+1} \approx g_t ,有 mt+1γmt+ηgtmˉtm_{t+1} \approx \gamma m_t + \eta g_t \equiv \bar{m}_t。此時,即可用 mˉt\bar{m}_t 近似表示未來動量加入到 θ\theta 的迭代式中。

類似的,在 Adam 可以加入 mˉtm^t\bar{m}_t \leftarrow \hat{m}_t 的變形,將 m^t\hat{m}_t 展開有

mt^=mt1β1t=η[β1mt11β1t+(1β1)gt1β1t] \hat{m_t} = \frac{m_t}{1-\beta_1^t} = \eta[ \frac{\beta_1 m_{t-1}}{1-\beta_1^t} + \frac{(1 - \beta_1)g_t}{1-\beta_1^t} ]
引入

mˉt=η[β1mt1β1t+1+(1β1)gt1β1t] \bar{m}_t = \eta[ \frac{\beta_1 m_{t}}{1-\beta_1^{t+1}} + \frac{(1 - \beta_1)g_t}{1-\beta_1^t} ]
再進行更新,
θt+1=θt1v^t+ϵmˉt \theta_{t+1} = \theta_t - \frac{1}{\sqrt{\hat{v}_t} + \epsilon } \bar{m}_t
即可在 Adam 中引入 Nesterov 加速效果。

12. 選擇使用哪種優化算法

那麼,我們應該選擇使用哪種優化算法呢?如果輸入數據是稀疏的,選擇任一自適應學習率算法可能會得到最好的結果。選用這類算法的另一個好處是無需調整學習率,選用默認值就可能達到最好的結果。

總的來說,RMSprop是Adagrad的擴展形式,用於處理在Adagrad中急速遞減的學習率。RMSprop與Adadelta相同,所不同的是Adadelta在更新規則中使用參數的均方根進行更新。最後,Adam是將偏差校正和動量加入到RMSprop中。在這樣的情況下,RMSprop、Adadelta和Adam是很相似的算法並且在相似的環境中性能都不錯。Kingma等人[9]指出在優化後期由於梯度變得越來越稀疏,偏差校正能夠幫助Adam微弱地勝過RMSprop。綜合看來,Adam可能是最佳的選擇。

有趣的是,最近許多論文中採用不帶動量的SGD和一種簡單的學習率的退火策略。已表明,通常SGD能夠找到最小值點,但是比其他優化的SGD花費更多的時間,與其他算法相比,SGD更加依賴魯棒的初始化和退火策略,同時,SGD可能會陷入鞍點,而不是局部極小值點。因此,如果你關心的是快速收斂和訓練一個深層的或者複雜的神經網絡,你應該選擇一個自適應學習率的方法。

13. 可視化分析

在這裏插入圖片描述 在這裏插入圖片描述
圖 3: SGD optimization on loss surface contours 圖 4: SGD optimization on saddle point

圖 3 和圖 4 兩張動圖直觀的展現了不同算法的性能。(Image credit: Alec Radford)

圖 3 中,我們可以看到不同算法在損失面等高線圖中的學習過程,它們均同同一點出發,但沿着不同路徑達到最小值點。其中 Adagrad、Adadelta、RMSprop 從最開始就找到了正確的方向並快速收斂;SGD 找到了正確方向但收斂速度很慢;SGD-M 和 NAG 最初都偏離了航道,但也能最終糾正到正確方向,SGD-M 偏離的慣性比 NAG 更大。

圖 4 展現了不同算法在鞍點處的表現。這裏,SGD、SGD-M、NAG 都受到了鞍點的嚴重影響,儘管後兩者最終還是逃離了鞍點;而 Adagrad、RMSprop、Adadelta 都很快找到了正確的方向。
關於兩圖的討論,也可參考[2]和[8]。
可以看到,幾種自適應算法在這些場景下都展現了更好的性能。

14. Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章