出神入化:特斯拉AI主管、李飛飛高徒Karpathy的33個神經(jīng)網(wǎng)絡(luò)「煉丹」技巧
Andrej Karpathy 是深度學(xué)習(xí)計算機(jī)視覺領(lǐng)域、生成式模型與強(qiáng)化學(xué)習(xí)領(lǐng)域的研究員。博士期間師從李飛飛。在讀博期間,兩次在谷歌實習(xí),研究在 Youtube 視頻上的大規(guī)模特征學(xué)習(xí),2015 年在 DeepMind 實習(xí),研究深度強(qiáng)化學(xué)習(xí)。畢業(yè)后,Karpathy 成為 OpenAI 的研究科學(xué)家,后于 2017 年 6 月加入特斯拉擔(dān)任人工智能與自動駕駛視覺總監(jiān)。
今日他發(fā)布的這篇博客能為深度學(xué)習(xí)研究者們提供極為明晰的洞見,在 Twitter 上也引發(fā)了極大的關(guān)注。
1. 誰說神經(jīng)網(wǎng)絡(luò)訓(xùn)練簡單了?
很多人認(rèn)為開始訓(xùn)練神經(jīng)網(wǎng)絡(luò)是很容易的,大量庫和框架號稱可以用 30 行代碼段解決你的數(shù)據(jù)問題,這就給大家留下了(錯誤的)印象:訓(xùn)練神經(jīng)網(wǎng)絡(luò)這件事是非常簡單的,不同模塊即插即用就能搭個深度模型。
簡單的建模過程通常如下所示:
- >>> your_data = # plug your awesome dataset here
 - >>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)# conquer world here
 
這些庫和示例令我們想起了熟悉標(biāo)準(zhǔn)軟件及模塊,標(biāo)準(zhǔn)軟件中通??梢垣@取簡潔的 API 和抽象。
例如 Request 庫的使用展示如下:
- >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
 - >>> r.status_code200
 
