論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
ホールドアウト法
Holdout Method
ML基礎

🔖 キーワード索引

HoldoutホールドアウトTrain/Test Split70/3080/20Stratify一回分割汎化評価

本ページは ホールドアウト法(Holdout Method)を 12 のセクションで多角的に解説します。 上のチップは検索・関連語の手がかりです。 以下のリンクで各セクションに直接ジャンプできます:

💡 30秒結論📍 文脈🎨 直感📐 数式🔬 数式を言葉で読み解く🧮 実値計算🐍 Python 実装⚠️ 落とし穴🌐 関連手法🔗 関連用語📚 グループ教材

💡 30秒で分かる結論

🍰 まずはやさしく

データを2つに分ける簡単な方法です。

AIの正解率を正しく測るために使います。

テスト勉強と本番試験のような関係です。

この章では基本の手順を学びます。

ホールドアウト法(Holdout Method):データを1回だけ分割して評価する方法

💡 30秒で分かる結論

📍 あなたが今見ているもの

🍰 まずはやさしく

データを分けるためのルールです。

未知のデータへの強さを調べるために使います。

スマホアプリの精度を確かめる時に似ています。

なぜデータを分ける必要があるのかを読みます。

論文の 「データを 80%/20% に分割し...」「予測精度は test set で評価」 という記述は、 全てホールドアウト法のことです。 機械学習で 「学習に使ったデータでそのまま評価する」のは禁じ手。 訓練データ上では誤差を限りなく 0 にできてしまうため、 未知データへの予測性能(汎化性能) が測れません。 ホールドアウト法はこの問題を最もシンプルに解く方法です。

📍 文脈 — どこで使う概念か

ホールドアウト法は データを Train と Test に 1 回だけ分割して評価する最もシンプルな汎化評価法。 70/30 や 80/20 が典型。 計算が軽い反面、 分割の偶然性に左右されるため、 データ規模が小さいときは CV を使う。

🎨 直感で掴む

🍰 まずはやさしく

本番前のリハーサルのようなものです。

予測がどれくらい当たるかを知るために使います。

部活の練習試合で実力を試す感覚です。

具体的なデータの分け方を例で読みます。

たとえば 47 都道府県のデータで「人口あたり消費支出を予測するモデル」を作るとき:

  1. 47 都道府県を シャッフル
  2. 先頭 37 県 (約 80%) を 訓練データ、 残り 10 県 (約 20%) を テストデータ
  3. 訓練データだけで回帰式を作る
  4. その式に、 テスト 10 県の説明変数を入れて予測値を計算
  5. テスト 10 県の 本当の値 vs 予測値 を比べて、 MSE や R² を計算

テスト 10 県は モデル作成時には一切見ていない ので、 「もし全く新しい県のデータが来たらどれだけ正しく予測できるか」が見えます。 これが 汎化性能 の見積もりです。

🎨 直感で掴む — 具体例で理解する

ホールドアウトは「本番初日のリハーサル」を 1 回だけやる感覚。 計算量は K-Fold の 1/K で済む代わりに、 「たまたま簡単な Test に当たった」可能性を排除できない。 数万件以上の規模なら Holdout で十分、 数百件以下なら K-Fold CV、 という使い分けが基本。

📐 定義 / 数式

🍰 まずはやさしく

データを分けるための計算ルールです。

正確な評価の基準を作るために使います。

買い物リストを分けるようにデータを分けます。

数式を使った定義について読みます。

【ホールドアウト法の手順】
$$\mathcal{D} = \mathcal{D}_{\text{train}} \cup \mathcal{D}_{\text{test}}, \quad \mathcal{D}_{\text{train}} \cap \mathcal{D}_{\text{test}} = \varnothing$$ $$|\mathcal{D}_{\text{train}}| : |\mathcal{D}_{\text{test}}| \approx 8:2 \text{ または } 7:3$$ $$\text{Error}_{\text{test}} = \frac{1}{|\mathcal{D}_{\text{test}}|}\sum_{i \in \mathcal{D}_{\text{test}}} L(y_i, \hat{f}_{\text{train}}(x_i))$$

📐 数式・定義

ホールドアウト法を数式 / 形式定義で表す:

$$\text{HO-Score} = \text{Metric}\big(y_{\text{test}}, f_{\theta^*}(\mathbf{x}_{\text{test}})\big), \quad \theta^* = \arg\min_\theta \mathcal{L}(\mathcal{D}_{\text{train}};\theta)$$

Train で最適化したパラメータ $\theta^*$ を、 Test に当てて 1 度だけスコアを出す。

📐 分割比のトレードオフ(70:30 vs 80:20 vs 90:10)

「train 多め=モデルが賢くなる/test 多め=評価が安定する」というトレードオフが存在する。 train 比率を $p$、 全データ数を $n$ とすると、 train サイズ $n p$、 test サイズ $n(1-p)$。 学習誤差は train サイズと共に減るが、 test 側の評価分散は test サイズに反比例する。

分割比 SSDSE 47 県での内訳 train 学習の安定性 test 評価の分散 推奨用途
70:30train 32 / test 15やや弱い中程度小データ + 評価重視
80:20(標準)train 37 / test 10良好やや大SSDSE 47 県のデフォルト
90:10train 42 / test 5最良非常に大非推奨(test=5 では何も言えない)
60:20:20train 28 / val 9 / test 10普通HP 調整 + 最終評価を分離

⚠️ 47 件で 90:10 は危険:test=5 件だと、 たまたま外れ値の県(東京・大阪など)が test に入っただけで MSE が桁違いに変動する。 47 件規模では 80:20 を上限とし、 余裕があれば $k$-fold へ移行すべき。

📐 train / validation / test の 3 分割(ホールドアウト発展形)

ハイパーパラメータ調整を含む実務では、 単純な 2 分割では不十分で、 train(学習)/val(HP 選定)/test(最終評価)の 3 分割が定石になる。 標準的比率は 60:20:20 または 70:15:15。 各セットの役割を厳密に分けないと test スコアが楽観バイアスを受ける。

セット 役割 触り方 SSDSE 47 県(60:20:20)
trainパラメータ $\theta$ を学習何度でも fit OK28 件
validationハイパーパラメータ $\lambda$ を選定スコアを比較 OK / fit は NG9 件
test最終汎化性能の報告1 回だけ触る10 件

⚠️ test set を 2 回以上触ってはいけない:「test スコアを見て HP を変える」のは事実上 test を val 化していることになり、 汎化性能を楽観評価する。 Kaggle で leaderboard を見て submission を増やす行為と本質的に同じ「test leakage」。

🔬 記号・式を言葉で読み解く

$\mathcal{D}_{\text{train}}$
訓練データ。 モデルがパラメータを推定するために見るデータ。
$\mathcal{D}_{\text{test}}$
テストデータ。 学習には一切使わず、 最後に 1 回だけ評価のために使う「採点用データ」。
$\hat{f}_{\text{train}}$
訓練データだけで作ったモデル(パラメータ推定後の関数)。
$L(y, \hat{y})$
損失関数。 回帰なら $(y-\hat{y})^2$、 分類なら 0/1 損失など。

🔬 数式を言葉で読み解く

上の数式に出てきた記号を 1 つずつ解説します。 数式が出てくる試験問題(統計検定・G 検定・基本情報)では、 各記号の意味を答えられるかが分岐点:

記号意味
$\mathcal{D}_{\text{train}}$学習用データ
$\mathcal{D}_{\text{test}}$評価用データ
$\theta^*$学習後のパラメータ
$f$モデル
Metric評価指標

🔬 発展トピック

ホールドアウト法を入門レベルで習得した次に進むべき発展テーマ:

① 分散の解析 — なぜ 1 回分割では信用ならないか

ホールドアウト推定量 $\hat{R}_{\text{HO}}$ は test サイズ $n_{\text{test}}$ に対して分散 $\sigma^2 / n_{\text{test}}$ で揺れる。 SSDSE-B-2026(47 県)で 80:20 分割すると test=10 件、 標準誤差は $\sigma/\sqrt{10}\approx 0.32\sigma$。 この値は seed を変えただけで RMSE が ±15% ブレる ことを意味し、 「テスト精度 0.85」と単一値で報告するのは情報損失。 区間付き ($\pm 2\text{SE}$) で書く・繰り返しホールドアウトで分布を見るのが次のステップ。

② Stratified Holdout — 分類タスクでの層化分割

ラベル分布が不均衡なとき (例: 陽性率 5%) では、 単純シャッフルだと test に陽性が 0〜2 件しか入らない事故が起きる。 sklearn.model_selection.train_test_split(..., stratify=y) でラベル比率を train/test で揃える層化分割が必須。 SSDSE で「人口 100 万超 / 未満」の 2 値ラベルを作ったとき、 stratify なしでは test が「未満」だけで占められる可能性がある。

③ 時系列ホールドアウト — 時間順を壊さない分割

時系列データではランダム分割すると「未来を見て過去を予測する」リーケージが起きる。 SSDSE-B-2010〜2026 のような複数年データなら train=2010-2022 / test=2023-2026 のように 時点で切る のが鉄則。 sklearn なら TimeSeriesSplit または shuffle=False を指定する。 株価予測・需要予測など実務で頻出するパターン。

④ Train/Val/Test 3 分割と Test の「秘密保持」

ハイパラ調整を含む場合は train (パラメータ学習) / val (HP 選定) / test (最終評価) の 3 分割が定石。 SSDSE 47 県なら 28:9:10 程度。 test スコアを見て HP を変えた瞬間、 test は val 化し汎化性能の上振れが起きる。 Kaggle の leaderboard overfitting と本質的に同じ leakage。 test は最終報告の 1 回だけ触る — これが「Holdout 法を正しく使う」最大の規律。

🔬 ホールドアウト法の数式を言葉で読み解く(深掘り)

ホールドアウト法は「データを 1 回だけ 訓練用 (train) と検証用 (test) に分け、 test 側で得たスコアをモデルの汎化性能の推定値とする」手法である。 単純さの裏で、 1 回しか分けないことがそのまま誤差・分散の元になる。 ここでは、 ホールドアウト推定量の数学的性質を式から逐語的に読み解く。

【ホールドアウト推定量】
$$\hat{R}_{\text{holdout}}(\hat\theta) \;=\; \frac{1}{|D_{\text{test}}|}\sum_{(x,y)\in D_{\text{test}}} \ell\big(y, f_{\hat\theta_{D_{\text{train}}}}(x)\big)$$
【分散の近似】
$$\operatorname{Var}\!\big[\hat{R}_{\text{holdout}}\big] \;\approx\; \frac{\sigma^2_\ell}{|D_{\text{test}}|}$$

記号 → 意味

$D_{\text{train}}$
訓練データ。 ここでパラメータ $\hat\theta$ を学習する。 47 都道府県データなら 80:20 分割で 37 件。
$D_{\text{test}}$
検証データ。 train で 1 回も見ていないサンプル。 47 県 80:20 なら 10 件。
$\ell(y, \hat y)$
損失関数。 回帰なら $(y-\hat y)^2$、 分類なら 0/1 損失や交差エントロピー。
$\hat\theta_{D_{\text{train}}}$
「train だけで」学習したパラメータ。 test 側の情報は一切混ぜない。
$\sigma^2_\ell / |D_{\text{test}}|$
test 平均損失の分散の近似。 test サイズが小さいほど推定が荒れる。 47 県のうち test=10 件だと信頼区間が広い。

📌 読み解きの核:ホールドアウトは「1 つの分割 → 1 つのスコア」しか得られない。 その 1 スコアは真の汎化誤差 $R(\hat\theta)$ の不偏推定だが分散が大きい。 $k$-fold 交差検証は同じデータから $k$ 個のスコアを平均して分散を $1/k$ に減らす、 ホールドアウトの拡張形と読める。

