CS231n課程筆記翻譯:神經網路筆記3(下)

譯者註:本文智能單元首發,譯自斯坦福CS231n課程筆記Neural Nets notes 3,課程教師Andrej Karpathy授權翻譯。本篇教程由杜客翻譯完成,堃堃和鞏子嘉進行校對修改。譯文含公式和代碼,建議PC端閱讀。

原文如下

內容列表:

  • 梯度檢查
  • 合理性(Sanity)檢查
  • 檢查學習過程
    • 損失函數
    • 訓練與驗證準確率
    • 權重:更新比例
    • 每層的激活數據與梯度分布
    • 可視化
  • 參數更新 譯者註:下篇翻譯起始處
    • 一階(隨機梯度下降)方法,動量方法,Nesterov動量方法
    • 學習率退火
    • 二階方法
    • 逐參數適應學習率方法(Adagrad,RMSProp)
  • 超參數調優
  • 評價
    • 模型集成
  • 總結
  • 拓展引用

參數更新

一旦能使用反向傳播計算解析梯度,梯度就能被用來進行參數更新了。進行參數更新有好幾種方法,接下來都會進行討論。

深度網路的最優化是現在非常活躍的研究領域。本節將重點介紹一些公認有效的常用的技巧,這些技巧都是在實踐中會遇到的。我們將簡要介紹這些技巧的直觀概念,但不進行細節分析。對於細節感興趣的讀者,我們提供了一些拓展閱讀。

隨機梯度下降及各種更新方法

普通更新。最簡單的更新形式是沿著負梯度方向改變參數(因為梯度指向的是上升方向,但是我們通常希望最小化損失函數)。假設有一個參數向量x及其梯度dx,那麼最簡單的更新的形式是:

# 普通更新nx += - learning_rate * dxn

其中learning_rate是一個超參數,它是一個固定的常量。當在整個數據集上進行計算時,只要學習率足夠低,總是能在損失函數上得到非負的進展。

動量(Momentum)更新是另一個方法,這個方法在深度網路上幾乎總能得到更好的收斂速度。該方法可以看成是從物理角度上對於最優化問題得到的啟發。損失值可以理解為是山的高度(因此高度勢能是U=mgh,所以有Upropto h)。用隨機數字初始化參數等同於在某個位置給質點設定初始速度為0。這樣最優化過程可以看做是模擬參數向量(即質點)在地形上滾動的過程。

因為作用於質點的力與梯度的潛在能量(F=-nabla U)有關,質點所受的力就是損失函數的(負)梯度。還有,因為F=ma,所以在這個觀點下(負)梯度與質點的加速度是成比例的。注意這個理解和上面的隨機梯度下降(SDG)是不同的,在普通版本中,梯度直接影響位置。而在這個版本的更新中,物理觀點建議梯度只是影響速度,然後速度再影響位置:

# 動量更新nv = mu * v - learning_rate * dx # 與速度融合nx += v # 與位置融合n

在這裡引入了一個初始化為0的變數v和一個超參數mu。說得不恰當一點,這個變數(mu)在最優化的過程中被看做動量(一般值設為0.9),但其物理意義與摩擦係數更一致。這個變數有效地抑制了速度,降低了系統的動能,不然質點在山底永遠不會停下來。通過交叉驗證,這個參數通常設為[0.5,0.9,0.95,0.99]中的一個。和學習率隨著時間退火(下文有討論)類似,動量隨時間變化的設置有時能略微改善最優化的效果,其中動量在學習過程的後階段會上升。一個典型的設置是剛開始將動量設為0.5而在後面的多個周期(epoch)中慢慢提升到0.99。

通過動量更新,參數向量會在任何有持續梯度的方向上增加速度。

Nesterov動量與普通動量有些許不同,最近變得比較流行。在理論上對於凸函數它能得到更好的收斂,在實踐中也確實比標準動量表現更好一些。

Nesterov動量的核心思路是,當參數向量位於某個位置x時,觀察上面的動量更新公式可以發現,動量部分(忽視帶梯度的第二個部分)會通過mu * v稍微改變參數向量。因此,如果要計算梯度,那麼可以將未來的近似位置x + mu * v看做是「向前看」,這個點在我們一會兒要停止的位置附近。因此,計算x + mu * v的梯度而不是「舊」位置x的梯度就有意義了。

————————————————————————————————————————

