第17章 發現過擬合和欠擬合
我們現在將更深入的研究和討論在深度學習背景下的過擬合和欠擬合(underfitting and overfitting)。這里將提供一些圖表來將它們匹配到你自己的訓練損失/精確度曲線上,這個對於第一次以本書來學習深度學習/機器學習且之前還沒有面對過擬合和欠擬合時是特別有用的。
我們將創建一個用於keras的(近似)實時訓練監視器,你可以用來照看網絡的訓練過程。之后,在我們能畫出損失和精確度曲線之前我們將等待直到網絡訓練完成。
等待直到訓練結束才可視化損失和精確度將是計算浪費的,特別是當訓練要花費幾小時甚至幾天時間時,我們可能在前幾次epoch后就應該根據曲線停止訓練的。
因此,如果我們能在每一次epoch都能畫出訓練和損失並且能可視化結果,這將是很有用的。這樣我們可以做出更好更有用的決定,是否應該更早的停止訓練還是繼續訓練。
1 什么是過擬合和欠擬合
當訓練自己的神經網絡時,我們需要高度注意過擬合和欠擬合。欠擬合發生在我們的模型在訓練集上不能獲得足夠低的損失的時候。這種情況下,模型在訓練數據上不能學習到潛在的模式。相對的,過擬合發生在網絡模型對訓練數據模擬的太好且在驗證集上泛化的不好的時候。
因此,我們在訓練機器學習模型的目標是:
(1)盡可能地降低訓練損失;
(2)同時確保訓練和測試損失之間的差距相當小。
通過調整神經網絡的能力可以控制一個模型是否過擬合或欠擬合。我們通過添加更多的層和神經元來增加能力。類似的,我們可以通過移除層和神經元以及應用正則化技術(權重decay、dropout、數據增加、早停等等)來降低能力。圖17.1表示了模型能力與過擬合和欠擬合之間的關系。
圖17.1 模型容量與過擬合欠擬合關系示意圖
x軸為模型能力,y軸為損失,其中損失越低越合理。通常一個模型開始訓練時處於欠擬合區域(如圖形左側)。理想下,這時隨着訓練進行訓練損失和驗證損失都開始下降,這表明我們的網絡實際上正在學習。
但是,隨着模型能力的提高(更深的網絡、更多的神經元、沒有正則化等等),我們將達到網絡的“最佳能力”。從這點開始,我們的訓練和驗證損失/精確度開始出現分歧,且一個可看到的差距開始出現。我們的目標是限制這個差距,即保持模型的泛化能力。
如果我們不能限制這個差距,我們將進入“過擬合區域”(如圖形右側),從這點上,我們的訓練損失或者保持穩定或者持續下降,但是我們的驗證損失將穩定並最終增加。驗證損失在一系列連續epoch上增加是過擬合的強烈指示。
那么,我們如何克服過擬合呢?一般來說,有兩種方式:(1)降低模型的復雜度,選擇一個具有更少層和神經元的更淺層網絡;(2)應用正則化方法。
使用更小的神經網絡可能對於小數據集有用,但是一般來說,這不是好的選擇。而是,我們應該使用正則化技術,如weight decay、dropout、數據增加、早停等等。實際上,使用正則化技術而不是降低網絡大小來克服過擬合常常是更有效的,除非你有合理的理由相信你的網絡架構對於這個問題僅僅是太大了。
1.1 學習速率的影響
學習率對過擬合的影響如圖17.2所示:
圖17.2 學習率對損失的影響
在x軸上為一個神經網絡的epoch數目,對應於y軸的損失。理想下,我們的驗證損失和訓練損失看起來應當向綠色曲線類似,此時損失下降快速,但又不會如此快以至於錯誤最低損失位置。
理想上,訓練損失和驗證損失曲線是擬合的,但是現實中需要大量訓練來實現。因此,我們僅僅關注訓練和驗證損失之間的差距即可,即一旦這個差距不再動態增加,那么我們知道此時有了一個可接受的過擬合水平。但是,如果我們不能保持這種差距而且驗證和訓練損失之間的差距急劇加大,那么我們知道存在過擬合的風險。一旦驗證損失開始增加,那么此時存在強過擬合。
1.2 關注你的訓練曲線
當訓練你自己的神經網絡時,同時關注於訓練數據和驗證數據上的損失和正確率曲線。在最開始的幾輪epoch中,你的神經網絡可能曲線很好,可能輕微欠擬合——但是這種模式可能快速改變,你可能發現訓練和驗證損失開始差異。當這個發生時,看你的模型:
(1)使用任何正則化技術了嗎?
(2)學習率太高了嗎?
(3)網絡太深了嗎?
再次強調,訓練一個深度學習網絡半科學半藝術。學習怎樣閱讀這些曲線的最佳方式是盡可能多的訓練網絡然后查看它們的曲線。隨着時間推移,你將有種有用還是無用的感覺,但是不要期望一開始就有這種能力。
最后,你也將接受過擬合在某些特定的數據集上是不可避免的現實。例如,在cifar-10數據集上很容易過擬合,如果訓練和驗證損失開始出現分歧,不要悲傷,嘗試盡可能控制這個差距就可以了。
也要意識到隨着在最后幾輪epoch中學習率很低(如使用了學習率調度器),這也將更容易發生過擬合。這一點將在Practitioner Bundle和ImageNet Bundle中更清晰的討論。
1.3 驗證損失比訓練損失低會怎樣?
另一種奇怪的現象是驗證損失比訓練損失低。這怎么可能?當這個模式從訓練數據中嘗試學習,一個網絡怎么可能在驗證集上執行的更好?訓練性能不是應該比驗證或測試損失常常更好嗎?
不一定。會有多種可能造成這種現象。最簡單的解釋是你的訓練數據似乎都是很難分類的實例,而你的驗證數據則是由簡單數據點構成的。但是除非你有目的的以這種方式對數據抽樣,否則一個隨機的訓練和測試數據划分不可能完全划分成這種數據點。
另一種可能的原因是數據增加(data augmentation)。我們將在Practitioner Bundle中詳細討論數據增加,但要點是在訓練過程中,我們隨機的改變訓練圖像,通過對它們應用隨機變換如移動、旋轉、縮放、剪切等。由於這些變化,該網絡不斷看到增加了的訓練數據的樣例,這是正則化的一種方式,使得網絡在驗證集上有更好的泛化能力但是可能在訓練集上執行的比較差。
第三個原因可能是你訓練的“不夠努力”。你可能考慮增加學習率以及調整正則化強度。
2 監視訓練過程
在本節的第一部分,我們創建一個TrainingMonitor回調函數,它在當用keras訓練網絡時的每一輪epoch結束后調用。這個監視器將訓練和驗證集的損失和正確率序列化到磁盤上,之后構建數據圖表。
在訓練中應用這個監視器將使我們照看訓練過程而且較早的畫出過擬合,允許我們終止實驗且繼續嘗試調整參數。
1.4 創建一個訓練監視器
具體程序見GitHub的chapter17/pyimagesearch/callbacks/下。
為了記錄損失和正確率曲線,我們導入keras自帶的BaseLogger類。我們定義的類有三個參數,一是我們要指定的輸出圖表目錄,另兩個分別為json文件路徑,以及使用ctrl+c訓練時的參數(該參數這里默認為0,在Practitioner中使用)。之后創建on_train_begin()函數,用於訓練開始時調用。之后創建on_train_end()函數,用於每輪訓練結束時調用:
這個方法自動應用來自keras的參數,epoch為表示epoch數目,logs為字典,保存了當前epoch的訓練和驗證損失+正確率。我們周期性查詢logs,然后更新我們的歷史記錄。代碼結束后,我們的歷史幾率H中包含4個鍵:
最后,我們更新我們最終的圖表。具體代碼見GitHub。
1.5 照看訓練
我們以cifar10數據集訓練為例。代碼見GitHub的chapter17/的cifar10_monitor.py。
我們在程序中加入一行:print("[INFO process ID: {}".format(os.getpid())),這是作者的一個小技巧,如果同時訓練多個網絡,而且某些運行的很差,需要關閉時,可以打開任務管理器,直接kill掉該腳本對應的進程即可。
我們的文件名使用進程ID.png或進程ID.json的格式:
然后調用keras的callback函數配置,在訓練網絡的model.fit()中加入callbacks參數。此時,整個代碼就完成了。運行即可。
Python cifar10_monitor.py --output output
在運行過程中,在output/目錄下會看到有對應進程ID的兩個文件。在運行2輪之后,就可以打開對應文件查看信息了。使用監視器程序可以在每輪訓練結束后都可以照看訓練過程並且監視訓練。監視圖表如圖所示:
可看到在epoch 5時,扔處於欠擬合狀態,網絡從訓練數據中學習,訓練損失和驗證損失都持續下降。
在epoch10的時候,我們看到過擬合的跡象,但是還沒有過擬合警報,訓練損失和驗證損失出現分歧,但是這些分歧是正常的,而且指示了我們的網絡正在從訓練數據中繼續學習潛在模式。
但是在epoch 25時,我們到達過擬合區域,訓練損失持續下降,但是驗證損失保持穩定,這是很清晰的過擬合跡象且表明壞事情正在發生。
在epoch 50時,我們確實處於麻煩中。驗證損失開始增加,這確是過擬合了。這時你就應當停止實驗,然后開始調整參數繼續訓練了。
如果我們讓訓練持續訓練到epoch 100時,過擬合將只會變得更糟。訓練損失和驗證損失之間的差距將是巨大的。基於這些圖標,我們可以清晰的看到過擬合是什么時候發生在哪的。當運行自己的實驗時,確保使用監視器來輔助查看訓練過程。
最后,當你認為出現過擬合跡象的時候,不要高興的太早而過早kill掉進程,要確保繼續訓練10-15epoch來確定過擬合確實發生了。