選自PCC,作者:Matt H、Daniel R,機器之心編譯。
本文介紹了構建深度神經網路的一些基本技巧,從通用技巧、神經網路除錯和案例研究三方面展開。
在我們的機器學習實驗室中,我們已經在許多高效能的機器上進行了成千上萬個小時的訓練,積累了豐富的經驗。在這個過程中,並不只有電腦學習到了很多的知識,事實上我們研究人員也犯了很多錯誤,並且修復了很多漏洞。
在本文中,我們將根據自身經驗(主要基於 TensorFlow)向大家提供一些訓練深度神經網路的實用祕訣。有些建議可能對你來說可能已經很熟悉了,但是其他人可能並不太瞭解。另外還有些建議可能並不適用,甚至可能對於特定的任務來說是不好的建議,所以請謹慎使用!
這些都是一些廣為人知的方法,我們也是站在了巨人的肩膀上!本文的目的只是高屋建瓴地對如何在實踐中使用它們進行總結。
通用祕訣
使用 ADAM 優化器。它確實很有效,相對於較傳統的優化器(如原版梯度下降),我們更喜歡使用 ADAM。在 TensorFlow 環境下使用 ADAM 時,請注意:如果你想要儲存和恢復模型權重,請記住在設定完 AdamOptimizer 後設定 Saver,這是因為 ADAM 也有需要恢復的狀態(即對應於每個權重的學習率)。
ReLU 是最好的非線性(啟用函式),這就好比 Sublime 是最好的文字編輯器。但說實話,ReLU 確實是執行速度最快、最簡便的,而且令人驚訝的是,它們在工作時梯度並不會逐漸減小(從而能夠防止梯度消失)。儘管 sigmoid 是一個常用啟用函式,但是它在 DNN 中傳播梯度的效果並不太好。
不要在輸出層使用啟用函式。這應該是顯而易見的,但是如果你通過一個共用的函式構建每一層,那這可能是一個很容易犯的錯誤:請確保在輸出層不要使用啟用函式。
為每一層新增一個偏置項。這是機器學習的入門知識:本質上,偏置項將一個平面轉換到最佳擬合位置。在 y=mx+b 式中,b 是偏置項,使直線能夠向上或向下移動到最佳的擬合位置。
使用方差縮放初始化。在 TensorFlow 中,該方法寫作 tf.contrib.layers.variance_scaling_initializer()。根據我們的實驗,這種初始化方法比常規高斯分佈初始化、截斷高斯分佈初始化及 Xavier 初始化的泛化/縮放效能更好。粗略地說,方差縮放初始化根據每一層輸入或輸出的數量(在 TensorFlow 中預設為輸入的數量)來調整初始隨機權重的方差,從而幫助訊號在不需要其他技巧(如梯度裁剪或批歸一化)的情況下在網路中更深入地傳播。Xavier 和方差縮放初始化類似,只不過 Xavier 中每一層的方差幾乎是相同的;但是如果網路的各層之間規模差別很大(常見於卷積神經網路),則這些網路可能並不能很好地處理每一層中相同的方差。
白化(歸一化)輸入資料。在訓練中,令樣本點的值減去資料集的均值,然後除以它的標準差。當網路的權重在各個方向上延伸和擴充套件的程度越小,你的網路就能更快、更容易地學習。保持資料輸入以均值為中心且方差不變有助於實現這一點。你還必須對每個測試輸入也執行相同的歸一化過程,所以請確保你的訓練集與真實資料類似。
以合理地保留動態範圍的方式對輸入資料進行縮放。這個步驟和歸一化有關,但是應該在歸一化操作之前進行。例如,在真實世界中範圍為 [0, 140000000] 的資料 x 通常可以用「tanh(x)」或「tanh(x/C)」來進行操作,其中 C 是某個常數,它可以對曲線進行拉伸,從而在 tanh 函式的動態傾斜(斜率較大)部分對更大輸入範圍內的資料進行擬合。尤其是在輸入資料在函式的一端或者兩端都不受限的時候,神經網路將在資料處於 (0,1) 時學習效果更好。
一般不要使用學習率衰減。在隨機梯度下降(SGD)中,降低學習率是很常見的,但是 ADAM 天然地就考慮到了這個問題。如果你真的希望達到模型效能的極致,請在訓練結束前的一小段時間內降低學習率;你可能會看到一個突然出現的很小的誤差下降,之後它會再次趨於平緩。
如果你的卷積層有 64 或 128 個濾波器,這就已經足夠了。特別是對於深度網路來說,比如 128 個濾波器就已經很多了。如果你已經擁有了大量的濾波器,那麼再新增更多的濾波器可能並不會提升效能。
池化是為了變換不變性(transform invariance)。池化本質上是讓網路學習到影象「某個部分」的「一般概念」。例如,最大池化能夠幫助卷積網路對影象中特徵的平移、旋轉和縮放具備一定的魯棒性。
神經網路的除錯
如果網路學習效果很差(指網路在訓練中的損失/準確率不收斂,或者你得不到想要的結果),你可以試試下面的這些祕訣:
過擬合!如果你的網路學習效果不佳,你首先應該做的就是去過擬合一個訓練資料點。準確率基本上應該達到 100% 或 99.99%,或者說誤差接近 0。如果你的神經網路不能對一個資料點達到過擬合,那麼模型架構就可能存在很嚴重的問題,但這種問題可能是十分細微的。如果你可以過擬合一個資料點,但是在更大的集合上訓練時仍然不能收斂,請嘗試下面的幾條建議。
降低學習率。你的網路會學習地更慢,但是它可能會找到一個之前使用較大的步長時沒找到的最小值。(直觀地說,你可以想象一下你正在走過路邊的溝渠,此時你想要走進溝的最深處,在那裡模型的誤差是最小的。)
提高學習率。這將加快訓練速度,有助於加強反饋迴路(feedback loop)。這意味著你很快就能大概知道你的網路是否有效。儘管這樣一來網路應該能更快地收斂,但是訓練結果可能不會太好,而且這種「收斂」狀態可能實際上是反覆震盪的。(使用 ADAM 優化器時,我們認為在許多實驗場景下,~0.001 是比較好的學習率。)
減小(小)批量處理的規模。將批處理大小減小到 1 可以向你提供與權重更新相關的更細粒度的反饋,你應該將該過程在 TensorBoard(或者其他的除錯/視覺化工具)中展示出來。
刪掉批歸一化層。在將批處理大小減小為 1 時,這樣做會暴露是否有梯度消失和梯度爆炸等問題。我們曾經遇到過一個好幾個星期都沒有收斂的網路,當我們刪除了批歸一化層(BN 層)之後,我們才意識到第二次迭代的輸出都是 NaN。在這裡使用批量歸一化層,相當於在需要止血帶的傷口上貼上了創可貼。批歸一化有它能夠發揮效果的地方,但前提是你確定自己的網路沒有 bug。
加大(小)批量處理的規模。使用一個更大的批處理規模——還覺得不夠的話,如果可以,你不妨使用整個訓練集——能減小梯度更新的方差,使每次迭代變得更加準確。換句話說,權重更新能夠朝著正確的方向發展。但是!它的有效性存在上限,而且還有一些實體記憶體的限制。我們發現,這條建議通常不如前兩個建議(將批處理規模減小到 1、刪除批歸一化層)有用。
檢查你矩陣的重構「reshape」。大幅度的矩陣重構(比如改變影象的 X、Y 維度)會破壞空間區域性性,使網路更不容易學習,因為這時網路也必須學習重構。(自然特徵變得支離破碎。事實上自然特徵呈現出空間區域性性也是卷積神經網路能夠如此有效的原因!)使用多個影象/通道進行重構時要特別小心;可以使用 numpy.stack() 進行適當的對齊操作。
仔細檢查你的損失函式。如果我們使用的是一個複雜的函式,可以試著把它簡化為 L1 或 L2 這樣的形式。我們發現 L1 對異常值不那麼敏感,當我們遇到帶有噪聲的批或訓練點時,可以進行稍小幅度的調整。
如果可以,仔細檢查你的視覺化結果。你的視覺化庫(matplotlib、OpenCV 等)是否調整資料值的範圍或是對它們進行裁剪?你可以考慮使用一種視覺上均勻的配色方案。
案例研究
為了使上文描述的過程更有關聯性,下面給出了一些用於描述我們構建的卷積神經網路的部分真實迴歸實驗的損失圖(通過 TensorBoard 進行視覺化)。
最初,網路完全沒有學習:
我們試著裁剪資料值,防止它們超越取值範圍:
看看這些沒有經過平滑的值有多麼「瘋狂」!學習率太高了嗎?我們試著降低學習率,並且在一組輸入資料上進行訓練:
你可以看到學習率最初的幾個變化發生在哪裡(大約訓練了 300 步和 3000 步時)。顯然,這裡我們進行的學習率下降調整太快了。所以如果給它更長的學習率衰減時間,它將表現得更好(損失更低):
可以看到,學習率在第 2000 步和第 5000 步時下降。這種情況更好,但是仍然不夠完美,因為損失並沒有降到 0。
然後我們停止學習率衰減,並且嘗試通過 tanh 函式將輸入值移動到一個更狹窄的範圍內。這很顯然將誤差值帶到了 1 以下,但是我們始終不能過擬合訓練集:
在這裡我們發現了,通過刪除批歸一化層,網路很快地在一兩次迭代之後輸出 NaN。我們禁用了批歸一化,並將初始化方法改為方差縮放法。這讓一切都不一樣了!我們可以過擬合僅僅包含一兩個輸入的測試集。然而,下面的圖對 Y 軸進行了裁剪。初始誤差值遠遠高於 5,這說明誤差減小了近 4 個數量級:
上方的圖是非常平滑的,但是你可以看到,它極其迅速地過擬合了測試輸入,並且隨著時間推移,整個訓練集的損失降到了 0.01 以下。這個過程沒有降低學習率。之後,我們在學習率降低了一個數量級之後繼續訓練,得到了更好的結果:
這些結果要好得多!但是如果我們以幾何級別降低學習率,而不是將訓練分成兩部分,會如何呢?
在每一步中將學習率乘以 0.9995,結果不是很好:
這大概是因為學習率下降地太快了。乘數如果取 0.999995 會更好,但是結果和完全不衰減相差無幾。我們從這個特定的實驗序列中得出結論:批歸一化隱藏了糟糕的初始化導致的梯度爆炸;並且除了在最後故意設計的一個學習率衰減可能有幫助,減小學習率對 ADAM 優化器並沒有特別的幫助。與批歸一化一樣,對值進行裁剪掩蓋了真正的問題。我們還通過 tanh 函式控制高方差的輸入值。
我們希望這些基本的訣竅在你對構建深度神經網路更加熟悉的時候能夠提供幫助。通常,正是簡單的事情讓一切變得不同。