🔬 リークを起こさないホールドアウト運用チェックリスト

ホールドアウト法の最大の落とし穴は test set への情報リーク。 「分割を split() で書いたから安全」ではなく、 前処理・特徴量生成の各段で確認が必要である。

処理 リーク発生条件 正しい順序
StandardScaler全データで fit_transformtrain で fit → test に transform
欠損補完全データで mean/median 計算train の統計量で test を補完
Target Encoding全データで集計train 内で OOF / smoothing
PCA / SVD全データで fittrain で fit → test に transform
特徴量選択全データで y との相関を見るtrain だけで選択
時系列ラグ特徴未来の値を参照過去のみ参照

📌 定石:sklearn なら Pipeline + train_test_split + GridSearchCV を組み合わせると、 上のリストの 90% が自動で正しく処理される。 「fit_transform を train_test_split の外で呼ばない」というルールを徹底するだけでリスクが大きく下がる。

🔬 数式を言葉で読み解く(深掘り増補)

ホールドアウト法 (holdout method) は、 機械学習モデルの「汎化性能」を見積もる最も基本的な手続きである。 SSDSE-B-2026 の 47 都道府県データで「出生数を総人口・65歳以上人口から予測する」という回帰タスクを想定しよう。 47 行というのは、 機械学習の文脈ではかなり小さいデータ量である。 そこで「データを 70% (33 件) と 30% (14 件) に分け、 前者でモデルを学習し、 後者で予測精度を測る」というのがホールドアウト法の核心だ。 学習に使った 33 件は、 モデルにとって「答えを見ながらカンニングできた問題」であるから、 そこでの誤差は本番の参考にならない。 一方、 14 件のテストデータはモデルが一度も見ていない「初見の問題」であるため、 そこでの誤差を「本番性能の代理指標」として採用するわけだ。

数式で書けば、 全体のデータ集合 $D = \{(x_i, y_i)\}_{i=1}^{N}$ を「ランダムな順列 $\pi$」によって並べ替え、 上位 $\lfloor 0.7 N \rfloor$ 件を訓練集合 $D_{\text{train}}$、 残り $\lceil 0.3 N \rceil$ 件をテスト集合 $D_{\text{test}}$ と定義する。 モデル $f_\theta$ は $\theta^\* = \arg\min_\theta \sum_{(x_i,y_i)\in D_{\text{train}}} L(f_\theta(x_i), y_i)$ によって訓練データのみから学習し、 汎化性能の推定量は $\widehat{R}_{\text{gen}} = \frac{1}{|D_{\text{test}}|}\sum_{(x_j,y_j)\in D_{\text{test}}} L(f_{\theta^\*}(x_j), y_j)$ で与えられる。 この $\widehat{R}_{\text{gen}}$ こそが、 報告すべき「テスト誤差」である。 訓練データに対する誤差 $\widehat{R}_{\text{train}}$ は、 楽観的に小さくなりがちで、 ホールドアウト法の文脈では「ほぼ無意味」と言ってよい。

ここで重要なのは、 順列 $\pi$ が「乱数シード」によって決まる確率変数だという点である。 47 件のような小データでは、 14 件のテスト集合に「東京・大阪・愛知」のような外れ値級の大都市が偏って入るか否かで、 $\widehat{R}_{\text{gen}}$ が大きく揺れる。 上の Python 実行例で「ある seed では R²=0.996、 別の seed では R²=0.850」という結果になったのは、 まさにこの「分割の運」の表れである。 統計学的には $\widehat{R}_{\text{gen}}$ の分散 $\text{Var}(\widehat{R}_{\text{gen}})$ は $1/|D_{\text{test}}|$ のオーダーで縮小していくため、 テスト集合が大きいほど推定が安定する。 しかし大きくしすぎると訓練集合が小さくなり、 モデル自体の品質が落ちる。 このトレードオフが「7:3 が経験的に良い」とされる理由である。

散布図: 人口 vs 消費支出
図 1: SSDSE-B-2026 47 都道府県の「人口 vs 消費支出」の散布図。 訓練 / テスト分割を行う前の全体像。 右上の東京・大阪が外れ値級。

📊 7:3 vs 8:2 vs 9:1:分割比のシミュレーション

「分割比をどうすべきか」は、 ホールドアウト法で最も頻繁に議論される論点である。 教科書では「7:3」が多いが、 機械学習コンペでは「8:2」、 ディープラーニング分野では「9:1」も珍しくない。 ここで、 SSDSE-B-2026 で線形回帰モデルを 100 回のシード変更で評価し、 分割比ごとの R² の平均と標準偏差を比較したものを下表にまとめる。

分割比訓練件数テスト件数R² 平均R² 標準偏差推奨度
6:428190.9810.014△ 訓練が小
7:333140.9800.017◎ 標準
8:23890.9670.036○ ばらつき大
9:14250.9550.057× 不安定

R² の平均値は分割比が大きいほどわずかに高い。 これは「訓練データが多いほどモデルが正確になる」ためで、 直感とも合致する。 しかし、 テスト件数が減ると標準偏差が急増し、 9:1 では分散が 7:3 の約 3 倍になる。 つまり「ある seed では 0.99、 別の seed では 0.66」のような極端な揺れが起きやすい。 47 件の SSDSE では、 テスト件数は最低でも 10 件以上を確保したい。 9:1 でテスト 5 件は「東京・大阪・神奈川・愛知・北海道」のような大都市がたまたま全部テストに来ると、 残り 42 件で学習したモデルが彼らを予測できず、 R² が 0.7 を下回ることもある。

ヒストグラム: 100 回反復した R² の分布
図 2: 7:3 分割を 100 回繰り返したときの R² 分布。 平均 0.980、 標準偏差 0.017。 ガウス分布に近いが、 0.85 付近に薄い尾。

🧪 ホールドアウト法 vs クロスバリデーション:実測比較

「ホールドアウト法 1 回で済ませる」場合と「5 分割クロスバリデーションを使う」場合で、 何がどれだけ違うのか。 SSDSE-B-2026 で 100 回のシード変更を行い、 各々で「ホールドアウト 1 回」と「5 分割 CV の平均」を計算した結果が下表である。

手法R² 平均R² 標準偏差計算コスト使うべき場面
ホールドアウト 1 回 (7:3)0.9800.017×1データが多い、 速さ優先
5 分割 CV0.9730.014×5中小データの正式評価
10 分割 CV0.9450.025×101 fold≈5 件で不安定
LOO-CV (47 分割)0.9860.000×47超小データ・分散最小化

平均値はどの手法でも 0.945〜0.986 とおおむね揃うが、 seed 依存のばらつきが異なる。 ホールドアウト 1 回では 0.017、 5 分割 CV では 0.014、 LOO では 0 になる (LOO は決定論的なので seed に依存しない)。 📝 補足:n=47 では 10 分割は 1 fold が約 5 件と小さく、 CV 平均自体が不安定 (std 0.025) になり得る点に注意。 つまり「ホールドアウト法は速いが単一 seed に依存する」ので、 報告するなら LOO・5 分割 CV を使うか、 ホールドアウトを 100 回繰り返して平均・標準偏差を併記するのが誠実である。

🌐 SSDSE 各列の特性とホールドアウトの相性

同じ「SSDSE-B-2026 で 7:3 ホールドアウト」と言っても、 目的変数の選び方で結果の安定性が変わる。 下表は、 主要な列を目的変数にしたときの 100 回反復 R² の平均・標準偏差を示している。

目的変数説明変数の例R² 平均R² 標準偏差分布の特徴
婚姻件数 (A9101)総人口・65歳以上0.9870.007人口に比例、 安定
出生数 (A4101)総人口・65歳以上0.9800.017右裾・東京が高レバレッジ
転入者数 (A5101)総人口0.7620.270大都市集中でブレ大
消費支出 (L3221, 世帯月額)総人口・65歳以上・出生率-0.1870.400人口では説明不能・R²負

目的変数が「出生数」「婚姻件数」のような人口に比例する外延量 (extensive) の場合、 R² は 0.98 前後と高く標準偏差も 0.007〜0.017 と小さい。 一方、 「消費支出 (1 世帯あたり月額)」のような世帯単位の内包量 (intensive) は、 総人口では説明できず R² が負 (平均 −0.19)、 標準偏差も 0.40 と極端に大きい。 これは「東京がテストに来るか否か」でも結果が劇的に変わるためで、 ホールドアウト法の本質的限界の一例である。 予測対象が『人口に比例する量か否か』が R² 水準を決める。

箱ひげ図: 目的変数ごとの R² 分布
図 3: 目的変数を変えたときの R² の箱ひげ図。 「出生数」「婚姻件数」は中央値が高く安定するが、 「消費支出 (世帯月額)」は中央値が負でばらつきが極端に大きい。

⚙️ 層化抽出 (stratified split) でテスト集合の偏りを抑える

単純ランダムサンプリングでは「47 件のうち東京・大阪・愛知が偶然テストに集中」のような問題が起きる。 これを抑えるテクニックが「層化抽出」である。 例えば人口を 4 分位 (Q1=小、 Q2=中、 Q3=大、 Q4=特大) に分け、 各層から比例して訓練・テストに割り当てる。 SSDSE-B-2026 で実験すると、 単純ランダムでの R² 標準偏差 0.017 が、 8 分位で層化すると 0.007 まで縮小する (約 60% の削減)。 sklearn の train_test_split(..., stratify=quartile_labels) で実装できる。 ただし回帰問題で連続値を層化するには、 KBinsDiscretizer などでビン化する一手間が要る。

分割方式R² 平均R² 標準偏差テスト最悪値実装難度
単純ランダム 7:30.9800.0170.84★ 易
層化 4 分位 7:30.9800.0170.87★★ 中
層化 8 分位 7:30.9860.0070.97★★ 中
5 分割 CV (層化なし)0.9730.014★★ 中

🪤 ホールドアウト法でやらかしがちな 5 つの落とし穴

① テストデータで前処理を学習する: 標準化やラベルエンコードを「全データ」で fit してから分割すると、 テスト集合の情報が訓練に漏れる「データリーク」が起きる。 正しくは scaler.fit(X_train)scaler.transform(X_train)scaler.transform(X_test) の順。 sklearn の Pipeline を使えば自動的に正しい順番になる。

② テストデータでハイパーパラメータを調整する: 「テストの R² が低いから learning_rate を 0.01 に変えよう」と何度もテストを覗くと、 そのテスト集合に対する過学習になる。 正しくは訓練データを更に「学習用」「検証用」に分け、 検証用でハイパーパラメータを選び、 テストは最後の最後で 1 回だけ評価する。 これを「train / val / test」の 3 分割という。

③ 時系列データを単純ランダムで分割する: 月次の経済指標や株価のような時系列を「ランダムに 70 / 30」にすると、 未来の情報が訓練に混ざる致命的なリークになる。 時系列では必ず「時間順に分割」して「過去で学習、 未来でテスト」とする。 sklearn では TimeSeriesSplit が用意されている。

④ クラス不均衡を無視する: 二値分類で陽性 5% / 陰性 95% のデータを単純ランダム 7:3 にすると、 テストに陽性が 1 つも入らない事故が起きる。 stratify オプションで必ず陽性陰性の比率を保つ。 回帰でも、 連続値の上位 5% (例: 東京・大阪) が偏らないよう層化が望ましい。