酷!這些庫和框架的開發(fā)者背負(fù)起理解用戶 Query 字符串、url、GET/POST 請求、HTTP 連接等的大量需求,將復(fù)雜度隱藏在幾行代碼后面。這就是我們熟悉與期待的。
然而,神經(jīng)網(wǎng)絡(luò)不一樣,它們并不是現(xiàn)成的技術(shù)。我在 2016 年撰寫的一篇博客中試圖說明這一點,然而現(xiàn)在的情況似乎更加糟糕了。
Backprop + SGD 不是魔法,無法讓你的網(wǎng)絡(luò)運(yùn)行;批歸一化也無法奇跡般地使網(wǎng)絡(luò)更快收斂;RNN 也不能神奇地讓你直接處理文本。不要因為你可以將自己的問題表示為強(qiáng)化學(xué)習(xí),就認(rèn)為你應(yīng)該這么做。如果你堅持在不理解技術(shù)原理的情況下去使用它,那么你很可能失敗。
2. 背著我不 work 的神經(jīng)網(wǎng)絡(luò)
當(dāng)你破壞代碼或者錯誤配置代碼時,你通常會得到某種異常。你在原本應(yīng)該插入字符串的地方插入了整數(shù);導(dǎo)入出錯;該關(guān)鍵字不存在……此外,為了方便 debug,你還很可能為某個功能創(chuàng)建單元測試。
這還只是開始。訓(xùn)練神經(jīng)網(wǎng)絡(luò)時,有可能所有代碼的句法都正確,但整個訓(xùn)練就是不對??赡軉栴}出現(xiàn)在邏輯性(而不是句法),且很難通過單元測試找出來。
例如,你嘗試截?fù)p失度而不是梯度,這會導(dǎo)致訓(xùn)練期間的異常值被忽視,但語法或維度等檢測都不會出現(xiàn)錯誤。又或者,你弄錯了正則化強(qiáng)度、學(xué)習(xí)率、衰減率、模型大小等的設(shè)置,那么幸運(yùn)的話網(wǎng)絡(luò)會報錯,然而大部分時候它會繼續(xù)訓(xùn)練,并默默地變糟……
因此,「快速激烈」的神經(jīng)網(wǎng)絡(luò)訓(xùn)練方式?jīng)]有用,只會導(dǎo)致困難。現(xiàn)在,這些經(jīng)驗性困難是使神經(jīng)網(wǎng)絡(luò)正常運(yùn)行的攔路虎,你需要更加周密詳盡地調(diào)試網(wǎng)絡(luò)才能減少困難,需要大量可視化來了解每一件事。
在我的經(jīng)驗中,深度學(xué)習(xí)成功的重要因素是耐心和注重細(xì)節(jié)。
如何解決
基于以上兩點事實,我開發(fā)了一套將神經(jīng)網(wǎng)絡(luò)應(yīng)用于新問題的特定流程。該流程嚴(yán)肅地執(zhí)行了上述兩項原則:耐心和注重細(xì)節(jié)。
具體來說,它按照從簡單到復(fù)雜的方式來構(gòu)建,我們在每一步都對即將發(fā)生的事作出準(zhǔn)確的假設(shè),然后用實驗來驗證假設(shè)或者調(diào)查直到發(fā)現(xiàn)問題。我們試圖盡力阻止大量「未經(jīng)驗證的」復(fù)雜性一次來襲,這有可能導(dǎo)致永遠(yuǎn)也找不到的 bug/錯誤配置。如果讓你像訓(xùn)練神經(jīng)網(wǎng)絡(luò)那樣寫它的代碼,你會想使用非常小的學(xué)習(xí)率,然后猜測,再在每次迭代后評估整個測試集。
1. 梳理數(shù)據(jù)
訓(xùn)練神經(jīng)網(wǎng)絡(luò)不要碰代碼,先徹底檢查自己的數(shù)據(jù)。這一步非常關(guān)鍵。我喜歡用大量時間瀏覽數(shù)千個樣本,理解它們的分布,尋找其中的模式。幸運(yùn)的是,人類大腦很擅長做這件事。有一次,我發(fā)現(xiàn)數(shù)據(jù)中包含重復(fù)的樣本,還有一次我發(fā)現(xiàn)了損壞的圖像/標(biāo)簽。我會查找數(shù)據(jù)不均衡和偏差。我通常還會注意自己的數(shù)據(jù)分類過程,它會揭示我們最終探索的架構(gòu)。比如,只需要局部特征就夠了還是需要全局語境?標(biāo)簽噪聲多大?
此外,由于神經(jīng)網(wǎng)絡(luò)是數(shù)據(jù)集的壓縮/編譯版本,你能夠查看網(wǎng)絡(luò)(錯誤)預(yù)測,理解預(yù)測從哪里來。如果網(wǎng)絡(luò)預(yù)測與你在數(shù)據(jù)中發(fā)現(xiàn)的不一致,那么一定是什么地方出問題了。
在你對數(shù)據(jù)有了一些感知之后,你可以寫一些簡單的代碼來搜索/過濾/排序標(biāo)簽類型、標(biāo)注規(guī)模、標(biāo)注數(shù)量等,并沿任意軸可視化其分布和異常值。異常值通常能夠揭示數(shù)據(jù)質(zhì)量或預(yù)處理中的 bug。
2. 配置端到端訓(xùn)練/評估架構(gòu)、獲取基線結(jié)果
現(xiàn)在我們已經(jīng)理解了數(shù)據(jù),那我們就可以開始構(gòu)建高大上的多尺度 ASPP FPN ResNet 并訓(xùn)練強(qiáng)大的模型了嗎?當(dāng)然還不到時候,這是一個充滿荊棘的道路。我們下一步需要構(gòu)建一個完整的訓(xùn)練、評估架構(gòu),并通過一系列實驗確定我們對準(zhǔn)確率的置信度。
在這個階段,你們選擇一些不會出錯的簡單模型,例如線性分類器或非常精簡的 ConvNet 等。我們希望訓(xùn)練這些模型,并可視化訓(xùn)練損失、模型預(yù)測和其它度量指標(biāo)(例如準(zhǔn)確率)。當(dāng)然在這個過程中,我們還需要基于一些明確假設(shè),從而執(zhí)行一系列對照實驗(ablation experiments)。
該階段的一些技巧與注意事項:
- 固定隨機(jī) seed:始終使用固定的隨機(jī) seed 能保證很多屬性,例如在我們兩次運(yùn)行相同代碼時能得到相同的輸出。這能消除變化因子,從進(jìn)行合理的判斷。
 - 簡化:確保禁用不必要的技巧。例如,在這個階段肯定需要關(guān)閉數(shù)據(jù)增強(qiáng)。數(shù)據(jù)增強(qiáng)可以在后期引入,并作為一種強(qiáng)大的正則化策略。不過在這個階段引入的話,它就有機(jī)會帶來一些愚蠢的 bug。
 - 使用多數(shù)據(jù)、少次數(shù)的驗證評估:當(dāng)我們在繪制測試損失時,我們需要在整個比較大的測試集中執(zhí)行評估。不要過幾個批量就繪制一次測試損失,然后再依賴 TensorBoard 的平滑處理。我們雖然追求的是準(zhǔn)確率,但也要防止犯這些低級錯誤。
 - 在初始化中驗證損失:驗證你的損失函數(shù)在初始化中有比較合理的損失值。例如,如果你正確地初始化最終層,那么你應(yīng)該通過-log(1/n_classes) 度量初始化的 Softmax 值。L2 回歸和 Huber 損失函數(shù)等都有相同的默認(rèn)值。
 - 優(yōu)秀的初始化:正確地初始化最終層。例如,如果你正在對均值為 50 的一些數(shù)據(jù)做回歸處理,那么初始化的最終偏置項就應(yīng)該為 50。如果你有一個非平衡數(shù)據(jù)集(兩類樣本數(shù) 1:10),那么就需要在 logits 上設(shè)置偏置項,令模型在初始化時預(yù)測概率為 0.1。正確配置這些偏置項將加快收斂速度,因為網(wǎng)絡(luò)在前面幾次迭代中基本上只在學(xué)習(xí)偏置。
 - 人類基線結(jié)果:監(jiān)控?fù)p失值等其他度量指標(biāo)(例如準(zhǔn)確度),這些指標(biāo)應(yīng)該是人類能解釋并檢查的。盡可能評估你自己(人類)獲得的準(zhǔn)確率,并與構(gòu)建的模型做對比。或者對測試數(shù)據(jù)進(jìn)行兩次標(biāo)注,其中一次為預(yù)測值,另一次為標(biāo)注值。
 - 獨(dú)立于輸入的基線結(jié)果:訓(xùn)練一個獨(dú)立于輸入的基線模型,例如最簡單的方法就是將所有輸入都設(shè)置為 0。這樣的模型應(yīng)該比實際輸入數(shù)據(jù)表現(xiàn)更差,你的模型是否準(zhǔn)備好從任何輸入中抽取任何信息?
 - 在批數(shù)據(jù)上過擬合:在單個批數(shù)據(jù)上使得過擬合(兩個或多個少樣本)。為此,我們需要增加模型擬合能力,并驗證我們能達(dá)到的損失值(即 0)。我還想在同一張圖中顯示標(biāo)簽和預(yù)測值,并確保損失值一旦達(dá)到最小,它們就能對齊了。
 - 驗證訓(xùn)練損失的下降:在這一階段,你可能希望在數(shù)據(jù)集上實現(xiàn)欠擬合,該階段的模型應(yīng)該是極簡的。然后我們嘗試增加一點模型的擬合能力,再看看訓(xùn)練損失是否稍微下降了一些。
 - 在輸入網(wǎng)絡(luò)前可視化:在運(yùn)行模型之前,我們需要可視化數(shù)據(jù)。也就是說,我們需要可視化輸入到網(wǎng)絡(luò)的具體數(shù)據(jù),即可視化原始張量的數(shù)據(jù)和標(biāo)簽。這是「真實來源」,我有很多次都是因為這個過程而節(jié)省了大量時間,并揭示了數(shù)據(jù)預(yù)處理和數(shù)據(jù)增強(qiáng)過程中的問題。
 - 可視化預(yù)測過程:我喜歡在訓(xùn)練過程中對一個固定的測試批數(shù)據(jù)進(jìn)行模型預(yù)測的可視化。這展示了預(yù)測值如何變化的過程,能為我們提供關(guān)于訓(xùn)練過程的優(yōu)秀直覺。很多時候,如果網(wǎng)絡(luò)以某種方式小幅度波動,那么模型最可能在嘗試擬合數(shù)據(jù),這也展示了一些不穩(wěn)定性。太低或太高的學(xué)習(xí)率也很容易注意到,因為抖動量比較大。
 - 使用反向傳播繪制依賴性:你的深度學(xué)習(xí)代碼通常包括復(fù)雜的、矢量化的、Boardcast 操作。一個常見的 bug 是,人們會無意間使用 view 而不是 transpose/permute,從而混合了批量數(shù)據(jù)中的維度信息。然而,你的網(wǎng)絡(luò)仍然可以正常訓(xùn)練,只不過它們學(xué)會忽略了其它樣本中的數(shù)據(jù)。一種 debug 的方法是將某些樣本 i 的損失設(shè)置為 1.0,然后運(yùn)行反向傳播一直到輸入,并確保第 i 個樣本的梯度不為零。更一般的,梯度為我們提供了網(wǎng)絡(luò)中的依賴性關(guān)系,它們在 debug 中非常有用。
 - 一般化特殊案例:這是一種更為通用的代碼技巧,但是我經(jīng)??吹饺藗冊谑褂眠@些技巧時會新產(chǎn)生 Bug,尤其是在從頭構(gòu)建一般函數(shù)時。相反,我喜歡直接寫非常具體的函數(shù),它只包含我現(xiàn)在需要做的事情。我會先讓這個函數(shù)能 work,然后再一般化好函數(shù),并確保能取得相同的結(jié)果。通常這個過程會體現(xiàn)在向量化代碼中,我會先用循環(huán)編寫某個過程,然后再一次一個循環(huán)地將它們轉(zhuǎn)化為向量化化代碼。
 
