交差検証(Cross-Validation)とは?K-Fold・層化・時系列・データリークまで図解で解説

手元のデータでモデルを作ったとき、いちばん知りたいのは「このモデルは、まだ見ていないデータでどれくらい当たるのか」です。ところが訓練に使ったデータで測った精度は、答えを見ながら答案を採点しているようなもので、まったく当てになりません。かといってデータの2割をテスト用に取り分けて一発勝負で測ると、今度は「たまたま簡単なデータが取り分けられただけ」という運の要素が入り込みます。

交差検証(Cross-Validation, CV) は、この「一発勝負の運」を消すための手法です。データを何個かの塊に分け、塊を1つずつ順番に検証役に回して、全部の結果を平均します。全データが一度ずつ検証役を経験するので、限られたデータを捨てることなく、しかも安定した評価が得られます。

交差検証は、たとえば次のような場面でそのまま効いてきます。

  • モデル選びとハイパーパラメータ調整 — リッジ回帰の正則化の強さ $\alpha$、決定木の深さ、SVMの $C$ と $\gamma$。これらは「CVで測った誤差がいちばん小さい値」を選ぶのが標準です。scikit-learn の GridSearchCV が内部でやっているのはまさに交差検証です。
  • 医療・製造・金融での性能報告 — 「この診断モデルの精度は92%です」と言うとき、その92%がどういう分割で測られたのかが問われます。分割の仕方を間違えると、実運用で半分も当たらないモデルに92%という数字が付いてしまいます。本記事の後半で、正解率が実測 0.52 から 0.86 まで水増しされる典型的な失敗を実演します。

そして交差検証のいちばん怖いところは、間違った使い方をしてもエラーが出ないことです。プログラムは正常終了し、綺麗な数字を返します。ただしその数字が嘘なだけです。この記事では、K-Fold の理論から始めて、どういうデータで何を使うべきか、そしてどこで嘘が混入するかを、すべて実測値と図で示していきます。

5分割交差検証の模式図。データを5つの塊に分け、検証に使う塊を1つずつずらしながら5回学習し、得られた5つの精度を平均してCV推定値とする

この図が交差検証のすべてです。左の表の1行が1回の学習に対応し、橙色の塊がその回の検証データ、青色の塊が訓練データです。第1回は塊1を検証に、第2回は塊2を検証に、というように検証役を1つずつずらしていくため、5回終わったときには全データがちょうど1度ずつ検証側を経験しています。そして右のように5つの精度を平均した値が、最終的な性能の推定値になります。

交差検証は何を推定しようとしているのか

まず「正しい答え」が何なのかをはっきりさせます。私たちが本当に知りたいのは、真のデータ分布 $P$ から新しくサンプルが来たときの平均的な損失、すなわち期待汎化誤差です。

$$ \text{Err} = \mathbb{E}_{(x, y) \sim P}\left[\ell(f(x), y)\right] $$

ここで $P$ はデータの真の分布、$\ell$ は損失関数(誤り率、二乗誤差など)、$f$ は学習されたモデルです。問題は、$P$ が未知であることです。もし $P$ が分かっているなら機械学習など必要ありません。手元にあるのは $P$ から抽出された有限個のサンプル $\mathcal{D} = \{(x_i, y_i)\}_{i=1}^N$ だけです。

そこで、$\mathcal{D}$ の一部を「まだ見ていないデータのふり」をさせて $\text{Err}$ を近似する、というのが評価の基本方針になります。いちばん素朴なのがホールドアウト法で、$\mathcal{D}$ を訓練用8割・テスト用2割に一度だけ切り、テスト側の誤差を報告します。

ホールドアウト法には3つの弱点があります。

  1. テストデータの選び方で評価が変動する — 切り方を変えれば数字も変わります
  2. データの一部が学習に使われない — 2割を評価専用に取られる分、モデルは弱くなります
  3. 小規模データでは信頼性が低い — 40件のテストで正解率を測っても、1件の当たり外れが2.5ポイント動きます

1つ目がどれくらい深刻なのかを、実際に測ってみます。200件のデータに対し、乱数シードだけを500通り変えて「2割ホールドアウト」と「5分割CV」をそれぞれ500回実行し、返ってきた正解率の分布を比べたものが次の図です。

ホールドアウト法と5分割交差検証の安定性比較。乱数シードを500通り変えたときの正解率の分布とシード順の推移

データもモデルも一切変えていないのに、ホールドアウトの正解率は最小 0.675 から最大 1.000 まで、幅にして 0.325 も振れました(標準偏差 0.0474)。一方 5分割CVの平均値は 0.845〜0.895 の幅 0.050 に収まり、標準偏差は 0.0089 でした。ばらつきは 5.30 倍の差です。右のグラフを見ると、赤いホールドアウトの折れ線が上下に暴れ回るのに対し、緑のCVは長期平均(0.875)の周りをぴたりと這っていることが分かります。

