CHAPTER 11-Training Deep Neural Nets-part3

本篇文章是個人翻譯的,如有商業用途,請通知本人謝謝.

Faster Optimizers 

訓練一個非常大的深度神經網絡可能會非常緩慢。 到目前爲止,我們已經看到了四種加速培訓的方法(並且達到更好的解決方案):對連接權重應用良好的初始化策略,使用良好的激活功能,使用批量規範化以及重用預訓練網絡的部分。 另一個巨大的速度提升來自使用比普通漸變下降優化器更快的優化器。 在本節中,我們將介紹最流行的:動量優化,Nesterov加速梯度,AdaGrad,RMSProp,最後是Adam優化。

劇透:本節的結論是,您幾乎總是應該使用Adam_optimization,所以如果您不關心它是如何工作的,只需使用AdamOptimizer替換您的GradientDescentOptimizer,然後跳到下一節! 只需要這麼小的改動,訓練通常會快幾倍。 但是,Adam優化確實有三個可以調整的超參數(加上學習率)。 默認值通常工作的不錯,但如果您需要調整它們,可能會有助於知道他們做什麼。  Adam optimization結合了來自其他優化算法的幾個想法,所以先看看這些算法是有用的。

Momentum optimization
想象一下,一個保齡球在一個光滑的表面上平緩的斜坡上滾動:它會緩慢地開始,但是它會很快地達到最終的速度(如果有一些摩擦或空氣阻力的話)。 這是Boris Polyak在1964年提出的Momentum優化背後的一個非常簡單的想法.相比之下,普通的Gradient Descent只需要沿着斜坡進行小的有規律的下降步驟,所以需要更多的時間才能到達底部。

回想一下,梯度下降只是通過直接減去損失函數J(θ)相對於權重(θJ(θ))乘以學習率η的梯度來更新權重θ。 方程是:θ←θ-η∇θJ(θ)。 它不關心早期的梯度是什麼。 如果局部梯度很小,則會非常緩慢。


動量優化很關心以前的梯度:在每次迭代時,它將動量矢量m(乘以學習率η)的局部梯度相加,並且通過簡單地減去該動量矢量來更新權重(參見公式11-4)。 換句話說,梯度用作加速度,不用作速度。 爲了模擬某種摩擦機制,避免動量過大,該算法引入了一個新的超參數β,簡稱爲動量,它必須設置在0(高摩擦)和1(無摩擦)之間。 典型的動量值是0.9。

您可以很容易地驗證,如果梯度保持不變,則終端速度(即,權重更新的最大大小)等於該梯度乘以學習率η乘以例如,如果β= 0.9,則最終速度等於學習速率的梯度乘以10倍,因此動量優化比梯度下降快10倍! 這使Momentum優化比Gradient Descent快得多。 特別是,我們在第四章中看到,當輸入量具有非常不同的尺度時,損失函數看起來像一個細長的碗(見圖4-7)。 梯度下降速度很快,但要花很長的時間才能到達底部。 相反,動量優化會越來越快地滾下山谷底部,直到達到底部(最佳)。


在不使用批處理標準化的深層神經網絡中,上層通常最終輸入具有不同的尺度,所以使用Momentum優化會有很大的幫助。 它也可以幫助滾過局部的最佳狀態。

由於動力的原因,優化器可能會超調一些,然後再回來,再次超調,並在穩定在最小值之前多次振盪。 這就是爲什麼在系統中有一點摩擦的原因之一:它消除了這些振盪,從而加速了收斂。

在TensorFlow中實現Momentum優化是一件簡單的事情:只需用MomentumOptimizer替換GradientDescentOptimizer,然後躺下來賺錢!


Momentum優化的一個缺點是它增加了另一個超參數來調整。 然而,0.9的動量值通常在實踐中運行良好,幾乎總是比梯度下降快。


Nesterov Accelerated Gradient
Yurii Nesterov在1983年提出的Momentum優化的一個小變量幾乎總是比vanilla Momentum優化更快。 Nesterov Momentum優化或Nesterov加速梯度(Nesterov Accelerated Gradient,NAG)的思想是測量損失函數的梯度不是在局部位置,而是在動量方向稍微靠前(見公式11-5)。 與vanilla Momentum優化的唯一區別在於梯度是在θ+βm而不是在θ處測量的。


這個小小的調整是可行的,因爲一般來說,動量矢量將指向正確的方向(即朝向最優方向),所以使用在該方向上測得的梯度稍微更精確,而不是使用 原始位置的梯度,如圖11-6所示(其中∇1代表在起點θ處測量的代價函數的梯度,∇2代表位於θ+βm的點處的梯度)。


正如你所看到的,Nesterov更新稍微靠近最佳值。 過了一段時間,這些小的改進加起來,NAG最終比常規的Momentum優化快得多。 此外,請注意,當動量推動權重橫跨山谷時,▽1繼續推進越過山谷,而2推回山谷的底部。 這有助於減少振盪,從而更快地收斂。