3. 過擬合
到了這個階段,我們應(yīng)該對數(shù)據(jù)集有所了解了,而且有了完整的訓(xùn)練+評估流程。對于任何給定的模型,我們可以計算出我們信任的度量。而且還為獨(dú)立于輸入的基線準(zhǔn)備了性能,一些 dumb 基線的性能(建議超過這些),我們?nèi)祟惖谋憩F(xiàn)有大致的了解(并希望達(dá)到這一點)?,F(xiàn)在,我們已經(jīng)為迭代一個好的模型做好了準(zhǔn)備。
我準(zhǔn)備用來尋找好模型的方法有兩個階段:首先獲得足夠大的模型,這樣它能夠過擬合(即關(guān)注訓(xùn)練損失),然后對其進(jìn)行適當(dāng)?shù)恼齽t化(棄掉一些訓(xùn)練損失以改進(jìn)驗證損失)。我喜歡這兩個階段的原因是,如果我們不能用任何模型實現(xiàn)較低的誤差率,則可能再次表明一些問題、bug 和配置錯誤。
該階段的一些技巧與注意事項:
- 選擇模型:為了達(dá)到理想的訓(xùn)練損失,我們可能希望為數(shù)據(jù)選擇一個合適的架構(gòu)。當(dāng)我們在挑選模型時,我的建議即別好高騖遠(yuǎn)。我看到很多人都非??释婚_始就堆疊一些新的模塊,或創(chuàng)造性地用于各種異質(zhì)架構(gòu),從而想一步到位做好。我建議可以找最相關(guān)的論文,并直接利用它們的簡單架構(gòu),從而獲得良好性能。后面再基于這個架構(gòu)做修改和改進(jìn),并將我們的想法加進(jìn)去就行
 - Adam 是一般選擇:在配置基線模型地早期階段,我喜歡使用 Adam 算法(學(xué)習(xí)率為 3e-4)。在我的經(jīng)驗中,Adam 對超參數(shù)的容忍度更高,不太好的學(xué)習(xí)率也能獲得一般的效果。對于卷積網(wǎng)絡(luò)來說,一般經(jīng)過仔細(xì)調(diào)整的 SGD 幾乎總會略優(yōu)于 Adam,但學(xué)習(xí)率的可能區(qū)域要窄得多。
 - 一次復(fù)雜化一個:如果你有多個特性插入分類器,我建議你一個個插入,從而確保能獲得期待的性能提升。不要在最開始時就一次性全加上,這樣你會弄不清楚性能提升到底是哪個特性帶來的。還有其它增加復(fù)雜性的方法,例如你可以先嘗試插入較小的圖像,然后再慢慢地加大。
 - 別相信默認(rèn)的學(xué)習(xí)率衰減:如果你修改來自其它領(lǐng)域的代碼,你應(yīng)該小心使用學(xué)習(xí)率衰減方法。對于不同問題,你不僅希望使用不同的衰減策略,同時因為 Epoch 的數(shù)量不同,衰減過程也會不一樣。例如數(shù)據(jù)集的大小,會影響 Epoch 的數(shù)量,而很多學(xué)習(xí)率衰減策略是直接與 Epoch 相關(guān)的。在我自己的工作中,我經(jīng)常整個地關(guān)閉學(xué)習(xí)率衰減,即使用常數(shù)學(xué)習(xí)率。
 