ここで重要なのは、両者の平均はほぼ同じ(0.8746 と 0.8731)だという点です。ホールドアウトが間違っているのではなく、精度が低い(分散が大きい)だけです。つまり交差検証がやっているのは、同じものを何度も測って平均することで測定精度を上げるという、実験科学ではごく当たり前の操作なのです。運悪く 0.675 というシードを引いた人が「このモデルはダメだ」と結論してしまう事故を、交差検証は防いでくれます。

では、その「何度も測る」を具体的にどう組み立てるのか。次はK-Fold交差検証の定義を見ていきます。

K-Fold交差検証の定義

K-Fold交差検証では、データを $K$ 個の互いに素な部分集合(フォールド)に分割します。

$$ \mathcal{D} = \mathcal{D}_1 \cup \mathcal{D}_2 \cup \cdots \cup \mathcal{D}_K, \quad \mathcal{D}_i \cap \mathcal{D}_j = \emptyset \ \ (i \neq j) $$

「互いに素」という条件が効いています。もしフォールドが重なっていたら、あるサンプルが訓練にも検証にも入ってしまい、そのサンプルについては答えを知った状態で採点することになります。重なりを禁じることで、各回の検証が「未知データでの評価」として意味を持ちます。

各フォールド $k = 1, \dots, K$ について、次の3ステップを実行します。

  1. $\mathcal{D}_k$ を検証セット、残り $\mathcal{D} \setminus \mathcal{D}_k$ を訓練セットとする
  2. 訓練セットだけでモデル $f^{(-k)}$ を学習する(上付きの $(-k)$ は「$k$ 番目のフォールドを抜いて学習した」という意味)
  3. 検証セット $\mathcal{D}_k$ で誤差を計算する

そして $K$ 回分の誤差を平均したものが、K-Fold CV の推定値です。

$$ \text{CV}_K = \frac{1}{K} \sum_{k=1}^{K} \frac{1}{|\mathcal{D}_k|} \sum_{(x, y) \in \mathcal{D}_k} \ell\left(f^{(-k)}(x), y\right) $$

二重の和のうち、内側の和は「フォールド $k$ の中での平均誤差」、外側の和は「$K$ 個のフォールドをまたいだ平均」です。各フォールドのサイズが等しい場合、この式は結局「全 $N$ サンプルについて、そのサンプルを含まないモデルで予測した誤差の平均」と一致します。つまり 全データが1度ずつ検証側に回り、しかも自分自身を学習していないモデルで採点される、という性質がここに現れています。

ここで自然な疑問が生まれます。$K$ はいくつにすればよいのでしょうか。$K=2$ でも $K=10$ でも上の式は成立しますが、返ってくる数字の性質は大きく変わります。

Kの選択 — バイアスとバリアンスのトレードオフ

$K$ の選択には次のようなトレードオフがあります。

$K$ バイアス バリアンス 計算コスト
小さい(例:2) 高い(訓練データが少ない) 低い 低い
大きい(例:$N$) 低い 高い(フォールド間の相関) 高い

まずバイアスの正体をはっきりさせます。$K$ 分割にすると、各回の訓練に使えるのは全体の $(K-1)/K$、つまり $N – N/K$ サンプルだけです。$N=100$ なら $K=2$ で50件、$K=10$ で90件です。モデルは訓練データが少ないほど弱くなるので、$K$ が小さいCVは「本来より弱いモデル」を評価してしまい、誤差を実際より大きく見積もります。式で書けば、

$$ \mathbb{E}[\text{CV}_K] \approx \text{Err}_{(N – N/K)} $$

であり、右辺は訓練サンプル数 $N – N/K$ での期待誤差です。私たちが知りたいのは $N$ 件すべてで学習したモデルの $\text{Err}_{(N)}$ ですから、$\text{Err}_{(N-N/K)} > \text{Err}_{(N)}$ の分だけ悲観的にずれるわけです。

このずれを実際に測ってみます。$N=100$ の訓練セットを200回独立に生成し、それぞれで各 $K$ のCVを回します。同時に「その100件で学習したモデルを4万件の独立テストで評価した値」を真の汎化誤差として記録し、両者を比べました。

Kの選択によるバイアスとバリアンスの実測。K=2,3,5,10,20,Nについて、バイアス・標準偏差・CV推定値の分布を200回の独立試行で比較

