卷積神經網路中的優化演算法比較
卷積神經網路一般力求能夠做到 end to end 的訓練, end to end 訓練也是目前深度學習的主流. 訓練中主要採用 Back Propagation 演算法, 而 BP 演算法是我們早就學過的梯度下降演算法的一種實現. 因此, CNN 中的各種優化演算法還是在梯度下降演算法上面做手腳, 這篇文章的主要目的是總結和比較深度學習中各種常用的優化演算法. 這這篇文章中不會涉及到怎麼求(偏)導數, 我們假設已經得到了(偏)導數.
Stochastic Gradient Descent
基本的 SGD
原始的 SGD 方法在機器學習中經常被稱為 Vanilla update, 這和我們在高中學習過的梯度下降演算法沒有什麼區別, 都是先求(偏)導數, 然後, 以一定的學習速率, 利用該(偏)導數去更新要優化的引數.
# Vanilla updatex += - learning_rate * dx |
Momentum Update
Momentum update 是一種加快收斂速度的方法(注意, 這裡說的不是計算速度). 這個方法來自於物理中的啟發. loss function 看做是一座山, 對於引數的初始化看做是在這個山坡上面隨機放一個小球. 這個小球的初始速度為 0, 其相對於地面(或者更嚴格的說是山谷, 也是Loss Function 優化過程中的區域性最優點)的勢能為 U=mgh,
其中, m 是小球的質量, g 是重力加速度,
h 是小球的位置相對於山谷最低點的高度. 因為 m
# Momentum updatev = mu * v - learning_rate * dx # integrate velocityx += v # integrate position |
這裡的變數 mu
就是我們在各種深度學習框架中/論文中見到的 momentum. 一般設定為 0.9, 0.99 等. 然而, 把 mu
mu
的作用是在小球在山坡上滾動的過程中來降低小球的動能.
否則, 如果沒有這個因素, 那麼, 小球永遠不會停下來, 優化過程也永遠不會停止.
Nesterov Momentum
Nesterov Momentum 是最近比較流行的一種與傳統的momentum update稍有不同的方法, Nesterov Momentum 在凸優化中有較強的理論保證收斂, 並且,在實踐中, Nesterov Momentum也比單純的 Momentum 的效果要好. 核心思想是: 注意到 momentum 方法, 如果只看 mu
* v
項, 那麼, 當前的 x
進過 momentum 的作用會變成 x+
mu*v
. 因此, 可以把 x+ mu*v
這個位置看做是當前優化的一個”展望”位置, 所以, 可以在 x+
mu*v
求導, 而不是原始的 x
x_ahead = x + mu * v# evaluate dx_ahead (the gradient at x_ahead instead of at x)v = mu * v - learning_rate * dx_aheadx += v |
自適應優化演算法
在優化的過程中, 如果學習率設定的過大, 那麼, 小球就會在山谷之間跳來跳去, 無法到達最低點, 如果是非常緩慢地降低學習速率, 那麼, 雖然小球跳來跳去的現象會有緩解, 但是, 效果不明顯, 浪費了計算資源, 而如果快速降低學習速率, 那麼, 小球可能會很快到達一個(不是那麼好的)區域性最優點, 而不能到達一個更好的區域性最優點. 通常, 在實驗中有下面三種方法:
Step decay 每隔幾個epoch減少一次learning rate, 一般是每執行5個epoch左右把learning rate減少一半, 或者是每隔20個epoch減少為原來的1/10. 然而,具體的learning rate 的調整方法還是要看網路的設計和模型. 另外, 還有一種方法是, 在訓練的過程中觀察training error和validation error, 當validation error不再減小的時候, 減小learning rate.
Exponential decay 的數學描述是 α=α0e−kt, 其中 α0,k 是超引數, t 是迭代次數.
1/t decay 的數學描述是α=α01+kt 其中 (α0,k) 是超引數, t 是迭代次數.
PS: 我一般使用第一種方法.
逐個調整引數
上面討論過的所有的方法都是所有的引數都使用相同的調整策略. 調整學習率的代價一般比較高, 因此, 有許多研究還是如何自動調學習率, 甚至是調整每一個需要優化的引數的學習率. 在這些方法中往往會引入更多的超引數, 但是, 這些方法對超引數的設定並不是那麼敏感, 因此, 總會比上面討論過的調整學習率的方法要好用.
AdaGrad
pass
# Assume the gradient dx and parameter vector xcache += dx**2x += - learning_rate * dx / (np.sqrt(cache) + eps) |
cache
的大小和gradient的大小相同, 記錄的是每個引數的梯度的平方和. 之後,cache
用來以
element-wise 的方式 normalize 引數的更新. 從上面的程式碼中可以看出, 梯度較高的權重的有效 learning rate 會減小, 相應的梯度較低的權重的有效 learning rate 會增大. 這裡, 讓我感到不解的是, sqrt
操作非常重要,如果沒有 sqrt
操作,
該演算法的效果非常差. eps
用來避免出現除 0 錯誤的,一般設定為 10−4 到 10−8 之間.
(這種方法,在 CNN 中訓練中極為常見, 比如在 batch normalization 中也是通過這種方法避免出現除 0 的錯誤). Adagrad 的缺點是,在深度學習中, 這種方法導致學習率的調整太激進, 因此常常過早結束了學習過程.
RMSProp
RMSProp是一個非常高效的演算法, 但是目前並沒有發表. Hinton 老爺子果然任性. RMSProp 對 AdaGrad 稍作改進, 是的演算法不再像 AdaGrad 那麼激進 它使用的是 moving average of squared gradients, 有關 moving average 的相關解釋可以參考 batch normalization 的實現說明.
cache = decay_rate * cache + (1 - decay_rate) * dx**2x += - learning_rate * dx / (np.sqrt(cache) + eps) |
decay_rate
是超引數, 一般可以設定為0.9,0.99,0.999.
其中, x+=
操作和Adagrad完全相同, 但是, 變數cache
是leaky的,所以,
RMSProp 仍然根據每個權重的gradient來調整learning rate.
Adam
Adam 是最近提出的一種演算法, 該演算法和(下面將要介紹的)帶有momentum的RMSprop比較類似, 過程類似於:
m = beta1*m + (1-beta1)*dxv = beta2*v + (1-beta2)*(dx**2)x += - learning_rate * m / (np.sqrt(v) + eps) |
該方法和 RMSProp 唯一的區別是 “smooth” 過程, 這裡使用的是 m
來做 smooth 操作, 而不是使用原始的 gradient vector dx
.
論文中推薦的超引數為 eps=1e-6, bata1=0.9, beta2=0.999
, 在實踐中, 推薦使用Adam方法. Adam
演算法通常會比 RMSProp 演算法效果好. 另外,也可以嘗試 SGD+Nesterov Momentum. 完整的Adam演算法中還包括 bias 的糾正機制, 這是因為,在剛開始的幾個steps中,m
和 v
都要初始化,
並且在 warm up 之前他們都 biased at zero. 更多的細節可以參考論文原文.