DL Troubleshooting:我的bug來自哪裡?完整DL debug流程!
這次內容一樣大量參考Full Stack Deep Learning 2019,加上一部份個人解讀,歡迎大家去看原始影片介紹。
有做過DL的research或是自行開發DL model經驗的人大部分都有一個非常非常痛恨的疑問,那就是
到底我的bug來自哪裡?

Andrej Karpathy對DL的debugging給出了非常深刻的描述XD,我們大部分reproduce別人的model甚至自己開發的時候,都會經歷這麼一段非常痛苦的時刻,這個痛苦我個人認為比傳統的debugging更有過之而無不及。
為甚麼呢?
請大家想像一個情況,假設你今天要reproduce別人的model,而你的結果如下。

左側是paper裡面寫的learning curve,經過多個iteration最終降到 30%以下的error,而右邊是你reproduce出來的結果,此時你發現你的結果跟paper裡的有一段不小的差距,那你要如何debug?
大多數的人在此時完全毫無頭緒怎麼辦!為甚麼?因為太多可能性了!

不好的結果(Poor model performance)可能來自於
- Implementation bugs:有時候label跟data根本沒對齊,有時候不小心鎖死weight,有時候讀錯data,這都是很常見的implementation bugs,進而導致performance掛在那邊。
- Hyperparameter choices:很多DL的model都對hyperparameter choice非常敏感,尤其像是learning rate,差個2 3倍就有可能從收斂變成完全發散調,drop out rate差一點、LSTM的hidden state大小都會直接影響到最終performance。
- Data/model fit:有些model就只適合處理某幾個dataset,常常我們把research上很好用的model利用到我們自己的dataset上卻緊緊比baseline高一點。
- Dataset construction:有時候甚至根本Data本身就有問題,class imbalance 、 noisy label等問題在業界真實收集到的data非常常見,而當你在不同dataset上reproduce別人的model時根本不清楚怎樣的performance是合理的。
光是上面有列出的幾種可能,裡面就蘊含著數百種的可能,並且如果考慮到組合的效應,有些問題沒有同時解掉效果就都沒有明顯改善,這就已經讓我們夠麻煩了。
更不用考慮其他更多更多的可能
像是很多paper裡面其實有用到learning rate scheduler才做到很好的效果,但是卻很少人會report這個結果,有些有自己特殊的feature engineering的方法,有些甚至不公開他的dataset讓你無從觀察,這些都讓DL的debugging更困難一層。
而這種reproduce很困難的問題,在近幾年KDD就推出了reproducibility factor,把是否能夠reproduce一篇paper result當成一個重要的指標,如果沒辦法被reproduce出來,在2018的NeurIPS也由FAIR的研究員Joelle Pineau給了一個talk名為Reproducible, Reusable, and Robust Reinforcement Learning,也提出了RL reproducibility checklist

這裡並沒有要深入提reproducibility這件事,但是這個問題絕對是每個ML engineer切身的痛,也是區分一個有經驗的ML engineer跟一個菜鳥的差別。
那我們到底該怎麼做?
既然Debugging那麼困難,那我們就從簡單的事情開始一步一步做起!!

FSDL給出了一個框架型的debugging或是說build DL的先後流程,在這種流程下debugging的難度相對會被簡化。
這裡每一步其實都很複雜,我先給個quick summary。

總共分為五步,接下來我一步一步講,但是核心就是上面的quick summary,一定要把上面的流程熟記在心,這裡每一步都很複雜,我只會大概談一下,因為實際上很case by case。
Start simple
Start simple的核心就是「先有baseline再說」,所以我們挑選比較簡單的dataset、比較簡單的architecture、比較基準的hyperparameters。
一開始為了快速製造出baseline跟ML pipeline,能多簡單就多簡單,這樣才可以更高效,所以一開始可能使用一個比較小但是跟我們問題類似的dataset,然後class只用比較少幾種class,然後使用一些比較基礎的architecture像是LeNET,或是resnet18之類的,然後hyperparameters也挑選比較標準的像是adam learning rate= 3e-4或1e-3,這樣去跑實驗。
而因為data小、model小,所以一開始可以重複跑很多次,把我們目前的最好效果跑出來。
實際上在公司初期我們要定義產品時,在不確定哪個問題可以被ML解掉,我們也會進行類似的Proof of Concept,拿手邊or線上就有的data,定義一個跟最終問題類似但是被簡化的問題,然後拿現有的model(有code,或是有package能夠輕鬆做出來的),去快速train一遍,確認一下一件事是否能夠被達成,這邊proof of concept跟ML project的start simple雖然目標不同(一個是要驗證一個問題能不能被ML解,另一個是為了先做出基準baseline),但是因為都要求快速驗證,所以其實流程很像。
這邊特別提一下,在初期做的時候我們也會試試看non-ML/DL solution能做到多好,如果做一做發現rule based就可以把這個問題解掉,或是用simple ML + rule based就可以解掉,那就不用浪費時間、資源在後期train model。
Implement & debug
而在implement & debug這塊,用不同框架的debug細節其實差很多,但是大致流程是類似的。