真の汎化誤差の平均は 0.1638 でした。これに対しCVの平均は、$K=2$ で 0.1763(バイアス +0.0125)、$K=3$ で +0.0068、$K=5$ で +0.0042、$K=10$ で +0.0025、$K=20$ で +0.0017、$K=N=100$(LOOCV)で +0.0019 と、$K$ を増やすほど単調にゼロへ近づいています。すべて正の値である点も重要で、理論どおり「CVは誤差を大きめに(性能を低めに)見積もる」ことが確認できます。

一方、中央のグラフのばらつき(標準偏差)は 0.0461 → 0.0446 → 0.0420 → 0.0404 → 0.0415 → 0.0401 と推移しました。$K=10$ あたりで下げ止まって、それ以降はほとんど改善しません。「$K$ を増やせば平均する数が増えるのだから、$1/K$ でどんどん安定するはず」と思うかもしれませんが、実測はそうなっていません。この下げ止まりの理由が、次の分散の式にあります。

$$ \text{Var}(\text{CV}_K) = \frac{\sigma^2}{K} + \frac{K-1}{K} \rho \sigma^2 $$

ここで $\sigma^2$ は各フォールドの誤差の分散、$\rho$ はフォールド間の誤差の相関です。この式は、$K$ 個の確率変数の平均の分散 $\text{Var}(\bar{Z}) = \frac{1}{K^2}\left(\sum_i \text{Var}(Z_i) + \sum_{i \neq j} \text{Cov}(Z_i, Z_j)\right)$ に、$\text{Var}(Z_i) = \sigma^2$ と $\text{Cov}(Z_i, Z_j) = \rho\sigma^2$ を代入すれば得られます。実際に代入すると、分散の項が $K$ 個、共分散の項が $K(K-1)$ 個あるので、

$$ \text{Var}(\text{CV}_K) = \frac{1}{K^2}\left(K\sigma^2 + K(K-1)\rho\sigma^2\right) = \frac{\sigma^2}{K} + \frac{K-1}{K}\rho\sigma^2 $$

となり、上の式に一致します。第1項は $K$ とともに $1/K$ で減りますが、第2項は $K \to \infty$ で $\rho\sigma^2$ に収束して減りません。ここがポイントです。$K$ を大きくすると各回の訓練セットどうしの重なりが増えます。$K=N$ のLOOCVなら、隣り合う2回の訓練セットは $N-2$ 件を共有していて、ほぼ同じモデルです。似たモデルの誤差は似た方向にずれるので $\rho$ が大きくなり、いくら平均しても分散が下がらなくなります。

左:フォールド間相関ρと分散の関係。右:訓練サンプル数と正解率の学習曲線、K=2/5/10がそれぞれ何件で学習するか

左のグラフは分散の式そのものです。$\rho = 0$(フォールドが完全に独立)なら $K=50$ の分散は $K=2$ の25分の1になりますが、$\rho = 0.5$ の時点で $K=50$ と $K=10$ の差はほとんど消えています。右のグラフは実測の学習曲線で、$N=100$ のときに $K=2$ は50件(正解率 0.729)、$K=5$ は80件(0.756)、$K=10$ は90件(0.759)で学習することを示しています。50件と80件の差 0.027 が、そのまま $K=2$ の悲観バイアスとして現れるわけです。曲線が90件付近でほぼ平らになっているので、$K=10$ から $K=100$ に増やしてもバイアスはもう大して改善しません。

つまり $K=5$ か $K=10$ という定番の推奨は、「バイアスがほぼ消える程度には訓練データを確保でき、かつ計算量が現実的」という2つの条件が交わる場所を選んだものです。では、$K$ を極限まで大きくした $K=N$ の場合はどうなるのでしょうか。

Leave-One-Out交差検証(LOOCV)

$K = N$、つまり1サンプルだけを検証に回して残り全部で学習する、を $N$ 回繰り返すのが Leave-One-Out 交差検証です。

$$ \text{LOOCV} = \frac{1}{N} \sum_{i=1}^{N} \ell\left(f^{(-i)}(x_i), y_i\right) $$

各回の訓練データは $N-1$ 件なので、$N$ 件で学習した本命モデルとほぼ同じ強さです。バイアスの観点では最良で、実際 $N=100$ の実測でもバイアスは +0.0019 と最小級でした。しかし代償が2つあります。

1つは計算コストです。モデルを $N$ 回学習する必要があります。$N=400$ のデータで実測すると次のようになりました。

左:分割数Kと計算時間の関係(両対数)。右:ハット行列による一撃計算とn回学習した素朴なLOOCVの一致

