機器學習中踩過的坑,如何讓你變得更專業?

  • 2020 年 6 月 6 日
  • AI

踩過坑才知道哪些路是不可行的,有時候犯錯誤也能幫助我們變得更加專業。

數據科學家 Archy de Berker 在本文中詳述了他和周圍同伴們在機器學習探索中踩過的坑,這也都是大家經常性遇到的問題。他希望通過這一篇文章,帶大家了解機器學習中一些有趣的錯誤——一些只有你深入了這個領域才能接觸到的錯誤。

這不是一篇入門級的文章,想要讀懂本文,最好先在 Pytorch 或 Tensorow 上多練習下毀壞模型。

本文主要集中在綠色分布的錯誤,但紫色分布和黃色分布也會部分涉及

一、機器學習中的常見錯誤

Berker 將機器學習中出現的各種錯誤歸為三大類,嚴重程度由低到高。

1、這些錯誤只會浪費你的時間

計算科學中最難的兩件事是命名和快取失效,圖中這條推文高度概括了此類錯誤。shape error 是最可怕又最常見的錯誤,通常是由於大小不一致的矩陣相乘導致。

本文不會花太多時間來討論這類錯誤,因為錯得非常明顯。大家很容易找到錯誤,然後進行修復,然後再犯錯,然後再修復。這是個不斷重複的過程。

2. 這些錯誤會導致結果不準確

這類錯誤會讓你付出很大代價,因為它會造成模型結果不準確。

2015年,有一架從澳大利亞悉尼飛往馬來西亞吉隆坡的亞航航班因出現技術故障,在墨爾本機場緊急降落。如果模型的結果不準確,就如同這架飛機的技術故障,最終會飛到錯誤的目的地。

舉個例子,假如你在模型中新增了一個特徵並同時增加了許多參數,在未進行超參數調優的情況下對比之前的性能,發現增加特徵後模型性能變差了,於是得出結論,增加的特徵會讓模型性能變差。這是不對的,實際上你需要更加規範化的操作,以尋求更具表現力的模型。

錯誤對於模型的影響會隨著時間而加重,導致更加不準確的實驗結果。因此,儘早發現錯誤是非常有價值的。

3、這些錯誤會讓你誤認為自己的模型已經「完美」

這是很嚴重的錯誤,會讓你高估模型的性能。這種錯誤通常很難發現,因為我們從心底里不願承認看似」完美「的模型可能是假象。

當模型表現得出奇差時,我們傾向於不相信然後再測試一遍,但當模型表現得出奇好時,我們通常會相信並開始沾沾自喜。這就是所謂的確認偏差(Confirmation Bias),即個人無論合乎事實與否,都傾向於偏好支援自己的見解、猜想。

模型性能看似「完美」,通常是因為過擬合的原因,導致訓練數據已經不再有代表性,或者是因為選錯了評價指標,這兩點後文都會詳細解釋。

如果你只能從本文帶走一點,希望你記住:沒有什麼比你發現模型的真正結果實際上很糟糕這件事更令人尷尬和沮喪的了。

二、機器學習的生命周期

機器學習就如同上圖香腸機的三個階段一樣:獲取數據,將數據輸入到模型中,然後通過一些指標來量化輸出。

接下來我們會討論到每個階段中一些看似愚蠢的錯誤。

1、輸出什麼:評價指標

機器學習可以歸結為不斷減少損失函數值的過程。

但是,損失函數絕不是最終的優化目標,它只是一個近似值。例如訓練分類任務時,通過交叉熵損失函數來優化訓練集或者驗證集,但實際上我們更加信任在測試集上的結果或者說 F1、AUC 評價指標。在實際優化目標的數據差異非常小的情況下,在模型評價上採用低置信度來加速評價過程會導致問題更加嚴重。

無論在哪種環境下,如果損失函數已經不能代表模型的真實表現,那麼麻煩就大了。

此外還有一些讓模型更糟糕的做法:

1)混合訓練集和測試集

混合訓練集和測試集是很容易的,而且通常會訓練出來很不錯的性能,但這樣的模型在複雜的真實環境中會表現非常糟糕。