Nesterov動量。既然我們知道動量將會把我們帶到綠色箭頭指向的點,我們就不要在原點(紅色點)那裡計算梯度了。使用Nesterov動量,我們就在這個「向前看」的地方計算梯度。

————————————————————————————————————————

也就是說,添加一些注釋後,實現代碼如下:

x_ahead = x + mu * vn# 計算dx_ahead(在x_ahead處的梯度,而不是在x處的梯度)nv = mu * v - learning_rate * dx_aheadnx += vn

然而在實踐中,人們更喜歡和普通SGD或上面的動量方法一樣簡單的表達式。通過對x_ahead = x + mu * v使用變數變換進行改寫是可以做到的,然後用x_ahead而不是x來表示上面的更新。也就是說,實際存儲的參數向量總是向前一步的那個版本。x_ahead的公式(將其重新命名為x)就變成了:

v_prev = v # 存儲備份nv = mu * v - learning_rate * dx # 速度更新保持不變nx += -mu * v_prev + (1 + mu) * v # 位置更新變了形式n

對於NAG(Nesterovs Accelerated Momentum)的來源和數學公式推導,我們推薦以下的拓展閱讀:

  • Yoshua Bengio的Advances in optimizing Recurrent Networks,Section 3.5。
  • Ilya Sutskevers thesis (pdf)在section 7.2對於這個主題有更詳盡的闡述。

學習率退火

在訓練深度網路的時候,讓學習率隨著時間退火通常是有幫助的。可以這樣理解:如果學習率很高,系統的動能就過大,參數向量就會無規律地跳動,不能夠穩定到損失函數更深更窄的部分去。知道什麼時候開始衰減學習率是有技巧的:慢慢減小它,可能在很長時間內只能是浪費計算資源地看著它混沌地跳動,實際進展很少。但如果快速地減少它,系統可能過快地失去能量,不能到達原本可以到達的最好位置。通常,實現學習率退火有3種方式:

  • 隨步數衰減:每進行幾個周期就根據一些因素降低學習率。典型的值是每過5個周期就將學習率減少一半,或者每20個周期減少到之前的0.1。這些數值的設定是嚴重依賴具體問題和模型的選擇的。在實踐中可能看見這麼一種經驗做法:使用一個固定的學習率來進行訓練的同時觀察驗證集錯誤率,每當驗證集錯誤率停止下降,就乘以一個常數(比如0.5)來降低學習率。

  • 指數衰減。數學公式是alpha=alpha_0e^{-kt},其中alpha_0,k是超參數,t是迭代次數(也可以使用周期作為單位)。
  • 1/t衰減的數學公式是alpha=alpha_0/(1+kt),其中alpha_0,k是超參數,t是迭代次數。

在實踐中,我們發現隨步數衰減的隨機失活(dropout)更受歡迎,因為它使用的超參數(衰減係數和以周期為時間單位的步數)比k更有解釋性。最後,如果你有足夠的計算資源,可以讓衰減更加緩慢一些,讓訓練時間更長些。

二階方法

在深度網路背景下,第二類常用的最優化方法是基於牛頓法的,其迭代如下:

displaystyle xleftarrow x-[Hf(x)]^{-1}nabla f(x)

這裡Hf(x)是Hessian矩陣,它是函數的二階偏導數的平方矩陣。nabla f(x)是梯度向量,這和梯度下降中一樣。直觀理解上,Hessian矩陣描述了損失函數的局部曲率,從而使得可以進行更高效的參數更新。具體來說,就是乘以Hessian轉置矩陣可以讓最優化過程在曲率小的時候大步前進,在曲率大的時候小步前進。需要重點注意的是,在這個公式中是沒有學習率這個超參數的,這相較於一階方法是一個巨大的優勢。

然而上述更新方法很難運用到實際的深度學習應用中去,這是因為計算(以及求逆)Hessian矩陣操作非常耗費時間和空間。舉例來說,假設一個有一百萬個參數的神經網路,其Hessian矩陣大小就是[1,000,000 x 1,000,000],將佔用將近3,725GB的內存。這樣,各種各樣的-牛頓法就被發明出來用於近似轉置Hessian矩陣。在這些方法中最流行的是L-BFGS,該方法使用隨時間的梯度中的信息來隱式地近似(也就是說整個矩陣是從來沒有被計算的)。

