構(gòu)建深度神經(jīng)網(wǎng)絡(luò),我有20條「不成熟」的小建議
在我們的機(jī)器學(xué)習(xí)實(shí)驗(yàn)室中,我們已經(jīng)在許多高性能的機(jī)器上進(jìn)行了成千上萬個(gè)小時(shí)的訓(xùn)練,積累了豐富的經(jīng)驗(yàn)。在這個(gè)過程中,并不只有電腦學(xué)習(xí)到了很多的知識(shí),事實(shí)上我們研究人員也犯了很多錯(cuò)誤,并且修復(fù)了很多漏洞。
在本文中,我們將根據(jù)自身經(jīng)驗(yàn)(主要基于 TensorFlow)向大家提供一些訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)的實(shí)用秘訣。有些建議可能對(duì)你來說可能已經(jīng)很熟悉了,但是其他人可能并不太了解。另外還有些建議可能并不適用,甚至可能對(duì)于特定的任務(wù)來說是不好的建議,所以請(qǐng)謹(jǐn)慎使用!
這些都是一些廣為人知的方法,我們也是站在了巨人的肩膀上!本文的目的只是高屋建瓴地對(duì)如何在實(shí)踐中使用它們進(jìn)行總結(jié)。
通用秘訣
使用 ADAM 優(yōu)化器。它確實(shí)很有效,相對(duì)于較傳統(tǒng)的優(yōu)化器(如原版梯度下降),我們更喜歡使用 ADAM。在 TensorFlow 環(huán)境下使用 ADAM 時(shí),請(qǐng)注意:如果你想要保存和恢復(fù)模型權(quán)重,請(qǐng)記住在設(shè)置完 AdamOptimizer 后設(shè)置 Saver,這是因?yàn)?ADAM 也有需要恢復(fù)的狀態(tài)(即對(duì)應(yīng)于每個(gè)權(quán)重的學(xué)習(xí)率)。
ReLU 是***的非線性(激活函數(shù)),這就好比 Sublime 是***的文本編輯器。但說實(shí)話,ReLU 確實(shí)是運(yùn)行速度最快、最簡便的,而且令人驚訝的是,它們?cè)诠ぷ鲿r(shí)梯度并不會(huì)逐漸減小(從而能夠防止梯度消失)。盡管 sigmoid 是一個(gè)常用激活函數(shù),但是它在 DNN 中傳播梯度的效果并不太好。
不要在輸出層使用激活函數(shù)。這應(yīng)該是顯而易見的,但是如果你通過一個(gè)共用的函數(shù)構(gòu)建每一層,那這可能是一個(gè)很容易犯的錯(cuò)誤:請(qǐng)確保在輸出層不要使用激活函數(shù)。
為每一層添加一個(gè)偏置項(xiàng)。這是機(jī)器學(xué)習(xí)的入門知識(shí):本質(zhì)上,偏置項(xiàng)將一個(gè)平面轉(zhuǎn)換到***擬合位置。在 y=mx+b 式中,b 是偏置項(xiàng),使直線能夠向上或向下移動(dòng)到***的擬合位置。
使用方差縮放初始化。在 TensorFlow 中,該方法寫作 tf.contrib.layers.variance_scaling_initializer()。根據(jù)我們的實(shí)驗(yàn),這種初始化方法比常規(guī)高斯分布初始化、截?cái)喔咚狗植汲跏蓟? Xavier 初始化的泛化/縮放性能更好。粗略地說,方差縮放初始化根據(jù)每一層輸入或輸出的數(shù)量(在 TensorFlow 中默認(rèn)為輸入的數(shù)量)來調(diào)整初始隨機(jī)權(quán)重的方差,從而幫助信號(hào)在不需要其他技巧(如梯度裁剪或批歸一化)的情況下在網(wǎng)絡(luò)中更深入地傳播。Xavier 和方差縮放初始化類似,只不過 Xavier 中每一層的方差幾乎是相同的;但是如果網(wǎng)絡(luò)的各層之間規(guī)模差別很大(常見于卷積神經(jīng)網(wǎng)絡(luò)),則這些網(wǎng)絡(luò)可能并不能很好地處理每一層中相同的方差。
白化(歸一化)輸入數(shù)據(jù)。在訓(xùn)練中,令樣本點(diǎn)的值減去數(shù)據(jù)集的均值,然后除以它的標(biāo)準(zhǔn)差。當(dāng)網(wǎng)絡(luò)的權(quán)重在各個(gè)方向上延伸和擴(kuò)展的程度越小,你的網(wǎng)絡(luò)就能更快、更容易地學(xué)習(xí)。保持?jǐn)?shù)據(jù)輸入以均值為中心且方差不變有助于實(shí)現(xiàn)這一點(diǎn)。你還必須對(duì)每個(gè)測(cè)試輸入也執(zhí)行相同的歸一化過程,所以請(qǐng)確保你的訓(xùn)練集與真實(shí)數(shù)據(jù)類似。
以合理地保留動(dòng)態(tài)范圍的方式對(duì)輸入數(shù)據(jù)進(jìn)行縮放。這個(gè)步驟和歸一化有關(guān),但是應(yīng)該在歸一化操作之前進(jìn)行。例如,在真實(shí)世界中范圍為 [0, 140000000] 的數(shù)據(jù) x 通??梢杂谩竧anh(x)」或「tanh(x/C)」來進(jìn)行操作,其中 C 是某個(gè)常數(shù),它可以對(duì)曲線進(jìn)行拉伸,從而在 tanh 函數(shù)的動(dòng)態(tài)傾斜(斜率較大)部分對(duì)更大輸入范圍內(nèi)的數(shù)據(jù)進(jìn)行擬合。尤其是在輸入數(shù)據(jù)在函數(shù)的一端或者兩端都不受限的時(shí)候,神經(jīng)網(wǎng)絡(luò)將在數(shù)據(jù)處于 (0,1) 時(shí)學(xué)習(xí)效果更好。
一般不要使用學(xué)習(xí)率衰減。在隨機(jī)梯度下降(SGD)中,降低學(xué)習(xí)率是很常見的,但是 ADAM 天然地就考慮到了這個(gè)問題。如果你真的希望達(dá)到模型性能的***,請(qǐng)?jiān)谟?xùn)練結(jié)束前的一小段時(shí)間內(nèi)降低學(xué)習(xí)率;你可能會(huì)看到一個(gè)突然出現(xiàn)的很小的誤差下降,之后它會(huì)再次趨于平緩。
如果你的卷積層有 64 或 128 個(gè)濾波器,這就已經(jīng)足夠了。特別是對(duì)于深度網(wǎng)絡(luò)來說,比如 128 個(gè)濾波器就已經(jīng)很多了。如果你已經(jīng)擁有了大量的濾波器,那么再添加更多的濾波器可能并不會(huì)提升性能。
池化是為了變換不變性(transform invariance)。池化本質(zhì)上是讓網(wǎng)絡(luò)學(xué)習(xí)到圖像「某個(gè)部分」的「一般概念」。例如,***池化能夠幫助卷積網(wǎng)絡(luò)對(duì)圖像中特征的平移、旋轉(zhuǎn)和縮放具備一定的魯棒性。
神經(jīng)網(wǎng)絡(luò)的調(diào)試
如果網(wǎng)絡(luò)學(xué)習(xí)效果很差(指網(wǎng)絡(luò)在訓(xùn)練中的損失/準(zhǔn)確率不收斂,或者你得不到想要的結(jié)果),你可以試試下面的這些秘訣:
過擬合!如果你的網(wǎng)絡(luò)學(xué)習(xí)效果不佳,你首先應(yīng)該做的就是去過擬合一個(gè)訓(xùn)練數(shù)據(jù)點(diǎn)。準(zhǔn)確率基本上應(yīng)該達(dá)到 100% 或 99.99%,或者說誤差接近 0。如果你的神經(jīng)網(wǎng)絡(luò)不能對(duì)一個(gè)數(shù)據(jù)點(diǎn)達(dá)到過擬合,那么模型架構(gòu)就可能存在很嚴(yán)重的問題,但這種問題可能是十分細(xì)微的。如果你可以過擬合一個(gè)數(shù)據(jù)點(diǎn),但是在更大的集合上訓(xùn)練時(shí)仍然不能收斂,請(qǐng)嘗試下面的幾條建議。
降低學(xué)習(xí)率。你的網(wǎng)絡(luò)會(huì)學(xué)習(xí)地更慢,但是它可能會(huì)找到一個(gè)之前使用較大的步長時(shí)沒找到的最小值。(直觀地說,你可以想象一下你正在走過路邊的溝渠,此時(shí)你想要走進(jìn)溝的最深處,在那里模型的誤差是最小的。)
提高學(xué)習(xí)率。這將加快訓(xùn)練速度,有助于加強(qiáng)反饋回路(feedback loop)。這意味著你很快就能大概知道你的網(wǎng)絡(luò)是否有效。盡管這樣一來網(wǎng)絡(luò)應(yīng)該能更快地收斂,但是訓(xùn)練結(jié)果可能不會(huì)太好,而且這種「收斂」?fàn)顟B(tài)可能實(shí)際上是反復(fù)震蕩的。(使用 ADAM 優(yōu)化器時(shí),我們認(rèn)為在許多實(shí)驗(yàn)場(chǎng)景下,~0.001 是比較好的學(xué)習(xí)率。)
減小(小)批量處理的規(guī)模。將批處理大小減小到 1 可以向你提供與權(quán)重更新相關(guān)的更細(xì)粒度的反饋,你應(yīng)該將該過程在 TensorBoard(或者其他的調(diào)試/可視化工具)中展示出來。
刪掉批歸一化層。在將批處理大小減小為 1 時(shí),這樣做會(huì)暴露是否有梯度消失和梯度爆炸等問題。我們?cè)?jīng)遇到過一個(gè)好幾個(gè)星期都沒有收斂的網(wǎng)絡(luò),當(dāng)我們刪除了批歸一化層(BN 層)之后,我們才意識(shí)到第二次迭代的輸出都是 NaN。在這里使用批量歸一化層,相當(dāng)于在需要止血帶的傷口上貼上了創(chuàng)可貼。批歸一化有它能夠發(fā)揮效果的地方,但前提是你確定自己的網(wǎng)絡(luò)沒有 bug。
加大(小)批量處理的規(guī)模。使用一個(gè)更大的批處理規(guī)模——還覺得不夠的話,如果可以,你不妨使用整個(gè)訓(xùn)練集——能減小梯度更新的方差,使每次迭代變得更加準(zhǔn)確。換句話說,權(quán)重更新能夠朝著正確的方向發(fā)展。但是!它的有效性存在上限,而且還有一些物理內(nèi)存的限制。我們發(fā)現(xiàn),這條建議通常不如前兩個(gè)建議(將批處理規(guī)模減小到 1、刪除批歸一化層)有用。
檢查你矩陣的重構(gòu)「reshape」。大幅度的矩陣重構(gòu)(比如改變圖像的 X、Y 維度)會(huì)破壞空間局部性,使網(wǎng)絡(luò)更不容易學(xué)習(xí),因?yàn)檫@時(shí)網(wǎng)絡(luò)也必須學(xué)習(xí)重構(gòu)。(自然特征變得支離破碎。事實(shí)上自然特征呈現(xiàn)出空間局部性也是卷積神經(jīng)網(wǎng)絡(luò)能夠如此有效的原因!)使用多個(gè)圖像/通道進(jìn)行重構(gòu)時(shí)要特別小心;可以使用 numpy.stack() 進(jìn)行適當(dāng)?shù)膶?duì)齊操作。
仔細(xì)檢查你的損失函數(shù)。如果我們使用的是一個(gè)復(fù)雜的函數(shù),可以試著把它簡化為 L1 或 L2 這樣的形式。我們發(fā)現(xiàn) L1 對(duì)異常值不那么敏感,當(dāng)我們遇到帶有噪聲的批或訓(xùn)練點(diǎn)時(shí),可以進(jìn)行稍小幅度的調(diào)整。
如果可以,仔細(xì)檢查你的可視化結(jié)果。你的可視化庫(matplotlib、OpenCV 等)是否調(diào)整數(shù)據(jù)值的范圍或是對(duì)它們進(jìn)行裁剪?你可以考慮使用一種視覺上均勻的配色方案。
案例研究
為了使上文描述的過程更有關(guān)聯(lián)性,下面給出了一些用于描述我們構(gòu)建的卷積神經(jīng)網(wǎng)絡(luò)的部分真實(shí)回歸實(shí)驗(yàn)的損失圖(通過 TensorBoard 進(jìn)行可視化)。
最初,網(wǎng)絡(luò)完全沒有學(xué)習(xí):
我們?cè)囍眉魯?shù)據(jù)值,防止它們超越取值范圍:
看看這些沒有經(jīng)過平滑的值有多么「瘋狂」!學(xué)習(xí)率太高了嗎?我們?cè)囍档蛯W(xué)習(xí)率,并且在一組輸入數(shù)據(jù)上進(jìn)行訓(xùn)練:
你可以看到學(xué)習(xí)率最初的幾個(gè)變化發(fā)生在哪里(大約訓(xùn)練了 300 步和 3000 步時(shí))。顯然,這里我們進(jìn)行的學(xué)習(xí)率下降調(diào)整太快了。所以如果給它更長的學(xué)習(xí)率衰減時(shí)間,它將表現(xiàn)得更好(損失更低):
可以看到,學(xué)習(xí)率在第 2000 步和第 5000 步時(shí)下降。這種情況更好,但是仍然不夠***,因?yàn)閾p失并沒有降到 0。
然后我們停止學(xué)習(xí)率衰減,并且嘗試通過 tanh 函數(shù)將輸入值移動(dòng)到一個(gè)更狹窄的范圍內(nèi)。這很顯然將誤差值帶到了 1 以下,但是我們始終不能過擬合訓(xùn)練集:
在這里我們發(fā)現(xiàn)了,通過刪除批歸一化層,網(wǎng)絡(luò)很快地在一兩次迭代之后輸出 NaN。我們禁用了批歸一化,并將初始化方法改為方差縮放法。這讓一切都不一樣了!我們可以過擬合僅僅包含一兩個(gè)輸入的測(cè)試集。然而,下面的圖對(duì) Y 軸進(jìn)行了裁剪。初始誤差值遠(yuǎn)遠(yuǎn)高于 5,這說明誤差減小了近 4 個(gè)數(shù)量級(jí):
上方的圖是非常平滑的,但是你可以看到,它極其迅速地過擬合了測(cè)試輸入,并且隨著時(shí)間推移,整個(gè)訓(xùn)練集的損失降到了 0.01 以下。這個(gè)過程沒有降低學(xué)習(xí)率。之后,我們?cè)趯W(xué)習(xí)率降低了一個(gè)數(shù)量級(jí)之后繼續(xù)訓(xùn)練,得到了更好的結(jié)果:
這些結(jié)果要好得多!但是如果我們以幾何級(jí)別降低學(xué)習(xí)率,而不是將訓(xùn)練分成兩部分,會(huì)如何呢?
在每一步中將學(xué)習(xí)率乘以 0.9995,結(jié)果不是很好:
這大概是因?yàn)閷W(xué)習(xí)率下降地太快了。乘數(shù)如果取 0.999995 會(huì)更好,但是結(jié)果和完全不衰減相差無幾。我們從這個(gè)特定的實(shí)驗(yàn)序列中得出結(jié)論:批歸一化隱藏了糟糕的初始化導(dǎo)致的梯度爆炸;并且除了在***故意設(shè)計(jì)的一個(gè)學(xué)習(xí)率衰減可能有幫助,減小學(xué)習(xí)率對(duì) ADAM 優(yōu)化器并沒有特別的幫助。與批歸一化一樣,對(duì)值進(jìn)行裁剪掩蓋了真正的問題。我們還通過 tanh 函數(shù)控制高方差的輸入值。
我們希望這些基本的訣竅在你對(duì)構(gòu)建深度神經(jīng)網(wǎng)絡(luò)更加熟悉的時(shí)候能夠提供幫助。通常,正是簡單的事情讓一切變得不同。
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號(hào)“機(jī)器之心( id: almosthuman2014)”】