所以,訓練集、驗證集、測試集數據是不能相交的,各自需要包含不同的樣本數據。我們要思考模型需要怎樣的泛化能力,這最終會通過測試集的性能來量化。

以商店收據的數據為例,使用商店的收據進行分析預測,那麼測試集顯然需要包含以前沒見過的新數據,但是測試集是否也需包含以前沒見過的新商品以保證模型不會對特定商店過度測試呢 (過擬合)?

最好的方式是一次性將數據分為訓練集、驗證集和測試集,然後放在不同的文件夾下,且命名都應該非常明確,例如 TrainDataLoader 和 TestDataLoader。

2)錯誤使用損失函數

錯誤使用損失函數其實是很少出現的,因為已經有無數的材料教會大家如何使用損失函數。最常見的兩種錯誤使用損失函數的情況,一個是搞不清楚損失函數要使用概率分布還是對數(即是否需要添加 softmax),另一個就是混淆了回歸函數和分類函數。

即使在學術界,混淆回歸函數和分類函數也是很普遍的。例如亞馬遜用戶的評價和評星數據 Amazon Reviews 數據集,經常被頂級實驗室用於分類任務,但這其實是不太對的,因為與 1 星評價相比,5 星評價顯然更類似於 4 星評價,應該採用有序回歸。

2、選錯評價指標

不同的任務使用的損失函數不一樣,而在模型性能的驗證上,我們也經常組合多個側重點不一樣的評價指標來評估模型性能。例如,機器翻譯首選 BLEU 作為評價指標,自動文摘採用 ROUGE 來驗證性能,而對於其他任務,可以選用準確性,精確度或召回率作為評價指標。

通常,評價指標比損失函數容易讓人理解。一個好的思路是儘可能多地記錄日誌。

認真思考如何劃分不相交的訓練集、測試集和驗證集,讓模型具有優異而不過度的泛化能力。在訓練過程中可以使用評價指標來測試模型性能,而不必等到最後才開始使用測試集來測試。這樣有助於更好地理解模型當前的訓練結果,防止問題到最後才暴露。

評價指標的選擇上要多注意。舉個例子,你不能用簡單使用準確性來評估序列模型的性能,因為序列間的非對齊情況會導致準確率為 0。因此對於序列數據要採用距離來評估。選錯評價指標是非常痛苦的事情。

還是以序列模型為例,請確保排除了所有特殊字元,特殊字元通常是序列的開頭、結尾和填充。如果忘記了排除特殊字元,可能會得到看起來不錯的模型性能,但這樣的模型實際上只能預測充滿填充字元的長序列。

有一個讓作者印象非常深刻的錯誤,其曾經做過一些語義解析工作,目的是將自然語言語句轉換為資料庫查詢,回答諸如「明天從蒙特利爾到亞特蘭大有多少趟航班?」這樣的典型 SQL 問題。為了評價模型的準確性,他們將模型轉義的 SQL 查詢發送到資料庫,檢查返回的內容是否與真實查詢的內容匹配。他設置了一種情況,如果向資料庫發送毫無意義的查詢,資料庫返回「error」。然後,他發送了已經被損壞的預測 SQL 和真實 SQL 到資料庫查詢,兩者都返回「error」,模型將這種情況計算為: 100%準確。

這就引出了指導原則,你犯的任何錯誤只會使性能變差。要堅持檢查模型實際做出的預測,而不僅僅是關注評價指標的結果。

3、避免評價指標的錯誤

1) 首先跑一遍所有評價指標

在沒有任何訓練的情況下如果模型表現很好,那一定是有問題的。

2) 所有過程都記錄日誌

機器學習是一門定量學科,但數字有時候也可能會騙人,所有可以想到的數字都記錄日誌,但要以容易理解的方式記錄。

在 NLP 中,這通常意味著你需要顛倒標記,這過程很複雜,但百分百是值得的,日誌提供了模型訓練過程中的定性解釋。例如,語言模型通常從學習輸出類似 eeeeeeeeee <PAD> <PAD> <PAD>字元串開始,因為這些都是數據中最常見的字元。

