交差検証
田中VPoE:「モデルの性能を1回の分割で評価するのは不安定だ。たまたま良いテストデータに当たっただけかもしれない。交差検証を使えば、より信頼性の高い評価ができる。」
あなた:「データの分割を変えて何度も評価するんですね。」
田中VPoE:「そうだ。特にデータ量が限られる場合、交差検証は必須のテクニックだ。ただし、時系列データでは注意が必要だ。」
K-fold 交差検証
データを K 個に分割し、各分割を順番にテストデータとして使います。
from sklearn.model_selection import cross_val_score, KFold
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=100, random_state=42)
# 5-fold 交差検証
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=kf, scoring='f1')
print(f"各 Fold の F1 スコア: {scores}")
print(f"平均 F1 スコア: {scores.mean():.3f}")
print(f"標準偏差: {scores.std():.3f}")
print(f"95%信頼区間: {scores.mean():.3f} +/- {scores.std() * 1.96:.3f}")
K-fold の仕組み
Fold 1: [テスト] [学習] [学習] [学習] [学習]
Fold 2: [学習] [テスト] [学習] [学習] [学習]
Fold 3: [学習] [学習] [テスト] [学習] [学習]
Fold 4: [学習] [学習] [学習] [テスト] [学習]
Fold 5: [学習] [学習] [学習] [学習] [テスト]
最終スコア = 5回の平均
K の選び方
| K | メリット | デメリット |
|---|---|---|
| 3 | 高速 | 学習データが少なく不安定 |
| 5 | バランスが良い | 一般的な選択 |
| 10 | より安定した評価 | 計算コスト増 |
| N(LOOCV) | 最もバイアスが低い | 非常に遅い |
Stratified K-fold
クラスの比率を各 Fold で維持する交差検証です。不均衡データでは必須です。
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 各 Fold のクラス比率を確認
for fold, (train_idx, val_idx) in enumerate(skf.split(X_train, y_train)):
y_fold_train = y_train.iloc[train_idx]
y_fold_val = y_train.iloc[val_idx]
print(f"Fold {fold+1}: 学習データ離反率={y_fold_train.mean():.3f}, "
f"検証データ離反率={y_fold_val.mean():.3f}")
# cross_val_score のデフォルトは分類で StratifiedKFold
scores = cross_val_score(model, X_train, y_train, cv=5, scoring='f1')
時系列データの交差検証
時系列データでは、未来のデータが学習データに含まれてはいけません。
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
# 時系列分割の可視化
for fold, (train_idx, val_idx) in enumerate(tscv.split(X_sorted)):
print(f"Fold {fold+1}: 学習={len(train_idx)}件 "
f"(idx {train_idx[0]}-{train_idx[-1]}), "
f"検証={len(val_idx)}件 "
f"(idx {val_idx[0]}-{val_idx[-1]})")
時系列分割の仕組み
Fold 1: [学習] [テスト] [ ] [ ] [ ] [ ]
Fold 2: [学習] [学習 ] [テスト] [ ] [ ] [ ]
Fold 3: [学習] [学習 ] [学習 ] [テスト] [ ] [ ]
Fold 4: [学習] [学習 ] [学習 ] [学習 ] [テスト] [ ]
Fold 5: [学習] [学習 ] [学習 ] [学習 ] [学習 ] [テスト]
常に過去のデータで学習し、未来のデータで検証します。
cross_validate で詳細な情報を取得
from sklearn.model_selection import cross_validate
cv_results = cross_validate(
model, X_train, y_train,
cv=StratifiedKFold(5, shuffle=True, random_state=42),
scoring=['f1', 'precision', 'recall', 'roc_auc'],
return_train_score=True,
)
print("=== 交差検証結果 ===")
for metric in ['f1', 'precision', 'recall', 'roc_auc']:
train_key = f'train_{metric}'
test_key = f'test_{metric}'
print(f"\n{metric}:")
print(f" 学習: {cv_results[train_key].mean():.3f} (+/- {cv_results[train_key].std():.3f})")
print(f" 検証: {cv_results[test_key].mean():.3f} (+/- {cv_results[test_key].std():.3f})")
gap = cv_results[train_key].mean() - cv_results[test_key].mean()
if gap > 0.1:
print(f" ※ 過学習の兆候あり(差: {gap:.3f})")
交差検証の注意点
前処理はFold内で行う
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# 正しい: Pipeline を使えば各 Fold 内でフィット/変換される
pipeline = Pipeline([
('scaler', StandardScaler()),
('model', RandomForestClassifier(n_estimators=100, random_state=42)),
])
scores = cross_val_score(pipeline, X_train, y_train, cv=5, scoring='f1')
# 間違い: 全データでスケーリングしてから交差検証(データリーケージ)
# scaler = StandardScaler()
# X_scaled = scaler.fit_transform(X_train) # 全データでフィット
# scores = cross_val_score(model, X_scaled, y_train, cv=5, scoring='f1')
テストデータに交差検証は使わない
学習データ → 交差検証でモデル選定・パラメータ調整
テストデータ → 最終評価(1回だけ)
まとめ
- K-fold 交差検証で複数の分割によるロバストな評価ができる
- 不均衡データでは Stratified K-fold を使う
- 時系列データでは TimeSeriesSplit で未来データの漏洩を防ぐ
- 前処理は Pipeline に含め、Fold 内で実行する
- テストデータは最終評価にのみ使用する
チェックリスト
- K-fold 交差検証の仕組みを説明できる
- Stratified K-fold の必要性を理解した
- 時系列データの交差検証手法を把握した
- Pipeline と交差検証を組み合わせる方法を理解した
次のステップへ
次のレッスンでは、混同行列の詳しい読み方と閾値調整によるビジネス最適化を学びます。
推定読了時間: 30分