左の両対数グラフは、計算時間が $K$ にほぼ比例して増えることを示しています。5分割なら CV 1回あたり 0.007 秒で済むところ、LOOCV($K=400$)は 0.50 秒かかりました。同じ結果を得るのに 69倍の時間です。今回はロジスティック回帰という軽いモデルなので0.5秒で済んでいますが、これが1回の学習に10分かかる勾配ブースティングやニューラルネットなら、LOOCVは数日コースになります。$N$ が数千を超えたら現実的な選択肢ではありません。

もう1つは分散です。前節で見たとおり、LOOCVの訓練セットどうしは $N-2$ 件を共有していて相関 $\rho$ が非常に高く、$N$ 回も平均しているのに分散が思ったほど下がりません。今回の実測では $K=10$(0.0404)と $K=N$(0.0401)でほとんど差がありませんでした。$N$ 倍の計算を払って得られる改善が事実上ゼロなのです。

ただしLOOCVには、線形モデルに限って計算コストが消し飛ぶという裏技があります。線形回帰では次の等式が成り立ちます。

$$ \text{LOOCV} = \frac{1}{N} \sum_{i=1}^{N} \left( \frac{y_i – \hat{y}_i}{1 – h_{ii}} \right)^2 $$

ここで $h_{ii}$ はハット行列 $H = X(X^\top X)^{-1}X^\top$ の対角成分(レバレッジ)、$\hat{y}_i$ は全データで学習したモデルの予測値です。右辺には $f^{(-i)}$ が一切出てきません。フルデータで1回だけ学習すれば、$N$ 回分のLOOCV誤差が全部出てしまうのです。

なぜこうなるのかを追ってみます。$A = X^\top X$ とおくと、$i$ 番目を抜いたときの計画行列は $A_{(-i)} = A – x_i x_i^\top$ です。ここで Sherman–Morrison の公式

$$ (A – x x^\top)^{-1} = A^{-1} + \frac{A^{-1} x x^\top A^{-1}}{1 – x^\top A^{-1} x} $$

を使います。$h_{ii} = x_i^\top A^{-1} x_i$ なので分母は $1 – h_{ii}$ です。これを $\hat{\beta}^{(-i)} = A_{(-i)}^{-1}(X^\top y – x_i y_i)$ に代入して整理すると、途中の項がきれいにまとまって

$$ \hat{\beta}^{(-i)} = \hat{\beta} – \frac{A^{-1} x_i (y_i – \hat{y}_i)}{1 – h_{ii}} $$

が得られます。つまり 1点を抜いた係数は、フルデータの係数から補正項を1つ引くだけで表せます。両辺に $x_i^\top$ を掛けると $x_i^\top \hat{\beta}^{(-i)} = \hat{y}_i – h_{ii}(y_i – \hat{y}_i)/(1 – h_{ii})$ となり、残差 $e_i = y_i – \hat{y}_i$ を使って引き算すると

$$ y_i – x_i^\top \hat{\beta}^{(-i)} = e_i + \frac{h_{ii} e_i}{1 – h_{ii}} = \frac{e_i(1 – h_{ii}) + h_{ii}e_i}{1 – h_{ii}} = \frac{e_i}{1 – h_{ii}} $$

となって、目的の式が導けました。分母の $1 – h_{ii}$ は「その点が自分の予測をどれだけ引っ張っていたか」の補正で、レバレッジの高い点ほど、抜いたときの予測の悪化が大きいことを表しています。

この等式が本当に成り立つかを実測で確認したのが、先ほどの図の右パネルです。$n=60$、$p=4$ のリッジ回帰($\alpha=1$)で、素朴に60回学習したLOOCV MSE は 1.076227、ハット行列による一撃計算も 1.076227 で、各サンプルごとの誤差の最大絶対差は $7.1 \times 10^{-15}$(倍精度の丸め誤差レベル)でした。近似ではなく厳密な恒等式であることが数値的にも確かめられます。

ここまでは「データが独立同分布である」ことを暗黙に仮定してきました。ここからは、その仮定が崩れる3つの典型的な場面 — クラス不均衡、時間構造、グループ構造 — を順に見ていきます。

層化K-Fold交差検証

不良品検知や希少疾患の診断のように、陽性が全体の数%しかないデータを考えます。200件中10件だけが陽性のデータを10分割すると、1フォールドあたり20件。陽性の期待値は1件です。期待値が1件ということは、0件のフォールドが普通に出るということです。陽性が1件も入っていないフォールドでは、再現率も適合率も定義できず、F1スコアは0として扱われます。

層化K-Fold(Stratified K-Fold) は、各フォールドでクラスの構成比を元データと揃えるように分割します。クラス $c$ の全体での割合を $\pi_c = N_c / N$ とすると、各フォールド $\mathcal{D}_k$ について