如果是處理影像任務,那麼日誌就更加麻煩了,因為你不能將圖片以文本的形式存為日誌。可以通過使用 ASCII 解決這一問題,即在 OCR 的訓練過程中使用 ASCII 保存日誌,從而能可視化輸入的影像數據:

3)研究驗證集

使用測試評價指標來確定集合中性能最佳和最差的樣本。了解樣本情況,使用一些量化置信度的方法(如 softmax),了解模型可能在哪些分布上表現良好、哪些分布上會表現糟糕,在回歸任務中殘差分析是很有用的。

但是請記住,正如 Anscombe Quartet 指出的那樣,平均值可能會誤導你。

Anscombe Quartet:所有 4 個模型的均值和方差均相同,而且它們都擬合了同一條回歸線 t。因此,不用過分依賴統計結果,要理解數據本身。

如果遇到多維問題,嘗試繪製錯誤與單個特徵的關係圖來找出原因。是否存在模型表現非常差的輸入空間區域?如果是這樣,你可能需要在該數據區域補充更多數據或進行數據增強。

考慮消融和干擾在模型性能中的影響。諸如 LIME 和 Eli5 之類的工具可以讓模型變簡單。下面這篇文章很好地描述了擾動分析,揭示了用於 X 射線分類的 CNN 模型使用 X 射線機本身引入的標籤來確定患者是否患有肺炎,而不是 X 射線機的使用本身可能和患病率之間存在的相關性:

//medium.com/@jrzech/what-are-radiological-deep-learning-models-actually-learning-f97a546c5b98

三、模型

現在很多課程和文章都將重點放在建模方面。但實際上,作為機器學習從業者,大部分時間都是在處理數據和指標,而不是研究創新的演算法。    

深度學習錯誤中的絕大多數都是形狀錯誤( shape error),從而導致很多淺顯的錯誤發生。

1、模型錯誤類型

模型錯誤類型很多,如下:

1) 包含不可微分運算操作的模型

在深度學習模型中,一切都必須是端到端可微分的,以支援反向計算。因此,你可能希望不可微分操作能夠在 TensorFlow 等深度學習框架中被明確標識出來。這是不對的,正如 Berker 曾經對 Keras Lambda 層感到特別困惑,因為它可以破壞反向計算。一個解決辦法是使用 model.summary() 進行檢查,以驗證大多數參數是可訓練的,如果發現有不可訓練參數的 layer,則可能是破壞了自動微分能力。

2)在測試時沒有成功關閉 dropout

我們都知道,在測試數據時需要關閉 dropout,否則可能獲得的是隨機結果。這可能非常令人困惑,尤其是對於正在部署模型並開始跑測試集的人而言。

這一問題可以通過 eval() 來解決。另外需要注意的是,在訓練模型時 dropout 可能會導致一個奇怪現象——模型在驗證集上的準確性高過比訓練集上的準確性。這是因為在驗證集上用到了 dropout,這看起來可能是欠擬合了,而且可以會造成一些讓你頭疼的問題。

3)維度參數錯誤

不同框架在樣本數 (batch size),序列長度 (sequence length) 和通道數 (channels) 上有不一樣的約定,有些框架提供了在這三者上的修改空間,但其他的框架是不允許任意修改的,修改就會出錯。

維度參數錯誤可能會產生奇怪現象。例如,如果你弄錯了樣本數和序列長度,那麼最終可能會忽略部分樣本的資訊,並且無法隨著時間保存資訊。

2、避免模型的錯誤

1)模組化,可測試

 如果發現有不可訓練參數的層,則可能是破壞了自動微分能力。

 編寫結構合理的程式碼並進行單元測試是有助於避免模型錯誤的。

 將模型分為幾個離散的程式碼塊,每個程式碼塊有明確的功能定義,就可以對其進行有效的測試。測試的重點,在於驗證變化樣本數和輸入數據量的情況下,模型是否與預期一致?Berker 推薦了 Chase Roberts 的一篇帖子,詳細介紹了 ML 程式碼的單元測試:

//medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765

2)維度論斷

Berker 傾向於將維度論斷加入到 ML 程式碼中,讓讀者可以清楚地知道哪些維度應該更改,哪些不應該更改。當然,如果發生意外,它會引發錯誤。

