前言
作為一名每天與神經(jīng)網(wǎng)絡(luò)訓(xùn)練/測試打交道的同學(xué),是否經(jīng)常會遇到以下這幾個問題,時常懷疑人生:
怎么肥事,訓(xùn)練正常著呢,咋效果這么差呢?
嗯。。再等等是不是loss就更低了。啊?明明loss更低了呀,為啥效果更差了?
又是怎么肥事?我改了哪里,效果提升了這么多?阿哈哈哈哈收工下班。
總而言之,當(dāng)模型效果不如預(yù)期的時候去調(diào)試深度學(xué)習(xí)網(wǎng)絡(luò)是一件頭疼且繁瑣的事情,為了讓這件麻煩事情更加僅僅有條,筆者結(jié)合實際經(jīng)驗簡單整理了一些checklist,方便廣大煉丹師傅掌握火候。
1. 從最簡單的數(shù)據(jù)/模型開始
現(xiàn)在開源社區(qū)做的很好,同學(xué)們用模型也十分方便,但也有相應(yīng)的問題。以句子情感識別為例,新入手的同學(xué)可能一上來就調(diào)出HuggingFace/transformers代碼庫,然后一股腦BERT/Roberta啥的跑個結(jié)果,當(dāng)然文檔做好的開源代碼一般都能照著跑個好結(jié)果,但改到自己數(shù)據(jù)集上往往就懵逼了,啊這?51%的二分類準(zhǔn)確率(可能夸張了點,但如果任務(wù)要比二分類稍微復(fù)雜點,基本結(jié)果不會如預(yù)期),也太差了吧,HuggingFace/transofmrers這些模型不行啊。算了,咱換一個庫吧,再次求助github和谷歌搜索。其實可能都還不清楚數(shù)據(jù)輸入格式對不對?數(shù)據(jù)量夠不夠?評測指標(biāo)含義是否清楚?Roberta的tokenizer是咋做的?模型結(jié)構(gòu)是什么樣子?
所以第1個checklist是:請盡量簡單!
模型簡單
數(shù)據(jù)簡單
模型簡單:解決一個深度學(xué)習(xí)任務(wù),最好是先自己搭建一個最簡單的神經(jīng)網(wǎng)絡(luò),就幾層全連接的那種。
數(shù)據(jù)簡單:一般來說少于10個樣本做調(diào)試足夠了,一定要做過擬合測試(特別是工作的同學(xué),拿過來前人的代碼直接改個小結(jié)構(gòu)就跑全量數(shù)據(jù)訓(xùn)練7-8天是可能踩坑的哦,比如某tensorflow版本 GPU embedding查表,輸入超出了vocab size維度甚至可能都不報錯哦,但cpu又會報錯)!如果你的模型無法在7、8個樣本上過擬合,要么模型參數(shù)實在太少,要么有模型有bug,要么數(shù)據(jù)有bug。為什么不建議1個樣本呢?多選幾個有代表性的輸入數(shù)據(jù)有助于直接測試出非法數(shù)據(jù)格式。但數(shù)據(jù)太多模型就很難輕松過擬合了,所以建議在10個以下,1個以上,基本ok了。
2. loss設(shè)計是否合理?
loss決定了模型參數(shù)如何更新,所以記得確定一下你的loss是否合理?
初始loss期望值和實際值誤差是否過大,多分類例子。
橘個??:CIFAR-10用Softmax Classifier進行10分類,那么一開始每個類別預(yù)測對的概率是0.1(隨機預(yù)測),用Softmax loss使用的是negative log probability,所以正確的loss大概是:-ln(0.1)= 2.303左右。
初始loss測試,二分類例子。假設(shè)數(shù)據(jù)中有20%是標(biāo)簽是0,80%的標(biāo)簽是1,那么一開始的loss大概應(yīng)該是-0.2ln(0.5)-0.8ln(0.5)=0.69左右,如果一開始loss比1還大,那么可能是模型初始化不均勻或者數(shù)據(jù)輸入沒有歸一化。
比如多任務(wù)學(xué)習(xí)的時候,多個loss相加,那這些loss的數(shù)值是否在同一個范圍呢?
數(shù)據(jù)不均衡的時候是不是可以嘗試一下focal loss呢?
3. 網(wǎng)絡(luò)中間輸出檢查、網(wǎng)絡(luò)連接檢查
Pytorch已經(jīng)可以讓我們像寫python一樣單步debug了,所以輸入輸出shape對齊這步目前還挺好做的,基本上單步debug走一遍forward就能將網(wǎng)絡(luò)中間輸出shape對齊,連接也能對上,但有時候還是可能眼花看漏幾個子網(wǎng)絡(luò)的連接。
所以最好再外部測試一下每個參數(shù)的梯度是否更新了,訓(xùn)練前后參數(shù)是否都改變了。
那么具體的模型中間輸出檢查、網(wǎng)絡(luò)連接檢查是:
確認所有子網(wǎng)絡(luò)的輸入輸出shape對齊,并確認全部都連接上了,可能有時候定一個一個子網(wǎng)絡(luò),但放一邊忘記連入主網(wǎng)絡(luò)啦。
梯度更新是否正確?如果某個參數(shù)沒有梯度,那么是不是沒有連上?
如果參數(shù)的梯度大部分是0,那么是不是學(xué)習(xí)率太小了?
時刻監(jiān)測一下梯度對不對/時刻進行修正。經(jīng)典問題:梯度消失,梯度爆炸。
參數(shù)的梯度是否真的被更新了?有時候我們會通過參數(shù)名字來設(shè)置哪些梯度更新,哪些不更新,而這個時候有木有誤操作呢?
讀者可以參考stanford cs231n中的Gradient checking:
https://cs231n.github.io/neural-networks-3/#gradcheck
https://cs231n.github.io/optimization-1/#gradcompute
另外用tensorboard來檢查一下網(wǎng)絡(luò)連接/輸入輸出shape和連接關(guān)系也是不錯的。
4. 時刻關(guān)注著模型參數(shù)
所謂模型參數(shù)也就是一堆矩陣/或者說大量的數(shù)值。如果這些數(shù)值中有些數(shù)值異常大/小,那么模型效果一般也會出現(xiàn)異常。一般來說,讓模型參數(shù)保持正常有這么幾個方法:
調(diào)整batch size(或者說mini-batch)。
統(tǒng)計梯度下降中,我們需要的batch size要求是:1、batch size足夠大到能讓我們在loss反向傳播時候正確估算出梯度;2、batch size足夠小到統(tǒng)計梯度下降(SGD)能夠一定程度上regularize我們的網(wǎng)絡(luò)結(jié)構(gòu)。batch size太小優(yōu)化困難,太大又會導(dǎo)致:Generalization Gap和Sharp Minima(具體參考:論文https://arxiv.org/abs/1609.04836,On Large-Batch Training for Deep Learning: Generalization Gap and Sharp Minima)。
調(diào)整learning rate學(xué)習(xí)率。
學(xué)習(xí)率太小可能會導(dǎo)致局部最優(yōu),而太大又會導(dǎo)致模型無法收斂。
具體讀者可以學(xué)習(xí)斯坦佛cs231n這個部分:
https://cs231n.github.io/neural-networks-3/#anneal,另外關(guān)于學(xué)習(xí)率幾個常用的網(wǎng)站:
Pytorch:https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html
Tensorflow:
https://www.tensorflow.org/api_docs/python/tf/train/exponential_decay
Keras:
https://keras.io/callbacks/#learningratescheduler。
梯度裁剪。
在反向傳播的時候,將參數(shù)的梯度限制在一個范圍之類:[-min, max]。對于梯度消失和梯度爆炸很有幫助。
Batch normalization。將每層的輸入進行歸一化,有助于解決internal covariate shift問題。當(dāng)然最近transformer流行的是Layer normalization。不同normalization的區(qū)別可以學(xué)習(xí)張俊林老師關(guān)于normalization的博客。
Dropout。
網(wǎng)絡(luò)參數(shù)隨機失活,有效防止過擬合/正則的常用手段。但是如果batch normalization和Dropout一起使用的話,建議先學(xué)習(xí)這個文章:Pitfalls of Batch Norm in TensorFlow and Sanity Checks for Training Networks 還有這個文章:Understanding the Disharmony between Dropout and Batch Normalization by Variance Shift。
Regularization。
Regularization對于模型的泛化能力很重要,他對于模型的參數(shù)量和復(fù)雜度做了一個懲罰,能在不顯著增加模型bias的情況下降低模型的variance。
但需要注意的是:
通常情況下,我們的loss是data loss加上regularization loss,那么如果regularization loss比data loss更大更重要了,那么魔性的data loss可能就學(xué)不好了。
使用什么優(yōu)化器?
一般來說SGD作為baseline就可以了,但如果想要更好的效果比如使用Adam,還有很多其他SGD的改進可以使用。
這個部分建議閱讀這篇文章進行學(xué)習(xí):An overview of gradient descent optimization algorithms。
5. 詳細記錄調(diào)試/調(diào)參數(shù)過程
可能這會兒換了一個learning rate,過會兒增大了dropout,過會兒又加了一個batch normalization,最后也不知道自己改了啥。
一個好的辦法是是使用excel(雖然有些古老,其實還是很有效的,可以記錄各種自己想要記錄的變量)將重點改進,改進結(jié)果進行存放,另外合理使用tensorboard也是不錯。
由于要實驗或者改的地方太多,通常就時不時忘記/不方便使用git了,而是copy一大堆名字相似的文件,這個時候,請千萬注意你的代碼結(jié)構(gòu)/命名規(guī)則,當(dāng)然使用好bash腳本將使用的參數(shù),訓(xùn)練過程一一存放起來也是不錯的選擇。
總之,無論是古老的工具,先進的工具,能將實驗過程進行記錄/復(fù)現(xiàn)的就是好工具。具體使用什么工具因人而異,可能有些人就是不喜歡用git。。。當(dāng)然也有一些別人開發(fā)好的工具可以使用啦,比如:Comet.ml。
模型對數(shù)據(jù)/超參數(shù),甚至是隨機種子、GPU版本,tensorflow/pytorch版本,所以請盡可能記錄好每個部分,并且最好時刻可以復(fù)現(xiàn)。最后小時候?qū)W的控制變量法也很重要哦。
總結(jié)
將以上內(nèi)容做一個總結(jié):
簡單模型,簡單數(shù)據(jù),全流程走通。
調(diào)整/選擇合理的loss函數(shù)/評價指標(biāo),最好檢查一下初始loss是否符合預(yù)期。
查看網(wǎng)絡(luò)中間輸出、子網(wǎng)絡(luò)是否都連接上了。
時刻關(guān)注模型參數(shù)。無論是優(yōu)化器的改變、學(xué)習(xí)率的改變、增加正則方法或者梯度裁剪,主要作用都是在修正/更新模型參數(shù)。
詳細記錄實驗過程。保持良好的訓(xùn)練/測試流程和習(xí)慣,SOTA近在眼前~。
參考文獻:
https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21