作者:Andrej Karpathy
編譯:ronghuaiyang
導讀
這幾天被Andrej Karpathy這篇博客刷屏了,今天忍不住過了遍,真的是滿滿的乾貨,感覺就像是之前零散的劍招被一本內功心法一下子串了起來,給大家翻譯一下,大家看的時候可以省點力氣,英文好的同學請看原文。
幾周前,我發布了一條關於「最常見的神經網絡錯誤」的推文,列出了一些與訓練神經網絡相關的常見錯誤。這條推文獲得了比我預期更多的關注(包括一個網絡研討會)。顯然,很多人都曾親身經歷過「卷積神經網絡就是這麼工作的」和「我們的convnet實現了最先進的結果」之間的巨大差距。
所以我想,把我tweet的這個話題擴展到應該有的篇幅,可能會很好玩。然而,我不想詳細列舉更多常見的錯誤或充實它們,我想更深入地探討一下如何能夠完全避免這些錯誤(或快速修復它們)。這樣做的訣竅是遵循一個特定的過程,據我所知,這個過程並不經常被記錄下來。讓我們從兩個重要的觀察開始。
1) 神經網絡訓練是一個有漏洞的抽象概念
據說開始訓練神經網絡很容易。許多庫和框架都覺得使用30行代碼來解決數據問題很了不起,這給人一種即插即用的(錯誤的)印象。常見的做法是:
>>> your_data = # plug your awesome dataset here
>>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)
# conquer world here
在我們的腦子裡,標準的軟體就應該是這樣的,通常可以獲得乾淨的api和抽象。使用Requests庫演示一下:
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
那樣是很酷!開發人員承擔了理解查詢字符串、url、GET/POST請求、HTTP連接等重擔,並在很大程度上隱藏了幾行代碼背後的複雜性。這是我們所熟悉和期望的。不幸的是,神經網絡不是這樣的。如果你稍微和訓練ImageNet分類器不太一樣的話,那麼它們就不是「現成的」技術了。我試圖在我的帖子" Yes you should understand backprop "中指出這一點,通過選擇反向傳播並將其稱為「泄漏的抽象」,但真實情況要糟糕得多。Backprop + SGD不會神奇地讓你的網絡工作。Batch norm並不能神奇地使其更快地收斂。RNNs也不會神奇地讓你的文本即插即用。僅僅因為你可以用增強學習表示你的問題並不意味著你應該這樣做。如果你堅持使用這種技術而不了解它的工作原理,你很可能會失敗。這使我想到……
2) 神經網絡訓練默默的失敗了
當你破壞或配置代碼錯誤時,通常會得到某些異常。你輸入了一個整數,但是期望輸入是一個字符串。這個函數只需要3個參數。導入失敗。這個key不存在。這兩個列表中的元素數量不相等。此外,通常可以為某個功能創建單元測試。
這只是訓練神經網絡的一個開始。從語法上來說,一切都是正確的,但整件事的安排並不正確,而且這真的很難判斷。「可能的錯誤面」很大,符合邏輯(與語法相反),並且很難進行單元測試。例如,在數據增強過程中,當你從左到右翻轉圖像時,可能忘記了翻轉標籤。你的網絡仍然可以(令人震驚地)很好地工作,因為你的網絡可以在內部學會檢測翻轉的圖像,然後左右翻轉它的預測。或者你的自回歸模型不小心把它試圖預測的東西作為輸入,因為有一個減一的錯誤。或者你試著修剪你的梯度,但結果卻減少了損失,導致異常值的樣本在訓練中被忽略。或者你從一個預訓練的檢查點初始化你的權重,但沒有使用原始平均值。或者你只是搞砸了正則化強度、學習率、衰減率、模型大小等的設置。因此,只有在幸運的情況下,錯誤配置的神經網絡才會拋出異常,大多數情況下,它會訓練,但是,這樣默默地工作更糟。
因此,用「簡單粗暴」的方法來訓練神經網絡是行不通的,只會導致痛苦。現在,痛苦是讓神經網絡正常工作的一個非常自然的部分,但是它可以通過徹底的,防禦性的,偏執的,以及對幾乎所有可能的事情的可視化著迷的方式來減輕。根據我的經驗,耐心和對細節的關注是深度學習成功最重要的因素。
方案
基於以上兩個事實,我為自己開發了一個特定的過程,當我將神經網絡應用到一個新的問題時,我將遵循這個過程,我將嘗試描述它。你將看到,它非常認真地對待上述兩個原則。特別是,它從簡單到複雜,每一步我們都對將要發生的事情做出具體的假設,然後通過實驗驗證它們,或者進行調查,直到我們發現一些問題。我們極力避免的是同時引入大量「未經驗證」的複雜性,這必然會引入錯誤/錯誤配置,而這些錯誤/錯誤配置將永遠無法找到(如果有的話)。如果編寫你的神經網絡代碼就像訓練代碼一樣,你會想要使用非常小的學習率,然後去猜測,再在每次疊代之後評估完整的測試集。
1. 與數據融為一體
訓練神經網絡的第一步是完全不接觸任何神經網絡代碼,而是從徹底檢查數據開始。這一步至關重要。我喜歡花大量的時間(以小時為單位)瀏覽數千個樣本,理解它們的分布並尋找模式。幸運的是,你的大腦非常擅長這個。有一次,我發現數據中包含重複的樣本。另一次我發現了損壞的圖像/標籤。我尋找數據的不平衡和偏差。我通常還會注意我自己對數據進行分類的過程,這暗示了我們最終將探索的架構類型。舉個例子,非常局部的特徵是否足夠,或者我們是否需要全局上下文?有多少多樣性,它以什麼形式出現?哪些多樣性是假的,是不是可以可以通過預處理挑出來?空間位置重要嗎,我們用不用平均池化?細節有多重要,我們能在對圖像降採樣多少倍?標籤的噪聲程度怎麼樣?
此外,由於神經網絡實際上是對數據集的壓縮/編譯版本,你可以查看你的網絡的預測,並了解它們可能來自何處。如果你的網絡給你的預測與你在數據中看到的不一致,那就錯了。
一旦你有了一個定性的感覺,寫一些簡單的代碼來搜索/過濾/排序也是一個好主意,不管你能想到什麼(例如標籤的類型,標註的尺寸,標籤的數量,等等),然後可視化它們的分布,看看在每一個維度上的異常值。異常值幾乎總是會發現數據質量或預處理中的一些bug。
2. 配置端到端的訓練/評估框架+獲取一個基線
現在我們了解了我們的數據了,那我們是不是就可以使用我們的超炫的多尺度ASPP FPN ResNet,並開始訓練非常牛逼的模型了呢?肯定是不行的,那樣做是自尋死路。我們的下一步是建立一個完整的訓練+評估框架,通過一系列的實驗獲得對其正確性的信任。在這個階段,最好選擇一些你不可能搞砸的簡單模型——例如一個線性分類器,或者一個非常小的卷積網絡。我們想要訓練它,可視化損失,所有的其他指標(例如準確性),模型預測,並在此過程中執行一系列帶有明確假設的消融實驗。
這個階段的小貼士和技巧:
- 固定隨機種子。總是使用固定的隨機種子來保證當你運行代碼兩次時,將得到相同的結果。這消除了產生變化的一個因素,將有助於你保持理智。
- 簡化。確保取消任何不必要的想法。例如,在這個階段一定要關閉所有的數據增強。數據增強是一種正則化策略,我們稍後可能會將其合併進來,但目前它只是引入一些愚蠢bug的另一個機會。
- 將有效數字添加到您的eval中。當繪製測試損失時,在整個(大型)測試集上運行評估。不要只在每個batch上繪製測試,然後依賴於在Tensorboard中平滑它們。我們追求正確性,很願意放棄時間保持理智。
- 驗證損失@init。驗證損失從正確的損失值開始。例如,如果你正確地初始化你的最後一層,你應該在softmax的初始化上度量 -log(1/n_classes)。同樣的預設值可以用於L2回歸、Huber損失等。
- 正確初始化。正確初始化最後一層權重。例如,如果你要回歸一些平均值為50的值,那麼需要把偏差初始化為50。如果你有一個不平衡的數據集,其正負之比為1:10,那麼需要在你的logits上設置偏差,使你的網絡在初始化時預測機率為0.1。正確設置這些參數將加快收斂速度,並消除「曲棍球棒」損失曲線,在最初幾次疊代中,你的網絡基本上只是學習偏差。
- 人類基線。監控人類可解釋和可檢查的損失以外的指標(例如準確性)。只要有可能,評估你自己(人類)的準確性,並與之進行比較。或者,對測試數據進行兩次標註,對於每個樣本,將一個標註作為預測,將第二個標註作為基本事實。
- 獨立於輸入的基線。訓練一個獨立於輸入的基線(例如,最簡單的方法就是將所有輸入設置為零)。這應該比使用實際數據的情況更糟糕。也就是說,你的模型是否可以學會從輸入中提取有用信息?
- 在一個batch上過擬合。在只有一個batch上的少數幾個樣本(例如,只有兩個)上做到過擬合。為此,我們增加模型的容量(例如添加層或濾波器),並驗證我們可以達到的最低的可實現損失(例如零)。我也喜歡在相同的圖中可視化標籤和預測,並確保一旦我們達到最小損失,它們最終會完美地對齊。如果不是這樣的,那麼在某個地方就有一個bug,我們就無法繼續到下一個階段。
- 驗證減少訓練損失。在這個階段,你期望你的數據集是欠擬合的,因為你正在使用一個玩具模型。試著增加一點它的容量。你的訓練損失是不是減少了?
- 在進網絡之前進行可視化。在 y_hat=model(x)(或tf中的 sess.run)之前是可視化數據的正確位置。也就是說,你想把進入網絡的東西可視化,把原始的張量數據和標籤解碼成可視化的東西。這是唯一的「真理之源」。我已經數不清這個救了我多少次,暴露出了多少數據預處理和增強方面的問題。
- 可視化預測動態。在訓練過程中,我喜歡將固定測試批次的模型預測可視化。這些預測如何移動的「動態」將為你提供關於訓練進展的非常好的直覺。很多時候,如果網絡在某種程度上太過搖擺,暴露出不穩定性,你可能會覺得網絡「難以」擬合你的數據。非常低或非常高的學習率在抖動的數量中也很容易被注意到。
- 使用反向傳播來繪製依賴關係圖。你的深度學習代碼通常包含複雜、向量化和廣播等操作。我遇到過的一個比較常見的錯誤是,人們犯了這個錯誤(例如,他們使用 view而不是 transpose/permute),無意中混合了批處理維度上的信息。倒霉的是,你的網絡通常仍然訓練良好,因為它將學會忽略來自其他樣本的數據。調試這個(和其他相關問題)的一種方法是,將某個樣本i的損失設置為1.0,一直反向傳播到輸入,並確保只在第i個樣本上得到非零梯度。更一般地說,梯度提供了關於網絡中相互依賴的信息,這對於調試非常有用。
- 泛化到特殊的情況。這是一個通用的編碼技巧,但是我經常看到人們在做超出他們能力範圍的事情時會產生bug,從零開始編寫一個相對通用的功能。我喜歡為我現在正在做的事情寫一個非常具體的函數,讓它工作,然後泛化它,確保得到相同的結果。這通常適用於向量化代碼,我幾乎總是先寫出完整的循環版本,然後每次只將其轉換為向量化代碼。
3. 過擬合
在這個階段,我們應該對數據集有一個很好的理解,我們有完整的訓練+評估流程。對於任何給定的模型,我們都可以(可重複地)計算我們可信任的度量。我們還準備好了一個獨立於輸入的基線,一些簡單基線的性能(我們最好戰勝這些基線),並且我們對人類的性能有一個粗略的感覺(我們希望達到這個目標)。現在已經為疊代一個好的模型做好了準備。
我喜歡採用的找到一個好的模型的方法有兩個階段:首先獲得一個足夠大的模型,它可以過擬合(即關注訓練損失),然後適當地對其進行正則化化(放棄一些訓練損失,以改進驗證損失)。我喜歡這兩個階段的原因是,如果我們不能達到任何模型的低錯誤率,這可能再次表明一些問題、bug或錯誤配置。
這一階段的一些建議和技巧:
- 選擇模型。要獲得良好的訓練損失,需要為數據選擇合適的體系結構。當涉及到選擇這一點時,我的第一條建議是:不要逞能。我見過很多人,他們渴望瘋狂而富有創造性地將神經網絡工具箱的樂高積木堆在各種對他們來說有意義的奇特架構中。在項目的早期階段需要強烈抵制這種誘惑。我總是建議人們簡單地找到最相關的文件,並複製粘貼他們最簡單的架構,以獲得良好的性能。例如,如果你要對圖片進行分類,不要逞能,在第一次運行時複製粘貼一個ResNet-50即可。稍後你可以做一些更自定義的事情來解決這個問題。
- Adam是安全的。在設置基線的早期階段,我喜歡使用Adam,學習率為3e-4。根據我的經驗,adam對超參數要寬容得多,包括糟糕的學習率。對於ConvNets來說,一個良好調優的SGD幾乎總是略優於Adam,但是最優學習率區域要窄得多,而且是針對特定問題的。(注意:如果你在使用RNNs和相關的序列模型,則使用Adam更為常見。同樣,在項目的初始階段,不要逞能,跟隨大多數相關論文所做的事情。)
- 一次只複雜化一個。如果你有多個信號要插入分類器,我建議你將它們一個接一個地插入,每次都要確保你獲得預期的性能提升。不要一開始就把所有的東西都扔給你的模型。還有其他增加複雜性的方法——例如,你可以先插入較小的圖像,然後將其放大,等等。
- 不要相信默認的學習率衰減值。如果你在重新使用來自其他領域的代碼,那麼一定要非常小心學習率衰減。你不僅想為不同的問題使用不同的衰減策略,而且更糟的是,在一個典型的實現中,策略將基於當前epoch數,它可以根據數據集的大小變化很大。ImageNet在epoch 30時衰減10。如果你沒有訓練ImageNet,那麼你肯定不想要這個。如果你不小心的話,你的代碼可能會秘密地將你的學習速度過早地降低到零,從而不允許你的模型收斂。在我自己的工作中,我總是完全禁用學習率衰減(我使用一個常量LR),並在最後再調優它。
4. 正則化
理想情況下,我們現在擁有一個至少能在訓練集上擬合的很好的大型模型。現在是時候對其進行正則化,並通過放棄一些訓練精度來獲得一些驗證精度。以下是一些小貼士和技巧:
- 獲得更多數據。首先,在任何實際設置中對模型進行正則化的最佳方法和首選方法是添加更多真實的訓練數據。當你可以收集更多的數據時,花費大量的工程周期試圖從一個小的數據集中壓榨出性能,是一個非常常見的錯誤。據我所知,添加更多的數據幾乎是唯一保證單調地提高一個配置良好的神經網絡性能的方法,幾乎是無限期的。另一種是模型集成(如果你負擔的起的話),但最多也就5個模型。
- 數據增強。僅次於真實數據的是半假數據——嘗試更積極的數據增強。
- 創造性增強。如果半假數據做不到這一點,假數據可能也會有所作為。人們正在尋找擴展數據集的創造性方法。例如,領域隨機化、使用模擬、聰明的混合,比如將(潛在的模擬)數據插入場景,甚至是GANs。
- 預訓練。即使你有足夠的數據,如果可以的話,使用一個預先訓練好的網絡也不會有什麼壞處。
- 堅持監督學習。不要對無監督訓練過度興奮。據我所知,沒有哪個無監督學習的版本在視覺應用上取得了好的結果。
- 小的輸入維數。刪除可能包含假信號的特徵。如果你的數據集很小,任何添加的偽輸入都只是另一個過擬合的機會。同樣,如果底層細節不太重要,可以嘗試輸入更小的圖像。
- 小模型尺寸。在許多情況下,可以使用網絡上的領域知識約束來減小其大小。例如,過去流行在ImageNet的主幹頂部使用全連接層,但現在這些層已被簡單的平均池化所取代,從而消除了大量參數。
- 減少批大小。由於batch norm,較小的batch大小對應較強的正則化。這是因為batch的經驗平均值/std是完整的平均值/std的更近似的版本,所以比例和偏移量會使你的batch波動得更多。
- 添加dropout。對ConvNets使用dropout2d(空間dropout)。謹慎地使用這個函數,因為使用batch norm時,dropout似乎不太好用。
- 權值衰減。增加權值衰減的懲罰。
- 提前停止。基於已測量的驗證損失停止訓練,以便在模型即將過擬合時獲取它。
- 試試大一點的模型。我只是在提前停止後才提到這一點,但我在過去發現過幾次,當然更大的模型最終會過擬合得更多,但它們的「提前停止」性能往往比較小的模型好得多。
最後,為了進一步確信你的網絡是一個合理的分類器,我喜歡可視化網絡的第一層權重,並確保得到有意義的好邊緣。如果你的第一層過濾器看起來像噪音,那麼有些東西可能會出問題。類似地,網絡內的激活有時會顯示奇怪的紋理,這也提示有問題。
5. 調試
你現在應該「循環」使用你的數據集,探索廣闊的模型空間,為的是實現低驗證損失。這一步的一些小貼士和技巧:
- 隨機網格搜索。同時調優多個超參數,使用網格搜索來確保覆蓋所有設置聽起來很誘人,但是請記住最好使用隨機搜索。直觀地說,這是因為神經網絡對某些參數比其他參數更敏感。在極限情況下,如果一個參數a很重要,但是更改b沒有效果,那麼你寧願更完整地採樣a,而不是在幾個定點上多次採樣。
- 超參數優化。有大量的貝葉斯超參數優化工具箱,我的幾個朋友也有過成功使用的報告,但我個人的經驗是,探索一個非常大的模型空間和超參數的最先進的方法是使用實習生:)。只是開玩笑。
6. 再壓榨一波
一旦你找到了最好的架構類型和超參數,你仍然可以使用更多的技巧來再壓榨一下:
- 集成。模型集成是一種非常有保證的方法,可以獲得2%的準確率提升。如果你在測試時無法負擔計算開銷,可以考慮使用dark knowledge將你的集成提取到一個網絡中。
- 繼續訓練。我經常看到人們在驗證損失趨於平穩時停止模型訓練。在我的經驗中,網絡持續訓練的時間長得出乎意料。有一次,我在寒假期間模型訓練的時候不小心離開了,當我一月份回來的時候,那是SOTA(「最先進的技術」)。
結論
一旦你看到這兒了,你會得到所有成功的配方:你有一個深入了解的技術,數據集和問題,你建立了整個訓練/評估的基礎設施並且對其準確性有很高的信心,你探索了越來越複雜的模型,你的每一步都得到了預期的性能改進。你也已經準備好閱讀大量的論文,嘗試大量的實驗,並獲得SOTA結果。好運!
英文原文:http://karpathy.github.io/2019/04/25/recipe/
請長按或掃描二維碼關注本公眾號