富有表達力的 Tensorflow 程式碼,由 Keith Ito 提供。注意模組化和注釋。

至少要養成在程式碼中添加維度注釋的習慣,讓讀者可以直接閱讀而不需要記憶大量資訊。請前往以下地址查看 Keith Ito 實現 beautifulTacotron 的程式碼,這是一個注釋的優秀範例:

//github.com/keithito/tacotron/blob/master/models/tacotron.py

3)小數據簡單模型的過擬合問題

技巧:先確保模型在非常小的一部分數據集上進行過擬合訓練,短時間內排除明顯的錯誤。

盡量讓模型能輕鬆通過配置文件進行配置,並指定參數最少的測試配置。然後在 CI/CD 中添加一個步驟,檢查非常小的數據集的過擬合,並自動運行它。這將有助於捕獲破壞模型和訓練 管道的程式碼改動。

四、數據

1、首先,要了解數據

在開始建模之前,你應該就已經厭倦了數據探查吧。

大多數機器學習模型都在嘗試複製人腦的某些模式識別能力。在開始編寫程式碼之前需要熟悉數據,鍛煉模式識別能力,讓你的程式碼寫的更輕鬆!了解數據集有助於整體架構的考慮和指標的選擇,而且能夠迅速識別可能會出現性能問題的地方。

一般來說,數據本身就可以識別一些問題:數據不平衡,文件類型問題或者數據偏見。數據偏見很難通過演算法進行評估,除非你有一個非常「聰明」的模型能識別這些問題。例如,這個「聰明」的模型能自己意識到偏見,「所有貓的照片都是在室內拍攝的,所有狗的圖片都是在室外拍攝的,所以也許我正在訓練室內/室外分類器,而不是識別貓和狗的分類器?」。

Karpathy 為 ImageNet 建立了一個標註平台,以評估他自己的表現並加深他對數據集的理解。

正如 Karpathy 所說的那樣,數據探查的系統能夠完成數據查看、數據切塊和切片。2018 年在倫敦舉辦的 KDD 上,他在演講中強調,Uber 的許多 ML 工程師並不是在編寫程式碼來優化模型,而是編寫程式碼優化數據標籤。

要了解數據,首先需要明白以下三種數據分布:

  • 輸入數據的分布情況,例如平均序列長度,平均像素值,音頻時長

  • 輸出數據的分布情況,分類失衡是一個大問題

  • 輸出/輸入的分布情況,這通常就是你要建模的內容

2、 選擇如何載入數據

有效地載入和預處理數據是機器學習工程中比較痛苦的環節之一,往往要在效率和透明度之間權衡取捨。

像 Tensorow Records 這樣的專用數據結構可以將數據序列轉為大數據包,減少對磁碟的頻繁讀取/寫入,但是這樣的作法卻有損透明度:這些結構很難再進一步研究或者分解數據,如果你想要添加或者刪除一些數據,則必須重新序列化。

目前 Pytorch Dataset 和 DatasetLoader 是平衡透明度和效率比較好的辦法,專用的程式包 torchtext 處理文本數據集,torchvision 處理影像數據集,這些程式包提供了相對有效的載入方式,填充並批處理每個域中的數據。

3、 加快數據載入的方法

以下是 Berker 在加快數據載入的嘗試過程中所得到的經驗教訓:

 1)不要載入目前正在載入的數據

這是因為你最終會發現,這樣做可能會丟失數據或者載入了重複數據。Berker 曾踩過的坑:

  • 編寫正則表達式從文件夾中載入某些文件,但是在添加新文件時沒有更新正則文件,這意味著新文件無法成功載入

  • 錯誤計算一個Epoch中的步數導致跳過了一些數據集

  • 文件夾中有遞歸符號,導致多次載入相同的數據(在 Python 中,遞歸限制為 1000)

  • 無法完全遍歷文件層次結構,因而無法將數據載入到子文件夾中

2) 錯誤存放數據

不要把所有數據放在一個目錄中。

