JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

交叉驗證與誠實的估計

單次測試分數只是擲了一次骰子。本文教你如何衡量模型的真實表現,以及那個數字本身的波動,讓你不再自欺欺人。

你回報的分數本身就是一個隨機變數

你已經知道那條最高準則:絕不要用模型學習時用過的資料來衡量它,因為泛化——在*未見過*的資料上的表現——才是唯一重要的東西。於是你做一次留出法(holdout):從資料集中切出比如 20%,用剩下的訓練,再在留出的那一片上評分。乾淨又簡單。但本文要圍繞的那個令人不安的真相是:那個單一數字並不是你模型「真正的」準確率,它只是從一個分佈中抽出的一個樣本。

想想裡面摻進了多少隨機性。*哪* 20% 落進了測試集,本身就是一次拋硬幣。抽到容易的那一片,你的數字就很漂亮;抽到難的那一片,同一個模型看起來就差。當測試集很小時,這種運氣能讓回報出來的準確率上下擺動好幾個百分點。所以當有人說「我的模型準確率 91%」,誠實的讀法是「91% ± 某個值」——而那個 *某個值* 往往大到足以抹掉他們正在炫耀的全部優勢。

k 折:讓每個資料點都當一次測試點

k 折交叉驗證(k-fold cross-validation)是個優雅的解法。把資料打亂,切成 *k* 等份,每一份叫一折(fold)——五折或十折是常見選擇。然後訓練 *k* 次。每一輪裡,有一折坐在場外當測試集,其餘 *k*−1 折拿去訓練。每個資料點都恰好當一次測試點、當 *k*−1 次訓練點。最後你得到的是 *k* 個分數,而不是一個。

fold:  [ A ][ B ][ C ][ D ][ E ]   (k = 5)
round1  TEST train train train train  -> 0.88
round2  train TEST train train train  -> 0.91
round3  train train TEST train train  -> 0.85
round4  train train train TEST train  -> 0.90
round5  train train train train TEST  -> 0.89
         mean = 0.886   std = 0.022
5 折交叉驗證:每一折輪流當測試集;你回報均值和波動範圍。

會掉出兩個數字,兩個你都該在意。這 *k* 個分數的均值,是比任何單次留出都穩得多的泛化估計,因為走運和倒楣的劃分會互相抵消平均。各折之間的標準差,則正是你長久以來缺失的那個度量——*你的結果到底抖動多大*。一個平均 0.886、波動 0.02 的模型,確實強過一個平均 0.89、但各折從 0.80 散到 0.97 的對手——後者是個賭徒,不是個穩定選手。

這有個實際代價:*k* 折意味著要訓練 *k* 個模型,所以十折大約是單次擬合的 10 倍算力。對表格資料上的邏輯迴歸來說毫無壓力,但對一個訓練一次就要好幾天的大型深度網路往往是無法承受的。這個權衡是真實存在的,也正因如此,*k* 的選擇——乃至要不要做交叉驗證——取決於你的模型擬合一次有多貴。

訓練—測試差距:把兩個數字放在一起讀

交叉驗證告訴你模型有多好。訓練—測試差距(train-test gap)則告訴你*為什麼*,以及該往哪個方向修。它就是你在訓練資料上的分數和在留出資料上的分數之差。這一個差距,是整個模型評估裡最具診斷價值的數字,因為它能乾淨地拆成你之前見過的兩種經典失敗模式。

*大*差距——訓練上近乎完美、測試上平平——是過擬合的標誌:模型背下了它在新資料上無法復現的雜訊。*小*差距但*兩個*分數都差,則是欠擬合:模型太簡單,根本抓不住規律,所以哪裡都一樣失敗。這就是偏差—變異數權衡以你能從螢幕上直接讀出的數字露出真容。關鍵在於,兩者的療法方向相反,所以讀錯差距會讓你走向錯誤的一邊。

三分法:別燒掉你的測試集

這裡有個連老手都會中招的微妙陷阱。真實專案不會只訓練一個模型——而是會試幾十個:不同的超參數、特徵、架構。如果你靠測試分數來挑贏家,那測試集就悄悄變成了你訓練過程的一部分。你*在測試集上調了參*,它的數字現在是被樂觀地高估的——它告訴你的是你猜得多準,而不是你將來泛化得多好。

解法是訓練/驗證/測試三分法。在訓練集上訓練。在驗證集上調參、比較每一個候選。然後,在所有決定都鎖定之後,*僅此一次*,你拆封測試集並讀出分數。那個最終數字之所以誠實,*正是因為*你從未讓它影響過任何選擇。實踐中,k 折往往扮演驗證的角色(你在訓練+驗證資料上做交叉驗證來挑設置),而一個單獨的測試集則原封不動,留給最終報告。

這一切底下還埋著一顆雷:資料洩漏。如果你在劃分*之前*,用*整個*資料集算出的統計量去標準化特徵、填補缺失值、或做特徵選擇,那麼測試列的資訊就向後洩進了訓練——你那套漂亮的交叉驗證現在成了謊言。規則很機械:每一個預處理步驟都要在每一折*內部*、只在訓練那部分上擬合,再套用到留出那部分。先做劃分,在那之前別碰其他任何東西。

當資料不是獨立同分佈時,如何誠實地劃分

樸素 k 折假設你的每一列可以互換——隨便打亂,任何劃分都一樣好。但情況常常並非如此,而一次天真的打亂會悄悄洩露答案。時間序列是經典例子:打亂會讓模型用週五的資料去預測週一,而它在生產中永遠沒機會這麼幹。改用向前滾動的劃分——永遠用過去訓練、在未來上測試。誠實的分數會更低,而那個更低的數字才是真的。

分組資料是另一個大坑。如果你有 100 個病人、每人 10 張照片,按*照片*劃分會讓同一個病人同時出現在訓練和測試裡——模型認的是病人,不是病,分數純屬虛構。要改成按*組*(病人)劃分。而當某一類很稀少時,要用分層(stratified)k 折,讓每一折都保持相同的類別比例;否則某一折裡可能幾乎一個少數類樣本都沒有,它的分數就毫無意義了。

把它串起來:一份誠實清單

這一切都不是為了得到一個*更高*的數字,而是為了得到一個你能*信任*的數字——而信任意味著同時知道它的取值和它的抖動。一個沒有波動範圍、對著一個你懶得去超越的基線、在一個有洩漏的劃分上評出來的結果,比沒有結果還糟,因為它把虛假的信心帶進了現實。誠實的評估,多半就是「先不要自欺」這一條紀律。

  1. 先劃分,在任何前處理之前——當列不獨立時按時間或按組劃分;當某類稀少時做分層。
  2. 在訓練+驗證上用 k 折來估計表現,並回報各折的均值*和*標準差——絕不只給一個孤零零的數字。
  3. 看訓練—測試差距來診斷過擬合還是欠擬合,並朝正確的方向去修。
  4. 把測試集封存好;在所有決定都最終敲定之後,只拆封一次,得出你真正要回報的那一個數字。