Learning Objectives
- 說明為什麼模型表現要看沒看過的資料,什麼是泛化
- 用 train_test_split 切分資料,說明 test_size、random_state、stratify 的作用
- 從訓練與測試準確率的差距,判斷過擬合與欠擬合
- 說明切分只切一次時,測試分數為什麼會浮動
背答案不等於學會
考試前把考古題的答案全部背下來,考古題可以考滿分,但換一份新題目就不一定了。考古題的分數,看不出你到底學會了沒有。
模型也一樣。模型在訓練資料上表現好,可能只是把那些資料記住了。我們真正在意的是它遇到沒看過的資料時準不準,這個能力叫泛化(generalization)。
下面做一個有點極端的實驗:把標籤換成丟銅板的結果,這份資料根本沒有規律可以學。如果模型在這種資料上還能拿高分,那個分數就不可能代表「學到東西」。
逐步看:連丟銅板都能「學」到滿分
資料和標籤都是亂數產生的,兩者完全無關。把前 150 筆拿去訓練,留 50 筆不給模型看。先猜猜看:兩個分數各會是多少?
把一部分資料藏起來
做法很簡單:開始之前先把資料切成兩份。
| 資料 | 用途 | 比喻 |
|---|---|---|
| 訓練資料(training set) | 給模型學規則 | 上課和考古題 |
| 測試資料(test set) | 模型完全沒看過,只用來評估 | 正式考試 |
scikit-learn 的 train_test_split 會先隨機打亂再切,三個參數最常用:
test_size:測試資料的比例,預設 0.25;random_state:亂數種子。固定它,每次切法都一樣,別人才能重現你的結果;stratify=y:讓訓練與測試資料裡各類別的比例和原本相同。類別比例懸殊時,少了它,少數類別可能大多被切到其中一邊。
為什麼要隨機? 資料常常有順序,例如前面都是資工系、後面都是護理系。直接切前 75% 和後 25%,兩邊就不是同一種資料了。不過,如果資料有時間順序、要預測的是未來,就不能隨機切,要用過去訓練、未來測試(第 24 課的專題會用到)。
逐步看:train_test_split
回到 students.csv(程式產生的模擬資料,不是任何學校的真實資料)。注意每次切完的形狀,和兩邊的及格比例。
過擬合與欠擬合
模型越複雜,越能貼合訓練資料,但也越可能把資料裡的雜訊也記下來。用決策樹來看:樹的深度越深,規則越細、越複雜。
| 情況 | 訓練表現 | 測試表現 | 意思 |
|---|---|---|---|
| 欠擬合(underfitting) | 差 | 差 | 模型太簡單,連訓練資料的規律都學不到 |
| 剛好 | 不錯 | 不錯,兩者接近 | 學到可以推廣的規律 |
| 過擬合(overfitting) | 很好 | 明顯比較差 | 模型太複雜,連雜訊都記住了 |
判斷時要同時看兩個數字和基準,也就是什麼都不學的簡單猜法(分類時全部猜最多的類別)。接下來把深度從 1 調到 12,看兩條線怎麼走。
逐步看:樹越深,訓練越高,測試呢?
看輸出的三欄:深度、訓練準確率、測試準確率;最後畫成折線圖。注意兩條線之間的距離。
測試分數也會浮動
上面測試資料只有 99 筆,一筆就是 1%。深度 2 的 0.808 和深度 3 的 0.798,只差一筆資料,很難說誰比較好。
再做一個實驗:模型不變,只換切分的方式(random_state),看測試準確率會差多少。
逐步看:只換切法,分數差多少
同一份資料、同樣深度 3 的樹,切六次。看最低和最高差多少。
使用測試資料的規矩
從這個實驗可以得到兩個結論:
- 差 1~2% 不要過度解讀。 測試資料不多時,那可能只是這次切分的運氣。第 14 課的交叉驗證會多切幾次取平均,得到比較穩定的估計。
- 測試資料只能用來做最後的評估。 如果你一邊看測試分數一邊調模型(改深度、換特徵),調到分數最高為止,模型其實就間接「看過」測試資料了,那個分數會偏樂觀,就像先看過考題再考試。需要比較不同設定時,應該從訓練資料裡再切一份驗證資料(validation set)或用交叉驗證,第 14 課會教。
本課的深度實驗為了讓你看到現象,直接畫出了測試分數,不代表可以用這條線來選深度。
實作:自己寫切分與準確率
完成兩個函數:
holdout(n, test_ratio, seed):用np.random.default_rng(seed)的permutation(n)打亂 0 到 n−1,前int(n * test_ratio)個當測試資料,其餘當訓練資料,回傳(train_idx, test_idx);accuracy(y_true, y_pred):回傳猜對的比例,四捨五入到小數 3 位。
小測驗:切分資料
下面程式會印出什麼?
import numpy as np
from sklearn.model_selection import train_test_split
X = np.arange(20).reshape(10, 2)
y = np.arange(10)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
print(X_train.shape, y_test.shape)
train_test_split 的 stratify=y 是做什麼用的?
換一個 random_state 後,測試準確率從 0.78 變成 0.81,模型的設定完全沒動。最恰當的解讀是?
小測驗:讀懂兩個分數
訓練準確率 0.99、測試準確率 0.70,全部猜最多類別是 0.72。這是什麼情況?
訓練準確率 0.76、測試準確率 0.75,全部猜最多類別是 0.76。這是什麼情況?
小明反覆調整模型設定,每次都看測試分數,留下最高的那組,回報 0.90。這個 0.90 有什麼問題?
重點整理
- 模型表現要看沒看過的資料,訓練準確率高可能只是把資料背起來。這個能力叫泛化。
train_test_split:test_size預設 0.25;random_state讓切法可重現;stratify=y保持類別比例。有時間順序的資料不要隨機切。- 欠擬合:訓練和測試都差。過擬合:訓練好、測試明顯比較差。都要和基準比較。
- 測試資料不多時,只換切法分數就會浮動,差 1~2% 不要過度解讀。
- 測試資料不能拿來調模型,比較設定要用驗證資料或交叉驗證(第 14 課)。
到這裡你已經知道怎麼誠實地評估模型。下一課開始從頭寫模型,先從最基本的線性迴歸開始。