如果你有上百萬個文本文件全部放在一個文件夾中,那麼任何操作都會非常非常慢。有時候哪怕僅僅查看或計算的動作,都需要等待大量的文件夾載入,從而大大降低了工作效率。如果數據不在本地,而是遠程存儲在數據中心,使用 sshfs 掛載目錄,情況會更加糟糕。

第二個錯誤陷阱就是在預處理時沒有備份數據。正確的做法是將耗時的預處理結果保存到磁碟中,這樣就不必在每次運行模型時都要重來一遍,不過要確保不覆蓋原數據,並需要一直跟蹤在哪些數據上運行了哪些預處理程式碼。

下圖是很好的一個示例:

3)不恰當的預處理

在預處理中出現數據濫用的情況是常見的,尤其是在 NLP 任務中。

非 ASCII 字元的錯誤處理是一個很大的痛點,這種情況不常出現,因此很難發現。

分詞也會導致很多錯誤發生。如果使用的是基於詞的分詞,很容易基於一個數據集形成辭彙表,結果在另一個數據集上使用的時候發現,大量的辭彙在辭彙表上找不到。這種情況模型並不報錯,它只是在別的數據集上表現不好。

訓練集和測試集之間的辭彙差異同樣是問題,因為那些只出現在測試集的辭彙是沒有被訓練的。

因此,了解數據並儘早發現這些問題是非常有價值的。

4、避免數據處理的錯誤

1)儘可能多記錄日誌

確保每次數據處理時都有樣本數據的日誌,不應該只記錄模型結果日誌,還應該記錄過程日誌。

2) 熟記模型超參數

你需要非常熟悉模型超參數:

  • 有多少樣本數?

  • 一次訓練所選取的樣本數有多大?

  • 一個Epoch有多少批處理?

這些同樣要記錄日誌,或者可以添加一些論斷來確保所有內容都沒有拉下。

3)預處理過程中記錄所有狀態

某些預處理步驟需要使用或創建工件 ,因此需要記得將其保存下來。例如,使用訓練集的平均數和變數正則化數值數據,並保存平均數和變數,以便可以在測試時應用相同的變換。

同樣,在NLP中,如果不保存訓練集的辭彙表,就無法在測試時以相同的方式進行分詞。如果在測試中形成新的辭彙表並重新分詞就會產生無意義的結果,因為每個單詞都將得到一個完全不同的標記。

4) 降取樣

當數據集非常大(例如影像和音頻)時,將數據輸入到神經網路中,期望模型能夠學習到最有效的預處理方法。如果有無限的時間和計算能力,那麼這可能是個好方法,但是在實際情況中,降取樣是比較合適的選擇。

我們不需要全高清影像來訓練狗/貓分類器,可以使用擴張卷積 來學習降取樣器,或者傳統的梯度下降完成降取樣。

降取樣可以更快地完成模型擬合和評估,是較好的節約時間的做法。

五、結論

總結一下在機器學習應遵循的 5 條指導原則:

  • 從小處著手,實驗會進行的很快。減少循環時間能夠及早發現問題並更快地驗證假設。

  • 了解數據。不了解數據就無法做好建模的工作。不要浪費時間在花哨的模型上,要沉心靜氣地完成數據探查工作。

  • 盡量多地記錄日誌。訓練過程的資訊越多,就容易識別異常並進行改進。

  • 注重簡單性和透明性而不僅僅是效率。不要為了節省少量時間而犧牲了程式碼的透明性。理解不透明程式碼所浪費的時間要比低效演算法的運行時間多得多。

  • 如果模型表現優異令人難以置信,那可能就是有問題。機器學習中存在很多錯誤可能會「愚弄」你,成為一名優秀的科學家意味著要理性的發現並消除這些錯誤。

推薦閱讀:

Andrej Karpathy 也寫了一篇非常出色的部落格《A Recipe for Training Neural Networks》,同樣是講機器學習的常見錯誤,但 Karpathy 更加專註於技術細節和深度學習,閱讀地址如下:

//karpathy.github.io/2019/04/25/recipe/

via //towardsdatascience.com/rookie-errors-in-machine-learning-bc1c627f2789  雷鋒網雷鋒網雷鋒網