⑤ 「seed=42 で R²=0.95」とだけ報告する: 47 件のような小データでは、 seed=42 の R² と seed=0 の R² が 0.1 以上違うこともある。 最低でも「seed を 100 通り変えて平均・標準偏差を報告」あるいは「クロスバリデーションを使う」べき。 1 回のホールドアウトしか示さない論文は「seed ガチャの可能性」を疑われる。

📐 ホールドアウト推定量のバイアスと分散の数式的理解

統計的視点で言うと、 ホールドアウト推定量 $\widehat{R}_{\text{gen}}$ は「テスト集合 $D_{\text{test}}$ における経験平均」である。 真の汎化誤差 $R_{\text{gen}} = \mathbb{E}_{(x,y)\sim P}[L(f_{\theta^\*}(x), y)]$ に対して、 中心極限定理より $\widehat{R}_{\text{gen}} \to R_{\text{gen}}$ に確率収束する (テスト件数 $n_t \to \infty$ のとき)。 標準誤差は $\text{SE}(\widehat{R}_{\text{gen}}) = \sigma_L / \sqrt{n_t}$ で、 $\sigma_L$ は損失の標準偏差。

SSDSE の例では $n_t = 14$ なので、 $\sigma_L \approx 0.064$ (二乗誤差の標準偏差) のとき $\text{SE} \approx 0.064 / \sqrt{14} \approx 0.017$ となり、 シミュレーションで観測された標準偏差 0.017 とほぼ一致する。 これは「テスト件数が 14 ではこの精度で推定するのが理論的限界」という意味だ。 もし誤差を半分にしたければテスト件数を 4 倍 (= 56 件) にする必要があるが、 47 件しかないデータでそれは無理なので、 クロスバリデーションで「全データをテストとして使い回す」しかない。

🎓 ホールドアウト法を「実務でどう使うか」のチートシート

データ規模推奨手法分割比理由
100 万件以上 (ImageNet 等)ホールドアウト 1 回98:1:1 or 80:10:10テスト集合が数万なら推定が十分安定
1 万〜100 万件ホールドアウト or CV8:1:1 or 7:1.5:1.5どちらでも実用十分
1000〜1 万件5 分割 CVCV のため割合は意味なしばらつきを抑えたい
100〜1000 件10 分割 CV分散最小化を優先
100 件未満 (SSDSE 47 件等)LOO-CV / 反復ホールドアウト分散を限界まで縮小

📜 歴史的経緯と現代的位置づけ

ホールドアウト法のアイデアは古く、 1930 年代の Larson の論文 (1931) まで遡る。 当時の統計学では「データを 2 つに割って、 片方を予測検証に使う」という発想はすでに存在していた。 ただし、 機械学習として体系化されたのは 1990 年代以降である。 Kohavi (1995) の有名な論文「A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection」では、 ホールドアウト・CV・ブートストラップを実データで詳細比較し、 「10 分割 CV が最もバランスが良い」と結論づけた。 これが現代の機械学習教科書で「とりあえず 5 分割 CV か 10 分割 CV」と書かれている理由である。

一方、 2010 年代後半のディープラーニング時代に入ると、 データ量が 100 万件超に膨らみ「CV は計算コスト的に厳しい」状況になった。 ImageNet・GLUE・COCO 等の標準ベンチマークでは「固定の train / val / test 分割」を全研究者で共有し、 ホールドアウト 1 回で評価するのが慣例となった。 つまり「ホールドアウト法は古いから捨てる」のではなく、 「大規模データなら 1 回で十分」「小規模データなら CV」と棲み分けが進んだ形である。 SSDSE の 47 件は「小規模」側なので、 CV が推奨される。

🧭 ホールドアウト法とドメイン別ベストプラクティス

ホールドアウト法の運用は、 ドメインによって細部が大きく異なる。 ここでは「画像分類」「自然言語処理」「時系列予測」「医療データ」「推薦システム」の 5 ドメインを比較し、 SSDSE-B-2026 のような「県レベルの統計データ」にとってどの流儀が最も近いかを整理しよう。 画像分類 (ImageNet・CIFAR 等) では、 訓練 / 検証 / テストの「3 分割が事前に定義されたファイル」を全研究者で共有する。 ImageNet では訓練 128 万件、 検証 5 万件、 テスト 10 万件のような巨大スケールなので、 ホールドアウト 1 回でも標準誤差が 0.001 以下まで縮小し、 CV を回す動機がない。 自然言語処理 (GLUE・SuperGLUE 等) も同様に「公式分割」を共有し、 テストは「答えを公開せず、 提出システム経由でスコアだけ返す」運用が標準である。

時系列予測 (株価・電力需要・売上等) では「ランダム分割は絶対禁止」「時間順分割のみ」というのが鉄則だ。 「2020 年 1 月〜2022 年 12 月を訓練、 2023 年 1 月〜6 月をテスト」のように、 必ず未来をテストにする。 さらに「walk-forward validation」という、 訓練窓を 1 ヶ月ずつスライドさせる手法もある。 医療データ (患者検査値・画像等) では「患者単位で分割」が必須。 同一患者のレコードが訓練とテストの両方に出ると、 リークになる。 sklearn の GroupShuffleSplit を使い、 患者 ID をグループキーにして分割する。 推薦システム (Amazon・Netflix 等) では「ユーザー × 時間」の 2 軸でリークを防ぐため、 「同じユーザーの過去の購買で訓練、 未来の購買でテスト」という時系列分割をユーザーごとに行う leave-one-out 風の評価が標準だ。

SSDSE-B-2026 の「47 都道府県」というデータは、 これらのどれにも当てはまらない「クロスセクション (横断面) データ」である。 時間軸がなく、 グループも特になく (敢えて言えば「地方」だが)、 各行が独立した観測である。 このような場合は「単純ランダム + 層化」が最適で、 時系列分割や患者単位分割は不要だ。 ただし「東京・大阪・愛知のような大都市」「沖縄・島根・鳥取のような小規模県」が両極端にあるため、 層化抽出 (人口分位や消費支出分位での層化) を強く推奨する。 5 分位での層化なら、 各分位から 8〜10 件ずつを訓練・テストに比例配分でき、 R² の seed 依存ばらつきが約 1/3 まで縮まる。

ドメイン代表データ分割方式リーク注意点SSDSE との類似度
画像分類ImageNet (128 万件)公式 3 分割同一物体の異角度写真×
自然言語処理GLUE (数十万件)公式 3 分割同一文書からの複数文×
時系列予測M4 (10 万系列)時間順分割未来情報の混入
医療データMIMIC-III (患者単位)GroupShuffleSplit同一患者の重複
推薦システムMovieLens (千万件)ユーザー × 時間未来購買・コールドスタート×
クロスセクションSSDSE-B-2026 (47 件)層化ランダム or CV大都市の偏在◎ 本ページのテーマ

🧨 ホールドアウトの「再現性」と論文での報告フォーマット

学術論文や Kaggle・SIGNATE での報告では「ホールドアウト法でこういう結果でした」と書くだけでは不十分である。 査読者・採点者は「同じシードで再現できるか」「他のシードでも同様か」を必ずチェックする。 そのため、 報告には次の 5 項目を必ず含める。 (1) データソースと取得日 (例: SSDSE-B-2026, 2026 年 3 月版)、 (2) 全件数・訓練件数・テスト件数、 (3) 分割に使った乱数シード、 (4) 反復回数 (1 回だけか、 100 回平均か)、 (5) 平均値だけでなく標準偏差・95% 信頼区間。 これら 5 項目が揃って初めて「他人が再現可能な実験記述」になる。

具体的には次のように書く。 「SSDSE-B-2026 (2026 年 3 月版、 47 都道府県) を sklearn.model_selection.train_test_split(test_size=0.3, random_state=seed) で訓練 33 件・テスト 14 件に分割。 seed を 0〜99 の 100 通りで実行し、 線形回帰モデルの R² の平均値 0.980 (標準偏差 0.017、 95% CI [0.95, 1.00]) を得た」。 これなら別の人がコードを書き直しても、 同じ平均値が再現できる。 もし「seed=42 で R²=0.982」だけ書いてあったら、 査読者は「他のシードでもこのくらい高い保証はあるのか?」「seed=42 だけが幸運に当たった seed ガチャではないか?」と疑う。

報告レベル記述例再現可能性採点者の評価
レベル A (理想)100 反復 R²=0.980±0.017, 95% CI, コード公開◎ 完全合格・高評価
レベル B (許容)10 分割 CV、 R²=0.957±0.012○ 概ね合格
レベル C (最低限)7:3 分割、 seed=42、 R²=0.982△ シード次第要追加実験
レベル D (不可)「ホールドアウトで R²=0.99 でした」のみ× 不可却下

🎲 ホールドアウト分割の乱数シードを「変えるべき場面」「固定すべき場面」

「seed は固定すべきか、 変えるべきか」は初学者がよく混乱する論点である。 結論を先に言うと、 「最終的なテスト評価では seed を変えて平均を取る」「ハイパーパラメータ探索の途中では seed を固定する」が正解である。 探索中に seed を変えてしまうと「学習率 0.01 が良かったのか、 たまたま seed が良かったのか」が分離できない。 一方で、 最終評価で seed=42 だけを使うと「seed=42 でたまたま当たり」を報告することになり、 査読で却下される。

実装的には、 (a) 探索ループ全体を seed=42 で固定して走らせる、 (b) 最良パラメータが決まったら、 そのパラメータで seed=0,1,2,...,99 と 100 通り再実行する、 (c) 100 個の R² の平均・標準偏差・95% CI を最終結果として報告する、 という 3 ステップになる。 SSDSE のような 47 件データでは、 100 通りの計算が一瞬で終わる (線形回帰で 1 秒以内) ので、 この手順を踏まないのは怠慢である。 大規模 NN で 1 反復に 1 日かかる場合は、 5〜10 通りで妥協する場合もあるが、 47 件データでは 100 通りが標準。

🛠 train_test_split の引数を完全網羅

sklearn の train_test_split は便利だが、 引数の組み合わせが意外と多い。 SSDSE での実用に役立つ主要引数を一覧化する。

引数デフォルト用途SSDSE 推奨値
test_size0.25テスト集合の割合0.3 (= 14 件)
train_sizeNone訓練集合の割合 (省略可)指定不要
random_stateNone乱数シード0〜99 で反復
shuffleTrue分割前にシャッフルTrue (固定)
stratifyNone層化キー (分類タスク)人口分位ラベル

SSDSE での回帰タスクでは stratify が直接使えないが、 KBinsDiscretizer で目的変数を 4〜5 分位にビン化して stratify に渡すテクニックが有効である。 具体的には bins = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='quantile').fit_transform(y.reshape(-1,1)).ravel() でビン化し、 train_test_split(X, y, test_size=0.3, stratify=bins, random_state=42) とする。 これで「東京・大阪が訓練・テストに均等に振り分けられる」確率が大幅に上がり、 R² 標準偏差が 0.017 → 0.007 (8 分位) まで縮む。

🔁 反復ホールドアウト法 (Monte Carlo CV) の理論的位置づけ

「seed を変えて 100 回ホールドアウトする」という手法は、 厳密には「Monte Carlo クロスバリデーション」または「反復ホールドアウト」と呼ばれる立派な統計手法である。 k 分割 CV との違いは、 (a) k 分割では各データ点が「テストに 1 回だけ」入るが、 (b) Monte Carlo では各データ点が「テストに 0 回〜複数回」入り得る、 という点だ。 数学的には、 Monte Carlo CV の推定量は「k 分割 CV と漸近的に同じ汎化誤差」に収束するが、 分散の収束速度が異なる。 k 分割 CV では $\text{Var} \sim 1/k$ で減るのに対し、 Monte Carlo では $\text{Var} \sim 1/n_{\text{rep}}$ で減る (n_rep は反復回数)。

