1. 程式人生 > >深度學習中的優化方法總結

深度學習中的優化方法總結

梯度下降沿著整個訓練集的梯度方向下降。可以使用隨機梯度下降很大程度地加速,沿著隨機挑選的小批量資料的梯度下降。

批量演算法和小批量演算法

使用小批量的原因

  • n個樣本均值的標準差是σn√σn,其中σσ是樣本值真實的標準差。分母n−−√n表明使用更多的樣本來估計梯度的方法的回報是低於線性的。
  • 另一個促使從小數目樣本中獲得梯度的統計估計的動機是訓練集的冗餘。大量樣本可能對梯度做出了非常相似的貢獻。
  • 可能是由於小批量在學習過程中加入了噪聲,它們會有一些正則化效果。

其他

  • 魯棒性 不同的演算法使用不同的方法從小批量中獲取不同的資訊。有些演算法對取樣誤差比其他演算法更敏感,這通常有兩個原因。一個是它們使用了很難在少量樣本上精確估計的資訊,另一個是它們以放大采樣誤差的方式使用了資訊。 基於梯度gg的更新方法通常相對魯棒,並能使用較小的批量獲得成功,如100。使用Hessian矩陣HH,計算如H−1gH−1g更新的二階方法通常需要更大的批量,如10000。

  • 隨機順序 小批量是隨機抽取的這點也很重要。從一組樣本中計算出梯度期望的無偏估計要求這些樣本是獨立的。在資料集中的順序有很大影響的情況下,有必要在抽取小批量樣本前打亂樣本順序。 不以某種方式打亂樣本順序會極大地降低演算法的效能。

  • 非同步並行 在計算小批量樣本XX上最小化J(X)J(X)的更新時,同時可以計算其他小於樣本上的更新。
  • 無重複樣本,遵循真實泛化誤差的梯度 很多小批量隨機梯度下降方法的實現都會打亂資料順序一次,然後多次遍歷資料來更新引數。第一次遍歷,每個小批量樣本都用來計算真實泛化誤差的無偏估計。第二次遍歷,估計將會是有偏的,因為重新抽取了已經用過的樣本,而不是從和原先樣本相同的資料生成分佈中獲取新的無偏的樣本。
  • 線上學習中的SGD 線上學習中,樣本永遠不會重複,每次更新的樣本是從分佈中取樣獲得的無偏樣本。
  • 實際使用 在實際使用中,除非訓練集特別大,通常還是多次遍歷訓練集,額外的遍歷會由於減小訓練誤差而得到足夠的好處,以抵消其帶來的訓練誤差和測試誤差差距的增加。

基本演算法

隨機梯度下降SGD

SGD及其變種是深度學習中應用最多的優化演算法。按照資料生成分佈抽取m個小批量(獨立同分布)樣本,通過計算它們的梯度均值,我們可以得到梯度無偏估計。這裡寫圖片描述 SGD演算法中的一個關鍵引數是學習率。在實踐中,有必要隨著時間的推移逐漸降低學習率。 將第k步迭代的學習率記作ϵkϵk。一般會線性衰減學習率直到第ττ次迭代: ϵk=(1−α)ϵ0+αϵτϵk=(1−α)ϵ0+αϵτ 其中α=kτα=kτ。在ττ次迭代之後,一般使ϵϵ保持常數。 通常ϵτϵτ應設為大約ϵ0ϵ0的1%。 對於足夠大的資料集,SGD可能會在處理整個訓練集之前就收斂到最終測試集誤差的某個固定容差範圍內。 批量梯度下降在理論上比隨機梯度下降有更好的收斂率。可以在學習過程中逐漸增大批量大大小,以此權衡批量梯度下降和隨機梯度下降兩者的優點。

帶動量的SGD

指數加權平均數 vt=βvt−1+(1−β)θtvt=βvt−1+(1−β)θt 該公式利用過去11−β11−β項的θθ的加權平均來計算當前的vtvt偏差修正 使用vt1−βtvt1−βt來對進行修正,當t比較大的時候,分母接近於1。 在機器學習中,一般不進行偏差修正,人們一般不使用初期的引數。這裡寫圖片描述 另一種形式這裡寫圖片描述 在實踐中,動量引數一般取值為0.5,0.9和0.99。和學習率一樣,αα也會隨著時間不斷調整。一般初始值是一個較小的值,隨後會慢慢變大。隨著時間調整αα沒有收縮ϵϵ重要。

Nesterov動量

Nesterov動量中,梯度計算在施加當前速度之後。因此,Nesterov動量可以解釋為往標準動量方法中添加了一個校正因子。這裡寫圖片描述

標準的動量方法(由Nesterov在1983年提出)是在當前位置計算梯度,然後在累積的更新梯度方向上做一個大的跳躍。下面給出了一種更好地動量方法(由IIya Sutskever在2012年提出),其先在先前累積的梯度方向上做一個大的跳躍,再計算新的梯度並修正錯誤。
下面對兩種方法做了比較,圖中藍色箭頭是做兩次標準動量方法得到的;而圖中棕色箭頭是改進動量方法先做的一次大跳躍得到的,紅色箭頭是修正,綠色箭頭是進行一次改進動量方法得到的。可以看到,改進的比標準的要快很多。

