返回目錄
A
從資料到決策:系統化資料科學實踐手冊 - 第 6 章
第六章 模型評估與調參
發布於 2026-03-05 17:27
# 第六章 模型評估與調參
> **一句話提醒**:評估與調參是模型工程師與數據科學家的「品質保證站」。不只是在實驗室中做一次驗證,而是為了確保模型在真實環境中持續穩定地提供決策價值。
## 6.1 評估指標總覽
| 指標 | 定義 | 適用場景 | 取值範圍 | 典型解讀 |
|------|------|----------|----------|----------|
| Accuracy |
| (TP+TN)/(TP+TN+FP+FN) | 普通二分類 | 0–1 | 取值越高越好 |
| Precision | TP/(TP+FP) | 當預測為正類時,真的正類比例 | 0–1 | 重要於成本高的誤判 (如醫療) |
| Recall (Sensitivity) | TP/(TP+FN) | 捕捉所有正類 | 0–1 | 重要於漏判成本高 (如欺詐偵測) |
| F1‑Score | 2·Precision·Recall/(Precision+Recall) | 平衡精度與召回 | 0–1 | 兩者皆重要時使用 |
| ROC‑AUC |
| Area under ROC Curve | 描述 TP‑率 vs FP‑率 | 檢測分數模型 | 0–1 | 1 完美, 0.5 隨機 |
| PR‑AUC | Area under Precision‑Recall Curve | 不平衡資料 | 0–1 | 召回率高時更能反映真實表現 |
| Log‑Loss | 交叉熵損失 | 需要分數預測 | ≥0 | 0 越低越好 |
| MAE / MSE / R² | 回歸指標 | 回歸模型 | 依據 | 取值越低/高越好 |
> **選擇指標的關鍵**:先考量「失誤成本」與「業務價值」。例如信用卡欺詐,漏判成本遠高於誤判,則召回率與 PR‑AUC 更具決策價值。 | | | | | |
## 6.2 交叉驗證 (Cross‑Validation)
### 6.2.1 為什麼要交叉驗證?
- **避免過擬合**:僅靠一次拆分可能得到偶然的好表現。
- **穩健性評估**:透過多次重複,得到表現的分布。
- **資料不平衡**:配合 StratifiedKFold 確保每折樣本比例相近。
### 6.2.2 常用交叉驗證類型
| 方法 | 何時適用 |
|------|----------|
| K‑Fold | 一般分布均勻資料 |
| StratifiedKFold | 分類問題,樣本不平衡 |
| LeaveOneOut (LOO) | 資料極少,想盡可能多訓練 |
| TimeSeriesSplit | 時間序列資料,避免未來資訊泄露 |
### 6.2.3 範例程式碼
python
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=10000, n_features=20,
n_informative=10, n_repeated=0,
n_classes=2, weights=[0.995, 0.005],
flip_y=0, random_state=42)
model = RandomForestClassifier(n_estimators=200, random_state=42)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=skf, scoring='roc_auc')
print('ROC‑AUC scores per fold:', scores)
print('Mean ROC‑AUC:', scores.mean())
### 6.2.4 交叉驗證的常見陷阱
| 陷阱 | 解決方式 |
|------|----------|
| 1. **資料泄露** | 對資料做「先切再處理」的順序;不在整體資料上做特徵工程。 |
| 2. **不當抽樣** | StratifiedKFold 或自定義樣本比例。 |
| 3. **過度訓練** | 在交叉驗證內部再做 hyper‑parameter search 時,使用內部的 CV 或自訂分層。 |
## 6.3 ROC 與 PR 曲線
### 6.3.1 何時用 ROC,何時用 PR?
- **ROC**:當 FP 與 FN 成本相近時。
- **PR**:當正類稀少且誤判成本高時。<br>例如信用卡欺詐、疾病篩檢。
### 6.3.2 繪圖範例
python
from sklearn.metrics import roc_curve, precision_recall_curve, auc
import matplotlib.pyplot as plt
# 模型預測
y_proba = model.predict_proba(X)[:, 1]
# ROC
fpr, tpr, _ = roc_curve(y, y_proba)
roc_auc = auc(fpr, tpr)
# PR
precision, recall, _ = precision_recall_curve(y, y_proba)
pr_auc = auc(recall, precision)
plt.figure(figsize=(12, 5))
# ROC
plt.subplot(1, 2, 1)
plt.plot(fpr, tpr, label=f'ROC AUC = {roc_auc:.3f}')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
# PR
plt.subplot(1, 2, 2)
plt.plot(recall, precision, label=f'PR AUC = {pr_auc:.3f}')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision‑Recall Curve')
plt.legend()
plt.tight_layout()
plt.show()
### 6.3.3 判斷閾值的技巧
- **Youden’s J statistic**:最大化 `tpr - fpr`。
- **F1‑max**:在 PR 曲線上最大化 F1。
- **業務指標**:例如「成本最低」或「召回率達 95%」作為閾值。
## 6.4 參數調整策略
### 6.4.1 Grid Search
- **全搜尋**:穩健但計算成本高。
- **使用 `GridSearchCV`**:自動交叉驗證並輸出最佳參數。
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [None, 5, 10],
'min_samples_split': [2, 5]
}
gs = GridSearchCV(RandomForestClassifier(random_state=42),
param_grid, cv=5, scoring='roc_auc', n_jobs=-1)
gs.fit(X, y)
print('Best params:', gs.best_params_)
print('Best ROC‑AUC:', gs.best_score_)
### 6.4.2 Randomized Search
- **樣本化**:在大參數空間中隨機抽樣,速度快。
- **適用**:參數範圍廣,搜尋成本高。
python
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint
param_dist = {
'n_estimators': randint(50, 500),
'max_depth': [None] + list(range(5, 25, 5)),
'min_samples_split': randint(2, 10)
}
rs = RandomizedSearchCV(RandomForestClassifier(random_state=42),
param_dist, n_iter=50, cv=5,
scoring='roc_auc', n_jobs=-1, random_state=42)
rs.fit(X, y)
print('Best params:', rs.best_params_)
### 6.4.3 Bayesian Optimization (Optuna)
- **基於先前結果**:使用貝葉斯模型預測未測試參數的表現。
- **自動調整步驟**:可用於連續型參數或大參數空間。
python
import optuna
from optuna.samplers import TPESampler
def objective(trial):
n_estimators = trial.suggest_int('n_estimators', 50, 500)
max_depth = trial.suggest_int('max_depth', 5, 30)
min_samples_split = trial.suggest_int('min_samples_split', 2, 10)
clf = RandomForestClassifier(n_estimators=n_estimators,
max_depth=max_depth,
min_samples_split=min_samples_split,
random_state=42)
score = cross_val_score(clf, X, y, cv=5, scoring='roc_auc').mean()
return score
study = optuna.create_study(direction='maximize', sampler=TPESampler())
study.optimize(objective, n_trials=100)
print('Best params:', study.best_params)
print('Best ROC‑AUC:', study.best_value)
### 6.4.4 早停(Early Stopping)
- **對於迭代模型**(如 XGBoost, LightGBM):當驗證集表現不再提升時自動終止。
- **實作範例**:
python
import xgboost as xgb
params = {
'objective': 'binary:logistic',
'eval_metric': 'auc',
'learning_rate': 0.05,
'max_depth': 6,
'subsample': 0.8,
'colsample_bytree': 0.8
}
dtrain = xgb.DMatrix(X_train, label=y_train)
# 以驗證集作為 early stopping
watchlist = [(dtrain, 'train'), (xgb.DMatrix(X_val, label=y_val), 'eval')]
model = xgb.train(params, dtrain, num_boost_round=1000, evals=watchlist,
early_stopping_rounds=50, verbose_eval=False)
print('Best iteration:', model.best_iteration)
## 6.5 監測模型表現 (Model Monitoring)
| 監測項目 | 目的 | 工具 / 實作 |
|----------|------|-------------|
| **漂移檢測 (Concept Drift)** | 確保資料分布或目標分布未發生改變 | River、Alibi Detect、漂移指標 (KL Divergence) |
| **性能退化** | 監控關鍵指標 (Accuracy, F1 等) 變化 | MLflow、Prometheus + Grafana |
| **異常檢測** | 識別輸入資料或模型輸出是否異常 | Isolation Forest、LSTM‑Autoencoder |
| **成本跟蹤** | 以實際業務成本評估模型效益 | 自訂指標、Cost‑Sensitive learning |
> **實作示例**:使用 `river` 進行連續漂移監測。
python
import river
from river import drift
model = river.linear_model.LogisticRegression()
drift_detector = drift.ADWIN()
for x, y in data_stream:
y_pred = model.predict_one(x)
model.learn_one(x, y)
drift_detector.update(float(y_pred == y))
if drift_detector.change_detected:
print('Drift detected! Re‑training model.')
model = river.linear_model.LogisticRegression() # or retrain on recent batch
## 6.6 案例:信用卡欺詐模型評估(延伸)
| 指標 | 目標 | 目前值 |
|------|------|--------|
| Precision | 0.80 | 0.78 |
| Recall | 0.95 | 0.92 |
| F1‑Score | 0.87 | 0.85 |
| PR‑AUC | 0.70 | 0.68 |
| ROC‑AUC | 0.95 | 0.94 |
**改進建議**
1. **採用更多不平衡學習策略**:如 `SMOTE + undersampling` 或 `class_weight='balanced'`。
2. **調整閾值**:將 `F1‑max` 或 `Recall‑90%` 位置作為預測閾值。
3. **引入新特徵**:交易頻率、客戶風險等。
4. **使用 Bayesian Optimization 進一步調參**:優化 XGBoost 連續參數。
## 6.7 小結
- **評估指標**:不僅是數值,更關聯業務邊際。選擇正確的指標,才能做出有意義的調整。
- **交叉驗證**:提供穩健的性能估計,避免過擬合與資料泄露。
- **參數調整**:從 Grid Search、Randomized Search 到 Bayesian Optimization,依據資料規模與模型複雜度選擇合適工具。
- **模型監測**:在部署後持續追蹤表現與漂移,確保模型長期可用。
- **實務洞見**:在每一次模型迭代中,都要把「成本‑效益」視角放在首位,才能真正將資料科學價值落地。
> **一句話提醒**:模型的「表現」是量化的,而「價值」則取決於你能否將這些數字轉化為可衡量的業務改進。