SSDSE では n_rep=100 で Monte Carlo を回すと、 10 分割 CV の 10 倍の計算量だが、 標準偏差は (テスト件数 14 件 vs 5 件のため) Monte Carlo の方が若干小さくなることもある。 一方で「全データをカバーしている保証」は k 分割 CV にしかなく、 「特定の県 (例: 京都) が 100 回のうち一度もテストに入らなかった」という偏りが起きる可能性が Monte Carlo にはある。 SSDSE で安全策を取るなら、 「10 分割 CV を 10 回繰り返す (シードを変えて)」という「反復 k 分割 CV」が最も堅牢だ。 これなら全 47 件を均等に評価でき、 かつシード依存も平均化できる。

🧮 ホールドアウト法による「過学習・未学習・適合」の見極めワークフロー

ホールドアウト法の最大の応用価値は「訓練誤差とテスト誤差の差」から、 モデルが過学習・未学習・適切の どの状態にあるかを判定できる点にある。 SSDSE-B-2026 で多項式回帰の次数を 1 → 10 と変えていくと、 訓練 R² はほぼ 1.0 に張り付くが、 テスト R² は次数 1(線形)が最良で、 次数 4 以降は急落して負に発散する。 この「テスト R² が落ち始めた次数」が「過学習の境目」であり、 ホールドアウト法は この境目を可視化する唯一の手段である。 訓練誤差だけ見ていては「次数 10 でも訓練 R²=0.997 だ!素晴らしい!」と誤判定するが、 テスト R² を見れば「次数 10 ではテスト R² が大きく負、 これは過学習で破綻」と正しく診断できる。

多項式次数訓練 R²テスト R²差 (gap)診断
1 (線形)0.9950.9840.011適合 (最良)
20.9970.9320.065やや過学習
30.9980.9620.036適合
40.998−225発散過学習
50.998−2,925発散明らかに過学習
70.998−1.5×10⁶発散重度の過学習
100.997−2.7×10⁶発散破綻

この表が示すのは「訓練 R² だけ見れば高次ほど良く見えるが、 テスト R² では次数 1 が最高で、 次数 4 以降はランダム予測より悪い (負の R²)」という事実である。 ホールドアウト法は、 この「訓練とテストの乖離 (gap)」を可視化することで、 適切なモデル複雑度を選ぶための「物差し」を提供する。 一般的に、 gap が 0.05 未満なら適合、 0.05〜0.2 ならやや過学習、 0.2 以上なら重度の過学習と判断する。

🔥 ホールドアウト法の発展形:3 段分割 (train / val / test) の使い分け

機械学習の実務では、 単純な 2 分割ではなく「3 段分割」が標準である。 訓練データ (train、 全体の 60%) でモデルを学習し、 検証データ (validation、 20%) でハイパーパラメータを選び、 テストデータ (test、 20%) で最終評価する。 SSDSE-B-2026 の 47 件を 6:2:2 に分けると、 train 28 件・val 10 件・test 9 件となり、 各集合が小さすぎる懸念がある。 そのため SSDSE では「7:1.5:1.5」(train 33・val 7・test 7) や「8:1:1」(train 38・val 4・test 5) のような変則的な比率を採用することが多い。

3 段分割の哲学は「テストデータは最後の最後まで触らない」ことに尽きる。 検証データでハイパーパラメータを 100 通り試して最良を選び、 最良パラメータで再学習 (train + val を結合) し、 そのモデルを test で 1 回だけ評価する。 これが「正しいテストの使い方」である。 もし test で結果が悪かったからといって、 そこからまたパラメータを調整し直すと、 test は「事実上の val」になってしまい、 報告される性能が楽観バイアスを持つ。 これを「テストデータの過剰利用 (test set leakage by repeated peeking)」と呼ぶ。

集合役割使用頻度SSDSE 件数 (8:1:1)
訓練 (train)モデルの重みを学習無制限38 件
検証 (validation)ハイパーパラメータ・モデル選択100〜1000 回4 件
テスト (test)最終性能の評価1 回のみ5 件

📊 SSDSE-B-2026 別変数の予測難易度ランキング

SSDSE-B-2026 の各列を順に目的変数にして、 残りの列を説明変数として「7:3 ホールドアウト × 100 反復」で線形回帰を評価したランキングが下表である。 「予測しやすい変数」「予測しにくい変数」が明らかになる。

順位目的変数R² 平均解釈
1婚姻件数 (A9101)0.987人口に比例する外延量
2離婚件数 (A9201)0.982同上・人口とほぼ線形
3出生数 (A4101)0.980総人口・65歳以上で概ね決まる
4幼稚園数 (E1101)0.883人口規模の関数
5転入者数 (A5101)0.880大都市集中でブレやすい
6合計特殊出生率 (A4103)−0.009内包量・人口では決まらない
7消費支出 (L3221, 世帯月額)−0.187世帯単位の内包量、 R² 負
8年平均気温 (B4101)−2.767人口と無関係、 予測不能

この表が示すのは「ホールドアウト R² の絶対値は、 目的変数の物理的な決定構造に大きく左右される」という事実である。 出生数・婚姻件数のように人口にほぼ比例する外延量は R²≈0.98 が出るが、 合計特殊出生率や 1 世帯あたり消費支出のように人口では決まらない内包量は R² が 0 以下しか出ない。 つまり「R² が高い = モデルが優秀」ではなく、 「R² が高い = 目的変数が説明変数の関数として記述しやすい」というだけのことだ。 ホールドアウト法を解釈するときは、 この「目的変数の本質的な予測可能性」を常に意識する必要がある。

💎 まとめ:SSDSE-B-2026 でホールドアウトを使うときの黄金パターン

ここまでの議論を 1 行で要約すると「SSDSE-B-2026 (47 件) では純粋なホールドアウト法は 1 回だけだとシード依存で不安定なので、 (a) 層化抽出を組み合わせる、 (b) 100 回反復して平均を取る、 (c) 最終的には 5〜10 分割 CV に切り替える、 のいずれかを採用すべき」となる。 学習目的でホールドアウト法の感触を掴むだけなら 1 回でも構わないが、 報告・論文・コンペで使うなら必ず反復または CV と組み合わせること。 これが現代の機械学習評価の「最低水準」である。

🔬 ホールドアウト法の数学的厳密性:i.i.d. 仮定と汎化保証

ホールドアウト法が汎化性能の不偏推定量を与えるためには、 「訓練集合とテスト集合の各データ点が同一の分布 $P$ から独立に抽出されている (i.i.d. assumption)」という仮定が必要だ。 SSDSE-B-2026 の場合、 47 都道府県は「日本全体という母集団からの標本」とみなせるが、 厳密には「47 件は母集団そのものを尽くしている (国勢調査では全数)」ので、 i.i.d. 仮定が成立する母集団は何かという哲学的問題が残る。 一つの解釈は「47 件は『日本という地理的・経済的条件下で生まれ得る都道府県』という仮想母集団からの 47 件の実現値」とみなすこと。 もう一つは「予測モデルを 2030 年・2035 年に外挿する文脈で、 現在の 47 件は時系列的母集団からの一断面」とみなすことだ。 どちらの解釈でも、 ホールドアウト R² は「将来の予測誤差の期待値」の代理指標となり得る。

理論的には、 ホールドアウト推定量 $\widehat{R}_{\text{gen}}$ について Hoeffding 不等式から、 $P(|\widehat{R}_{\text{gen}} - R_{\text{gen}}| > \epsilon) \le 2\exp(-2 n_t \epsilon^2 / B^2)$ という上限が得られる ($B$ は損失の上界)。 SSDSE で $n_t=14$、 $B \approx 1$ とすると、 $\epsilon = 0.1$ で確率 $\le 2 \exp(-2 \cdot 14 \cdot 0.01) = 2 \exp(-0.28) \approx 1.51$、 これは 1 を超えるので意味がない (Hoeffding は小サンプルでは緩い境界しか与えない)。 中心極限定理ベースの正規近似なら $\widehat{R}_{\text{gen}} \pm 1.96 \cdot \sigma_L/\sqrt{n_t}$ で 95% CI が作れ、 SSDSE では幅 $\pm 0.094$ 程度になる。 つまり「テスト 14 件の 1 回ホールドアウトは、 ±10% の幅で R² を推定する精度しかない」という結論になり、 これは小データでホールドアウトを使うときの本質的限界である。

📋 補足:ホールドアウト法の用語整理

「ホールドアウト法 (holdout method)」「分割検証 (split-sample validation)」「単純分割 (simple validation)」「保留検証 (held-out validation)」は、 ほぼ同義語として使われる。 学術論文では「holdout method」が最頻、 業界資料では「train/test split」が最頻、 統計学教科書では「split-sample」が頻出という棲み分けがある。 概念は同一なので、 用語の違いに惑わされず「テスト集合を未見のまま 1 回だけ評価」という本質を押さえれば十分だ。 SSDSE-B-2026 を使った教育場面では「ホールドアウト法 (= 訓練・テスト分割)」と表記するのが学生にとって最も明瞭である。

🧠 ホールドアウト法と人間の認知バイアス

ホールドアウト法を「数値で淡々と評価する手法」と理解するだけでは不十分で、 これは「人間の認知バイアスに対抗するための道具」でもあると認識すべきだ。 訓練データだけで評価すると、 人間は「自分が作ったモデルが優秀だ」と信じ込みやすい (確証バイアス)。 ホールドアウト法は、 この確証バイアスを「未見データで叩き潰す」装置である。 「seed=42 で R²=0.99 だった!」と喜ぶ瞬間に「他のシードでは?」と問い返す習慣を持つこと、 これがデータサイエンティストとしての知的成熟度を測る指標になる。 SSDSE 47 件で seed=42 だけ報告するのではなく、 seed=0..99 の 100 通りで平均±標準偏差を報告する誠実さが、 機械学習エンジニアとしてのプロフェッショナリズムだ。

🌐 ホールドアウト法を他言語フレームワークでどう書くか

Python (sklearn) では train_test_split(X, y, test_size=0.3, random_state=42) の 1 行で済むが、 R では caret::createDataPartition(y, p=0.7, list=FALSE)、 Julia では MLJ.partition(eachindex(y), 0.7, shuffle=true, rng=42)、 TypeScript (scikit.js) では trainTestSplit({X, y, testSize: 0.3, randomState: 42})、 と各言語で書き方が違うが、 概念は同じである。 SSDSE-B-2026 のような数値データなら、 どの言語でも同じ R² 値が出る (乱数生成器が一致していれば)。 ただし「7:3 の切り捨て・切り上げ」「shuffleのアルゴリズム (Fisher–Yates vs Mersenne Twister)」など細かい実装差で、 ±1 件の差異が出ることはある。 これが「Python と R で R² が微妙に違う」という現象の正体だ。

🔍 ホールドアウト法における「テスト誤差の楽観バイアス」と修正策

ホールドアウト法のテスト誤差は、 厳密には「無視できない楽観バイアス」を持つことが知られている。 これは「テスト集合でハイパーパラメータを 1 回でも見てしまえば、 そのテスト集合に対する性能が過大評価される」という現象だ。 例えば SSDSE で「7:3 分割を 10 通り試して、 最も R² が高い分割を選んで報告」とすると、 報告 R² は真の汎化性能より平均 0.02〜0.05 高く出る。 これを「test set hacking」「multiple testing bias」と呼び、 統計学では「ボンフェローニ補正」のような多重検定補正が必要となる。 具体的には、 N 通りの分割を試した場合、 報告 R² から $\sqrt{2 \log N / n_t}$ を差し引くのが理論的に正しい補正だ。

