深度學習的優化器(各類 optimizer 的原理、優缺點及數學推導)

FromL77 發表於 2021-06-30
深度學習

深度學習優化器

深度學習中的優化器均採用了梯度下降的方式進行優化,所謂煉丹我覺得優化器可以當作灶,它控制著火量的大小、形式與時間等。

♠ 初級的優化器

首先我們來一下看最初級的灶臺(100 - 1000 元)

Batch Gradient Descent (BGD)

名字叫做批梯度下降,實際上每次迭代會使用全部的資料來更新梯度(應該是取所有資料的平均梯度),具體公式如下:

\[\theta = \theta - \eta \cdot \nabla_{\theta} J(\theta) \]

虛擬碼如下:

for i in range(epochs):
    grad = eval_grad(losses, dataset, params)
    params = params - lr * grad

由上可知,每一個 epoch 更新一次梯度,每更新一次梯度需要使用全部資料。那麼我們基本可以總結:

  • 優點:如果是凸優化一定能取得全域性最優解,如果是非凸優化可以取得區域性最優解。
  • 缺點:如果資料量過大會使得優化時間變長,優化變得十分緩慢,而且對 memory 也有一定要求。

Stochastic Gradient Descent (SGD)

隨機梯度下降的公式如下:

\[\theta = \theta - \eta \cdot \nabla_{\theta}J(\theta;x^{(i)};y^{(i)}) \]

SGD 可以避免 BGD 因為大資料集而造成的冗餘計算,比如 BGD 會對相似的資料進行重複計算。SGD 則是每次只選擇一個樣本的資料來進行更新梯度,虛擬碼如下:

for i in range(epochs):
    random_shuffle(dataset)
    for data_i in dataset:
	grad = eval_grad(losses, data_i, params)
	params = params - lr * grad

由上很容易知道,SGD 每次更新只用一個資料,因此其優化之路像一個喝醉酒的醉漢一樣。這其實也是有利有弊的:

  • 優點:
    • 由於一次只用一個資料,因此梯度更新很快
    • 當然也可以進行線上學習(不用收齊所有資料)
    • 也會處於一個高 variance 的狀態,更新時 loss 比較震盪,可能會使得其跳出區域性最優點到達一個更好的區域性最優。
  • 缺點:
    • 也正因為震盪,很難收斂於一個精準的極小值。
      但實驗表明,只要對學習率進行調整就可以使得 SGD 的最終收斂效果與 BGD 一致。

Mini-Batch Gradient Descent

小批量隨機梯度下降可以看作是 SGD 和 BGD 的中間選擇,每次選擇數量為 n 的資料進行計算,既節約的每次更新的計算時間和成本,也減少了 SGD 的震盪,使得收斂更加快速和穩定。其公式為:

\[\theta = \theta - \eta \cdot \nabla_{\theta}J(\theta;x^{(i:i+n)};y^{(i:i+n)}) \]

虛擬碼如下:

for i in range(epochs):
    random_shuffle(dataset)
    for batch_data in dataset:
	grad = eval_grad(losses, batch_data, params)
	params = params - lr * grad

這應該也是我們平時真正使用的隨機梯度下降,當 n = 1 時,MiniBGD = SGD,n = len(dataset) 時 MiniBGD = BGD。
至此,初級灶臺雖說有所改進,但是仍然存在一下問題:

  • 選擇合適的學習率仍然是一個玄學
  • 學習率 schedule 需要預設不能自適應資料集的特點
  • 學習率針對所有引數,而並非所有引數需要同樣的學習率
  • 對於非凸問題極易陷入區域性最優

♣ 進階的優化器

再來看一下進階的灶臺(1000 - 5000元)

Momentum

深度學習的優化器(各類 optimizer 的原理、優缺點及數學推導)