這裡寫圖片描述

自適應學習率演算法

習率演算法

AdaGrad

AdaGrad獨立地適應所有模型引數的學習率,縮放每個引數反比於其所有梯度歷史平方值總和的平方根

這裡寫圖片描述 在凸優化中,AdaGrad演算法具有一些令人滿意的理論性質。然而,經驗上對於訓練深度神經網路模型而言,從訓練開始時積累梯度平方會導致有效學習率過早和過量減小。

AdaDelta

AdaGrad主要存在三個問題

  • 其學習率是單調遞減的,訓練後期學習率非常小
  • 其需要手工設定一個全域性的初始學習率
  • 更新θθ時,左右兩邊的單位不同一

我們令每一個時刻的rr隨之時間按照ρρ指數衰減,這樣就相當於僅使用離當前時刻比較近的gg資訊,不會使得rr增加過快,分母過大而導致引數更新減緩。在AdaDelta中,累積平方梯度定義為 r←ρr+(1−ρ)g⊙gr←ρr+(1−ρ)g⊙g 在計算更新時,用梯度歷史該變數平方值期望的平方根代替學習率ηη,則得到Adadelta更新規則:這裡寫圖片描述 由此看出,甚至不需要設定預設學習率,因為更新規則已經不受它影響了

RMSProp

RMSProp演算法修改AdaGrad以在非凸設定下效果更好,改變梯度累積為指數加權的移動平均。 AdaGrad旨在應用於凸問題時快速收斂。AdaGrad根據平方梯度的整個歷史收縮學習率,可能使得學習率在達到這樣的凸結構前就變得太小了。 RMSprop使用指數衰減平均來丟棄遙遠過去的歷史,使其在找到凸結構後快速收斂,就像一個初始化於該碗裝結構的AdaGrad演算法例項。 相比於AdaGrad,使用移動平均引入了一個新的超引數ρρ,用來控制移動平均的長度範圍。這裡寫圖片描述這裡寫圖片描述

Adam

派生自短語”adaptive moments” Adam被看作結合RMSProp和具有一些重要區別的動量的變種。 首先,Adam中動量直接併入梯度一階矩(指數加權)的估計。 其次,Adam包括偏置修正,修澤和那個從原點初始化的一階矩(動量項)和(非中心的)二階矩的估計。RMSProp也採用了(非中心的)二階矩估計,然而缺失了修正因子。因此,RMSProp二階矩估計可能在訓練初期有很高的偏置。

Adam通常被認為對超引數的選擇相當魯棒,儘管學習率有時需要遵從建議的預設引數0.001這裡寫圖片描述

二階近似方法

牛頓法

牛頓法是基於二階泰勒級數展開在某點θ0θ0附近來近似J(θ)J(θ)的優化方法,其忽略了高階導數 J(θ)≈J(θ0)+(θ−θ0)T∇θJ(θ0)+12(θ−θ0)TH(θ−θ0)J(θ)≈J(θ0)+(θ−θ0)T∇θJ(θ0)+12(θ−θ0)TH(θ−θ0), 其中HH是JJ相對於θθ的Hessian矩陣在θ0θ0處的矩估計。通過求解這個函式的臨界點,得到牛頓引數更新規則: θ∗=θ0−H−1∇θJ(θ0)θ∗=θ0−H−1∇θJ(θ0) 因此,對於區域性的二次函式(具有正定的H),用H−1H−1重新調整梯度,牛頓法會直接跳到極小值。如果目標函式是凸的但非二次的(有高階項),該更新是迭代的。這裡寫圖片描述這裡寫圖片描述

共軛梯度

共軛梯度是一種通過迭代下降的共軛方向以有效避免Hessian矩陣求逆計算的方法。 在共軛梯段法中,我們尋求一個和先前線性搜尋方向共軛的搜尋方向,即它不會撤銷該方向上的進展。在訓練迭代t時,下一步的搜尋方向dtdt的形式如下: dt=∇θJ(θ)+βtdt−1dt=∇θJ(θ)+βtdt−1 其中,係數βtβt的大小控制我們應沿方向dt−1dt−1加回多少到當前搜尋方向。 如果dTtHdt−1=0dtTHdt−1=0,其中HH是Hessian矩陣,則兩個方向dtdt和dt−1dt−1被稱為共軛的。這裡寫圖片描述這裡寫圖片描述

BFGS

Broyden-Fletcher-Goldfarb-Shanno(BFGS)演算法具有牛頓法的一些優點,但沒有牛頓法的計算負擔。http://blog.csdn.net/u012151283/article/details/78148890#t3 擬牛頓法採用矩陣MtMt近似逆,迭代地低秩更新精度以更好地近似H−1H−1。 當Hessian逆近似MtMt更新時,下降方向ρtρt為ρt=Mtgtρt=Mtgt。該方向上的線性搜尋用於決定該方向上的步長ϵ∗ϵ∗。引數的最後更新為: θt+1=θt+ϵ∗ρtθt+1=θt+ϵ∗ρt