與常規的動量優化相比,NAG幾乎總能加速訓練。 要使用它,只需在創建MomentumOptimizer時設置use_nesterov = True:



AdaGrad
再次考慮細長碗的問題:梯度下降從最陡峭的斜坡快速下降,然後緩慢地下到谷底。 如果算法能夠早期檢測到這個問題並且糾正它的方向來指向全局最優點,那將是非常好的。

AdaGrad算法通過沿着最陡的維度縮小梯度向量來實現這一點(見公式11-6):


第一步將梯度的平方累加到矢量s中(⊗符號表示單元乘法)。 這個向量化形式相當於向量s的每個元素si計算si←si +(∂/∂θiJ(θ))^ 2; 換一種說法,每個si關於參數θi累加損失函數的偏導數的平方。 如果損失函數沿着第i維陡峭,則在每次迭代時,si將變得越來越大。

第二步幾乎與梯度下降相同,但有一個很大的不同:梯度矢量按比例縮小(⊘符號表示元素分割,ε是避免被零除的平滑項,通常設置爲). 這個矢量化的形式相當於計算對於所有參數θi(同時)。

簡而言之,這種算法會降低學習速度,但對於陡峭的尺寸,其速度要快於具有溫和的斜率的尺寸。 這被稱爲自適應學習率。 它有助於將更新的結果更直接地指向全局最優(見圖11-7)。 另一個好處是它不需要那麼多的去調整學習速率超參數η。


對於簡單的二次問題,AdaGrad經常表現良好,但不幸的是,在訓練神經網絡時,它經常停止得太早。 學習速率被縮減得太多,以至於在達到全局最優之前,算法完全停止。 所以,即使TensorFlow有一個AdagradOptimizer,你也不應該用它來訓練深度神經網絡(雖然對線性迴歸這樣簡單的任務可能是有效的)。


RMSProp
儘管AdaGrad的速度變慢了一點,並且從未收斂到全局最優,但是RMSProp算法通過僅累積最近迭代(而不是從訓練開始以來的所有梯度)的梯度來修正這個問題。 它通過在第一步中使用指數衰減來實現(見公式11-7)。


他的衰變率β通常設定爲0.9。 是的,它又是一個新的超參數,但是這個默認值通常運行良好,所以你可能根本不需要調整它。

正如您所料,TensorFlow擁有一個RMSPropOptimizer類:


除了非常簡單的問題,這個優化器幾乎總是比AdaGrad執行得更好。 它通常也比Momentum優化和Nesterov加速梯度表現更好。 事實上,這是許多研究人員首選的優化算法,直到Adam optimization出現。


Adam Optimization 

Adam,代表自適應矩估計,結合了動量優化和RMSProp的思想:就像動量優化一樣,它追蹤過去梯度的指數衰減平均值,就像RMSProp一樣,它跟蹤過去平方梯度的指數衰減平均值 (見方程式11-8)。


T代表迭代次數(從1開始)。

如果你只看步驟1,2和5,你會注意到Adam與Momentum優化和RMSProp的相似性。 唯一的區別是第1步計算指數衰減的平均值,而不是指數衰減的和,但除了一個常數因子(衰減平均值只是衰減和的1 - β1倍)之外,它們實際上是等效的。 步驟3和步驟4是一個技術細節:由於m和s初始化爲0,所以在訓練開始時它們會偏向0,所以這兩步將在訓練開始時幫助提高m和s。

動量衰減超參數β1通常初始化爲0.9,而縮放衰減超參數β2通常初始化爲0.999。 如前所述,平滑項ε通常被初始化爲一個很小的數,例如.這些是TensorFlow的AdamOptimizer類的默認值,所以你可以簡單地使用:



實際上,由於Adam是一種自適應學習速率算法(如AdaGrad和RMSProp),所以對學習速率超參數η的調整較少。 您經常可以使用默認值η= 0.001,使Adam更容易使用x相對於梯度下降。

迄今爲止所討論的所有優化技術都只依賴於一階偏導數(雅可比矩陣)。 優化文獻包含基於二階偏導數(Hessians)的驚人算法。 不幸的是,這些算法很難應用於深度神經網絡,因爲每個輸出有n ^ 2個Hessians(其中n是參數的數量),而不是每個輸出只有n個Jacobian。 由於DNN通常具有數以萬計的參數,二階優化算法通常甚至不適合內存,甚至在他們這樣做時,計算Hessians也是太慢了。

Training Sparse Models(訓練稀疏模型)

所有剛剛提出的優化算法都會產生密集的模型,這意味着大多數參數都是非零的。 如果你在運行時需要一個非常快速的模型,或者如果你需要它佔用較少的內存,你可能更喜歡用一個稀疏模型來代替。