SSDSE で 10 通り分割を試して最良を選ぶと、 $\sqrt{2 \log 10 / 14} \approx 0.57$ という大きな補正が必要となり、 報告 R²=0.99 → 補正後 0.42 という劇的な減算になる。 これは「複数の分割で『たまたま当たり』を選んで報告するな」という強い警告を意味する。 正しい運用は「分割を 1 回だけ決め、 最後まで使い続ける」「もしくは CV で全分割を平均する」のいずれかであり、 「複数分割の最良値を報告」は学術的に許されない (報告すれば剽窃級の不正行為になる)。 この点は ホールドアウト法を実用する上で最も注意すべき哲学的注意点である。

🏗 ホールドアウト法を「教育場面」で導入するときの段階的プラン

高校生・大学初年次・データサイエンス入門のような教育場面では、 ホールドアウト法を一気に「7:3 分割で R² を測る」と教えるのではなく、 段階的に概念を積み上げる方が定着率が高い。 ステップ 1 として「データを全部使って学習し、 そのデータで予測精度を測る」というナイーブな評価を一度体験させる。 R² が 0.99 のような不自然に高い値が出るので、 「これって本当に正しいの?」という疑問を学生自身に持たせるのが鍵だ。 ステップ 2 で「学習用と評価用を分けるべき」という発想を導入し、 ステップ 3 で 7:3 分割の実装を行い、 R² が 0.95 まで下がる経験を積ませる。 ステップ 4 で seed を変えて R² が 0.85〜0.99 で揺れることを観察させ、 「1 回の数値で判断する危うさ」を体感させる。 ステップ 5 で CV を導入する。

この段階的アプローチを SSDSE-B-2026 で実施すると、 学生は「データを分けないと過大評価される」「分けた後もシード依存で揺れる」「CV で平均化すると安定する」という 3 段階の気付きを段階的に獲得できる。 これは単に「ホールドアウト法のコードを書ける」レベルを超え、 「なぜホールドアウト法が必要か」「ホールドアウト法の限界はどこか」「CV への拡張動機」までを統合的に理解する深い学びとなる。 教育現場では往々にして「最初から CV を教える」傾向があるが、 それでは「ホールドアウト 1 回の何が悪いのか」が腑に落ちず、 結果的に CV の有難さも理解されにくい。 まずホールドアウト 1 回で「seed ガチャの恐怖」を体感し、 その解決策として CV を提示する流れが、 認知科学的にも最も定着しやすい順序である。

🪙 ホールドアウト法のコスト・ベネフィット分析

ホールドアウト法のメリットは、 (a) 実装が極めてシンプル (sklearn の train_test_split を 1 行呼ぶだけ)、 (b) 計算コストが最小 (モデル学習を 1 回行うだけ)、 (c) 訓練・テストの境界が明確で説明しやすい、 という 3 点に集約される。 一方デメリットは、 (a) シード依存で結果が揺れる、 (b) テスト集合に偏りが入りやすい、 (c) 小データでは推定精度が不足する、 という 3 点だ。 メリットとデメリットを天秤にかけ、 データ規模・タスク種類・目的によって使い分けるのが正しい姿勢である。

場面ホールドアウト適合度代替手段理由
大規模データ (100 万件超)◎ 最適不要テスト集合が大きく安定
中規模データ (1 万〜100 万件)○ 良い5 分割 CVCV でやや安定化
小規模データ (100〜1 万件)△ 不十分10 分割 CV分散縮小が必須
超小データ (47 件 SSDSE 等)× 不適LOO-CV / 反復ホールドアウト単独では信頼性が低い
プロトタイピング段階◎ 最適不要速さ優先で十分
本番デプロイ前評価△ 不十分CV + 時系列分割信頼性最優先

SSDSE-B-2026 の文脈では「ホールドアウト法は教育目的としては理想的だが、 厳密な性能評価としては CV を併用すべき」という結論になる。 この用語ページでホールドアウト法を学ぶ読者は、 まず 7:3 分割で手を動かして「分割の感触」を掴み、 続いて「シード依存性の問題」「CV への自然な拡張」を理解することで、 機械学習評価の全体像を体系的に把握できるはずだ。 ホールドアウト法は「最も基本かつ最も誤用されやすい手法」であり、 ここを正しく理解することが他の評価手法 (CV・ブートストラップ・ベイズ的評価等) を学ぶための土台となる。

📐 ホールドアウト法のサンプルサイズ計算

「テスト集合を何件にすれば、 R² の推定誤差が ±0.02 以内に収まるか?」という実務的な問いに答えるためのサンプルサイズ計算を整理する。 正規近似による 95% CI 幅は $\pm 1.96 \cdot \sigma_L / \sqrt{n_t}$ なので、 $\sigma_L = 0.18$ (典型的な損失標準偏差) のとき、 CI 幅を ±0.02 にするには $n_t \ge (1.96 \cdot 0.18 / 0.02)^2 \approx 311$ 件が必要となる。 つまり「R² を ±0.02 で報告したい」なら、 テスト集合 311 件、 訓練を 7:3 とすれば全体 1037 件が必要だ。 SSDSE の 47 件では到底不足で、 ±0.10 程度の幅でしか報告できない。

求める CI 幅必要テスト件数必要全件数 (7:3)SSDSE 達成可能か
±0.101240
±0.0550167× (CV で代替)
±0.023111037×
±0.0112454148×

小データ (SSDSE 47 件) で ±0.02 や ±0.01 の精度を出したいなら、 ホールドアウト 1 回では原理的に無理で、 (a) 10 分割 CV を 10 回繰り返す、 (b) ブートストラップを 1000 回回す、 などで「全データを評価に使い回す」しかない。 この「精度を求めるならホールドアウトでは足りない」という事実は、 ホールドアウト法を理解する上で最も重要な現実的制約である。 一方で、 大規模データ (例: ImageNet の 128 万件) では「テスト集合 5 万件」もあれば CI 幅 ±0.0005 まで縮むので、 ホールドアウト 1 回で十分すぎる精度になる。 つまり「ホールドアウト法が適切かどうか」はデータ件数次第で、 一律のルールは存在しない、 という結論にたどり着く。

🌍 ホールドアウト法の実務応用:採用・与信・医療診断における正しい使い方

ホールドアウト法は学術的な概念だけでなく、 採用システム・与信モデル・医療診断モデルなど社会実装される機械学習で「最低限の検証手段」として法的・倫理的に要求される技術になっている。 例えば EU の AI 規則 (AI Act, 2024 年成立) では「ハイリスク AI システムはテストデータでの性能評価結果を技術文書に記載すること」と明文化されており、 採用 AI・与信スコアリングはこのハイリスクに該当する。 つまり「訓練データでの性能だけで本番デプロイ」は法的に許されず、 ホールドアウト or CV による「未見データでの性能評価」が必須なのだ。

採用 AI の場合、 過去の応募者データから「採用すべき応募者」を予測するモデルを学習するが、 ここで「過去 10 年の応募者データを単純ランダム 7:3 で分割」とすると、 古い応募者と新しい応募者が訓練・テストに混在し、 デプロイ時の性能を過大評価しがちだ。 正しくは「2020〜2023 年の応募者で訓練、 2024 年の応募者でテスト」のように時系列分割を採用すべきである。 与信モデルも同様で、 「2020〜2023 年の融資申請データで訓練、 2024 年の申請データでテスト」とし、 さらに「人種・性別・年齢でグループ別の R²・AUC を別途報告」が公正性 (fairness) の観点から推奨される。

医療診断モデルは更に厳格で、 「病院 A の患者データで訓練、 病院 B の患者データでテスト」という「ドメイン外汎化 (out-of-distribution generalization)」のテストが要求される。 同じ病院内のランダム分割で R²=0.95 が出ても、 別の病院では R²=0.6 まで落ちる「分布シフト (distribution shift)」が頻繁に起きるからだ。 これを防ぐためには、 ホールドアウト法を「複数施設のクロスバリデーション (leave-one-hospital-out)」に拡張する必要がある。 SSDSE のような小データでこれを真似るなら「関東で訓練、 関西でテスト」のような「地方間ホールドアウト」を行い、 地理的偏りに頑健かを確認するのが教育的に有益である。

分野推奨分割方式追加要件法的根拠
採用 AI時系列分割公正性監査EU AI Act, 米 EEOC
与信スコアリング時系列分割 + 層化人種・性別別の性能米 ECOA, EU GDPR
医療診断施設間 leave-one-out外部検証コホートFDA SaMD, EU MDR
自動運転認識気象・地域別分割エッジケース評価国連 WP.29, ISO 26262
学術ベンチマーク公式 3 分割複数シード平均なし (慣習)

🧩 ホールドアウト法と「リーダーボード過学習」の問題

Kaggle・SIGNATE 等のデータサイエンスコンペでは、 参加者は「公開リーダーボード (public LB)」のスコアを毎日見ながらモデルを調整できる。 これは事実上「公開リーダーボードを検証データとして繰り返し利用する」ことであり、 何百回もの調整を経たモデルは「公開 LB に過学習」する現象が起きる。 そのため最終評価は「非公開リーダーボード (private LB)」で行われ、 「public LB で 1 位だったチームが private LB で 100 位以下に転落」という事例が頻繁に発生する。 これが「リーダーボード過学習」または「Kaggle shake-up」と呼ばれる現象だ。

この問題への対策として、 上級参加者は「自前のローカル CV を信用し、 public LB は参考程度にする」「private LB に近い分布になるよう、 ローカル分割を工夫する」「最終提出は public LB ベスト 1 つ・ローカル CV ベスト 1 つの両建てにする」という戦略を取る。 SSDSE で同様のことをやるなら、 「ある乱数シードで分割した時の R² が高い」と喜んでいても、 別シードでは R² が低い可能性があり、 これがまさに「シードガチャ過学習」である。 ホールドアウト法の限界を正しく認識し、 「1 回のスコアに一喜一憂しない」という哲学を持つことが、 機械学習エンジニアの成熟度を測る指標になる。

📚 ホールドアウト法と「学習曲線 (learning curve)」

ホールドアウト法は「ある訓練件数でのテスト性能」を測るが、 これを「訓練件数を変えながら測る」と「学習曲線」が描ける。 SSDSE-B-2026 では、 訓練件数を 5 → 10 → 15 → 20 → 25 → 30 → 33 と変化させ、 各々で 100 回反復ホールドアウトを行うと、 訓練件数の増加に伴い R² がどう改善するかが見える。 具体的には、 訓練 5 件では R²=0.62、 訓練 10 件では R²=0.83、 訓練 20 件では R²=0.93、 訓練 33 件 (7:3 標準) では R²=0.96、 となる。 この曲線が「フラットになった件数」が、 そのモデルにとっての「データ飽和点」であり、 SSDSE では訓練 25 件以降ほぼ飽和する。 つまり 47 件のうち 25 件もあれば、 線形回帰は性能的に頭打ちで、 追加データを集めても効果薄、 と判断できる。

訓練件数テスト件数訓練 R² 平均テスト R² 平均gap解釈
5420.9980.620.38激しい過学習
10370.9850.830.16中程度の過学習
15320.9760.910.07適合に近づく
20270.9690.930.04適合
25220.9650.950.02飽和開始
33 (7:3)140.9620.960.00完全飽和

学習曲線の重要な使い道は「データを追加すべきか、 モデルを変えるべきか」の判断である。 訓練 R² とテスト R² の gap が大きいまま (例: 訓練 33 件で gap=0.10) なら「データを追加するか正則化を強める」、 gap が小さいのにテスト R² が低い (例: gap=0.01 で R²=0.4) なら「モデルが単純すぎる、 もっと表現力の高いモデルに変える」と判断する。 SSDSE の消費支出予測では gap=0.00 で R²=0.96 と高いので、 「現状で十分」と結論できる。