相比於共軛梯度,BFGS的優點是其花費較少的時間改進每個線性搜尋。在另一方面,BFGS演算法必須儲存Hessian逆矩陣M,需要O(n2)O(n2)的儲存空間,使BFGS不適用於大多數具有百萬級引數的現代深度學習模型。

L-BFGS

L-BFGS演算法就是對擬牛頓演算法的一個改進。L-BFGS演算法的基本思想是:演算法只儲存並利用最近m次迭代的曲率資訊來構造海森矩陣的近似矩陣。 LBFGS,起始假設是G(t−1)G(t−1)是單位矩陣,而不是每一步都要儲存近似矩陣。每步儲存一些用於更新GkGk的向量,且每步儲存代價為O(n)O(n)。

BFGS演算法關於GkGk的迭代形式如下 Gk+1=(I−skyTkyTksk)Gk(I−yksTkyTksk)+sksTkyTkskGk+1=(I−skykTykTsk)Gk(I−ykskTykTsk)+skskTykTsk

令ρk=1yTkskρk=1ykTsk,Vk=I−ρkyksTkVk=I−ρkykskT,則有 Gk+1=VTkGkVk+ρksksTkGk+1=VkTGkVk+ρkskskT 對於給定的G0=IG0=I,可以迭代地計算GkGk,GkGk的計算只依賴於[si,yi]ki=0[si,yi]i=0k。 L-BFGS演算法通過儲存最近連續的m組si,yisi,yi向量,來近似地計算GkGk。

批標準化

批標準化是一種自適應重引數化的方法,試圖解決訓練非常深的模型的困難。批標準化主要解決的是訓練極深網路時梯度消失的問題。這裡寫圖片描述

BN起作用的原因

  1. 通過使得批量資料歸一化具有0均值1方差的統計分佈,避免資料處於啟用函式的飽和區,具有較大的梯度,從而加速網路的訓練過程。
  2. 減少了網路輸入變化過大的問題,使得網路的輸入穩定,減弱了與前層引數關係之間的作用,使得當前層獨立於整個網路

BN具有輕微正則化的效果,可以和dropout一起使用 主要是歸一化啟用值前的隱藏單元來加速訓練,正則化是副作用

BN具有正則化效果的原因

  1. 每個批量的資料僅根據當前批量計算均值和標準差,然後縮放
  2. 這就為該批量的啟用函式帶來了一些噪音,類似於dropout向每一層的啟用函式帶來噪音
  3. 若使用了較大的batch_size如512,則減小了噪音,減少了正則化帶來的效果

訓練過程中的演算法

這裡寫圖片描述 由於BN在最後重引數化過程中會學習得到一個γγ,所以神經網路中的bias可以省略掉。

推斷時演算法

由於在推斷的時,batch的大小不能確定,很有可能1次只有一個樣本,不通過對當前批量的資料的歸一化來加速訓練。作者提出使用訓練資料集上的全部資料來計算均值和方差。通常出與計算效率考慮,使用滑動平均的方法來計算這裡寫圖片描述

tensorflow實現

使用tf.layers.batch_normalization可以快速實現BN演算法。注意使用該API時在優化器呼叫時要加入以下程式碼:

update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
  with tf.control_dependencies(update_ops):
    train_op = optimizer.minimize(loss)

這裡還有一個我看到知乎匿名使用者的實現,感覺比較清晰

from tensorflow.python.training.moving_averages import assign_moving_average

def batch_norm(x, train, eps=1e-05, decay=0.9, affine=True, name=None):
    with tf.variable_scope(name, default_name='BatchNorm2d'):
        params_shape = tf.shape(x)[-1:]
        moving_mean = tf.get_variable('mean', params_shape,
                                      initializer=tf.zeros_initializer,
                                      trainable=False)
        moving_variance = tf.get_variable('variance', params_shape,
                                          initializer=tf.ones_initializer,
                                          trainable=False)

        def mean_var_with_update():
            mean, variance = tf.nn.moments(x, tf.shape(x)[:-1], name='moments')
            with tf.control_dependencies([assign_moving_average(moving_mean, mean, decay),
                                          assign_moving_average(moving_variance, variance, decay)]):
                return tf.identity(mean), tf.identity(variance)
        mean, variance = tf.cond(train, mean_var_with_update, lambda: (moving_mean, moving_variance))
        if affine:
            beta = tf.get_variable('beta', params_shape,
                                   initializer=tf.zeros_initializer)
            gamma = tf.get_variable('gamma', params_shape,
                                    initializer=tf.ones_initializer)
            x = tf.nn.batch_normalization(x, mean, variance, beta, gamma, eps)
        else:
            x = tf.nn.batch_normalization(x, mean, variance, None, None, eps)
        return x

優化策略和元演算法

  • 座標下降

參考資料