:從過擬合到調(diào)參避坑指南)
一直以為書架里那本翻得起了毛邊的深度學習教材最值錢的部分藏在偏后的模型優(yōu)化章節(jié)直到我在整理舊筆記時重新看到deeplearningbook_035-2這個編號。035是我給“泛化與正則化”這一節(jié)編的頁碼序號-2則代表這一節(jié)里關(guān)于“為什么模型在訓練集上生龍活虎一到新數(shù)據(jù)就露怯”的那段思考。說來也怪當年課堂上、視頻里我看過無數(shù)遍的結(jié)論遠沒有這頁紙加上幾次真實項目踩坑后給我的觸動大。如果你最近也剛好在為“訓練損失降得挺漂亮一換場景效果就稀碎”發(fā)愁這篇文章應該能幫你把線頭捋清楚。先說清楚這篇文章要解決什么。它不是某個比賽冠軍方案的精讀也不是某庫API的逐行解釋而是一份圍繞“深度模型泛化能力”的實戰(zhàn)記錄。我會用我實際做圖像分類項目時的一組調(diào)參日志作為線索把權(quán)重衰減、Dropout、數(shù)據(jù)增強、早停這些看起來誰都會用的手段放到真實過擬合場景里重新講一遍它們各自在什么時候管用、什么時候添亂、怎么組合才不容易翻車。適合誰看剛把模型跑通但不太清楚為什么別人的正則化設置隨便抄都能用的初學者以及有一定經(jīng)驗但想補一補系統(tǒng)性排查思路的從業(yè)者應該都能在這篇里找到自己需要的東西。1. 這頁筆記背后的核心問題模型在測試集上為什么失效先把這個最讓人頭疼的現(xiàn)象剝離出來看。訓練集上準確率能沖到98%驗證集上也有94%你滿心歡喜地把模型接到新數(shù)據(jù)上結(jié)果準確率掉到70%出頭。這種落差不是玄學而是典型的泛化失敗也就是過擬合。深度學習和傳統(tǒng)機器學習在這件事上的表現(xiàn)差異非常大根源在于深度網(wǎng)絡參數(shù)規(guī)模龐大逼近訓練集“每一個角落”的能力太強了。你可以把模型想象成一個記憶力超常的學生他不僅能記住知識點的推導過程連帶教材里的印刷錯誤、老師口頭禪的停頓節(jié)奏都一并背下來了。考試時題目只要和課本原文不一樣他就抓瞎。導致過擬合的具體原因通常是三類。第一類是模型的容量和任務的復雜度不匹配明明只需要幾百個參數(shù)就能描述的數(shù)據(jù)規(guī)律你偏用幾百萬參數(shù)的模型去擬合模型自然會去“記憶”而不是“歸納”。第二類是訓練數(shù)據(jù)量和有效信息量不足數(shù)據(jù)少、標注噪聲大、類別的樣本分布嚴重不均衡都會讓模型在局部細節(jié)上鉆牛角尖。第三類是訓練過程缺乏約束信號沒有對模型復雜度做任何限制優(yōu)化器就會肆無忌憚地往極端方向走。在很多項目里大家習慣性認為“只要數(shù)據(jù)夠多、訓練夠久模型自然就能學好”但我在實際項目里吃過虧以后才慢慢意識到正則化不是可有可無的附加項它本質(zhì)上是在訓練過程中“持續(xù)提醒”模型不要把全部賭注押在訓練集的局部模式上。這一頁筆記最后給我留下的結(jié)論是過擬合不是某個時刻突然發(fā)生的災難而是一個從訓練剛開始就逐漸累積的過程差別只在于你什么時候注意到它。所以后續(xù)所有正則化手段都可以看作是在回答同一個問題如何在不過分犧牲模型表達能力的前提下降低它對訓練集特有模式的敏感度下面幾個小節(jié)里討論的每一項技術(shù)都是針對這個問題的不同角度給出的答案只不過有些回答看起來簡單實際上暗含了很多適用前提。2. 把正則化講清楚從直觀實驗看權(quán)重衰減與Dropout的邊界很多教程喜歡把正則化列舉成一條一條的“技巧菜單”但這容易誤導人。我先從兩個最常用的手段說起重點不是背公式而是理解它們到底在限制模型的什么行為。2.1 權(quán)重衰減小權(quán)值假設為什么有效權(quán)重衰減Weight Decay的寫法很簡單一般就在優(yōu)化器里加一個參數(shù)比如PyTorch里Adam的weight_decay。它的原理是在原始損失函數(shù)后面加上所有權(quán)重的平方和懲罰項優(yōu)化器在降低訓練損失的同時也要把權(quán)重的絕對值往零的方向壓一壓。為什么權(quán)重數(shù)值小就能提升泛化能力因為神經(jīng)網(wǎng)絡的每一層輸出都是上一層的加權(quán)和權(quán)重值越小輸出對輸入的小幅擾動就越不敏感。換句話說模型學到的函數(shù)會變得更加平滑不會在某個數(shù)據(jù)點附近發(fā)生劇烈震蕩。我在一個早期項目里做過簡單的對照實驗同樣的模型結(jié)構(gòu)一個完全關(guān)閉權(quán)重衰減另一個設置weight_decay1e-4。從訓練曲線上看前者的訓練損失可以降到更低但這部分優(yōu)勢完全是“背題”背出來的它在驗證集上的表現(xiàn)到后期反而出現(xiàn)明顯波動而加了權(quán)重衰減的模型雖然訓練損失下降得慢一點驗證曲線卻平穩(wěn)得多。這個現(xiàn)象背后的道理是懲罰項限制了有效參數(shù)空間的范圍讓優(yōu)化器無法找到一個只對訓練集有效的極端解。當然權(quán)重衰減也不是越小越好。我在實驗中發(fā)現(xiàn)設置太小時它約等于沒有設置太大時模型會變得“過于保守”連訓練集上的基本規(guī)律都學不動。一般圖像模型從1e-4到5e-4開始試純文本模型有時需要調(diào)大一個量級但更穩(wěn)妥的做法還是先跑一小組網(wǎng)格搜索觀察不同強度下訓練損失和驗證損失的距離。2.2 Dropout隨機失活并非簡單的數(shù)據(jù)增廣替代Dropout的原理大家應該都聽過訓練時以一定概率隨機把一部分神經(jīng)元的輸出置零讓模型不能依賴某一個或某幾個神經(jīng)元的固定組合。它經(jīng)常被誤認為“窮人的數(shù)據(jù)增廣”但這個類比并不完全對。數(shù)據(jù)增廣改變的是輸入數(shù)據(jù)本身而Dropout改變的是模型內(nèi)部的協(xié)同適應模式它逼迫網(wǎng)絡學到更魯棒的冗余表達。這里有一個經(jīng)常踩的坑很多人到了測試階段忘了關(guān)閉Dropout或者錯誤地在BatchNorm前面加Dropout導致分布被擾動。正確的做法通常是訓練時啟用Dropout并且用model.eval()或者model.train()這樣的切換機制來確保推理階段不再隨機失活。我見過不止一個同學在測試階段拿到一個“不穩(wěn)定”的結(jié)果上下浮動好幾個點查了半天才發(fā)現(xiàn)是Dropout在推送前沒有關(guān)掉。Dropout的強度選擇也很講究。設置太小比如p0.1對容量極大的網(wǎng)絡幾乎沒什么約束力設置太大比如p0.5小數(shù)據(jù)集上的表現(xiàn)反而可能斷崖式下跌因為模型在訓練階段獲取的信息太稀疏根本學不到東西。我在實際圖像項目里更傾向于把Dropout看作一個“容量調(diào)節(jié)旋鈕”模型越大、數(shù)據(jù)越少這個旋鈕就擰得越緊但它始終不是替代數(shù)據(jù)增強的第一選擇。2.3 數(shù)據(jù)增強最容易被低估的正則化手段如果說權(quán)重衰減和Dropout是從模型內(nèi)部“做減法”數(shù)據(jù)增強就是從源頭“做加法”。它通過對原始訓練樣本進行不改變語義的變換比如圖像上的隨機裁剪、翻轉(zhuǎn)、色彩擾動或者文本里的同義詞替換、EDA方法來擴大訓練分布的覆蓋范圍。數(shù)據(jù)增強之所以是效果最自然的正則化是因為它并沒有給模型施加額外限制而是讓模型看到了更多樣的“合法樣本”。我在動手做細粒度圖像分類時發(fā)現(xiàn)數(shù)據(jù)增強帶來的收益經(jīng)常比調(diào)半天Dropout和權(quán)重衰減還明顯。一個很典型的例子原始訓練集里所有“窗戶”的照片都出現(xiàn)在畫面左側(cè)模型很容易學到“看到窗戶就等同于左邊有窗戶”這種偽規(guī)律。一旦測試數(shù)據(jù)里窗戶出現(xiàn)在右邊模型準確率立刻崩掉。而隨機水平翻轉(zhuǎn)的增強策略雖然只是簡簡單單改變圖片方向卻能讓模型不再依賴這種位置信息。不過數(shù)據(jù)增強有一個隱蔽的坑它改變了訓練分布的形態(tài)如果增強得太激進比如把物體裁得只剩下20%的區(qū)域模型會把大量注意力放在學習更難的樣本上導致欠擬合或訓練不穩(wěn)定。我在調(diào)參時習慣先固定一個合理的增強范圍跑通基線之后再逐步增強強度同時盯著訓練集和驗證集的差距變化而不是盲目認為增強越猛越好。3. 實戰(zhàn)調(diào)參記錄某圖像分類項目里的正則化組合理論說太多容易飄下面用一段真實的調(diào)參日志來說明。當時我手頭接到一個圖像分類小項目目標是對某類工業(yè)零件做缺陷識別判斷零件是否存在表面劃痕或裂紋。由于這一類缺陷樣本本身就少能用來做訓練的有效圖片只有幾千張而分類類別有十幾類屬于典型的小樣本細粒度分類任務。3.1 問題背景與初始模型表現(xiàn)我一開始直接搬了一個預訓練特征提取模型凍結(jié)前幾層只對后半部分進行微調(diào)優(yōu)化器選了Adam。初始配置里沒有加任何額外的正則化唯一沾點邊的是預訓練模型自帶的權(quán)重初始化。這個基線跑下來訓練集準確率沖到97.4%但驗證集只有84.6%。差距超過12個百分點效果就是典型的過擬合模型幾乎把訓練樣本的特征細節(jié)都記住了。當時記錄下來的訓練曲線有幾個容易忽略的細節(jié)訓練損失在第12個epoch之后還在緩慢下降而驗證損失從第8個epoch就開始反彈權(quán)重的L2范數(shù)在訓練中后期快速增長說明某些層的參數(shù)被優(yōu)化器推到比較大的范圍同時模型對輸入圖像上的微小噪點非常敏感稍微改一下亮度就能讓預測結(jié)果翻轉(zhuǎn)。這些跡象都指向同一個結(jié)論模型容量超出了數(shù)據(jù)能支撐的信息量必須引入外部約束。3.2 逐項加入正則化后的驗證集變化于是我開始按順序逐項加正則化并在每次只改變一個變量的前提下記錄指標變化。這是調(diào)參最基本的一個習慣一次只動一個旋鈕不然出了問題你根本不知道是誰惹的禍。配置方案訓練準確率驗證準確率訓練與驗證差距基線無額外正則化97.4%84.6%12.8%增加權(quán)重衰減1e-495.8%87.3%8.5%繼續(xù)增加Dropoutp0.393.6%89.2%4.4%疊加隨機裁剪翻轉(zhuǎn)增強91.2%91.7%-0.5%同時配合早?;隍炞C損失90.5%92.1%-1.6%這張表里很有意思的是最后一步的驗證準確率反超訓練準確率這在小數(shù)據(jù)集上并不罕見因為驗證集相對更容易或者訓練階段的數(shù)據(jù)增強給訓練樣本人為增加了難度。但重點不是數(shù)值高低而是每一步之間“訓練準確率和驗證準確率的距離”在穩(wěn)步縮小。這說明模型不再把能量集中在死記硬背上而是學會了真正區(qū)分缺陷存在的關(guān)鍵模式。3.3 值得記錄的失敗嘗試調(diào)參過程并不只有成功的操作有幾輪失敗的嘗試我認為更值得反思。第一輪失敗是我把Dropout強度直接拉到0.5期望一步到位減少過擬合結(jié)果驗證準確率不升反降跌到86%附近。原因是這個項目本身的訓練樣本量不夠大過高的隨機失活使得模型在訓練階段得不到足夠穩(wěn)定的梯度信號局部特征也學不完整。第二輪失敗是我嘗試在訓練集上把增強強度拉滿包括大幅度隨機擦除和夸張的顏色抖動。結(jié)果模型在驗證集上震蕩得很厲害準確率忽高忽低我后來把單次增強的幅度調(diào)回去又逐一觀察哪個變換對指標影響最大才定位到那條過于夸張的變換在制造和真實缺陷無關(guān)的偽紋路。第三輪失敗是我試圖用更復雜的正則化方式比如譜歸一化強行套到整個主干網(wǎng)絡上。譜歸一化本身對生成式模型比較友好但放在這個分類任務里不僅拖慢了訓練速度還讓收斂變得不穩(wěn)定。最后我放棄了這個方案重新回到基礎組合上。這件事給到我的教訓是不要為了“看起來高級”去追逐新興的正則化技巧先把手邊最常用的工具用到極致收益往往更高。4. 訓練監(jiān)控別讓驗證集成為你唯一的情報來源我剛接觸深度學習時判斷模型好壞的方式異常簡單粗暴看驗證集準確率高就是好低就是壞。但做了幾個項目之后我發(fā)現(xiàn)如果只盯驗證集上的單一數(shù)字你會失去很多診斷問題的窗口等發(fā)現(xiàn)不對勁的時候已經(jīng)浪費了好幾輪訓練。4.1 訓練集誤差與驗證集誤差的差距變化最基礎的監(jiān)控方法是把訓練誤差和驗證誤差放到同一張圖里持續(xù)觀察兩條曲線的距離。如果訓練誤差持續(xù)下降驗證誤差卻在某個點開始上揚這就是典型的過擬合信號這時候早停比繼續(xù)訓練更有價值。但如果兩條線都高高在上差距不大那問題就不是過擬合而是欠擬合或者模型結(jié)構(gòu)設計本身有缺陷。我習慣在訓練腳本里把每一輪的訓練損失和驗證損失都記錄下來并且寫一個簡單的判斷規(guī)則如果連續(xù)若干個epoch驗證損失都沒有刷新最低值就觸發(fā)早停。這個規(guī)則看似簡單卻能省下大量反復試錯的時間。另一個細節(jié)是驗證頻率不要太低小數(shù)據(jù)集上每隔一兩個epoch就應該看一次否則你很難定位驗證損失反彈的精確位置。4.2 權(quán)重的數(shù)值分布檢查有時候光看損失曲線還不夠。我在某次實驗里遇到一種怪現(xiàn)象驗證準確率一直上不去訓練損失卻在下降看起來像過擬合但加了正則化之后一點用都沒有。后來我把每一層權(quán)重的數(shù)值分布打印出來發(fā)現(xiàn)網(wǎng)絡后半部分的權(quán)重已經(jīng)膨脹到非常大同時梯度已經(jīng)小到近乎消失。這其實是優(yōu)化過程出了問題單純靠加正則化根本救不回來必須調(diào)整學習率或者檢查特征提取層的歸一化方式。權(quán)重數(shù)值分布檢查是很容易被忽視的手段它能幫你判斷疊加的正則化到底有沒有真正生效。比如你開了權(quán)重衰減就可以隔一段時間統(tǒng)計一下權(quán)重范數(shù)如果發(fā)現(xiàn)范數(shù)沒有發(fā)生任何趨勢性變化說明權(quán)重衰減的設置大概率被某個遮蔽點繞過去了比如某些庫默認不作用于特定參數(shù)組或者你在混合精度訓練里漏掉了相關(guān)配置。4.3 學習率與正則化強度的聯(lián)動這一點我想特別提一下學習率和正則化強度不是兩個完全獨立的超參數(shù)。學習率偏大時模型參數(shù)更新步長也大同等強度的權(quán)重衰減所產(chǎn)生的約束效果會被放大可能導致模型還沒充分探索就被“壓”住了學習率偏小時權(quán)重衰減有一定概率作用被減弱因為每一步的更新幅度都很小懲罰項帶來的梯度貢獻也相應變小。我在調(diào)這個工業(yè)缺陷識別項目時就發(fā)現(xiàn)這么一個問題把學習率從默認的3e-4降到1e-4之后原本合適的最佳權(quán)重衰減強度變得“太輕了”驗證準確率掉了一些反過來提高學習率并同步增大一點權(quán)重衰減反而把最佳驗證準確率又往上推了一截。這種聯(lián)動效應在超參數(shù)搜索時比單獨調(diào)每個參數(shù)更值得留意很多自動調(diào)參工具之所以給出的組合看起來違反直覺就是因為它們同時考慮了這種交互作用。5. 我踩過的三個坑和對應的排查思路如果只聊原理和成功日志這篇筆記的價值就少了一半。下面這幾個坑是我和周圍朋友在實操過程中真實踩到過的每一個都花了不少時間去排查希望你能繞開。5.1 坑一驗證集連續(xù)提升但測試集崩掉有一次我在一個新任務上做實驗驗證集上的準確率連續(xù)五個epoch穩(wěn)步上升我心想這波穩(wěn)了結(jié)果把最終模型放到留出的測試集上一測效果比驗證結(jié)果低了將近10個百分點。一開始我懷疑是測試代碼有Bug反復檢查之后發(fā)現(xiàn)代碼沒問題真正的原因出在驗證集本身它太小了而且是從同一個批次采集的數(shù)據(jù)里切出來的與訓練集存在一定的數(shù)據(jù)泄漏。排查思路是這樣的先檢查驗證集和訓練集的樣本來源有沒有重疊或高度相似的樣本然后看驗證集的樣本量如果只有幾十張圖準確率的置信區(qū)間會非常大一次波動擺個十個百分點都很正常最后用K折交叉驗證去替換單次劃分觀察指標是否穩(wěn)定。那次之后我再也不敢只用單一固定驗證集做重要決策了至少會額外留一份從不同來源收集的測試集來兜底。5.2 坑二Dropout加太猛導致欠擬合另一個高頻問題就是前面提過的Dropout強度一上來反而把模型從過擬合推到欠擬合的狀態(tài)。表面上驗證集和訓練集的準確率差距確實縮小了但兩者都在低位徘徊整體效果比不加Dropout還差。我當時的排查過程是先看訓練損失有沒有下降如果訓練損失本身就很高說明模型在訓練階段就沒有擬合到足夠好的程度這時候優(yōu)先需要減少Dropout強度或者增加模型容量而不是繼續(xù)加大正則化。把Dropout當成“容量調(diào)節(jié)器”來理解排查就容易很多。當發(fā)現(xiàn)模型欠擬合時第一件要做的事不是堆更多數(shù)據(jù)增強而是把Dropout關(guān)掉或者調(diào)低讓模型有足夠的表達空間等到訓練損失能夠降下來、驗證差距拉大時再一點點把Dropout加回去。這個先后順序在我后面的項目中幾乎每次都管用。5.3 坑三把正則化參數(shù)寫死在全流程里第三個坑是我自己最常犯的把一組正則在某個數(shù)據(jù)集上調(diào)好的參數(shù)直接復制到新項目里以為一勞永逸。正則化的最優(yōu)強度高度依賴數(shù)據(jù)規(guī)模、類別數(shù)、模型結(jié)構(gòu)和訓練時長在一項目里堪稱完美的設置換一個數(shù)據(jù)量差幾倍的任務時可能完全不適用。我現(xiàn)在會在每個新項目的初期都強制自己跑一個小網(wǎng)格搜索哪怕只是兩三個固定檔位比如權(quán)重衰減分別試1e-4、3e-4、1e-3Dropout分別試0.2、0.3、0.4把結(jié)果記錄到表格里再做決策。這個流程看起來繁瑣但能幫你快速建立對“當前任務正則化敏感度”的感覺比盲目沿用舊配置更可靠。6. 最后想補充的幾點經(jīng)驗做了這么多輪實驗之后再回頭看deeplearningbook_035-2這頁筆記我更傾向于把正則化和泛化看成同一個問題的兩面模型在訓練集上表現(xiàn)得越“輕松”越要警惕它是否在偷懶地記住答案而泛化能力強的模型往往在訓練過程中看起來并不是最耀眼的那個。我個人經(jīng)驗里價值最高的一個習慣是每次調(diào)正則化之前先在筆記本上寫下當前模型的過擬合證據(jù)比如訓練和驗證的差距、權(quán)重范數(shù)的變化趨勢、增強前后的性能波動然后再決定動哪個旋鈕。盲目調(diào)參不是勤奮而是偷懶。另外一個小技巧是盡量把正則化的選擇記錄成一張可復用的實驗清單包括數(shù)據(jù)集規(guī)模區(qū)間、模型結(jié)構(gòu)特征、建議的初始范圍以及出現(xiàn)過問題的組合。下次接到新任務時先翻一眼這張表能省掉不少在黑暗中試探的時間。代碼實現(xiàn)層面其實都很直接花更多精力去理解“為什么這個設置管用那個設置為什么翻車”才是長期提升項目能力的真正捷徑。