4. 正則化
理想情況下,我們現(xiàn)在至少有了一個擬合訓(xùn)練集的大模型。現(xiàn)在是時候?qū)λM(jìn)行正則化,并通過放棄一些訓(xùn)練準(zhǔn)確率來提升驗證準(zhǔn)確率了。技巧包括:
- 更多數(shù)據(jù):首先,在當(dāng)前任何實際環(huán)境中正則化模型的方式是增加更多真實的訓(xùn)練數(shù)據(jù)。在你能收集更多數(shù)據(jù)時,花費(fèi)大量工程時間試圖從小數(shù)據(jù)集上取得更好結(jié)果是很常見的一個錯誤。我認(rèn)為增加更多數(shù)據(jù)是單調(diào)提升一個較好配置神經(jīng)網(wǎng)絡(luò)性能的可靠方式。
 - 數(shù)據(jù)增強(qiáng):比真實數(shù)據(jù)較次的方法是半假數(shù)據(jù),試驗下更激進(jìn)的數(shù)據(jù)增強(qiáng)。
 - 創(chuàng)造性增強(qiáng):如果半假數(shù)據(jù)也沒有,假數(shù)據(jù)也還可以。人們在尋求擴(kuò)展數(shù)據(jù)集的創(chuàng)造性方法。例如,域隨機(jī)化、使用模擬數(shù)據(jù)、把數(shù)據(jù)插入場景這樣機(jī)智的混合方法,甚至可以用 GAN。
 - 預(yù)訓(xùn)練:即使你有足夠的數(shù)據(jù),你也可以使用預(yù)訓(xùn)練網(wǎng)絡(luò),基本沒什么損失。
 - 堅持監(jiān)督式學(xué)習(xí):不要對無監(jiān)督學(xué)習(xí)過于激動。據(jù)我所知,沒有什么無監(jiān)督學(xué)習(xí)方法在當(dāng)前計算機(jī)視覺任務(wù)上有很強(qiáng)的結(jié)果(盡管 NLP 領(lǐng)域現(xiàn)在有了 BERT 和其他類似模型,但這更多歸功于文本更成熟的本質(zhì)以及對噪聲比更好的信號)。
 - 更小的輸入維度:移除可能包含假信號的特征。如果你的數(shù)據(jù)集很小,任何加入的假輸入只會增加過擬合的可能。類似地,如果低級細(xì)節(jié)作用不大,試試輸入更小的圖像。
 - 更小的模型:在許多情況下,你可以在網(wǎng)絡(luò)上使用域知識約束來降低模型大小。例如,在 ImageNet 主干網(wǎng)絡(luò)頂部使用全連接層一度很流行,但它們后來被簡單的平均池化取代,消除了這一過程中大量的參數(shù)。
 - 減小批大小:由于 BN 基于批量大小來做歸一化,較小的批量大小具有更強(qiáng)的正則化效果。這主要因為一個批量的統(tǒng)計均值與標(biāo)準(zhǔn)差是實際均值和標(biāo)準(zhǔn)差的近似,所以縮放量和偏移量在小批量內(nèi)波動地更大。
 - drop:增加 dropout。在卷積網(wǎng)絡(luò)上使用 dropout2d(空間 dropout)。保守謹(jǐn)慎的使用 dropout,因為它對 batch 歸一化好像不太友好。
 - 權(quán)重衰減:增加權(quán)重衰減懲罰。
 - 早停(early stopping):基于你得到的驗證損失停止訓(xùn)練,從而在即將過擬合之前獲取模型。
 - 嘗試更大的模型:我過去多次發(fā)現(xiàn)更大模型最終都會很大程度的過擬合,但它們「早?!购蟮男阅芤刃∧P秃玫枚唷?/li>
 