$$ \frac{\left|\{(x, y) \in \mathcal{D}_k : y = c\}\right|}{|\mathcal{D}_k|} \approx \pi_c $$

が成り立つように、クラスごとに別々にシャッフルして配る、というだけの単純な工夫です。効果を実測しました。

層化K-Foldの効果。左:1シードでのフォールド別陽性率、中央:300フォールドの陽性率分布、右:F1スコアのばらつき

全体の陽性率 0.050(200件中10件)に対し、通常のK-Foldでは各フォールドの陽性率が 0.000 から 0.250 まで散らばりました(標準偏差 0.0484)。30シード×10分割=300フォールドのうち、陽性が1件も入らなかったフォールドは107個、実に 35.7% です。3回に1回以上の頻度で「評価不能なフォールド」が生まれていたことになります。層化K-Foldではすべてのフォールドが陽性率 0.050 ちょうど(標準偏差 0.0000)でした。

この違いは最終的な数字にそのまま出ます。F1スコアのCV推定値は、通常K-Foldが 0.3327(標準偏差 0.0613)、層化K-Foldが 0.4861(標準偏差 0.0176)。平均が 0.15 も違ううえ、ばらつきは3分の1以下です。通常K-Foldの 0.3327 は、陽性ゼロのフォールドでF1が0に潰れた分だけ不当に低く出た値であり、モデルの実力を表していません。分類問題では層化をデフォルトにすべきで、scikit-learn の cross_val_score も分類器を渡すと自動で層化K-Foldを使います。

クラスの偏りは層化で直せました。しかし、データが時間順に並んでいる場合は、もっと根本的な問題が起きます。

時系列データ向けの交差検証

株価、センサログ、需要予測 — 時間軸を持つデータに通常のK-Foldを使ってはいけません。理由は単純で、シャッフルして分割すると、未来のデータが訓練に混ざるからです。7月のデータで学習して6月を予測する、というモデルの精度に意味はありません。実運用では未来のデータは手に入らないからです。

正しいやり方は、訓練期間が常に検証期間より前になるように切ることです。フォールド $k$ について、

  • 訓練:$\{1, 2, \ldots, t_k\}$
  • 検証:$\{t_k + 1, \ldots, t_{k+1}\}$

とし、$t_1 < t_2 < \cdots$ と切れ目を後ろにずらしていきます。これが TimeSeriesSplit の考え方です。

時系列データの分割方式3種。通常のK-Fold(シャッフル)、拡張ウィンドウ、スライディングウィンドウの比較

左が通常のK-Foldです。橙色の検証点が時間軸全体にばらまかれ、その周りは全部青(訓練)になっています。検証したい時点の前後が既知という、実運用ではありえない状況です。中央と右が時系列向けの分割で、青(訓練)が必ず橙(検証)の左側にしかないことが一目で分かります。両者の違いは訓練期間の取り方です。

手法 訓練データ 特徴
拡張ウィンドウ $\{1, \ldots, t_k\}$ 過去の全データを使う。データが多いほど有利だが、古い時期の傾向を引きずる
スライディングウィンドウ $\{t_k – w, \ldots, t_k\}$ 直近 $w$ 点だけを使う。傾向が変わる(分布シフトのある)系列に強い

では、通常のK-Foldを時系列に使うと具体的にどれだけ嘘をつくのか。ランダムウォークに近い系列を40本生成し、(1)シャッフルした通常5分割CV、(2)TimeSeriesSplit、(3)本当に未来の50点で測った誤差、の3つを比べました。

時系列データにおける通常K-FoldとTimeSeriesSplitの比較。左は検証点の両隣が訓練に入っている拡大図、右は3手法の平均二乗誤差

左の拡大図が事故の現場です。橙色の検証点の両隣がほぼ必ず青(訓練)になっています。系列が滑らかにつながっている以上、両隣の値を知っていれば真ん中の値はほとんど当たります。これは予測ではなく内挿であり、未来を当てる能力とは何の関係もありません。

右の棒グラフが結果です。通常5分割CVの平均二乗誤差は 1.10、TimeSeriesSplit は 28.41、そして本当の未来50点での誤差は 31.15 でした。通常CVは真の誤差を 約28分の1に過小評価しています。一方 TimeSeriesSplit は真の誤差の 0.91 倍と、ほぼ正確に言い当てました。この傾向は特殊な系列で起きた偶然ではなく、40本すべてで通常CVがTimeSeriesSplitより小さい誤差を報告しています。

「CVで誤差1.1でした」という報告を信じて本番投入すると、実際には誤差31が返ってきます。エラーもワーニングも一切出ないまま、28倍の見積もり違いが起きるのです。なお金融時系列など自己相関の強いデータでは、訓練と検証の間に数点の空白(ギャップ)を入れる purged CVembargo という工夫も使われます。訓練期間の末尾と検証期間の先頭が近すぎると、そこだけ実質的に情報が漏れるためです。