看到動量這個單詞,我們不妨將優化想象為物理過程。如果將優化的空間比作現實的地形,地形的最低點是我們的目的地,優化的過程想象為需要將一輛小車到最低點,而優化器負責控制驅動(加減速、方向),資料的梯度則是負責操縱優化器的司機。
SGD 是每次選擇一個資料進行梯度更新,在”峽谷“地帶時,梯度經常異號就 loss 反覆震盪(如上圖)。這好比這輛車每開一步就換了一個司機(資料產生的梯度),每個司機的想法都不同。特別是到區域性最優的時候,由於梯度始終為 0 使得每個司機上車後都說剎車別走。
而 Momentum 會使 SGD 雖然每一步都要換司機,但是這個司機必須參考之前開過車所有司機的意見,當然主要參考最近幾次的,這樣即便在區域性最優,新司機覺得應該剎車(梯度為零)但是剛下車的幾位司機都認為應該向前衝,這使得小車可能衝過區域性最優(避免陷入區域性最優),在峽谷地形也會因為會參考幾個司機的建議(其實這裡近似於一個 mini-batch)而過分震盪(如上圖),公式如下:

\[\begin{aligned} v_t &= \gamma v_{t-1} + \eta \nabla_{\theta} J(\theta_{t-1}) \\ \theta_t &= \theta_{t-1} - v_t \end{aligned} \]

這就好像模擬將小球放在上坡,鬆手後,滾下坡去,它並不會在最低點直接停住,它會衝上對坡(因為有慣性,有動量)。要麼衝出去;要麼折回來反覆,最後因為能量耗盡停在最低點。

Nesterov Accelerated Gradient

Momentum 雖然會使考慮之前梯度與當前梯度來決定當前權重如何更新,但是它還不夠聰明,它不會預測未來可能的情況。由上面最後一式我們可以知道權重是由動量項更新的 \(v_t\) 來進行更新。我們來看看 NAG 是如何做的:

\[\begin{aligned} v_t &= \gamma v_{t-1} + \eta \nabla_{\theta} J(\theta_{t-1} - \gamma v_{t - 1}) \\ \theta_t &= \theta_{t-1} - v_t \end{aligned} \]

式中使用 \(\theta - \gamma v_{t - 1}\) 來近似預測未來的權重,並對它求導求得未來近似的梯度,來告訴權重未來可能的變化,並進行調整(以至於不在衝坡的時候過猛)。

♥ 智慧的優化器

最後是智慧的灶臺(5000 - 10000 元),之前的灶臺都要預先指定火的大小而且對所有食材用同樣大小的火,那有沒有更智慧的灶臺可以自己隨著烹飪時間甚至自己根據食材來控制火候呢?答案是肯定有的(不然叫什麼人工智慧^_-)。不過在介紹這個之前,我們先來看一個指數加權平均和偏差修正兩個技巧。
指數加權平均 & 偏差修正
公式推導如下:

\[\begin{aligned} v_t &= \beta v_{t-1} + (1 - \beta)\theta_{t} \\ v_{t-1} &= \beta v_{t-2} + (1 - \beta)\theta_{t - 1} \\ &\dots \\ v_{1} &= \beta v_{0} + (1 - \beta)\theta_{1} \end{aligned} \]

將上列等式由下分別迭代至上一式,並得知初始條件 \(v_{0}=0\)

\[v_t = (1 - \beta)\beta\theta_{t - 1} + (1 - \beta)\beta^2\theta_{t - 2} + \cdots + (1 - \beta)\beta^{n - 1}\theta_{1} \]

\(\beta\) 一般是一個小於 1 的數,由上式可知 t 時刻的變數會由前 t - 1 個時刻的變數共同決定,但每個變數前加了一個關於 \(\beta\) 的指數級係數,離 t 時刻越遠的權重越小,若是 \(\beta =0.9\) 第 20 個數的權重都在 0.01 左右了,後面的影響會更小。指數平均加權由此而來。其實我們很容易發現:

\[v_{1} = (1 - \beta)\theta_{1} \]

由於最初始的變數沒有多的變數可以平均,再加上 \((1 - \beta)\) 又很小,因此我們需要對初始時刻的變數進行一個修正:

\[\hat{v}_{t} = \frac{v_{t}}{1 - \beta^t} \]

上式可以解決由於在初期沒有變數可以平均的問題,使得 \({v}_{t} = \theta_{t}\)

Adagrad

Adagrad 是一種自適應梯度的優化器,它有什麼特點呢?它對不同引數使用不同的學習率,對於更新頻率較低的引數施以較大的學習率,對於更新頻率較高的引數用以較小的學習率。我們先來看一下公式:

\[g_{t,i} = \nabla_{\theta_t}J(\theta_{t, i}) \]

\(g_{t,i}\) 代表了第 t 步的第 i 個引數 \(\theta_{t, i}\) 的梯度,梯度更新則使用下列式子:

\[\theta_{t + 1, i} = \theta_{t, i} - \frac{\eta}{\sqrt{G_{t,ii}+\epsilon}} \cdot g_{t,i} \tag{a*} \]

\(G_t \in \mathbb{R}^{d \times d}\) 是一個對角矩陣,對角線上的元素 \(G_{t,ii}\) 計算如下:

\[G_{t,ii} = G_{t - 1,ii} + g_{t, i}^2 = \sum_{k = 0}^t g_{k, i}^2 \]

其實很容易發現,它就是 t 步之前所有步數關於引數 \(\theta_i\) 的平方和。可以看得出,如果 \(\theta_i\) 頻繁更新,那麼梯度肯定不為零,其平方必然大於零,(a*) 式中的 \(G_{t,ii}\) 會隨著更新次數的積累變得越來越大,分母越大,學習率變小,從而調小了 \(\theta_i\) 的學習率,比較而言更新少的學習率就大一些了。其總體的公式為:

\[\theta_{t} = \theta_{t - 1} - \frac{\eta}{\sqrt{G_{t - 1}+\epsilon}} \odot g_{t - 1} \]

實際上,Adagrad 在優化稀疏資料的時候表現會比較好,但是其缺點也是顯而易見的,由於 \(G_{t,ii}\) 是一個非負數,隨著步數增加很容易越累積越大,從導致學習率過早變小,學習緩慢。

Adadelta & RMSprop

對於這個問題 Adadelta & RMSprop 幾乎是在同一時間提出了改進方案。這裡只介紹 Adadelta 改進的最終版本,首先對梯度平方的累積改為了:

\[E[g^2]_t = \gamma E[g^2]_{t-1} + (1 - \gamma)g^2_t \]

上式其實就是使用了指數加權平均,使得第 t 步的 \(E[g^2]_t\) 只累加了離 t 較近的步數的梯度平方。每步權重更新的步長如下(並用均方根 RMS 進行表示):

\[\Delta \theta_t = -\frac{\eta}{\sqrt{E[g^2]_t + \epsilon}}g_t = -\frac{\eta}{RMS[g]_t}g_t \]

同時使用了指數加權平均來計算了步長的均方根:

\[\begin{aligned} E[\Delta\theta^2]_t &= \gamma E[\Delta\theta^2]_{t-1} + (1 - \gamma)\Delta\theta^2_t \\ RMS[\Delta\theta]_t &= \sqrt{E[\Delta\theta^2]_t + \epsilon} \end{aligned} \]

可能會比較疑惑計算這個幹嘛呢?這裡我們先講一下牛頓法,其公式如下:

\[x_n = x_{n-1} - \frac{f^{\prime}(x_{n-1})}{f^{\prime \prime}(x_{n-1})} \]

可以看出來其將二階導數的倒數作為”學習率“,所以一階導數和二階導數之比為步長即 \(\Delta x\),即有下列推導:

\[\begin{aligned} \because \quad&\Delta \theta \approx \frac{J^{\prime}(\theta)}{J^{\prime \prime}(\theta)} \\ \therefore \quad&\frac{1}{J^{\prime \prime}(\theta)} = \frac{\Delta \theta_t}{J^{\prime}(\theta)} \approx -\frac{RMS[\Delta\theta]_{t-1}}{RMS[g]_{t}} \end{aligned} \]

首先解釋一下,這裡沒有用 \(RMS[\Delta\theta]_{t}\) 是因為 \(\Delta \theta_t\) 還沒算出來。這裡基本可以理解為使用了 \(\Delta \theta_t\) 的均方根來近似 \(\Delta \theta_t\) 的值,使用了 \(g_t\) 的均方根來近似一階導數的值,從而近似了二階導數的倒數,最後 Adadelta 權重更新式子為:

\[\theta_t = \theta_{t-1} - \frac{RMS[\Delta\theta]_{t-2}}{RMS[g]_{t-1}}g_{t-1} \]

可見,Adadelta 甚至不需要指定學習率,RMSprop 思想大致相同,其更新公式如下:

\[\theta_t = \theta_{t-1} - \frac{\eta}{RMS[g]_{t-1}}g_{t-1} \]

Adam

Adaptive Moment Estimation 有點像是 Momentum 和 RMSProp 的結合體,首先對梯度,梯度的平方使用指數加權平均:

\[\begin{aligned} m_t = \beta_1 m_{t-1} + (1 - \beta_1)g_t \\ v_t = \beta_2 v_{t-1} + (1 - \beta_2)g_t^2 \end{aligned} \]

然後對其進行偏差修正:

\[\begin{aligned} \hat{m}_t = \frac{m_{t}}{1 - \beta_1^t} \\ \hat{v}_t = \frac{v_{t}}{1 - \beta_2^t} \end{aligned} \]

最後權重的更新公式如下:

\[\theta_t = \theta_{t-1} - \frac{\eta}{\sqrt{\hat{v}_{t-1}}+\epsilon}\hat{m}_{t-1} \]

原理就不過多介紹了,一般設定 \(\beta_1 = 0.9, \beta_2=0.999\),Adam 被認為是泛化極好,比起其他優化器效能也更好,並且是訓練神經網路的首選。

AdamW

深度學習的優化器(各類 optimizer 的原理、優缺點及數學推導)
AdamW 最主要是認為在使用 L2 正則化時,Adam 沒有正確使用。當我們使用 L2 正則化時,會在 loss 後面新增一個權重的平方項。比如我們如果使用的 SGD 則權重更新的表示式為(最後一項是正則化項): $$ \theta_t = \theta_{t-1} - \eta \cdot \nabla J(\theta_{t-1}) - \eta \lambda \theta_{t-1} $$ 如上圖所示,當我們使用動量的時候,實際上這裡的正則化不再與 SGD 中的相同,因為動量會累積之前的梯度,因此當使用動量的時候,權重更新的公式為: $$ \theta_t = \theta_{t-1} - m_t - (1 - \beta)\beta \eta\lambda\theta_{t - 1} + (1 - \beta)\beta^2\eta\lambda\theta_{t - 2} + \cdots $$ 聰明的小朋友已經發現了!因為動量裡面還有過往的梯度,因此在使用正則化的時候不能進行解耦,實際上的正則化項會包含以前的正則化項,當然像 Adam 還有一個梯度平方的累積就更不純粹了。於是 AdamW 直接在進行更改實現與無動量的 SGD 一樣的正則化: $$ \theta_t = \theta_{t-1} - \frac{\eta}{\sqrt{\hat{v}_{t-1}}+\epsilon}\hat{m}_{t-1} - \eta \lambda \theta_{t-1} $$ 最後我們來看看幾種優化器的效果圖吧!
深度學習的優化器(各類 optimizer 的原理、優缺點及數學推導)