然而,即使解決了存儲空間的問題,L-BFGS應用的一個巨大劣勢是需要對整個訓練集進行計算,而整個訓練集一般包含幾百萬的樣本。和小批量隨機梯度下降(mini-batch SGD)不同,讓L-BFGS在小批量上運行起來是很需要技巧,同時也是研究熱點。

實踐。在深度學習和卷積神經網路中,使用L-BFGS之類的二階方法並不常見。相反,基於(Nesterov的)動量更新的各種隨機梯度下降方法更加常用,因為它們更加簡單且容易擴展。

參考資料:

  • Large Scale Distributed Deep Networks 一文來自谷歌大腦團隊,比較了在大規模數據情況下L-BFGS和SGD演算法的表現。

  • SFO演算法想要把SGD和L-BFGS的優勢結合起來。

逐參數適應學習率方法

前面討論的所有方法都是對學習率進行全局地操作,並且對所有的參數都是一樣的。學習率調參是很耗費計算資源的過程,所以很多工作投入到發明能夠適應性地對學習率調參的方法,甚至是逐個參數適應學習率調參。很多這些方法依然需要其他的超參數設置,但是其觀點是這些方法對於更廣範圍的超參數比原始的學習率方法有更良好的表現。在本小節我們會介紹一些在實踐中可能會遇到的常用適應演算法:

Adagrad是一個由Duchi等提出的適應性學習率演算法

# 假設有梯度和參數向量xncache += dx**2nx += - learning_rate * dx / (np.sqrt(cache) + eps)n

注意,變數cache的尺寸和梯度矩陣的尺寸是一樣的,還跟蹤了每個參數的梯度的平方和。這個一會兒將用來歸一化參數更新步長,歸一化是逐元素進行的。注意,接收到高梯度值的權重更新的效果被減弱,而接收到低梯度值的權重的更新效果將會增強。有趣的是平方根的操作非常重要,如果去掉,演算法的表現將會糟糕很多。用於平滑的式子eps(一般設為1e-4到1e-8之間)是防止出現除以0的情況。Adagrad的一個缺點是,在深度學習中單調的學習率被證明通常過於激進且過早停止學習。

RMSprop。是一個非常高效,但沒有公開發表的適應性學習率方法。有趣的是,每個使用這個方法的人在他們的論文中都引用自Geoff Hinton的Coursera課程的第六課的第29頁PPT。這個方法用一種很簡單的方式修改了Adagrad方法,讓它不那麼激進,單調地降低了學習率。具體說來,就是它使用了一個梯度平方的滑動平均:

cache = decay_rate * cache + (1 - decay_rate) * dx**2nx += - learning_rate * dx / (np.sqrt(cache) + eps)n

在上面的代碼中,decay_rate是一個超參數,常用的值是[0.9,0.99,0.999]。其中x+=和Adagrad中是一樣的,但是cache變數是不同的。因此,RMSProp仍然是基於梯度的大小來對每個權重的學習率進行修改,這同樣效果不錯。但是和Adagrad不同,其更新不會讓學習率單調變小。

Adam。Adam是最近才提出的一種更新方法,它看起來像是RMSProp的動量版。簡化的代碼是下面這樣:

m = beta1*m + (1-beta1)*dxnv = beta2*v + (1-beta2)*(dx**2)nx += - learning_rate * m / (np.sqrt(v) + eps)n

注意這個更新方法看起來真的和RMSProp很像,除了使用的是平滑版的梯度m,而不是用的原始梯度向量dx。論文中推薦的參數值eps=1e-8, beta1=0.9, beta2=0.999。在實際操作中,我們推薦Adam作為默認的演算法,一般而言跑起來比RMSProp要好一點。但是也可以試試SGD+Nesterov動量。完整的Adam更新演算法也包含了一個偏置(bias)矯正機制,因為m,v兩個矩陣初始為0,在沒有完全熱身之前存在偏差,需要採取一些補償措施。建議讀者可以閱讀論文查看細節,或者課程的PPT。

拓展閱讀:

  • Unit Tests for Stochastic Optimization一文展示了對於隨機最優化的測試。

——————————————————————————————————————————

譯者註:上圖原文中為動圖,知乎專欄不支持動圖,知友可點擊原文鏈接查看。