時間構造による情報漏れを見ました。しかし実は、時系列ですらないデータでも同じ種類の事故が起きます。しかも、こちらのほうがはるかに頻繁に起きています。

データリーク — 交差検証で最も多い失敗

交差検証で最も多い失敗は、$K$ の選び方でも層化の有無でもありません。前処理を交差検証の外でやってしまうことです。

典型的なのが特徴選択です。「5000個の特徴のうち、目的変数との相関が高い上位20個を選んでからCVで評価する」— この手順は一見まっとうですが、決定的に間違っています。特徴選択の段階で全データのラベルを見てしまっているため、その後どれだけ丁寧にCVで分割しても、検証データの情報はすでに特徴の選び方に染み込んでいます。

どれくらい深刻かを、極端な設定で確かめます。特徴量を完全な乱数、ラベルも完全な乱数にします。特徴とラベルの間には真の関係が一切ありません。正しく評価すれば正解率は 0.5(当てずっぽう)になるはずです。

特徴選択のリーク実演。真の関係がゼロのデータで、CVの外・内で特徴選択した場合の正解率と、5000本の特徴の相関係数の分布

30回の反復で測った結果は次のとおりです。

  • (A) CVの外で特徴選択 → CV正解率 0.857(最小 0.780、最大 0.920)
  • (B) CVの内側で特徴選択 → CV正解率 0.520
  • (C) 独立な新規データで検証 → 正解率 0.493

真の関係がゼロのデータから、正解率 85.7% という数字が出ました。リークによる水増しは 33.7 ポイント、当てずっぽうの 0.5 と比べれば +35.7 ポイントです。しかも30回すべてで 0.78 を超えており、「たまたま良い結果が出た」のではなく、この手順は構造的に高い数字を出すことが分かります。正しく内側で選んだ (B) は 0.520 で、真値 0.5 のすぐそばに着地しています。

からくりは右のヒストグラムにあります。100サンプルで相関係数を計算すると、真の相関がゼロでも標本相関は $\pm 0.2$ 程度は普通にばらつきます。それを 5000本も試せば、$|r| > 0.28$ の特徴が20本は必ず見つかるのです。この20本は「たまたまこの100件のラベルと合っていた」だけの特徴であり、CVで分割したところで、検証データのラベルとも当然合っています。選抜の時点で答えを見てしまったからです。

同じ理屈が標準化にも当てはまります。StandardScaler を全データで fit してからCVに渡すと、検証データの平均と分散が訓練側の変換に漏れます。特徴選択ほど劇的ではありませんが、サンプル数が少ないほど無視できません。目的変数を使う前処理(特徴選択、ターゲットエンコーディング、外れ値除去、オーバーサンプリング)は特に危険で、これらをCVの外でやると必ず楽観バイアスが乗ります。

対策は単純です。前処理を含む全工程を1つのパイプラインにまとめ、それをCVに渡すこと。scikit-learn なら make_pipeline(StandardScaler(), SelectKBest(k=20), LogisticRegression()) のようにまとめて cross_val_score に渡せば、fit は各フォールドの訓練部分だけで実行され、検証部分には transform しか適用されません。手で fit_transform を呼んだ瞬間にリークが始まる、と覚えておくとよいでしょう。

前処理によるリークは「情報が漏れる経路」の一例にすぎません。次は、データの構造そのものが漏れの経路になるケースを見ます。

グループK-Fold — 同じ人のデータが両側に入る問題

20人の被験者から1人あたり10回ずつ心電図を記録したデータセットを考えます。合計200件。これを普通に5分割すると何が起きるでしょうか。

同一人物の10件は互いによく似ています。ランダムに分ければ、ある人の記録のうち8件が訓練に、2件が検証に入る、という状態になります。モデルは「Aさんの波形はこういう形」を訓練で覚え、検証でAさんの別の記録を当てます。これは新しい患者を診断する能力ではなく、既知の患者を思い出す能力です。

GroupKFold は、同じグループ(被験者・店舗・患者・機器)のサンプルが訓練と検証に分かれないように分割します。効果を実測しました。

グループK-Foldの効果。左:通常K-Foldで被験者の記録が訓練と検証に割れる様子、右:通常CV・GroupKFold・新規被験者での正解率比較

左のグラフは、通常のK-Foldで1回分割したときの内訳です。20人中18人の記録が訓練側と検証側の両方に分かれてしまっています。この状態で測った正解率は 1.000、つまり完璧です。

