03.1.1
交差検証
train_test_split では「どの 20% が検証に回ったか」で ROC-AUC が 0.5 にも 0.8 にもなり得ますが、5 分割の平均なら全データが 1 回ずつ検証に使われるので偶然に左右されにくくなります。このページでは 300 件の不均衡データでホールドアウトと cross_validate(cv=5) を比べ、前者が 0.528、後者が 0.844 という差が出る理由を追います。読み終えると、scoring に複数指標を渡して一度に集計し、クラス不均衡なら StratifiedKFold、時系列なら TimeSeriesSplit に切り替える判断ができるようになります。標本データを分割し、その一部でモデルを学習、残りでテストを行い、解析自体の妥当性を検証する手法 — 交差検証 (Wikipedia)
直感:答えを見て解いたテストでは実力が分からない #
過去問 100 問を丸暗記して同じ 100 問で 100 点を取っても実力とは言えないので、80 問で勉強し残り 20 問を初見のテストにする、というのがホールドアウト(train_test_split)です。ただし、どの 20 問が残るかで点数は変わります。交差検証は 100 問を 5 つの山に分け、山 1 をテストにして残りで勉強、次は山 2 をテストに、と 5 回くり返して平均を取るので、たまたま簡単な山がテストに回った偶然がならされます。典型例は k-fold(k 分割)と Stratified k-fold(各フォールドでラベル比率を揃える)の 2 種です。scikit-learn の cross_validate は分類では既定で層化分割を使います。
何が起きているか(ステップで見る) #
コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。
見るポイント:データを増やして 2 本の線の差を見る。2 本の差が残るなら過学習、2 本とも誤差が高いままなら学習不足(図の縦軸は RMSE で、低いほど良い)。訓練スコアと検証スコアの 2 本を描くために、各点で交差検証が使われている。
Python でホールドアウトと 5 分割を比べる #
Python 3.13 環境を前提とし、必要なライブラリを導入します。
| |
次のコードは、make_classification で陰性 2 : 陽性 8 の不均衡な 300 件のデータを作り、RandomForestClassifier(max_depth=4) を単一のホールドアウト(8:2 分割)と cross_validate の 5 分割で評価して、ROC-AUC を並べます。
| |
出力例:
Hold-out ROC-AUC: 0.528
5-fold ROC-AUC: 0.844
5-fold Accuracy: 0.858
実行結果の読み方 #
単一ホールドアウトでは ROC-AUC が 0.528 とほぼランダム(0.5)に見えますが、交差検証では 0.844 と安定した性能が確認できます。差の原因は 2 つで、1 つは 60 件の検証データが偶然難しい組になったこと、もう 1 つはホールドアウト側が predict の 0/1 で AUC を計算していることです。同じモデルでも「1 回の分け方」と「評価に渡す値」で評価が 0.3 も動くので、モデル比較では交差検証の平均と標準偏差を使います。
交差検証の設計で決めること #
フォールド数は一般的に 5 または 10 で、データが少ない場合は 1 件ずつ検証に回す LeaveOneOut も候補ですが、300 件なら 300 回学習することになり計算コストが高いです。クラス不均衡がある場合は StratifiedKFold などで各フォールドのラベル比率を揃えます。揃えないと、陽性が 1 件も入らないフォールドで ROC-AUC が計算できなくなります。scoring にタプルを渡すと複数指標を一度に計算できるので、ROC-AUC と Accuracy のように「順位の良さ」と「閾値 0.5 での正解率」を組み合わせるとバランスが見えやすいです。GridSearchCV や RandomizedSearchCV は内部で交差検証を行うので、その外側にもう 1 段の交差検証を置く ネステッド交差検証 にすると、ハイパーパラメータ探索による過学習を防げます。
使いどころと落とし穴 #
データに時系列の依存がある場合は、未来のデータで学習して過去を当てるリークが起きるので、cv=5 ではなく TimeSeriesSplit を使います。ビジネスで重要な指標(例: Recall や Log Loss)を scoring に含め、意思決定者と「何を平均しているか」の認識を合わせます。交差検証は k 回学習するため、1 回の学習に 10 分かかるモデルなら 5 分割で 50 分になり、n_jobs=-1 で並列化するか k を減らす判断が必要です。再現可能にするため、random_state と cv の設定値をノートブックに残し、モデル更新時にも同じ分割で比較できるようにしておきます。
まとめ #
交差検証は \(k\) 個のフォールドを順番に検証用に回して \(k\) 個のスコアを平均する手法で、300 件の例ではホールドアウトの ROC-AUC 0.528 に対して 5 分割の平均は 0.844 と、1 回の分け方による偶然を取り除いた値が得られます。scikit-learn では cross_validate(model, X, y, cv=5, scoring=("roc_auc", "accuracy")) で複数指標を同じ分割でまとめて取得でき、np.std を添えればばらつきも報告できます。クラス不均衡では StratifiedKFold、時系列では TimeSeriesSplit、ハイパーパラメータ探索を含めるならネステッド交差検証に切り替え、k 回分の計算コストを見積もって n_jobs で並列化します。分割を固定する random_state を残しておけば、モデル更新時にも同じ条件で比較できます。
分割数と交差検証 #
分割数 k を 2 から 10 まで変えると、各回の訓練データが 50% から 90% に増え、検証データが 50% から 10% に減る様子を確認できます。