上面的動畫可以幫助你理解學習的動態過程。左邊是一個損失函數的等高線圖,上面跑的是不同的最優化演算法。注意基於動量的方法出現了射偏了的情況,使得最優化過程看起來像是一個球滾下山的樣子。右邊展示了一個馬鞍狀的最優化地形,其中對於不同維度它的曲率不同(一個維度下降另一個維度上升)。注意SGD很難突破對稱性,一直卡在頂部。而RMSProp之類的方法能夠看到馬鞍方向有很低的梯度。因為在RMSProp更新方法中的分母項,演算法提高了在該方向的有效學習率,使得RMSProp能夠繼續前進。圖片版權:Alec Radford。

——————————————————————————————————————————

超參數調優

我們已經看到,訓練一個神經網路會遇到很多超參數設置。神經網路最常用的設置有:

  • 初始學習率。

  • 學習率衰減方式(例如一個衰減常量)。

  • 正則化強度(L2懲罰,隨機失活強度)。

但是也可以看到,還有很多相對不那麼敏感的超參數。比如在逐參數適應學習方法中,對於動量及其時間表的設置等。在本節中將介紹一些額外的調參要點和技巧:

實現。更大的神經網路需要更長的時間去訓練,所以調參可能需要幾天甚至幾周。記住這一點很重要,因為這會影響你設計代碼的思路。一個具體的設計是用僕程序持續地隨機設置參數然後進行最優化。在訓練過程中,僕程序會對每個周期後驗證集的準確率進行監控,然後向文件系統寫下一個模型的記錄點(記錄點中有各種各樣的訓練統計數據,比如隨著時間的損失值變化等),這個文件系統最好是可共享的。在文件名中最好包含驗證集的演算法表現,這樣就能方便地查找和排序了。然後還有一個主程序,它可以啟動或者結束計算集群中的僕程序,有時候也可能根據條件查看僕程序寫下的記錄點,輸出它們的訓練統計數據等。

比起交叉驗證最好使用一個驗證集。在大多數情況下,一個尺寸合理的驗證集可以讓代碼更簡單,不需要用幾個數據集來交叉驗證。你可能會聽到人們說他們「交叉驗證」一個參數,但是大多數情況下,他們實際是使用的一個驗證集。

超參數範圍。在對數尺度上進行超參數搜索。例如,一個典型的學習率應該看起來是這樣:learning_rate = 10 ** uniform(-6, 1)。也就是說,我們從標準分布中隨機生成了一個數字,然後讓它成為10的階數。對於正則化強度,可以採用同樣的策略。直觀地說,這是因為學習率和正則化強度都對於訓練的動態進程有乘的效果。例如:當學習率是0.001的時候,如果對其固定地增加0.01,那麼對於學習進程會有很大影響。然而當學習率是10的時候,影響就微乎其微了。這就是因為學習率乘以了計算出的梯度。因此,比起加上或者減少某些值,思考學習率的範圍是乘以或者除以某些值更加自然。但是有一些參數(比如隨機失活)還是在原始尺度上進行搜索(例如:dropout=uniform(0,1))。

隨機搜索優於網格搜索。Bergstra和Bengio在文章Random Search for Hyper-Parameter Optimization中說「隨機選擇比網格化的選擇更加有效」,而且在實踐中也更容易實現。

——————————————————————————————————————————

在Random Search for Hyper-Parameter Optimization中的核心說明圖。通常,有些超參數比其餘的更重要,通過隨機搜索,而不是網格化的搜索,可以讓你更精確地發現那些比較重要的超參數的好數值。

——————————————————————————————————————————

對於邊界上的最優值要小心。這種情況一般發生在你在一個不好的範圍內搜索超參數(比如學習率)的時候。比如,假設我們使用learning_rate = 10 ** uniform(-6,1)來進行搜索。一旦我們得到一個比較好的值,一定要確認你的值不是出於這個範圍的邊界上,不然你可能錯過更好的其他搜索範圍。

從粗到細地分階段搜索。在實踐中,先進行初略範圍(比如10 ** [-6, 1])搜索,然後根據好的結果出現的地方,縮小範圍進行搜索。進行粗搜索的時候,讓模型訓練一個周期就可以了,因為很多超參數的設定會讓模型沒法學習,或者突然就爆出很大的損失值。第二個階段就是對一個更小的範圍進行搜索,這時可以讓模型運行5個周期,而最後一個階段就在最終的範圍內進行仔細搜索,運行很多次周期。