第一步最重要一定是要先讓model run起來,就跟performance無關的error一定是要優先解掉的,這塊不好解的理由主要是很多DL package的error message都不知道在寫甚麼XD,但是從pytorch出來以及keras功能逐步完整後,這個問題逐漸縮小。
這裡run不起來的問題大部分太細節了,所以不贅述,但是在DL已經成熟的現在,大部分的問題都可以在stack overflow上找到解答,另外推薦一下pytorch的issue作者會親自回,以及pytorch跟tensorflow 2.0的文檔其實都在慢慢完善,可以多去看看官方document。
而第二步是最重要的一步!Overfit a single batch
這裡其實就是檢驗我們的model有沒有在training set上做到100%的能李,雖然叫做overfit a single batch,但是我更常做的是直接看training loss能不能降到超低,或是在1000筆的training data上能不能做到接近完美,這能確定我們的model能力是否夠強以及有沒有基礎的implementation bug,像是loss傳不回去之類的。
所以這裡重點其實是看loss curve
一般我們可以把loss curve出問題的狀況分4類
- Error goes up:這通常是learning rate設的稍為高了,或是loss function的正負號寫反。
- Error explodes:這也有可能是learning rate設太高,但是同時也有可能是numerical issue,尤其Error衝到inf之類的,那大概率就是我們的prediction出現Nan或是inf之類的太奇怪的數字。
- Error oscillates(震盪):一樣可能是learning rate太高XD,不過也有可能是有noisy data,或是label跟data沒對齊(被glob的謎之排序雷到,或是不小心只shuffle一個)。
- Error plateaus(停滯):這可能是learning rate太小,或是gradient傳不回整個model(包含freeze住weight跟model太大),reularization太高,loss function寫錯...等問題。
可以發現上面4個情況都有可能是learning rate造成的,所以我們習慣都是先tune好learning rate,如果learning rate調了兩三種大小,loss curve卻都差不多,那才開始解其他問題。
另一個重點是這裡也可以做error analysis,因為data量比較小,花一點時間做error analysis順便累積對data的熟悉度絕對不是壞事。
補充:我之前遇過一個問題是,我的data太整齊了,導致我麼model學不起來,簡單來講我的class有3種,而我data前1/3的data都是label 0,中間1/3都是label 1,後1/3都是label 2,這導致我的data loader每次抓一個batch的data近來,全部都是同label,所以我的model在訓練的過程中就不斷來回學習全部猜0、全部猜1、全部猜2三件事,導致我最終performance都卡在1/3。
最後是跟現有result做比較,現有result指的不只是別人的論文,相同的model執行在類似的dataset,類似的model執行在相同的dataset,別人的unofficial reproduction都可以,只要跟你的情況越像,並且有code,就都可以參考。
Evaluate
這邊就是一個checkpoint,了解一下目前做到多好。回頭看一下一開始的流程圖。

Evaluate後其實就是看目前的效果有沒有達標,如果沒有的話就有兩條路可以走,improve model/data跟tune hyperparameters。
Tune hyperparameters
這邊我在介紹sklearn 0.24的新function successive halving時,有系統性的整理過hyperparameters tuning的方法,目前除了auto ML的方法以外重要的方法都講了。請務必回去參考之前那篇,裡面有講實務上最好用的multi-fidelity search以及自動幫你安排運算資源的successive halving
Improve model/data
這邊請參考我之前的文章,告訴你用4分法的dataset判斷自己現在要做甚麼。
這邊就不贅述。請務必回去複習我的文章,這會讓你的DL流程順很多。
這樣就是一個完整的build ML project的流程,裡面每一步都還是很需要經驗,但是現在把一個複雜的問題拆解成一個5步的流程,並且明確告訴你每一步應該是針對甚麼情況做甚麼事,我在kaggle上以及自己工作時使用這個流程有非常明顯的效率提升。
不過還是要提醒一下,日常無聊就reproduce別人的model,累積自己的coding能力以及對DL的bug的敏感度才是根本的解法。
如果喜歡這篇文章可以幫我多拍手幾次XD,或是對於哪個類型文章有興趣都可以在留言區跟我講~ 後續會以中難度的ML/DS/AI知識為主,以及AI/HCI研究相關知識。