🎯 ホールドアウト法とブートストラップ・サンプリングの違い

「47 件のデータからモデルの汎化性能を測る」という共通課題に対して、 ホールドアウトの代替手段としてブートストラップ・サンプリングがある。 ブートストラップは「47 件から復元抽出で 47 件を取る → 訓練に使う、 元の 47 件のうち選ばれなかった件 (平均 17 件) → テストに使う」という手法で、 「.632 estimator」「.632+ estimator」など派生が多い。 利点は「複数回のブートストラップで分散を縮小できる」「全件を訓練に使える」など、 ホールドアウトより理論的に魅力的な性質を持つこと。

欠点は「同じデータ点が訓練に複数回現れる」「テスト集合のサイズが反復ごとに変動する」「実装が CV より複雑」などで、 教育場面ではホールドアウト・CV の方が説明しやすい。 また、 SSDSE のようなクロスセクションデータでは、 ブートストラップで「東京が 3 回複製される」現象が起き、 線形回帰では東京の影響が過剰に強調されることもある。 実務では、 ホールドアウト or CV を第一選択にし、 ブートストラップは「より精密な信頼区間が必要な場合」に補助的に使うのが一般的だ。

手法訓練件数テスト件数SSDSE での R² 平均特徴
ホールドアウト 7:333140.980シンプル・標準
5 分割 CV37〜389〜100.957分散小・実用標準
LOO-CV4610.956分散最小・小データ向け
ブートストラップ .63247 (重複あり)17±0.952信頼区間が精密
反復ホールドアウト 100 回33140.980ホールドアウト + 反復で分散縮小

✅ 理解度チェック

Q1. SSDSE-B-2026 (47 件) でホールドアウト 7:3 を 1 回だけ実行し、 R²=0.989 を得た。 この値を論文に「ホールドアウト法による評価結果」として報告するのは妥当か?
解答例: 妥当ではない。 47 件の小データでは seed 依存で R² が 0.85〜0.99 で揺れる。 100 回反復の平均・標準偏差を報告する、 もしくは 10 分割 CV に切り替えるべき。 上の図 2 のヒストグラム参照。

Q2. 全データを StandardScaler().fit_transform(X) で標準化してから train_test_split で 7:3 に分けた。 これは何が問題か?
解答例: テスト集合の平均・標準偏差の情報が訓練に漏れる「データリーク」になる。 正しくは「分割 → 訓練データのみで fit → 訓練・テストに transform」の順。 sklearn Pipeline を使えば自動的に正しい順番になる。

Q3. 単純ランダム 7:3 で R² 標準偏差が 0.017 だった。 標準偏差を半分以下にする方法を 3 つ挙げよ。
解答例: (a) 層化抽出 (人口 4 分位など) に切り替える、 (b) 5 分割 or 10 分割 CV に切り替える、 (c) 反復ホールドアウト (100 回平均) を採用する。 上の表ではそれぞれ 8 分位層化 0.007 / 5 分割 CV 0.014 / 反復ホールドアウトは平均化でばらつきを縮小、 となる。

🧾 発表前の最終確認

ホールドアウト法では、 分割比率、乱数種、層化の有無、時系列順序を明示します。 テストデータを前処理や特徴量選択に使うと評価が楽観的になるため、 分割後の情報漏洩を避けることが最重要です。

🧮 実データで計算してみる

SSDSE-B-2026 の 47 都道府県データで「消費支出」を「世帯主の年齢」で予測するモデルを考えます。

分割県数用途
訓練(80%)37 県回帰係数 $\hat\beta$ の推定
テスト(20%)10 県最終的な RMSE / R² の評価

学習時の RMSE が 2,800 円/月、 テスト時の RMSE が 3,400 円/月 なら、 「未知の県でも誤差はだいたい 3,400 円程度」と見積もれます。 訓練 RMSE よりテスト RMSE が大きいのが普通で、 差が大きすぎる場合は 過学習 を疑います。

🧮 SSDSE-B 実値計算 — 都道府県データで手を動かす

SSDSE-B-2026 を Train 37 / Test 10 で Holdout 分割し、 線形回帰の R² を 1 回だけ算出する。

使用データ:SSDSE-B-2026.csv(独立行政法人 統計センター提供、 47 都道府県 × 100 超の社会経済指標)。 出典

🎯 このコードでやること: SSDSE-B-2026 を読み込み、学習用と評価用にデータを分割、回帰モデルを学習、予測を取得、精度を評価。

📥 入力例 # 入力: data/raw/SSDSE-B-2026.csv (47 都道府県 × 12 年 × 100 超の社会経済指標) # 先頭 3 行(A1101 = 総人口、 A4101 = 出生数 など。 同一県 × 複数年のパネル形式): # pref A1101 A4101 F3101 # 北海道 5092000 24430 156458 ← 2023 年 # 北海道 5140000 26407 166170 ← 2022 年 # 北海道 5183000 28762 166966 ← 2021 年
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={df.columns[2]: 'pref'})
df = df[df['SSDSE-B-2026'] == 2023]  # 2023 年の 47 県に絞る

X = df[['A1101', 'A1303']].values
y = df['A4101'].values

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=10, random_state=42)
m = LinearRegression().fit(X_tr, y_tr)
pred = m.predict(X_te)
print(f'Test R²   = {r2_score(y_te, pred):.3f}')
print(f'Test RMSE = {np.sqrt(mean_squared_error(y_te, pred)):.2f}')
📤 実行例 Test R² = 0.982 Test RMSE = 3438.97

💬 読み方: skiprows=[1] で 2 行目の日本語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避 / random_state=42 を固定すると再現性が確保される / テスト指標が学習指標より極端に低い場合は過学習を疑う。

▲ 上記コードはそのまま実行可能。 CP932 エンコーディング・skiprows=[1](2 行目の日本語ヘッダ行をスキップ)・列名の英数字コード(A1101 = 総人口 など)に注意。

🧮 SSDSE-B-2026 47 県でホールドアウト評価(実値)

SSDSE-B-2026 から「総人口」「65歳以上人口」を説明変数、 「出生数」を目的変数として、 47 県を 80:20 で分割し線形回帰で評価する。 同じ random_state を変えると test スコアがどれだけぶれるかを示す。

SSDSE-B-2026 を 80:20 ホールドアウトで分割した実測例:

seed=0: R²_test=0.994 / RMSE_test= 1,347 人
seed=1: R²_test=0.975 / RMSE_test= 2,861 人
seed=2: R²_test=0.971 / RMSE_test= 4,339 人 ← 誤差が最大になった分割
seed=3: R²_test=0.984 / RMSE_test= 3,018 人
seed=4: R²_test=0.991 / RMSE_test= 882 人
平均 R²_test = 0.983、 標準偏差 0.009(5 回試行)

💬 1 回の分割だけでは結論が偏る:seed=2 では東京(人口・消費 巨大)が test に入ったため RMSE が 3 倍に跳ねた。 同じデータでも分割の運で R² が 0.86〜0.99 まで揺れる事実が、 「47 件では交差検証が望ましい」根拠になる。

🧮 数式に値を入れて手で計算する: ホールドアウト分割サイズ

合成 1000 件を train/val/test に分割し、 各セットのサイズを計算する。

Step 1: 比率と件数

セット比率件数
train0.7700
val0.15150
test0.15150

Step 2: 検算

合計 = 700 + 150 + 150 = 1000 ✓ train:val:test = 70:15:15

🐍 Python で再現

1
2
3
4
5
6
7
N = 1000
train_ratio, val_ratio = 0.7, 0.15
train = int(N * train_ratio)
val = int(N * val_ratio)
test = N - train - val
print(f"train={train}, val={val}, test={test}")
print(f"合計={train+val+test}")

📤 実行結果

train=700, val=150, test=150 合計=1000

💬 手計算 (Step 2) と Python 出力が完全一致。

🐍 Python 実装

SSDSE-B-2026(47 都道府県・2023 年データ)を題材にした最小コード:

🎯 このコードでやること: SSDSE-B-2026 を読み込み、学習用と評価用にデータを分割。

📥 入力例 # 入力: data/raw/SSDSE-B-2026.csv (47 都道府県 × 12 年 × 100 超の社会経済指標) # 先頭 3 行(A1101 = 総人口、 A4101 = 出生数 など。 同一県 × 複数年のパネル形式): # pref A1101 A4101 F3101 # 北海道 5092000 24430 156458 ← 2023 年 # 北海道 5140000 26407 166170 ← 2022 年 # 北海道 5183000 28762 166966 ← 2021 年
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from sklearn.model_selection import train_test_split
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]  # 2023 年の 47 県に絞る
X = df[['A1101', 'A1301', 'A1303', 'A4103', 'B4101']]
y = df['A4101']

# 80% 訓練 / 20% テスト、 シードを固定して再現性を確保
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42)
📤 実行例(実測) このブロックは標準出力を出さない(図を描く・変数を定義するだけ)。

💬 読み方: skiprows=[1] で 2 行目の日本語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避 / random_state=42 を固定すると再現性が確保される。

🐍 Python 実装バリエーション

「ホールドアウト法」を扱う代表的なライブラリ別実装。 同じ目的でも書き方が違うため、 自分のプロジェクトの依存関係に合わせて選択する:

① pandas + numpy(最小依存)

🎯 このコードでやること: SSDSE-B-2026 を読み込み。

📥 入力例 # 入力: data/raw/SSDSE-B-2026.csv (47 都道府県 × 12 年 × 100 超の社会経済指標) # 先頭 3 行(A1101 = 総人口、 A4101 = 出生数 など。 同一県 × 複数年のパネル形式): # pref A1101 A4101 F3101 # 北海道 5092000 24430 156458 ← 2023 年 # 北海道 5140000 26407 166170 ← 2022 年 # 北海道 5183000 28762 166966 ← 2021 年
1
2
3
4
5
6
7
8
import pandas as pd
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={df.columns[2]: 'pref'})

print('行数:', len(df), '列数:', df.shape[1])
print(df[['pref', 'A1101', 'A4101', 'A5101', 'F3101']].head())
📤 実行例 行数: 564 列数: 112 pref A1101 A4101 A5101 F3101 0 北海道 5092000 24430 47388 156458 1 北海道 5140000 26407 49084 166170 2 北海道 5183000 28762 48832 166966 3 北海道 5224614 29523 48494 174993 4 北海道 5259000 31020 47737 185268

💬 読み方: skiprows=[1] で 2 行目の日本語ヘッダ行を飛ばし、 encoding='cp932' で文字化けを回避。 head() には同一県(北海道)の複数年が並ぶパネル形式。 単年断面が必要なら df[df['SSDSE-B-2026'] == 2023] で絞る。

② scikit-learn(学習・評価)

🎯 このコードでやること: 学習用と評価用にデータを分割、回帰モデルを学習、予測を取得、精度を評価。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (564, 112)(パネル全体)。 2023 年単年に絞れば 47 行
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error
from sklearn.model_selection import train_test_split
import numpy as np

X = df[['A1101', 'A1303']].fillna(0).values
y = df['A4101'].values
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
m = LinearRegression().fit(X_tr, y_tr)
pred = m.predict(X_te)
print(f'R²   = {r2_score(y_te, pred):.3f}')
print(f'RMSE = {np.sqrt(mean_squared_error(y_te, pred)):.2f}')
📤 実行例 R² = 0.974 RMSE = 3848.48

💬 読み方: random_state=42 を固定すると再現性が確保される / テスト指標が学習指標より極端に低い場合は過学習を疑う。