為了更加確保網(wǎng)絡(luò)是個合理的分類器,我喜歡可視化網(wǎng)絡(luò)一層的權(quán)重,確保自己獲得了有意義的邊緣。如果一層的濾波器看起來像噪聲,那需要去掉些東西。類似地,網(wǎng)絡(luò)內(nèi)的激活函數(shù)有時候也會揭示出一些問題。
5. 精調(diào)
現(xiàn)在你應(yīng)該位于數(shù)據(jù)集一環(huán),探索取得較低驗證損失的架構(gòu)模型空間。這一步的一些技巧包括:
- 隨機(jī)網(wǎng)格搜索:在同時精調(diào)多個超參數(shù)時,使用網(wǎng)格搜索聽起來更誘惑,能夠確保覆蓋到所有環(huán)境。但記住,使用隨機(jī)搜索反而是方式。直觀上,因為神經(jīng)網(wǎng)絡(luò)對一些參數(shù)更為敏感。在極限情況下,如果參數(shù) a 很重要,改變 b 卻沒有影響,然后相比于多次在固定點采樣,你寧可徹底采樣 a。
 - 超參數(shù)優(yōu)化:如今社區(qū)內(nèi)有大量好的貝葉斯超參數(shù)優(yōu)化工具箱,我的一些朋友用過后覺得很成功。但我的個人經(jīng)驗是,探索好的、寬的模型空間和超參數(shù)方法是找個實習(xí)生。開玩笑而已,哈哈哈。
 