實現這一點的一個微不足道的方法是像平常一樣訓練模型,然後擺脫微小的權重(將它們設置爲0)。
另一個選擇是在訓練過程中應用強正則化,因爲它會推動優化器儘可能多地消除權重(如第4章關於套索迴歸的討論)。

但是,在某些情況下,這些技術可能仍然不足。 最後一個選擇是應用雙重平均,通常稱爲遵循正規化領導者(FTRL),一種由尤里·涅斯捷羅夫(Yurii Nesterov)提出的技術。 當與l1正則化一起使用時,這種技術通常導致非常稀疏的模型。 TensorFlow在FTRLOptimizer類中實現稱爲FTRL-Proximal的FTRL變體。


Learning Rate Scheduling(學習速率調度)

找到一個好的學習速度可能會非常棘手。 如果設置太高,訓練實際上可能偏離(如我們在第4章)。 如果設置得太低,訓練最終會收斂到最佳狀態,但這需要很長時間。 如果將其設置得太高,開始的進度會非常快,但最終會圍繞最佳方式跳舞,永遠不會安頓下來(除非您使用自適應學習速率優化算法,如AdaGrad,RMSProp或Adam,但是 即使這樣可能需要時間來解決)。 如果您的計算預算有限,那麼您可能必須在正確收斂之前中斷培訓,產生次優解決方案(參見圖11-8)。


通過使用各種學習速率和比較學習曲線,在幾個時期內對您的網絡進行多次訓練,您也許能夠找到相當好的學習速度。 理想的學習速度將會快速學習並收斂到良好的解決方案。

然而,你可以做得比不斷的學習速度更好:如果你從一個高的學習速度開始,然後一旦它停止快速的進步就減少它,你可以比最佳的恆定學習速度更快地達到一個好的解決方案。 有許多不同的策略,以減少訓練期間的學習率。 這些策略被稱爲學習時間表(我們在第4章中簡要介紹了這個概念),其中最常見的是:


預定的分段恆定學習率:

例如,首先將學習率設置爲η0= 0.1,然後在50個時期之後將學習率設置爲η1= 0.001。 雖然這個解決方案可以很好地工作,但是通常需要弄清楚正確的學習速度以及何時使用它們。


性能調度:

每N步測量驗證錯誤(就像提前停止一樣),當錯誤停止下降時,將學習速率降低一個因子λ。


指數調度:

將學習率設置爲迭代次數t的函數:。 這很好,但它需要調整η0和r。 學習率將由每r步下降10個因素。


冪調度:

設學習率爲。 超參數c通常被設置爲1.這與指數調度類似,但是學習速率下降要慢得多.

Andrew Senior等2013年的論文。 比較了使用Momentum優化訓練深度神經網絡進行語音識別時一些最流行的學習計劃的性能。 作者得出結論:在這種情況下,性能調度和指數調度都表現良好,但他們更喜歡指數調度,因爲它實現起來比較簡單,容易調整,收斂速度略快於最佳解決方案。

使用TensorFlow實現學習計劃非常簡單:

    initial_learning_rate = 0.1
    decay_steps = 10000
    decay_rate = 1/10
    global_step = tf.Variable(0, trainable=False, name="global_step")
    learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step,
                                               decay_steps, decay_rate)
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss, global_step=global_step)

設置超參數值後,我們創建一個不可跟蹤的變量global_step(初始化爲0)以跟蹤當前的訓練迭代次數。 然後我們使用TensorFlow的exponential_decay()函數來定義指數衰減的學習率(η0= 0.1和r = 10,000)。 接下來,我們使用這個衰減的學習率創建一個優化器(在這個例子中是一個MomentumOptimizer)。 最後,我們通過調用優化器的minimize()方法來創建訓練操作; 因爲我們將global_step變量傳遞給它,所以請注意增加它。 就是這樣!

由於AdaGrad,RMSProp和Adam優化自動降低了培訓期間的學習率,因此不需要添加額外的學習計劃。 對於其他優化算法,使用指數衰減或性能調度可顯着加速收斂。

完整代碼:

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_hidden2 = 50
n_outputs = 10

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1")
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2")
    logits = tf.layers.dense(hidden2, n_outputs, name="outputs")

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")

with tf.name_scope("train"):       # not shown in the book
    initial_learning_rate = 0.1
    decay_steps = 10000
    decay_rate = 1/10
    global_step = tf.Variable(0, trainable=False, name="global_step")
    learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step,
                                               decay_steps, decay_rate)
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss, global_step=global_step)

init = tf.global_variables_initializer()
saver = tf.train.Saver()

n_epochs = 5
batch_size = 50

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_model_final.ckpt")


發佈了77 篇原創文章 · 獲贊 25 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章