しかし GroupKFold で同一被験者を必ず同じ側に固めると、正解率は 0.515 まで落ちます。そして本当に新しい20人でテストすると 0.465。GroupKFold の 0.515 は真値 0.465 のすぐ近くにあり、通常CVの 1.000 は 48.5 ポイントの水増しだったことが分かります。この設定では、ラベルは被験者ごとにランダムに割り振っており、特徴からラベルを当てる手がかりは本来ゼロです。それでも通常CVは満点を返しました。

「正解率100%が出たら喜ぶ前に疑え」というのは、この種の事故が現実に多いからです。医療・音声・センサ・ユーザ行動 — 1つの個体から複数レコードが出るデータでは、GroupKFold(または層化と組み合わせた StratifiedGroupKFold)を使ってください。

ここまでは「分割の切り方」の話でした。最後に、分割の入れ子構造に関わる、もう一段階わかりにくい楽観バイアスを扱います。

ネストした交差検証

ハイパーパラメータを交差検証で選ぶこと自体は正しい手続きです。問題は、選ぶのに使ったCVスコアを、そのままモデルの性能として報告してしまうことです。

たとえばSVMで $C$ と $\gamma$ の組を20通り試し、CVスコアが最大だった組を選んだとします。このとき得られる「最大値」は、20回のノイズ混じりの測定のうち最も高く出たものです。20枚のコインを投げて表が最も多かったコインを選び、その表の出方でコインの実力を語るようなもので、選抜そのものが上振れを生みます。試すパラメータが多いほど、この上振れは大きくなります。

ネストした交差検証(Nested CV) はこれを分離します。外側のCVで性能を測り、その各回の訓練部分の内側でさらにCVを回してハイパーパラメータを選ぶ、という二重構造にします。外側の検証データは、パラメータ選択に一切使われません。

ネストした交差検証。左:外側CVと内側CVの構造、中央:25回反復での非ネストとネストの対応、右:楽観バイアスの分布

$n=120$、20通りのグリッドで25回反復した結果は次のとおりです。

  • 非ネストCV(グリッドの最良スコアをそのまま報告): 0.7260
  • ネストCV(外側で評価): 0.6963
  • 独立な2000点での実測: 0.7098

楽観バイアスは平均 +0.0297、25回中 18回で非ネストがネストを上回りました。最大では +0.100、つまり10ポイントも上振れした回があります。独立テストとの比較を見ると、非ネストは真値より +0.0162 高く、ネストは −0.0135 低く出ています。ネスト側がやや低めに出るのは、外側CVでは訓練データが $4/5$ に減るぶん前節までに見た悲観バイアスが乗るためで、これは織り込み済みの性質です。

実務上の注意点として、ネストCVは外側 $\times$ 内側 $\times$ グリッドサイズの回数だけ学習が走るため、計算量が跳ね上がります。上の例なら $5 \times 3 \times 20 = 300$ 回です。現実的な妥協案は、データを最初に訓練用とテスト用に分け、訓練用の中だけでCVによるパラメータ選択を行い、最後に一度だけテストで測るという方法です。ホールドアウトの不安定さは残りますが、「選択と評価に同じデータを使わない」という核心は守れます。

性能の測り方が固まったところで、最後に「CVの結果をどう読んでモデルを決めるか」という一手を紹介します。

1標準誤差則

CV誤差が最小になるパラメータを選ぶのが基本ですが、CV誤差そのものが推定値であり、誤差を持っています。誤差の範囲内で見分けがつかない複数の候補があるとき、最も単純な(正則化の強い)モデルを選ぶのが 1標準誤差則(one-standard-error rule) です。

手順は次のとおりです。まずCV誤差が最小の点 $\alpha_{\min}$ を見つけ、その点での標準誤差 $\text{SE}$ を計算します($K$ 分割なら $\text{SE} = s/\sqrt{K}$、$s$ はフォールド間の標準偏差)。次に閾値 $\text{CV}(\alpha_{\min}) + \text{SE}$ を引き、この線より下にある中で最も強い正則化を選びます。

1標準誤差則。リッジ回帰のα掃引に対するCV誤差±標準誤差と独立テスト誤差、CV最小のαと1SE則のαの比較

$n=80$、$p=60$ のリッジ回帰での実測です。CV誤差が最小になるのは $\alpha = 16.8$ で、そのときのCV MSE は 18.12、標準誤差は 1.73 でした。閾値は $18.12 + 1.73 = 19.85$ となり、この線を下回る最大の $\alpha$ は 40.1 です。1標準誤差則は 2.4倍強い正則化を選んだことになります。

