LESSON

交差検証

田中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分