6. 壓榨
一旦你找到好的架構(gòu)類型和超參數(shù),依然可以使用更多的技巧讓系統(tǒng)變得更好:
- 集成:模型集成是能將準(zhǔn)確率穩(wěn)定提升 2% 的一種好方式。如果你承擔(dān)不起測試階段的計算成本,試著使用《Distilling the Knowledge in a Neural Network》中的方法把你的模型蒸餾到一個網(wǎng)絡(luò)。
 - 一直訓(xùn)練:我經(jīng)??吹揭恍┤嗽隍炞C損失趨平時會中斷模型訓(xùn)練,以我的經(jīng)驗來看,網(wǎng)絡(luò)會長時間保持非直觀的訓(xùn)練。寒假時有一次我忘了關(guān)掉模型訓(xùn)練,一月回來后發(fā)現(xiàn)它取得了 SOTA 結(jié)果。
 
結(jié)論
一旦你做到了這些,你就具備了成功的所有要素:對神經(jīng)網(wǎng)絡(luò)、數(shù)據(jù)集和問題有了足夠深的了解,配置好了完整的訓(xùn)練/評估體系,取得高置信度的準(zhǔn)確率,逐漸探索更復(fù)雜的模型,提升每一步的表現(xiàn)?,F(xiàn)在萬事俱備,就可以去讀大量論文,嘗試大量實驗并取得 SOTA 結(jié)果了。
原文鏈接:https://karpathy.github.io/2019/04/25/recipe/
【本文是51CTO專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號“機(jī)器之心( id: almosthuman2014)”】
















 
 
 










 
 
 
 