独立テスト5000点で測った真の誤差は、$\alpha = 16.8$ で 19.66、$\alpha = 40.1$ で 20.83 でした。差は +1.17 で、標準誤差 1.73 より小さい、つまり統計的に区別がつかない範囲です。1標準誤差則は「性能を犠牲にせずに単純さを買う」ルールであって、性能を上げるルールではありません。この図で言えば、点線の真の誤差曲線が $\alpha = 10$ から $\alpha = 50$ の広い範囲でほぼ平らであり、その平坦な谷のどこを選んでも実質的に同じだからこそ、より単純な側を選ぶ意味が出てきます。

なお図をよく見ると、$\alpha$ が小さい領域ではCV誤差(青)が真の誤差(点線)を大きく上回っています。これは前半で見た悲観バイアスで、$n=80$ のうち $8$ 件を抜いて学習すると過学習が悪化するためです。CV曲線の絶対値は真の誤差とずれますが、どの $\alpha$ が良いかという順序はよく保たれている、というのがCVの実用上の性質です。

手法の使い分け

ここまでの内容を、データの性質から選べる形にまとめます。

データの性質 使うべき手法 理由
一般的な回帰・分類 K-Fold($K=5$ or $10$) バイアスと計算量のバランスが良い
分類(特にクラス不均衡) 層化K-Fold 陽性ゼロのフォールドを防ぐ。実測でF1が 0.33→0.49
極端に小規模($N < 50$) LOOCV バイアス最小。線形モデルならハット行列で一撃
時間順序がある TimeSeriesSplit 通常CVは誤差を28分の1に過小評価した
同一個体から複数レコード GroupKFold 通常CVで48.5ポイントの水増しが出た
ハイパーパラメータを選ぶ ネストCV(または訓練/テスト分離) 選択と評価を同じデータでやると +0.03 の上振れ
評価のばらつきが気になる Repeated K-Fold シードを変えて複数回まわし平均する

そして手法選び以前に守るべき原則が1つあります。前処理はすべてパイプラインに入れてCVに渡すこと。特徴選択を外でやるだけで、真の関係がゼロのデータから正解率 0.857 が出てしまうことを、本記事では実測で示しました。

まとめ

本記事では、交差検証(Cross-Validation)を理論と実測の両面から解説しました。

  • 交差検証の本質 — 同じものを何度も測って平均し、評価の分散を下げる操作。ホールドアウトの標準偏差 0.0474 に対し、5分割CVは 0.0089(5.30倍の安定性)
  • K-Fold CV — データをK個に分割し、各フォールドを順番に検証に使う。$K$ が小さいほど訓練データが痩せて誤差を大きく見積もる($K=2$ でバイアス +0.0125)
  • Kの選び方 — $K=5$ か $10$。$K$ を増やしてもフォールド間相関 $\rho$ のせいで分散は下げ止まる(実測でも $K=10$ 以降ほぼ横ばい)
  • LOOCV — バイアスは最小だが $N$ 回の学習が必要(5分割の69倍)。線形モデルならハット行列で1回の学習に圧縮できる(一致を $7.1\times10^{-15}$ の精度で確認)
  • 層化K-Fold — 各フォールドのクラス比を保つ。陽性5%のデータでは通常分割の35.7%のフォールドに陽性が入らなかった
  • 時系列CV — 訓練は常に検証より過去。通常CVは真の誤差を28分の1に見せた
  • グループCV — 同一個体のレコードを分割しない。通常CVは 1.000、真値は 0.465
  • ネストCV — 選択と評価を分ける。同じCVで選んで測ると平均 +0.0297 の上振れ
  • 1標準誤差則 — 誤差範囲内で最も単純なモデルを選ぶ

交差検証の手順自体は数行のコードで書けますが、どこで情報が漏れるかを見抜くことが実務では圧倒的に重要です。エラーが出ないまま数十ポイントの嘘が混入するのがこの分野の怖さであり、逆に言えば、正しく分割できているかを常に問える人は、それだけで信頼できる評価を出せます。

次のステップとして、以下の記事も参考にしてください。

画像なし
ハイパーパラメータチューニングの理論と実践
交差検証を内部で使うグリッドサーチやベイズ最適化など、パラメータ探索の手法を解説します。
画像なし
バイアス-バリアンストレードオフとは?過学習を数式で理解する
本記事で扱ったKの選択の背後にある、バイアスと分散の分解を詳しく解説します。
画像なし
正則化の理論 — L1(Lasso)とL2(Ridge)の幾何学的理解
1標準誤差則で選んだ正則化パラメータが何を制御しているのかを解説します。
画像なし
ブートストラップ法とは?公式が無い統計量の標準誤差と信頼区間を図解で解説
交差検証と並ぶ、リサンプリングによる誤差推定の手法を解説します。