Lesson 17 · 約 18 分鐘
Evaluation Methodology
train/val/test 切錯就白訓 · 選對 metric · 統計顯著性
17 ・ Evaluation Methodology:80% 履歷被 reject 是因為 eval 寫錯
模型訓出來、val accuracy 99% — 上線 50%。為什麼?Eval 寫錯比模型本身錯更 常見。這一講把 train/val/test 怎麼切、選哪個 metric、怎麼避免常見資料外洩 講清楚。卡住的時候回 lesson 14 §5 對照。
0. 一句話總結
1. Train / Val / Test 三套切法
| Set | 用途 | 看幾次? |
|---|---|---|
| Train | 訓練模型 | 每 step |
| Val | 調超參數 / 早停 / model selection | 每 epoch 一次 |
| Test | 最終評估,只看一次 | 訓練完才看 |
1.1 標準切比例
| 資料量 | 切法 |
|---|---|
| < 1K | 80% / 10% / 10% + cross-validation |
| 1K - 100K | 80% / 10% / 10% |
| 100K - 1M | 90% / 5% / 5% |
| > 1M | 98% / 1% / 1% — 1% × 1M = 10K 樣本已足夠統計顯著 |
為什麼大資料 val 可以這麼小:val 的點是估計 metric ± confidence interval。10K 樣本 95% CI 約 ±1%,夠了。
2. 切資料的四個坑(都很常見)
2.1 時序資料用隨機切
❌ 錯:
train, val = train_test_split(df, test_size=0.1, shuffle=True)時序場景(股票 / 銷售 / 用戶行為)隨機切 → train 看到「未來」資料 → val 假裝好。
✓ 對:按時間切
cutoff = df['date'].quantile(0.9)
train = df[df['date'] < cutoff]
val = df[df['date'] >= cutoff]2.2 同一個個體分到 train + val
場景:醫療影像。同一個病人有 100 張片子。隨機切 → 同病人的片子可能分到 train + val → 模型「認得這個人的解剖特徵」, val 假高。
✓ 對:Group-aware split
from sklearn.model_selection import GroupShuffleSplit
gss = GroupShuffleSplit(test_size=0.1, n_splits=1)
train_idx, val_idx = next(gss.split(X, y, groups=df['patient_id']))2.3 預處理在切之前 fit
❌ 錯:
scaler = StandardScaler().fit(all_data) # 用了整個 data 的 mean/std
train, val = split(all_data)val 的 mean/std 已洩進 train 的 scaler。
✓ 對:fit on train only
train, val = split(all_data)
scaler = StandardScaler().fit(train)
train = scaler.transform(train)
val = scaler.transform(val) # 用 train 的 scaler2.4 LLM 評測 set 跟訓練資料重疊
場景:你用 The Pile 訓 LLM,然後拿 LAMBADA 當 eval。但 LAMBADA 段落本來就在 The Pile 裡 → 你在測背誦能力。
✓ 對:訓練前先掃 eval set 跟 training set 的 n-gram 重疊,移除重疊段落。
3. K-Fold Cross-Validation:小資料的救星
資料量 < 5K 的時候,單一 split 變異太大(隨機切出來的 val 可能特別容易/難)。K-fold:
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = []
for train_idx, val_idx in kf.split(X):
model = MyModel()
model.fit(X[train_idx], y[train_idx])
scores.append(model.score(X[val_idx], y[val_idx]))
print(f'CV mean ± std: {np.mean(scores):.3f} ± {np.std(scores):.3f}')回報「平均 ± 標準差」 比單一數字誠實。± 大 = 資料太少 / 模型不穩定 / 切法可能有問題。
4. 選 Metric:任務決定
4.1 分類
| Metric | 何時用 |
|---|---|
| Accuracy | 類別平衡(50/50 等)、所有錯誤等價 |
| Precision | False positive 代價高(垃圾郵件分類:錯把正常郵件當垃圾) |
| Recall | False negative 代價高(癌症診斷:漏判) |
| F1 | Precision + recall 都重要,要單一數字 |
| AUC-ROC | 不關心具體 threshold,看排序能力 |
| PR-AUC | 不平衡資料(極稀少類別),AUC-ROC 會虛高 |
| Confusion matrix | Debug 用,看哪兩類最混 |
4.2 迴歸 / 連續值
| Metric | 何時用 |
|---|---|
| MSE / RMSE | 大誤差更該被罰(平方放大) |
| MAE | 大誤差按比例就好 |
| MAPE | 想要「平均誤差 X%」 的解讀 |
| R² | 想知道「模型解釋了多少變異」 (1 = 完美,0 = 跟猜平均一樣爛) |
4.3 LLM / 生成
| Metric | 何時用 |
|---|---|
| Perplexity | 預訓練品質、想對比不同 LM |
| BLEU / ROUGE | 機器翻譯 / 摘要 — 用 n-gram 重疊 |
| MT-Bench / AlpacaEval | 對話品質,GPT-4 當 judge |
| MMLU / GSM8K / HumanEval | 知識 / 推理 / 程式能力 |
| Human eval | 沒有更可信的;成本高 |
4.4 Retrieval / 推薦
| Metric | 何時用 |
|---|---|
| Recall@K | top-K 結果有沒有命中目標 |
| MRR (Mean Reciprocal Rank) | 命中位置越前面分數越高 |
| nDCG | 多級相關性(完全 / 部分相關),位置敏感 |
5. Statistical Significance:你的「贏」 是真的嗎?
新模型 val accuracy 0.873,舊模型 0.871 — 真的進步了嗎?
準則:單一 run 比另一個 run 高 0.5% 以下 = 可能只是 random seed 差異。
驗法:
# 跑 5 個 seed,看平均
for seed in [0, 1, 2, 3, 4]:
torch.manual_seed(seed)
model = train(...)
print(model.evaluate())
# 報「mean ± std」,不是單一數字± 重疊 = 統計上沒區別,你的「改進」 是 noise。
6. 黃金 Eval Workflow
7. 自我檢測
接下來
- 下一講 18 · Paper-reading walkthrough — 把〈Attention Is All You Need〉 section-by-section 拆開讀
- 想動手:挑你之前訓過的模型,重新切資料按 group-aware 來一次,看 val 數字落差(經驗:常常掉 5-15%)