貝葉斯超參數最優化是一整個研究領域,主要是研究在超參數空間中更高效的導航演算法。其核心的思路是在不同超參數設置下查看演算法性能時,要在探索和使用中進行合理的權衡。基於這些模型,發展出很多的庫,比較有名的有: Spearmint, SMAC, 和Hyperopt。然而,在卷積神經網路的實際使用中,比起上面介紹的先認真挑選的一個範圍,然後在該範圍內隨機搜索的方法,這個方法還是差一些。這裡有更詳細的討論。

評價

模型集成

在實踐的時候,有一個總是能提升神經網路幾個百分點準確率的辦法,就是在訓練的時候訓練幾個獨立的模型,然後在測試的時候平均它們預測結果。集成的模型數量增加,演算法的結果也單調提升(但提升效果越來越少)。還有模型之間的差異度越大,提升效果可能越好。進行集成有以下幾種方法:

  • 同一個模型,不同的初始化。使用交叉驗證來得到最好的超參數,然後用最好的參數來訓練不同初始化條件的模型。這種方法的風險在於多樣性只來自於不同的初始化條件。

  • 在交叉驗證中發現最好的模型。使用交叉驗證來得到最好的超參數,然後取其中最好的幾個(比如10個)模型來進行集成。這樣就提高了集成的多樣性,但風險在於可能會包含不夠理想的模型。在實際操作中,這樣操作起來比較簡單,在交叉驗證後就不需要額外的訓練了。

  • 一個模型設置多個記錄點。如果訓練非常耗時,那就在不同的訓練時間對網路留下記錄點(比如每個周期結束),然後用它們來進行模型集成。很顯然,這樣做多樣性不足,但是在實踐中效果還是不錯的,這種方法的優勢是代價比較小。

  • 在訓練的時候跑參數的平均值。和上面一點相關的,還有一個也能得到1-2個百分點的提升的小代價方法,這個方法就是在訓練過程中,如果損失值相較於前一次權重出現指數下降時,就在內存中對網路的權重進行一個備份。這樣你就對前幾次循環中的網路狀態進行了平均。你會發現這個「平滑」過的版本的權重總是能得到更少的誤差。直觀的理解就是目標函數是一個碗狀的,你的網路在這個周圍跳躍,所以對它們平均一下,就更可能跳到中心去。

模型集成的一個劣勢就是在測試數據的時候會花費更多時間。最近Geoff Hinton在「Dark Knowledge」上的工作很有啟發:其思路是通過將集成似然估計納入到修改的目標函數中,從一個好的集成中抽出一個單獨模型。

總結

訓練一個神經網路需要:

  • 利用小批量數據對實現進行梯度檢查,還要注意各種錯誤。

  • 進行合理性檢查,確認初始損失值是合理的,在小數據集上能得到100%的準確率。

  • 在訓練時,跟蹤損失函數值,訓練集和驗證集準確率,如果願意,還可以跟蹤更新的參數量相對於總參數量的比例(一般在1e-3左右),然後如果是對於卷積神經網路,可以將第一層的權重可視化。

  • 推薦的兩個更新方法是SGD+Nesterov動量方法,或者Adam方法。

  • 隨著訓練進行學習率衰減。比如,在固定多少個周期後讓學習率減半,或者當驗證集準確率下降的時候。

  • 使用隨機搜索(不要用網格搜索)來搜索最優的超參數。分階段從粗(比較寬的超參數範圍訓練1-5個周期)到細(窄範圍訓練很多個周期)地來搜索。

  • 進行模型集成來獲得額外的性能提高。

拓展閱讀

  • Leon Bottou的《SGD要點和技巧》。

  • Yann LeCun的《Efficient BackProp》。

  • Yoshua Bengio的《Practical Recommendations for Gradient-Based Training of Deep Architectures》。

譯者反饋

  1. 轉載須全文轉載且註明原文鏈接,否則保留維權權利;
  2. 請知友們通過評論和私信等方式批評指正,貢獻者均會補充提及。

推薦閱讀:

數據分析與認知計算產品 IBM Watson Analytics 試用體驗
Siri 的智能進化
最怪怪的5款人工智慧啥樣?
4年賺34億,讓李嘉誠拜師,他是天才還是魔鬼?
構建 CTC 語音識別解碼網路

TAG:机器学习 | 人工智能 | 神经网络 |