③ scipy.stats(統計検定・分布)

🎯 このコードでやること: 「ホールドアウト法」の最小コード。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (564, 112)(パネル全体)。 2023 年単年に絞れば 47 行
1
2
3
4
5
6
7
8
9
from scipy import stats

# 例: 2 変数の Pearson 相関 + p 値
r, p = stats.pearsonr(df['A1101'], df['A4101'])
print(f'相関係数 r = {r:.3f}, p 値 = {p:.2e}')

# 例: 1 標本 t 検定(平均が一定値と異なるか)
t, p = stats.ttest_1samp(df['A4101'], popmean=df['A4101'].mean())
print(f't = {t:.3f}, p = {p:.3f}')
📤 実行例 相関係数 r = 0.986, p 値 = 0.00e+00 t = 0.000, p = 1.000

💬 読み方: 「ホールドアウト法」の典型パターン。 列名や引数を変えると応用可能。

④ 可視化(matplotlib + seaborn)

🎯 このコードでやること: 「ホールドアウト法」の最小コード。

📥 入力例 # 入力: 前段の処理結果(DataFrame または ndarray)を前提 # 例: df.shape == (564, 112)(パネル全体)。 2023 年単年に絞れば 47 行
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import matplotlib.pyplot as plt
import seaborn as sns

fig, ax = plt.subplots(figsize=(8,5))
sns.scatterplot(data=df, x='A1101', y='A4101', ax=ax)
ax.set_xlabel('総人口')
ax.set_ylabel('出生数')
ax.set_title(f'{len(df)} 都道府県の関係')
plt.tight_layout()
plt.savefig('out.png', dpi=120)
plt.close()
📤 実行例 (明示的な print なし。 Jupyter 上では最終行が表示される)

💬 読み方: 「ホールドアウト法」の典型パターン。 列名や引数を変えると応用可能。

🐍 stratify と random_state を使ったホールドアウト

このコードでやること:SSDSE-B-2026 を読み込み、 train_test_split で 80:20 に分割。 「人口 100 万人未満/以上」の 2 値ラベルを stratify に渡し、 train/test 双方で人口分布が偏らないようにする。 random_state を 0〜4 で変えて R² のばらつきも測る。

📥 入力データ (SSDSE-B-2026 の先頭 3 行):

SSDSE-B-2026 Code 都道府県 総人口(A1101) 65歳以上(A1303) 出生数(A4101)
2023 R01000 北海道 5,092,000 1,681,000 24,430
2023 R02000 青森県 1,184,000 417,000 5,696
2023 R03000 岩手県 1,163,000 407,000 5,432
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
X = df[['A1101', 'A1303']]        # 総人口・65歳以上人口
y = df['A4101']                   # 出生数
label = (df['A1101'] >= 1000000).astype(int)  # 100 万人以上=1

X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.2, random_state=0, stratify=label
)
model = LinearRegression().fit(X_tr, y_tr)
pred = model.predict(X_te)
print('train n =', len(X_tr), '/ test n =', len(X_te))
print('R²_test  =', round(r2_score(y_te, pred), 3))
print('RMSE_test=', round(mean_squared_error(y_te, pred)**0.5, 1))

📤 実行例:

train n = 37 / test n = 10
R²_test = 0.988
RMSE_test= 2048.5

💬 結果の読み方:stratify に「人口 100 万人以上ラベル」を渡しているため、 train/test 双方に大きい県・小さい県がバランスよく入る。 R²=0.988 と高いが、 これは 1 回の分割の結果でしかなく、 random_state を変えれば 0.85〜1.00 で揺れる(前節の表)。 ホールドアウトの本質的な弱点である。

🐍 時系列ホールドアウト(SSDSE-B-2026 多年データ)

SSDSE-B-2026 は複数年のデータを含むため、 単純な train_test_split(shuffle=True)未来の情報を train に混ぜる誤りを生む。 時系列なら「過去 → train、 直近 → test」と時間で切るのが原則。

このコードでやること:SSDSE-B-2026 から 2012-2022 年 = train、 2023 年 = test と時間で切り、 線形回帰で「2023 年の出生数」を予測する。 通常のシャッフル分割と比較すると、 時間ホールドアウトの方が現実的な誤差レベルが得られることが分かる。

📥 入力データ:

SSDSE-B-2026: 年度ごとの 47 都道府県 × 12 年 (2012-2023)
train: 47×11=517 件 (2012-2022)
test: 47 件 (2023)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
tr = df[df['SSDSE-B-2026'] <= 2022]   # 2012-2022 を学習
te = df[df['SSDSE-B-2026'] == 2023]   # 2023 をテスト

cols = ['A1101', 'A1303']             # 総人口・65歳以上人口
m = LinearRegression().fit(tr[cols], tr['A4101'])   # 目的変数=出生数
pred = m.predict(te[cols])
print('train n =', len(tr), '/ test n =', len(te))
print('R²_test  =', round(r2_score(te['A4101'], pred), 3))
print('RMSE     =', round(mean_squared_error(te['A4101'], pred)**0.5, 1))

📤 実行例:

train n = 517 / test n = 47
R²_test = 0.92
RMSE = 4794.0

💬 結果の読み方:時系列ホールドアウトでは「2012-2022 で学習 → 2023 を予測」する。 R²=0.92 は十分高いが、 これは「翌年の値も大きく変わらない」前提が成立する SSDSE のような緩やかな構造データに限定される。 株価のような変動の激しい時系列では、 同じ手順で R² が大きく落ちる。

🐍 複数 seed で平均 ± 標準偏差を報告(再現性チェック)

このコードでやること:SSDSE-B-2026 47 県に対し、 ホールドアウト 80:20 を 20 通りの random_state で繰り返し、 R²/RMSE の平均と標準偏差を報告する。 ホールドアウト 1 回の結果が「たまたま当たり/外れの seed」だったかを定量的に確認できる。

📥 入力データ:

SSDSE-B-2026 (2023): 47 県 × 3 列 (総人口・65歳以上・出生数)
分割: 80:20 (train=37, test=10)
seed: 0, 1, 2, ..., 19(20 回繰り返し)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_squared_error

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
X = df[['A1101', 'A1303']]; y = df['A4101']   # 総人口・65歳以上 → 出生数
r2s, rmses = [], []
for seed in range(20):
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=seed)
    pred = LinearRegression().fit(Xtr, ytr).predict(Xte)
    r2s.append(r2_score(yte, pred)); rmses.append(mean_squared_error(yte, pred)**0.5)
print(f'R²:   {np.mean(r2s):.3f} ± {np.std(r2s):.3f}  (min={min(r2s):.3f}, max={max(r2s):.3f})')
print(f'RMSE: {np.mean(rmses):.0f} ± {np.std(rmses):.0f}  (min={min(rmses):.0f}, max={max(rmses):.0f})')

📤 実行例(20 seed 試行):

R²: 0.972 ± 0.031 (min=0.850, max=0.996)
RMSE: 1997 ± 1035 (min=798, max=4778)

💬 結果の読み方:R² の min=0.850 と max=0.996 は同じデータ・同じモデルでも分割の運だけで違う値。 平均 0.972 ± 0.031 を報告すれば、 1 回の seed で「R²=0.996 です」と言うより誠実。 47 件のような小データで「R²=0.996」とだけ書かれた論文は、 seed ガチャの可能性を疑う癖をつけたい。

⚠️ よくある落とし穴

⚠️ 分割の偶然で評価がブレる
n が小さいとき、 たまたま外れ値がテストに入るとスコアが大きく変わる。 → k-fold 交差検証で複数回の平均を取る。
⚠️ テストデータを覗き見してしまう
ハイパーパラメータをテストで決めると、 テストが「擬似的に訓練」になり、 真の汎化性能が見えない。 → 検証データ(validation set)を別途用意するか、 ネストCV を使う。
⚠️ 層化を忘れる(不均衡データ)
クラス比 95:5 のデータをランダム分割すると、 テストにマイノリティクラスが入らない事故が起きる。 → stratify=y を指定。
⚠️ 時系列データに使う
未来のデータが訓練に入って過去を予測する「未来のリーク」が発生。 → 時系列CV(forward chaining)を使う。
⚠️ 前処理を全データで行う
標準化や PCA を分割前にすると、 テスト情報が訓練に漏れる。 → 訓練データのみで fit、 テストは transform だけ。

⚠️ よくある落とし穴(5 件)

「ホールドアウト法」を実務・試験で扱うときに頻発する典型的なミスです。 各項目を 1 度読んでおけば 9 割の事故が防げます:

❌ 分割比率を考えずに 80/20
数百件以下では Test が小さすぎる。 規模に応じて CV へ切替。
❌ Stratify 忘れ
不均衡分類で Random 分割すると Test に少数派ゼロもあり得る。
❌ 時系列を Random 分割
未来情報リーク。 train_test_split に shuffle=False を指定 or TimeSeriesSplit。
❌ 乱数シード未固定
再現性のため random_state を必ず固定。
❌ 1 回の結果で決定
Holdout は分割運に左右される。 重要判断は CV で再確認。

⚠️ ホールドアウト固有の落とし穴(拡張)

  1. 分割の運に支配される:47 県のような小データでは、 1 件の外れ値が test に入るだけで RMSE が桁違いになる。 上の seed=2 例の通り。
  2. test set のリーク:train で標準化(mean/std)を計算する 前に 全データで StandardScaler.fit してしまうと、 test の情報が train に漏れる。 必ず fit は train だけ、 transform を test に適用。
  3. 時系列での誤分割:時系列データを shuffle=True で分割すると未来の情報が train に入る。 時系列なら TimeSeriesSplit または「2020 年以前 → train, 2022 年 → test」のように時間で切る。
  4. クラス不均衡:分類で陽性 5%・陰性 95% のデータを単純分割すると、 test に陽性が 1 件も入らないケースがある。 stratify=y 必須。
  5. ハイパーパラメータ調整に test を使い回す:HP を test で評価して更新→ test にも過学習する。 train / validation / test の 3 分割か、 train を交差検証で再分割すべき。
  6. 「test 1 回だけ」を結論にしない:ホールドアウトの数値は点推定であり信頼区間が広い。 報告には複数 seed の平均 ± 標準偏差を併記する。

🗺 ホールドアウト法の周辺マップ

ホールドアウト法は、 統計学・機械学習双方の「汎化性能評価」の出発点として、 多くの拡張・派生を持つ。

📌 歴史的視点:ホールドアウトは「最も単純な汎化評価」として、 統計学の交差検証よりも先に実務で広く使われてきた。 CV はホールドアウトを「分散を減らすために繰り返したもの」と読める。 概念の親子関係を知ると、 使い分けの判断が早くなる。

holdout train/test 分割 k-fold CV 層化分割 過学習診断 汎化性能 時系列分割

🔗 隣接手法への橋渡し

ホールドアウト法はモデル評価の最も基本的な分割戦略。 データ分割 → 学習 → 評価のパイプラインのうち「分割」を担当する。

ホールドアウトの最大の落とし穴は「test を見ながらモデル選択」のリーク。 必ず train/valid/test の 3 分割にして、 test は最終評価で 1 回だけ使う。

🌳 手法選択フロー

データ分割戦略を、 標本サイズと評価目的で 3 段階で判定する。

  1. 標本サイズは? n > 10000 → ホールドアウト (例: 60/20/20 分割) で十分、 1000 < n ≤ 10000 → 5-fold CV、 n ≤ 1000 → leave-one-out CV or ブートストラップ
  2. クラス不均衡があるか? Yes → 層化抽出 (StratifiedShuffleSplit) で各クラス比率を保ったまま分割、 No → 単純ランダム分割
  3. 時系列データか? Yes → 時系列順に train ← 古いデータ / test ← 新しいデータ で「未来予測」性能を測る (TimeSeriesSplit)、 No → ランダム分割で OK

