機器學習演算法如何調參?這裡有一份神經網路學習速率設置指南
作者:Jeremy Jordan,機器之心編譯,參與:黃小天、許迪
每個機器學習的研究者都會面臨調參過程的考驗,而在調參過程中,學習速率(learning rate)的調整則又是非常重要的一部分。學習速率代表了神經網路中隨時間推移,信息累積的速度。在理想情況下,我們會以很大的學習速率開始,逐漸減小速度,直至損失值不再發散。不過,說來容易做來難,本文作者對學習速率的調整思路進行了簡要介紹,希望能夠對你有所幫助。
在之前的文章里,我已經講了如何用反向傳播和梯度下降來訓練神經網路。為了訓練神經網路,其中一個需要設置的關鍵超參數是學習率。提醒一下,為了最小化此網路的損失函數,這個參數縮放了權重更新的幅度。
如果你把學習率設置太低,訓練會進展的很慢:因為你在網路的權重上只做了很少的調整。然而,如果你的學習率被設置的太高,它可能在你的損失函數上帶來不理想的後果。我已經可視化了這些案例——如果你發現這些圖很難理解,我建議你事先參考一下(至少)我此前發布的關於梯度下降的第一部分。
所以,我們怎樣找到最優學習速率呢?
讓我們看看 Tesla AI 主管、李飛飛高徒 Andrej Karpathy 怎麼說:
推文:3e-4 是對 Adam 最好的學習速率,妥妥地~
完美,我覺得我的工作完成了
好吧,並沒有……
第二條推:我想確認一下每個人都知道這是個玩笑吧……?
神經網路的損失函數地圖(loss landscape)(下圖所示)是網路參數值的函數,當在特定數據集上執行推斷(預測)時量化與使用特定參數配置相關的「誤差」。這個損失地圖可能對於很相似的網路架構也看起來很不同。下圖來自論文《Visualizing the Loss Landscape of Neural Nets》,其中展示了殘差連接可產生更平滑的拓撲結構。
最優學習率取決於你的損失地圖的拓撲結構,也就是由你的模型結構和數據集。當你用默認的學習率(由你的深度學習庫自動決定)可以提供一個差不多的結果,你也可以通過搜尋最優學習率來提高表現。我希望你在下一部分發現這很簡單。
一個找尋最優學習速率的系統化方法
最終,我們希望得到一個學習率,極大地減少網路損失。我們可以在逐步提高每一次小批量(迭代)的學習速率的同時通過做一個簡單實驗來觀察,記錄每一次增量之後的損失。這個逐步的增長可以是線性或指數的。
對於太慢的學習速率來說,損失函數可能減小,但是按照非常淺薄的速率減小的。當進入了最優學習率區域,你將會觀察到在損失函數上一次非常大的下降。進一步增加學習速率會造成損失函數值「跳來跳去」甚至在最低點附近發散。記住,最好的學習速率搭配著損失函數上最陡的下降,所以我們主要關注分析圖的坡度。
你應該為這個實驗設置你的學習率界限從而你能看到所有的三個階段,確保識別最優範圍。
設置時間表以在訓練中調整你的學習率
另一個大家常用的技巧是學習速率退火(learning rate annealing),推薦大家先從一個比較高的學習速率開始然後慢慢地在訓練中降低學習速率。這個方法背後的思想是我們喜歡快速地從初始參數移動到一個參數值「好」的範圍,但這之後我們又想要一個學習速率小到我們可以發掘「損失函數上更深且窄的地方」,(來自 Karparthy 的 CS231n 課程筆記:http://cs231n.github.io/neural-networks-3/#annealing-the-learning-rate)。如果你很難想像我剛才所言,回想一下太高的學習速率可以造成參數更新會在最小值和隨後的更新間「跳來跳去」,這點子會造成在極小值範圍內持續的有雜訊的收斂,或者在更極端的例子里可能造成從最小值發散出去。
學習速率退火的最流行方式是「步衰減」(Step Decay),其中學習率經過一定數量的訓練 epochs 後下降了一定的百分比。
更常見的,我們可以創建一個學習速率時間表(learning rate schedule),就是在訓練期間根據特定規則來更新學習速率。
周期性學習率
在上述論文中《Cyclical Learning Rates for Training Neural Networks》中,Leslie Smith 提出了一種周期性學習率表,可在兩個約束值之間變動。如下圖所示,它是一個三角形更新規則,但他也提到如何使用這一規則與固定周期衰減或指數周期衰減相結合。
注意:在本文最後,我將給出實現這一學習率的代碼。因此,如果你不關心數學公式的理解,可以跳過該部分。
我們可以將其寫為:
其中 x 被定義為
並且 cycle 被計算為
其中η_min 和η_max 定義學習率的界限,iterations 表徵已完成的小批量(mini-batches)的數量,stepsize 定義了一個周期長度的一半。據我所知,1?x 一直為正,因此看起來 max 操作並非絕對必要。
為了搞明白這一方程式如何工作,讓我們逐步利用可視化構建它。對於下面的視覺效果,三個完整周期的三角形更新以 100 次迭代的步長顯示。記住,一次迭代對應於一個小批量的訓練。
最重要的是,我們可以在訓練期間根據我們已完成的半個周期來確定「進程」。我們用半周期而不是全周期來衡量我們的進程,從而就可以在一個周期內實現對稱(後面你會更加清晰認識到這點)。
接下來,我們把半周期進程與在當前周期完成時的半周期數量進行對比。當一個周期開始時,我們有兩個半周期要完成;當一個周期結束時,該值達到零。
再接下來,我們將該值加 1,從而把函數移為以 y 軸為中心。現在我們參考半周期點展示一個周期內的進程。
在該點上,我們取絕對值以在每個周期內完成一個三角形。這就是我們分配給 x 的值。
但是,我們希望學習率表從最小值開始,在周期中間增加到最大值,然後再降低到最小值。我們可通過簡單計算 1-x 來實現這一點。
通過把學習率範圍的一部分添加到最小學習率(也稱為基本學習率),現在我們有了一個可以調整學習率的值。
Smith 寫到,周期性學習率背後的主要理論假設(與降低學習率相對反)是「增加學習率也許有一個短期的負面影響,但卻獲得了長期的正面影響」。確實,他的論文包含若干個損失函數演化的實例,與基準固定學習率相比,它們暫時偏離到較高的損失,並最終收斂到較低的損失。
為了直觀理解這一短期影響如何帶來長期的正面效果,重要的是理解我們收斂最小值的期望特徵。最終,我們想要我們的網路以一種泛化到不可見數據的方式從數據中學習。進而,具備良好泛化能力的網路是應該是魯棒的,參數的小變動並不會太大影響性能。考慮到這一點,尖銳的極小值導致很差的泛化能力也就合理了,正如參數值的小變動會導致巨大的較高損失。通過允許我們的學習率在次數上增加,我們可以「跳出」尖銳的極小值,雖然這會暫時增加損失,但可能最終收斂到更加理想的極小值。
注意:儘管「良好泛化的恰當極小值」已被廣泛接受,但也存在很有力的反論(https://arxiv.org/abs/1703.04933)。
此外,增加學習率允許「更快速地穿越鞍點高原」。如下圖所見,鞍點上梯度可以非常小。因為參數更新是一個梯度函數,這導致我們優化步驟非常短;在這裡增加學習率可以避免在鞍點卡住太久,這很有用。
注意:根據定義,鞍點是一個臨界點,其中一些維度觀測局部極小值,另一些維度觀測局部極大值。由於神經網路存在數千或數百萬個參數,在所有維度上觀測一個真正的局部極小值不太現實;這就是鞍點出現的意義。當我提到「尖銳的極小值」,實際上我們應該描畫一個鞍點,其中極小值維度非常陡峭,極大值維度非常寬廣(如下圖所示)。
帶有熱重啟的隨機梯度下降(SGDR)
帶有熱重啟的隨機梯度下降(SGDR)與周期性方法很相似,其中一個積極的退火表與周期性「再啟動」融合到原始的初始學習率之中。
我們可以將其寫為
其中η_t 是時間步 t 上的學習率,(在每一個 mini batch 間增長)
和
定義理想學習率的範圍,T_current 表徵上次再啟動之後 epoch 的數量,T_i 定義周期之中 epoch 的數量。讓我們試著分解這個等式。
這個退火表依賴於餘弦函數,其在-1 和 1 之間變化。
能夠取 0 到 1 之間的值,這是我們的餘弦函數的輸入。餘弦函數的相應區域在下圖用綠色突出顯示。通過添加 1,我們的函數在 0 到 2 之間變化,然後縮小 1/2,現在在 0 到 1 之間變化。因此,我們簡單地取極小值學習率,並添加指定學習率範圍的一部分。由於這一函數從 1 開始並降為 0,結果是一個從特定範圍的極大值開始並衰減為極小值的學習率。一旦我們的周期結束,T_current 重置為 0,我們從極大值學習率再開始循環這一過程。
作者也發現這個學習速率安排表可以適用於:
- 當訓練進行時延長周期
- 在每一周期之後衰減
和
在每一次重啟的時候徹底地提高學習速率,我們可以本質上的退出一個局部低點並且繼續探索損失地圖。
非常酷的主意:在每一輪循環後截圖一下權重,研究員可以通過訓練單個模型去建立一個全套模型。這是因為從一個周期到另一個周期,這個網路「沉澱」在不同的局部最優,像在下面圖中畫的一樣。
實現
找尋最優學習速率的和設定一個學習速率安排表都可以簡單的用 Keras 的回調函數中應用。
尋找最優學習速率範圍
我們可以寫一個 Keras 回調函數,就是追蹤與一個在確定範圍內變化的線性的學習速率相搭配的損失函數。
from keras.callbacks import Callbackimport matplotlib.pyplot as pltclass LRFinder(Callback): A simple callback for finding the optimal learning rate range for your model + dataset. # Usage ```python lr_finder = LRFinder(min_lr=1e-5, max_lr=1e-2, steps_per_epoch=10, epochs=3) model.fit(X_train, Y_train, callbacks=[lr_finder]) lr_finder.plot_loss() ``` # Arguments min_lr: The lower bound of the learning rate range for the experiment. max_lr: The upper bound of the learning rate range for the experiment. steps_per_epoch: Number of mini-batches in the dataset. epochs: Number of epochs to run experiment. Usually between 2 and 4 epochs is sufficient. # References Blog post: jeremyjordan.me/nn-learning-rate Original paper: https://arxiv.org/abs/1506.01186 def __init__(self, min_lr=1e-5, max_lr=1e-2, steps_per_epoch=None, epochs=None): super().__init__() self.min_lr = min_lr self.max_lr = max_lr self.total_iterations = steps_per_epoch * epochs self.iteration = 0 self.history = {} def clr(self): Calculate the learning rate. x = self.iteration / self.total_iterations return self.min_lr + (self.max_lr-self.min_lr) * x def on_train_begin(self, logs=None): Initialize the learning rate to the minimum value at the start of training. logs = logs or {} K.set_value(self.model.optimizer.lr, self.min_lr) def on_batch_end(self, epoch, logs=None): Record previous batch statistics and update the learning rate. logs = logs or {} self.iteration += 1 K.set_value(self.model.optimizer.lr, self.clr()) self.history.setdefault(lr, []).append(K.get_value(self.model.optimizer.lr)) self.history.setdefault(iterations, []).append(self.iteration) for k, v in logs.items(): self.history.setdefault(k, []).append(v) def plot_lr(self): Helper function to quickly inspect the learning rate schedule. plt.plot(self.history[iterations], self.history[lr]) plt.yscale(log) plt.xlabel(Iteration) plt.ylabel(Learning rate) def plot_loss(self): Helper function to quickly observe the learning rate experiment results. plt.plot(self.history[lr], self.history[loss]) plt.xscale(log) plt.xlabel(Learning rate) plt.ylabel(Loss)
設置一個學習速率表
步衰減
對於一個簡單的步衰減(step decay),我們可以用 LearningRateScheduler 回調。
import numpy as npfrom keras.callbacks import LearningRateSchedulerdef step_decay_schedule(initial_lr=1e-3, decay_factor=0.75, step_size=10): Wrapper function to create a LearningRateScheduler with step decay schedule. def schedule(epoch): return initial_lr * (decay_factor ** np.floor(epoch/step_size)) return LearningRateScheduler(schedule)lr_sched = step_decay_schedule(initial_lr=1e-4, decay_factor=0.75, step_size=2)model.fit(X_train, Y_train, callbacks=[lr_sched])
周期性學習速率
要應用周期性學習速率技巧,我們可以參考這個 repo(https://github.com/bckenstler/CLR),其已在論文中實現了該技術。實際上這個 repo 已在論文附錄中被引用。
帶有重啟的隨機梯度下降
要應用這個 SGDR 技巧,我們可以參考:https://github.com/keras-team/keras/pull/3525/files 原文鏈接:https://www.jeremyjordan.me/nn-learning-rate/
推薦閱讀:
※別錯過這張AI商用清單:你的生產難題可能被一個應用解決
※智能安全帽解決方案
※他研究了5000家AI公司,說人工智慧應用該這麼做!
※突圍機器寫詩紅海,360這款黑科技讓全民皆「詩人」
※AlphaGo背後的秘密!解讀谷歌全面重磅開放的雲TPU