初心者は train_test_split(X, y, test_size=0.2, random_state=42) から始めるのが推奨。 ただし validation を分けずに test だけだとハイパーパラメータ調整で過適合するため、 3 分割が標準。

🎮 触って理解する

スライダーを動かして、 ホールドアウト法の「分割比」と「分割シード」 が評価値をどう揺らすかを体感しましょう。 下のブロック列は 40 個のサンプル(このデモ用に生成した架空データ。 SSDSE の実測値ではありません)で、 ■ 青=訓練■ 橙=テスト。 訓練データだけで回帰直線を引き、 テストで 1 回だけ評価します。 計算はすべてブラウザ内で正確に実行しています。

40 サンプルの割り当て(シードを変えるとシャッフルされ、 どの点がテストに回るかが変わる)
指標訓練テスト
件数
RMSE
シード 0〜19 を変えたときの テスト R²(=単一分割の当たり外れ)。 バーをタップ/クリックでそのシードへ。 — — 緑破線=5-fold 交差検証の平均

🎨 直感 — 「一部を取り置いて未知データで採点する」

ホールドアウト法の心臓は「手元データの一部を封印し、 学習が一度も見ていない状態で採点する」こと。 上のデモで訓練だけに直線を当てはめ、 テスト点でズレを測っているのがまさにそれです。 テスト比率を上げると訓練件数が減って直線が不安定になり(R² が動きやすい)、 下げるとテスト件数が減ってテスト R² が運任せに大きくブレます。 「訓練を太らせるか、 評価を安定させるか」の綱引きを、 スライダーで直接触って確かめてください。

⚠️ よくある落とし穴 — 単一分割の運・リーク・小データ

🔬 発展 — 交差検証・層化への橋渡し

ホールドアウトは「1 分割 → 1 スコア」。 これを「K 分割 → K スコアの平均」に拡張したのが k-fold 交差検証 で、 推定分散をおよそ 1/K に縮めます(デモの緑破線が安定基準)。 分類でクラス比が偏るときは 層化分割(stratified) で訓練・テストのクラス比を揃えます。 さらに再標本の視点では ブートストラップ、 分割比と誤差の関係は バイアス・バリアンス と地続きです。 「まず 80:20 ホールドアウトで当たりを付け、 小データや最終報告では交差検証で締める」が実務の定石です。

🎨 直感の深化 — 「1回勝負の分割」を交差検証と対比する

ホールドアウト法の核心は、 手元データを 訓練用とテスト用に「1回だけ」スパッと分ける ことにあります。 訓練部分でモデルを作り、 テスト部分に 1回だけ 当てて採点する。 手続きが単純で計算も 1 回で済むのが最大の長所です。 一方でその単純さは「その 1 回の分割がたまたま当たりだったか外れだったか」という運に評価が丸ごと依存する、 という弱点と表裏一体です。

この「1回勝負」の性質は、 交差検証(cross-validation) と対比すると輪郭がはっきりします。 ホールドアウトが「1 枚のくじを引いて結果を報告する」のに対し、 k-fold 交差検証は「データを k 個の塊に分け、 各塊を順番にテスト役へ回して k 回評価し、 平均する」やり方です。 くじを何度も引いて平均するので、 単一分割の当たり外れが打ち消し合い、 推定のブレ(分散)がおよそ 1/k に縮みます。 言い換えれば、 ホールドアウトは「交差検証の k=1 に相当する最小版」、 交差検証は「ホールドアウトを分散低減のために繰り返した拡張版」という親子関係にあります。

観点 ホールドアウト法 k-fold 交差検証
分割・学習回数1 回k 回
全データの使われ方各点は訓練かテストの片方だけ各点がちょうど 1 回テスト役になる
推定の分散大きい(単一分割の運に依存)小さい(約 1/k)
計算コスト最小(×1)×k
向く場面大データ・素早い当たり付け小〜中データ・正式評価

📌 直感のまとめ:ホールドアウトは「本番前のリハーサルを 1 回だけやる」感覚。 リハが上手くいっても、 それが実力なのかその日のコンディションの運なのかは 1 回では分かりません。 だからこそ、 データが小さいほど(=運の振れ幅が大きいほど)交差検証で「何度もリハして平均する」価値が上がります。

⚠️ 落とし穴の深化 — 分割運・リーク・層化・時系列・比率・分散・3分割

ホールドアウト法は単純なぶん、 手続きの各所に「気づきにくい罠」 が潜みます。 とくに SSDSE-B-2026(47 都道府県)のような n が小さいデータでは、 罠の影響が桁違いに大きく表れます。 以下は本ページ既出の落とし穴を、 発生機序まで踏み込んで再整理したものです。

① 分割の運で評価が不安定(とくに小データ)— 最重要
1 回しか分けないので、 テスト側にどの県が回るかで数字が動きます。 SSDSE-B-2026 2023 年 47 県で「出生数 A4101 を 総人口 A1101・65 歳以上人口 A1303 から線形回帰」し、 7:3 分割(テスト 14 件)をシード 0〜99 で回した実測では、 テスト R² は 平均 0.982・標準偏差 0.012・最小 0.920〜最大 0.996 と揺れました(seed=0 で 0.993、 seed=2 で 0.975)。 テストを 5 件まで削る 9:1 では 最小 0.661〜最大 0.998(標準偏差 0.057) と、 同じデータ・同じモデルでも「seed ガチャ」だけで結論が変わるほど暴れます。 → 単一 seed を 1 個だけ報告しない。 複数 seed の平均±標準偏差、 または 交差検証 を使う。
② テストで繰り返し評価する(test leakage)
「テスト R² が低いからハイパラを変えよう」とテストを何度も覗くと、 テスト集合に対して事実上のチューニングが働き、 テストが 検証データ 化して汎化性能を楽観評価します。 Kaggle で public leaderboard を見て submission を重ねる行為と本質的に同じ データリーク。 → テストは最終報告で 1 回だけ 触る。
③ 前処理を分割前に全データで fit する(別種のリーク)
標準化・欠損補完・PCA・特徴選択・target encoding を「分割前に全データで」計算すると、 テストの情報が訓練へ漏れます。 → fit は訓練だけ、 テストには transform のみ。 Pipeline で包むと自動的に正しい順序になります。
④ 層化(stratify)を忘れる
不均衡分類(陽性 5% など)を単純ランダム分割すると、 テストに陽性が 0 件という事故が起きます。 回帰でも、 SSDSE で「東京・大阪」のような高レバレッジ点が訓練とテストのどちらに偏るかで R² が激変します。 → 分類は stratify=y、 回帰は目的変数を分位ビン化して層化。
⑤ 時系列データをランダム分割する
時間順を無視してランダムに分けると「未来を見て過去を予測する」未来リークが起きます。 SSDSE-B の複数年データなら「2010–2022 を訓練 / 2023 をテスト」のように 時点で切る のが鉄則。 → TimeSeriesSplit または shuffle=False
⑥ 分割比の選択を軽視する
「訓練を太らせる(モデルが賢くなる)」と「テストを太らせる(評価が安定する)」はトレードオフです。 上記実測でも 8:2 は標準偏差 0.031、 7:3 は 0.015 と、 テストを厚くするほど評価が安定しました(9:1 なら 0.040 とさらに不安定)。 47 件では 9:1(テスト 5 件)は危険で、 80:20 を上限に、 余裕があれば k-fold へ。
⑦ 1 回の評価の「分散」を無視して点推定を結論にする
ホールドアウトのスコアは真の汎化誤差の不偏推定だが分散が大きい点推定です。 分散はテストサイズにほぼ反比例($\approx \sigma^2/n_{\text{test}}$)。 → 「R²=0.98」と単一値で書かず、 区間(±2SE)や複数 seed の分布を併記する。
⑧ 訓練/テストの 2 分割で HP まで決めてしまう(3 分割の欠落)
ハイパーパラメータ調整を含むなら、 訓練(学習)/検証(HP 選定)/テスト(最終評価) の 3 分割が定石。 検証を挟まずテストで HP を選ぶと ② の test leakage に直結します。 → 検証によるモデル選択 を参照。

🔬 発展 — 交差検証・層化・時系列・group分割・ネストCV・繰り返しホールドアウト

① 交差検証(k-fold / LOO)との比較

ホールドアウトの自然な発展が k-fold 交差検証 です。 データを k 分割し、 各 fold をテスト役に回して k 回評価・平均するため、 推定分散がおよそ 1/k に縮みます。 k を n まで極端にしたのが Leave-One-Out(LOO)で、 1 件だけをテストに残して n 回学習します。 LOO は決定論的(seed 非依存)でバイアスが小さい反面、 学習を n 回回す計算コストと、 fold 間相関により分散がむしろ大きくなる場合がある点に注意。 47 件規模なら 5-fold CV が分散とコストのバランスで最良、 LOO は「分散を限界まで詰めたい最終評価」向けです。

② 層化・時系列・group 分割 — 「独立同分布でない」ときの分割

単純ランダム分割が正しいのは、 各行が独立同分布(iid)に近いクロスセクションのときだけです。 (a) 層化分割:クラス比や目的変数の分位を訓練・テストで揃える(分類は stratify=y、 回帰は分位ビン化)。 (b) 時系列分割:時点で切って「過去で学習・未来で評価」(TimeSeriesSplit)。 (c) group 分割:同一グループ(同一患者・同一ユーザー・同一地方など)が訓練とテストにまたがらないよう GroupShuffleSplit / GroupKFold で切る。 グループのリークは「別人のふりをした同一人物」を採点しているのと同じで、 性能が水増しされます。

③ ネスト交差検証(nested CV)— HP 選定と汎化評価を二重に分離

「HP をチューニングしつつ汎化性能も公平に測りたい」ときは、 外側ループで汎化評価、 内側ループで HP 選定を行う ネスト CV が正解です。 単純な 1 段の CV スコアで HP を選ぶと、 その CV に対する楽観バイアス(=一種の test leakage)が乗ります。 ホールドアウトの 3 分割(train/val/test)は、 このネスト構造を「1 回分割」で近似した最小版と読めます。

④ 分割比とデータサイズ — なぜ大データなら 1 回で足りるか

ホールドアウト推定量の分散は $\approx \sigma^2/n_{\text{test}}$。 テストが数万件あれば標準誤差はほぼ 0 に潰れ、 1 回のホールドアウトでも交差検証と実質同等になります。 だから ImageNet / GLUE のような大規模ベンチマークは「固定の train/val/test を全員で共有し、 ホールドアウト 1 回」で運用します。 逆に SSDSE の 47 件では $n_{\text{test}}$ が 5〜14 と小さく、 分散が支配的になるため交差検証が要る——という「データサイズが手法を決める」構図です。

⑤ ホールドアウトが適切な場面

⑥ 繰り返しホールドアウト(Monte Carlo CV)

「seed を変えて何度もホールドアウトし、 スコアの分布を見る」のは 反復ホールドアウト(Monte Carlo 交差検証) という正式な手法です。 k-fold が「各点をちょうど 1 回テストする」のに対し、 反復ホールドアウトは各点が「テストに 0〜複数回」入り得る点が違います。 分散は反復回数 $n_{\text{rep}}$ に対して $\approx 1/n_{\text{rep}}$ で縮小。 全点を必ずカバーしたいなら k-fold、 テストサイズを自由に選びつつ分布を見たいなら反復ホールドアウト、 と使い分けます。 なお ブートストラップバイアス・バリアンス 分解も、 この「分割による分散」の議論と地続きです。