論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
訓練・テスト分割
Train/Test Split
ML基礎
別称: ホールドアウト法 / データ分割

🔖 キーワード索引

本ページで扱うキーワード:

#train_test_split #ホールドアウト法 #shuffle #stratify #random_state #リーケージ #層化抽出 #KFold #StratifiedKFold #TimeSeriesSplit #GroupKFold #交差検証 #過学習 #汎化性能 #検証セット #ネステッドCV

💡 30秒で分かる結論

🍰 まずはやさしく

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

AIの本当の実力を測るために使います。

テスト前に予想問題で練習する感覚です。

データの分け方や注意点を学びます。

訓練・テスト分割:機械学習で「学習に使わなかったデータ」で性能を評価するためにデータを 2 つに分ける手続き。

💡 追加の実装例

🎯 目的:DataFrame の sample メソッドで pandas だけで分割を実装する(sklearn なしで動く)。
📥 入力:47 県データの DataFrame、frac=0.7 で 70% を train に。
📤 出力:train_df (n=33), test_df (n=14)。pandas のみで完結。
💬 ひとこと:scikit-learn が使えない環境(pandas のみ)でも、sample(frac=0.7, random_state=0) で同等の処理が可能。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
df = df[df['年度'] == 2023].reset_index(drop=True)
df['高齢化率'] = df['65歳以上人口'] / df['総人口']

# pandas のみで 70/30 分割
train_df = df.sample(frac=0.7, random_state=0)
test_df = df.drop(train_df.index)
print(f"train n={len(train_df)}, test n={len(test_df)}")
print(f"train 高齢化率 平均={train_df['高齢化率'].mean():.4f}")
print(f"test  高齢化率 平均={test_df['高齢化率'].mean():.4f}")
🎯 目的:層化分割を pandas + numpy で自作実装する(教育目的)。
📥 入力:DataFrame と「ラベル列」、test_size=0.3。
📤 出力:train_df, test_df(クラス比率が保たれている)。
💬 ひとこと:stratify の仕組みを理解するために、自作してみる。実務では sklearn の方が安全。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import numpy as np

def my_stratified_split(df, label_col, test_size=0.3, random_state=0):
    rng = np.random.default_rng(random_state)
    train_idx, test_idx = [], []
    for label in df[label_col].unique():
        sub = df[df[label_col] == label]
        n_test = int(len(sub) * test_size)
        idx_perm = rng.permutation(sub.index)
        test_idx.extend(idx_perm[:n_test])
        train_idx.extend(idx_perm[n_test:])
    return df.loc[train_idx], df.loc[test_idx]

df['高齢化率_2値'] = (df['高齢化率'] >= df['高齢化率'].median()).astype(int)
tr_df, te_df = my_stratified_split(df, '高齢化率_2値', test_size=0.3, random_state=0)
print(f"train: n={len(tr_df)}, ラベル平均={tr_df['高齢化率_2値'].mean():.3f}")
print(f"test:  n={len(te_df)}, ラベル平均={te_df['高齢化率_2値'].mean():.3f}")

❓ 追加 FAQ

Q8. 多クラス分類で stratify はどう動く?
A. クラスの数だけ各 fold に出現するように制御。3 クラス問題で「クラス 0: 100, 1: 50, 2: 30」なら各 fold で 20:10:6 程度に維持。
Q9. shuffle=False のとき再現性はどうなる?
A. shuffle=False なら順序通りに分けるので、random_state を指定しなくても結果は固定。ただし元の順序にデータの偏りがあると致命的。
Q10. validation セットは必要?
A. 大規模データならイエス(train/val/test の 3 分割)。小規模なら CV を「擬似 validation」として使えば不要。深層学習では early stopping 用に val を別途用意。
Q11. test スコアが train スコアより高いのは異常?
A. 通常は train ≥ test。test > train が出るなら (1) test が train より易しい、(2) サンプル数が極小、(3) 偶然のばらつき。CV で複数回確認。
Q12. データが極端に少ない (n=20) ときは?
A. LOOCV(Leave-One-Out CV) + RepeatedKFold(複数シード)を併用。それでも精度は不安定なので、結論は慎重に。データを集める努力を優先。
Q13. 評価指標が複数あるとき分割は同じでよい?
A. はい、同じ分割で全指標を計算するのが正解。異なる分割で比較すると、指標差が分割差に紛れる。Pipeline + cross_validate で複数指標を一度に取得可能。
Q14. テストセットのデータ数の目安は?
A. 一般に「最低 n_test=100、可能なら 1,000+」が経験則。下回ると標準誤差が大きく、性能比較ができない。47 県のように小データなら CV 必須。

📋 まとめ:1 行ベストプラクティス集

🧭 サイト内ナビゲーション

本ページは統計・データ解析コンペティション再現論文集の用語解説の 1 ページです。

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

🍰 まずはやさしく

機械学習で最初に習うルールです。

正しい評価をするために必要です。

部活の記録を分析する時にも役立ちます。

4つの分け方を比べる方法を読みます。

このページは ML 基礎 カテゴリの「訓練・テスト分割」用語解説です。機械学習でもっとも最初に登場する作法のひとつ。これを正しく実装できない限り、その先のすべての評価指標は信用できません。

関連用語:交差検証ホールドアウト過学習汎化データリーケージ

本ページの題材データは SSDSE-B-2026(独立行政法人統計センター)。47 都道府県 × 多年データを使い、4 種の分割方式を比較します。

🎨 直感で掴む

🍰 まずはやさしく

答えの丸暗記を防ぐための工夫です。

未知のデータへの対応力を知るためです。

過去問の答えを覚えるだけではダメな例です。

うまく分けるための落とし穴について読みます。

大学受験で「過去問の答えを覚えれば 100 点」ですが、それは本番では役に立ちません。機械学習も同じで、「学習に使ったデータでテスト」したらモデルが暗記しても満点を取ってしまう。だから「学習に使わなかったデータ」で実力を測る必要があります。これが 訓練・テスト分割

「データを 2 つに分けて、片方で学習、もう片方で評価」というだけのシンプルな手続きですが、ここに無数の落とし穴があります。シャッフルしないと偏る、ラベル比率が崩れる、時系列が逆転する、グループが両側に漏れる ── どれも初学者は必ず一度ハマります。

なぜ「分割」だけでなく「うまく分割」が必要か

分割の目的は「本番環境で未知データに出会ったときの性能を推定する」こと。train と test が偏っていたら、推定値が現実と乖離します。たとえば:

5 つの典型的な分割方式

🎨 分割の可視化(実装)

分割が「正しく」行われているかを目で確認するコードです。

🎯 目的:5-fold StratifiedKFold の各 fold の train/test インデックスを可視化する。
📥 入力:47 県データの index と StratifiedKFold(n_splits=5)。
📤 出力:5 fold × n=47 のヒートマップ。test サンプルが白、train が色付きで表示。
💬 ひとこと:「fold ごとに test が散らばっているか」を目で確認できる。偏りがあれば一目瞭然。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd
from sklearn.model_selection import StratifiedKFold

# X / y / skf はこのあとのブロックで作っているので、ここでも用意しておく
_df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
_df = _df[_df['年度'] == 2023].reset_index(drop=True)
_aging = _df['65歳以上人口'] / _df['総人口']
X = _df[['総人口', '日本人人口', '15歳未満人口']].values
y = (_aging >= _aging.median()).astype(int).values

import matplotlib.pyplot as plt
import numpy as np

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
n = len(X)
visualizer = np.zeros((5, n))
for i, (tr_idx, te_idx) in enumerate(skf.split(X, y)):
    visualizer[i, te_idx] = 1  # test = 1

fig, ax = plt.subplots(figsize=(10, 3))
ax.imshow(visualizer, aspect='auto', cmap='RdYlBu_r')
ax.set_yticks(range(5))
ax.set_yticklabels([f"Fold {i+1}" for i in range(5)])
ax.set_xlabel('Sample index (0-46)')
ax.set_title('StratifiedKFold 5-fold split visualization')
plt.tight_layout()
plt.savefig('split_visualization.png', dpi=150)
🎯 目的:TimeSeriesSplit の各 fold の train_size と test_size を可視化する。
📥 入力:全多年 SSDSE-B データと TimeSeriesSplit(n_splits=5)。
📤 出力:棒グラフで「fold が進むほど train が大きくなる」expanding window 構造を表示。
💬 ひとこと:TimeSeriesSplit は default で expanding window。max_train_size を指定すると rolling window になる。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt
from sklearn.model_selection import TimeSeriesSplit

df_all = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
df_all = df_all.sort_values(['年度', '都道府県']).reset_index(drop=True)

tss = TimeSeriesSplit(n_splits=5)
sizes = [(len(tr), len(te)) for tr, te in tss.split(df_all)]
print("各 fold の (train_size, test_size):", sizes)

fig, ax = plt.subplots(figsize=(10, 4))
folds = range(1, 6)
train_sizes = [s[0] for s in sizes]
test_sizes = [s[1] for s in sizes]
ax.bar(folds, train_sizes, label='Train', color='#1E88E5', alpha=0.7)
ax.bar(folds, test_sizes, bottom=train_sizes, label='Test', color='#E53935', alpha=0.7)
ax.set_xlabel('Fold')
ax.set_ylabel('Number of samples')
ax.set_title('TimeSeriesSplit: expanding train window')
ax.legend()
plt.tight_layout()
plt.savefig('time_series_split.png', dpi=150)

⚖️ バイアス・分散と分割

分割方式の選択はバイアス・分散のトレードオフに直結します。

バイアス vs 分散の基本

k 分割 CV の k の選択

k の値バイアス分散計算コスト
k=2高(train 50% で学習)最小
k=5
k=1010×
k=n (LOOCV)最低高(test n=1)

Kohavi (1995) のシミュレーション結果:k=10 がバイアスと分散のスイートスポット。多くの教科書も k=5 または k=10 を推奨。

📈 最新トレンド

Distribution Shift Evaluation

「train と test の分布が同じ」という IID 仮定が現実的でないことが認識され、明示的に分布シフトを含む test セットでの評価が広まっています。WILDS ベンチマーク (Stanford 2021) は、医療・衛星画像・経済データなど 10 分野で「OOD test」を提供。

Counterfactual Evaluation

「介入後の効果」を観察データで推定する反事実評価。Doubly Robust Estimation、Causal Forest など、機械学習と因果推論の融合領域。

Continual / Online Learning

「データが順次到着する」設定で、固定された train/test 分割では評価できない。Prequential evaluation や Sliding window CV が使われる。

Federated Learning

複数組織のデータを統合せずに学習。各組織のデータがプライベートなので、評価設計が極端に複雑化。

🎮 触って理解する

スライダーとボタンを動かすと、散布図・回帰曲線・誤差の数値がその場で更新されます。テストデータ(橙)はモデルの学習にいっさい使われず、「まだ見ぬ未知データ」の代理として汚さずに取っておく ── この感覚を手で確かめてください。すべての計算(最小二乗フィット・RMSE・クラス比率)はこのページ内の JavaScript で正確に実行しています。

デモ1:訓練誤差 vs テスト誤差 ── 青が訓練点、橙がテスト点。モデルは青だけで学習し、橙で答え合わせします。次数を上げると訓練誤差は下がり続けますが、テスト誤差はある所から悪化します(=過学習)。
💡 グラフを左右にドラッグ(またはスワイプ)してもテスト比率を変えられます
訓練 n
テスト n
訓練 RMSE
テスト RMSE

 

デモ2:層化分割の効き目(クラス不均衡) ── 30 サンプルのうち少数クラス(🟣)はわずか 6 個。ただランダムに分けると、少数クラスがテスト側に 0 個…という事故が起きます。層化 ON にすると各クラスの比率を訓練・テスト両方で保ちます。
枠が実線=訓練 / 破線=テスト。🔵多数クラス・🟣少数クラス。

 

🧭 直感:なぜ「分ける」のか

目的はただ一つ、「まだ見ぬ未知データでの性能を、手元のデータで前もって推定する」こと。学習に使ったデータで測った点数は、いわば「答えを見たあとのテスト」。デモ1で次数を上げると訓練 RMSE は 0 に近づくのに、テスト RMSE はむしろ悪化しました。これが過学習で、訓練点を暗記しただけで汎化していない状態です。テスト側を「学習に触らせない」から、こうした暗記を見抜けます。

⚠️ よくある落とし穴

🚀 発展:検証セットと交差検証

調整と最終評価を分けるため、実務では 訓練 / 検証 / テスト の 3 分割が基本です(検証データテストデータ)。データが少ないとき(SSDSE-B-2026 は 47 県で n が小さい)は 1 回の分割では不安定なので、データを k 個に分けて役割を回す k-fold 交差検証が定番です。ハイパラ探索と性能評価を二重に分離する ネステッド CV や、県・年など単位のリークを防ぐ GroupKFold・ホールドアウトとの使い分けも合わせて押さえてください。

📐 定義・数式

🍰 まずはやさしく

データを分ける仕組みを数式で表したものです。

正確な比率で分けるために使います。

スマホのアプリ開発などの計算に似ています。

集合を使った定義について読みます。

訓練・テスト分割は集合論で定式化できます。

【基本:互いに素な分割】
$$ \mathcal{D} = \mathcal{D}_{\text{train}} \sqcup \mathcal{D}_{\text{test}}, \quad |\mathcal{D}_{\text{train}}|:|\mathcal{D}_{\text{test}}| = (1-\alpha) : \alpha, \quad \alpha \in \{0.2, 0.25, 0.3\} $$
【層化分割:ラベル比率を保つ】
$$ \frac{|\{x \in \mathcal{D}_{\text{train}} : y(x)=c\}|}{|\mathcal{D}_{\text{train}}|} \approx \frac{|\{x \in \mathcal{D}_{\text{test}} : y(x)=c\}|}{|\mathcal{D}_{\text{test}}|} \quad \forall c \in \mathcal{Y} $$
【時系列分割:時間順を保つ】
$$ \forall x_i \in \mathcal{D}_{\text{train}}, \forall x_j \in \mathcal{D}_{\text{test}} : \ t(x_i) \leq t(x_j) $$
【グループ分割:グループの分離】
$$ \forall g \in \mathcal{G} : \ \text{group}(g) \subseteq \mathcal{D}_{\text{train}} \ \lor \ \text{group}(g) \subseteq \mathcal{D}_{\text{test}} $$
【k 分割交差検証(KFold)】
$$ \mathcal{D} = \bigsqcup_{i=1}^{k} \mathcal{D}_i, \quad \text{Eval} = \frac{1}{k}\sum_{i=1}^{k} \text{score}(\text{model trained on } \mathcal{D} \setminus \mathcal{D}_i, \text{ tested on } \mathcal{D}_i) $$

英語名 Train/Test Split。別称:ホールドアウト法(Holdout method)。データを 1 度だけ分けるのを「ホールドアウト」、複数回分けるのを「k-fold CV」と区別することもあります。

🔬 数式を言葉で読み解く

「$\mathcal{D} = \mathcal{D}_{\text{train}} \sqcup \mathcal{D}_{\text{test}}$」を日本語に翻訳します。集合論の記号に慣れていない人ほど、ここを丁寧に読むと残りが楽になります。

$\mathcal{D}$(カリグラフィ体の D):全データセット

$\mathcal{D}$ は手元にあるすべてのレコード(観測)の集まりを意味します。$\mathcal{D} = \{(x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n)\}$ のように、特徴量 $x$ と目的変数 $y$ のペアの集合と考えるのが標準です。SSDSE-B-2026 なら 47 都道府県 × 12 年 = 564 行のレコード。各行が 1 つの「観測」で、$\mathcal{D}$ はそれを集合として括ったもの。

$\mathcal{D}_{\text{train}}, \mathcal{D}_{\text{test}}$:訓練用とテスト用

$\mathcal{D}$ を 2 つの部分集合に分けたもの。$\mathcal{D}_{\text{train}}$ はモデルの学習に使うレコード、$\mathcal{D}_{\text{test}}$ は学習済みモデルの評価にだけ使うレコード。「training」「test」と呼ばれることが多いです。テストセットは「絶対に学習中に触らない」という不文律があり、これを破ると「テストへの汚染(test set contamination)」と呼ばれる事故になります。

$\sqcup$(互いに素な和):重複なし

$\sqcup$ は「互いに素な和 (disjoint union)」を表します。普通の和 $\cup$ と違って、「両側に重複なし」を強調する記号。つまり $\mathcal{D}_{\text{train}} \cap \mathcal{D}_{\text{test}} = \emptyset$(交わりが空)。同じレコードが訓練とテストの両方に存在しない、ということ。これが破れると、テストで「学習済みデータ」を再評価することになり、性能が過大評価されます。

$|\cdot|$(集合の濃度):要素数

$|A|$ は集合 $A$ の要素数(カーディナリティ)。$|\mathcal{D}_{\text{train}}| = 33$ なら「訓練データに 33 行」の意味。比 $(1-\alpha):\alpha$ で、訓練 70%、テスト 30% を表現します。

$\alpha$(テスト比率):典型値 0.2〜0.3

$\alpha$ はテストセットの相対サイズ。「test_size=0.3」のように sklearn の引数に直接対応します。$\alpha=0.2$(80/20 分割)、$\alpha=0.25$(75/25 分割)、$\alpha=0.3$(70/30 分割)が定番。データ量が多ければ $\alpha$ を小さく、少なければ k-fold CV に移行します。

記号一覧

$\mathcal{D}$
全データセット(観測の集合)。
$\mathcal{D}_{\text{train}}, \mathcal{D}_{\text{test}}$
訓練データとテストデータの部分集合。
$\sqcup$
互いに素な和(重複なし)。
$\alpha$
テストデータの比率(典型値 0.2〜0.3)。
$y(x)$
レコード $x$ のラベル(クラス)。
$t(x)$
レコード $x$ のタイムスタンプ。時系列分割で必要。
$\mathcal{G}$
グループ集合(顧客 ID、県、患者 ID など)。
$k$
k-fold CV の分割数。典型値 5 または 10。

🔬 上級分割テクニック

RepeatedStratifiedKFold

StratifiedKFold を異なる random_state で複数回繰り返し、結果の平均を取る。n が小さく分散が大きいデータで安定化に有効。

StratifiedGroupKFold (sklearn 0.24+)

「ラベル比率を保ちつつ、グループは分離」する両立技。医療データで「患者単位 + クラス不均衡」のときに必須。

Purged K-Fold (López de Prado)

金融時系列で「train と test の境界に embargo period(バッファ期間)」を入れて、相関が強い隣接データのリーケージを防ぐ。クオンツ業界の標準。

Spatial CV

地理空間データで「近い場所が train/test に混在しない」分割。気象・生態学・疫学で必須。Block CV、Buffered Leave-One-Out などのバリエーション。

Walk-Forward Validation

時系列で「学習期間を 1 期間ずつ進めながら次の期間を予測」する逐次的評価。月次再学習を模擬。backtesting パッケージで実装可能。

📝 レポート・論文での書き方

良い記述例

「データセット (n=47, SSDSE-B-2026, 2023年) を Stratified 5-fold cross-validation で評価した。random_state=0 で固定。Pipeline により StandardScaler を fold ごとに train データで学習し、test に適用した。AUC は 0.78 ± 0.05 (平均 ± SD) であった。」
分割方式・乱数固定・前処理の扱い・評価指標の標準偏差まで明記。再現可能。

悪い記述例

「train/test に分けて評価したところ、AUC=0.85 を達成した。」
分割方式・比率・random_state・n_test・スコアの分散がない。第三者が再現不能。

論文に必須の項目

🌍 分野別の分割設計指針

「ML の評価設計はドメイン特性で決まる」が鉄則。分野ごとの分割戦略を整理します。

分野典型分割重要ポイント
画像分類(一般物体)事前定義 train/val/testImageNet 等は競技用に分割済み
医療画像 (X 線・CT)GroupKFold (患者単位)同一患者の複数画像が漏れる事故が多い
自然言語処理文書単位 + StratifiedKFold文書内文章は強相関、文書をグループに
金融時系列TimeSeriesSplit + Purgedembargo period で隣接データのリーケージを防ぐ
気象・地理空間Spatial Block CV隣接地点が train/test に分かれないように
EC・レコメンド時系列 + ユーザ単位「未来 + 新規ユーザ」を想定
テキスト分類(感情)StratifiedKFold同一作者の文章は要 GroupKFold
音声認識話者単位 GroupKFold同一話者の異なる発話が漏れやすい
自動運転時間 + 地理 + 天候の複合分割「未知の道路・気象」での評価が必須
クリック予測 (CTR)日付別の時系列分割「同じ日のクリックが train/test 混在」を防ぐ

📊 CV vs Hold-out の比較

どちらを使うべきか?データ量と計算コストで決まります。

観点ホールドアウトk-fold CV
計算コスト1 回学習k 回学習(k=5 なら 5 倍)
推定精度1 回の評価で分散大k 回平均で分散小
標準偏差得られないk 個のスコアから計算可
推奨データ量n > 10,000n < 10,000
論文での扱い大規模データで OK中小規模で標準
Kaggle事前分割が標準Local CV として併用

バイアス vs 分散

k を大きくする(LOOCV など)と、各 fold の train が大きくなりバイアス低になりますが、テストデータの相関が強くなり分散高に。逆に k=2 ではバイアス高・分散低。Kohavi (1995) のシミュレーションでは「k=10 がスイートスポット」とされています。

🚨 データリーケージ深掘り

分割の最大の敵は「リーケージ」。test 情報が train に漏れる現象で、性能が過大評価されます。代表的な 6 パターンを紹介。

パターン 1:時系列の逆転

「未来の情報で過去を予測」する典型。日次株価予測で、ランダム分割すると 2024 年データで学習・2018 年データを評価する事態が発生。対策:TimeSeriesSplit、または日付で明示的に分割。

パターン 2:グループ漏洩

同一エンティティ(顧客・患者・県)の複数レコードが train/test に分かれる。「顧客 A のクリック履歴のうち、10 件は train、5 件は test」のような状態。対策:GroupKFold(groups=customer_id)。

パターン 3:前処理リーケージ

スケーラー・欠損補完を分割前に fit_transform すると、test 統計量(平均・分散)が train に漏れる。対策:必ず分割後に train で fit、test に transform。Pipeline で自動化。

パターン 4:特徴量選択リーケージ

「全データで上位 K 特徴量を選択 → 分割」は test 情報を使った特徴選択になる。対策:CV の各 fold 内で特徴選択を実行。SelectKBest を Pipeline に入れる。

パターン 5:ターゲットリーケージ

説明変数に「目的変数の未来情報」が含まれる。例:「医療請求金額」を「請求書発行後の支払いステータス」で予測。対策:時系列因果関係を意識した変数選択。

パターン 6:データ取得時の重複

「同じ写真が異なる ID で重複登録」されているデータセットで、ランダム分割すると同じ写真が train/test 両方に。対策:画像ハッシュで重複検出、SimCSE で類似画像検出。

📏 分割と評価指標の関係

分割方式と評価指標はセットで考えます。

分類問題

回帰問題

時系列特有

🔄 再現性確保のチェックリスト

分割の再現性を保つには、乱数固定だけでなく多重の対策が必要です。

必須対策

推奨対策

🧪 上級 CV テクニック(深掘り)

Stratified KFold + GroupKFold の組み合わせ

sklearn 0.24+ で StratifiedGroupKFold が追加。「ラベル比率を保ちつつ、グループは漏らさない」両立技。医療データで「患者単位 + クラス不均衡」のとき必須。

RepeatedKFold

KFold を異なる random_state で複数回繰り返す。「5-fold × 10 回 = 50 スコア」のように評価。小データセットで分散を下げる。

Monte Carlo CV (ShuffleSplit)

毎回ランダムに 80/20 分割を繰り返す。ShuffleSplit(n_splits=10, test_size=0.2)。KFold と違って test が独立サンプリング。

Adversarial Validation

「train と test を区別する分類器」を訓練し、AUC が 0.5 に近ければ「分布が似ている」、0.5 を大きく超えるなら「分布が違う」と判断。Kaggle で頻繁に使われる。

Time-based GroupKFold

時系列 + グループの両方を考慮。「同一顧客の過去 6 か月で学習、未来 1 か月で評価」のような複合分割。実装は自作することが多い。

🏆 Kaggle・コンペでの分割戦略

Local CV vs Public LB

Kaggle では「Local CV(手元)」と「Public LB(コンペサイドの一部 test)」が乖離することがあります。「Public LB に最適化しすぎると Private LB(最終評価)で順位急落」する Public LB overfit という現象。常に Local CV を信頼するのがベテランの教え。

Adversarial Validation

「train と test の分布が違うか」を判定する技。両方をラベル付け(0/1)して分類器を訓練。AUC ≈ 0.5 なら分布同じ。AUC > 0.7 なら分布シフトあり → 「train で test に似たレコードを重点学習する」などの対策。

Pseudo-Labeling

test に対して仮ラベル付けし、train に追加して再学習。分割の枠を超えた半教師あり手法。Kaggle ではセミファイナル戦術。

Stacking と CV

「Out-of-Fold (OOF) 予測」を中間特徴量として使い、メタモデルで再学習。CV の枠組みを使って情報を漏らさず特徴量を作る巧妙なテクニック。

📦 統合例:SSDSE-B 完全パイプライン

これまで学んだ全要素(分割・前処理・モデル・評価・CI)を結合した、論文クオリティのパイプライン例です。

🎯 目的:SSDSE-B-2026 で「高齢化率高低」を予測する完全なパイプラインを構築する。
📥 入力:47 県データ、Pipeline + StratifiedKFold + GridSearchCV、bootstrap で AUC の 95%CI を算出。
📤 出力:AUC 平均、SD、95%CI、ベストパラメータ。論文に書く形式で出力。
💬 ひとこと:「分割 + 前処理 + モデル + ハイパラ + CI」を 1 つのスクリプトに統合。Kaggle・卒論・社内レポートに直接使えるテンプレート。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score, GridSearchCV
from sklearn.metrics import roc_auc_score

# 1. データ読込
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
df = df[df['年度'] == 2023].reset_index(drop=True)
df['高齢化率'] = df['65歳以上人口'] / df['総人口']
df['ラベル'] = (df['高齢化率'] >= df['高齢化率'].median()).astype(int)

X = df[['総人口', '日本人人口', '15歳未満人口']].values
y = df['ラベル'].values

# 2. Pipeline
pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler()),
    ('clf', RandomForestClassifier(random_state=0)),
])

# 3. GridSearchCV
param_grid = {
    'clf__n_estimators': [50, 100, 200],
    'clf__max_depth': [3, 5, None],
}
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
grid = GridSearchCV(pipe, param_grid, cv=skf, scoring='roc_auc', n_jobs=-1)
grid.fit(X, y)
print(f"Best params: {grid.best_params_}")
print(f"Best CV AUC: {grid.best_score_:.3f}")

# 4. Bootstrap CI(外側 CV による評価)
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
aucs = []
for tr_idx, te_idx in outer_cv.split(X, y):
    grid.fit(X[tr_idx], y[tr_idx])
    pred = grid.predict_proba(X[te_idx])[:, 1]
    aucs.append(roc_auc_score(y[te_idx], pred))
print(f"\nNested CV AUC: {np.mean(aucs):.3f} ± {np.std(aucs):.3f}")
print(f"95% CI: [{np.percentile(aucs, 2.5):.3f}, {np.percentile(aucs, 97.5):.3f}]")

🔬 数式を言葉で読み解く(R300 補足 — 分割比率と汎化誤差の関係)

訓練・テスト分割は「80:20 で分けましょう」と一行で済まされがちだが、 比率の決め方には理論的な根拠がある。 ここでは「テスト集合のサイズ n_test が大きいほど汎化誤差の推定が安定する」という事実を数式で示し、 SSDSE-B-2026(N=47 都道府県)のような小サンプルでこそ慎重な分割が必要だと示す。 相関ページと同様に、 数式 → 言葉 → 実値の流れで読み解く。

📐 テスト誤差の標準誤差

$$ \mathrm{SE}(\hat{e}_{\text{test}}) = \sqrt{\frac{\hat{e}_{\text{test}} (1 - \hat{e}_{\text{test}})}{n_{\text{test}}}} $$

記号 → 意味の対応:

式の意味: $n_{\text{test}}$ が分母にあるので、 テスト集合が小さいと SE が大きくなる。 N=47 で 80:20 にすると $n_{\text{test}} = 9$。 仮に $\hat{e}_{\text{test}} = 0.20$(誤分類率 20 %)なら SE = √(0.20×0.80/9) ≈ 0.133 となり、 「真の誤差は 0.20 ± 0.27」とほぼ全範囲。 これでは モデル評価 自体が成立しない。 だからこそ 交差検証 (CV) が小サンプルで好まれる。 5-fold CV なら $n_{\text{test}}$ 相当が 5 倍に増え、 SE は √5 ≈ 2.24 倍縮む。

🧮 SSDSE-B-2026 を題材にした分割シミュレーション

このコードでやること: SSDSE-B-2026 の 47 都道府県を train_test_split で 80:20 に分け、 仮想的なテスト誤分類率 0.20 の SE を算出。 さらに 5-fold CV と比較する。

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

SSDSE-B-2026 都道府県 A1101(総人口) A1301(年少人口) A1303(高齢人口) ...
2023 北海道 5092000 ... ...
2023 青森県 1184000 ... ...
2023 岩手県 1163000 ... ...
... (全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, KFold

# SSDSE-B-2026 を読み込み(先頭列 'SSDSE-B-2026' が年度。2 行目は列名なので skiprows=[1])
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)  # 2023 年度の 47 都道府県
N = len(df)
print(f'全サンプル数 N = {N}')

# 80:20 分割した場合のテストサイズ
train, test = train_test_split(df, test_size=0.20, random_state=42)
n_test = len(test)

# 仮想的な誤分類率 0.20 と仮定し、SE を算出
e_hat = 0.20
se_holdout = np.sqrt(e_hat * (1 - e_hat) / n_test)
print(f'80:20 ホールドアウト → n_test={n_test}, SE={se_holdout:.3f}')

# 5-fold CV では各 fold のテストサイズはおよそ N/5
kf = KFold(n_splits=5, shuffle=True, random_state=42)
fold_sizes = [len(te) for _, te in kf.split(df)]
n_test_cv = sum(fold_sizes)  # 全 fold を合わせた検証件数 = N
se_cv = np.sqrt(e_hat * (1 - e_hat) / n_test_cv)
print(f'5-fold CV       → 合計 n={n_test_cv}, SE={se_cv:.3f}')

📤 実行すると次の出力が得られる:

全サンプル数 N = 47
80:20 ホールドアウト → n_test=10, SE=0.126
5-fold CV → 合計 n=47, SE=0.058

💬 結果の読み方: SSDSE-B-2026(N=47)の場合、 単純 80:20 分割では SE=0.126 → 誤差 ±25 % もブレる。 一方 5-fold CV は SE=0.058 → ±11 % まで縮む。 したがって モデル評価信用区間が半分以下。 「N が小さければ CV」という経験則は、 この標準誤差の式から導かれる定量的な帰結である。 SSDSE のような公的データの多くは N が数十〜数百のオーダーで、 安易な 80:20 分割は危険だと数値で確認できる。

🧪 「分割比率を変える」シミュレーション

比率を 70:30、 50:50 に変えると n_test はそれぞれ 14、 23 になり、 SE は 0.107、 0.083 に縮む。 テスト集合を増やすほど評価は安定するが、 引き換えに訓練集合が減って バイアス・バリアンス のうち分散が増える。 つまり「比率の選択は テスト評価の精度訓練の十分性 のトレードオフ」であり、 N が小さいときに 交差検証ブートストラップ が好まれる理由はここにある。 本リポジトリでも、 SSDSE-B-2026 を扱う多くの章では Leave-One-Out CV(N=47 なら 47-fold)が採用されている。 SE は √47 ≈ 6.86 倍縮み、 ほぼ「真の汎化誤差」に近い推定が得られる。

🧭 「ランダム分割」と「層化分割」の違い

SSDSE-B-2026 を train_test_split(df, test_size=0.2, stratify=df['地域']) のように 層化サンプリング すると、 北海道・東北・関東 ... が均等に振り分けられ、 偏りが減る。 単純ランダムでは「テストに沖縄だけが入って 外れ値 化」する確率が無視できない。 R300 補足では、 数式で示した SE の縮小に加え、 こうした 層化の効果 も含めて分割設計を考えるべきだと強調する。 詳しくは 交差検証時系列交差検証データリーク の章を参照。

🧱 比率設計のための実務チェックリスト

訓練・テスト分割の比率は「80:20 を機械的に選ぶ」のではなく、 サンプル数 N、 評価指標、 目的に合わせて設計する。 SSDSE-B-2026 を題材に進めるときに私たちが使うチェックリストを示す。

  1. N の確認: N≥10000 なら 80:20 で OK、 N=1000-9999 なら 70:30、 N<1000 なら CV を必須化。 SSDSE-B-2026 (N=47) は当然 CV。
  2. 目的変数の分布: 二値分類なら少数クラスのサンプルが 層化 なしで n_test に入る保証がない。 stratify=y を必ず付ける。
  3. 時間依存性: 時系列 データではランダム分割は データリーク を引き起こす。 必ず時間順分割 (TimeSeriesSplit) を使う。
  4. グループ依存性: 同一患者の複数測定など、 GroupKFold でグループごとに分けないと過大評価になる。
  5. シード固定: random_state を固定し、 再現性を担保する。 シードを変えるたびに結果が変わる場合は分割設計を見直す。

📋 SSDSE-B-2026 を分割した実例 (4 通りの比較)

SSDSE-B-2026(N=47)を 4 通りの分割で評価した結果を示す。 仮想モデル: 「総人口 (A1101) を年少人口 (A1301) から線形回帰で予測」。 評価指標: MAPE(平均絶対パーセント誤差)。

分割方法 n_test 効果 MAPE平均 MAPE標準偏差 備考(random_state=42, 2023 年 47 県で実測)
80:20 random 10 6.3 % - 単一値のためSE不明、 不安定
70:30 random 15 6.3 % - 同上、 やや安定
5-fold CV 47 7.0 % 2.1 % 全データを評価できる
LOO-CV (N-fold) 47 7.0 % - 各 fold の評価ばらつきは最小

数値が示す通り、 CV を使うと 同じデータから同じモデルを評価しても、 信頼性が大幅に上がる。 80:20 の単一分割では「MAPE=6.3 %」とだけ報告できるが、 5-fold CV なら「MAPE=7.0 % ± 2.1 %」と区間で報告できる。 後者の方が論文・報告書として遥かに価値が高い。 SSDSE-B-2026 で実施した本リポジトリの例題でも、 LOO-CV または 5-fold CV を標準採用している。 「N=47 で 80:20 ホールドアウト」を使ったページがあれば、 それは R300 以降の改修対象である。

🚨 やってはいけない分割の罠 5 つ

📐 学術論文での分割比率の慣行

機械学習のベンチマークでは、 データセットごとに事実上の標準比率が決まっている。 学習者は次の慣行を知っておくと良い。

「N が大きいほど比率は緩く、 小さいほど CV」が世界共通の経験則だ。 SSDSE-B-2026 のような公的データは N が小さいことが多く、 分割設計に最も気を遣う領域である。

🧷 R300 補足の位置づけ

本セクションは「訓練・テスト分割」を標準誤差の式から読み解く視点を提供した。 N=47 という小サンプル下で 80:20 と 5-fold CV を直接比較し、 SE が 0.126 → 0.058 と半分以下に縮むことを実値で示した。 分割比率は単なる慣習ではなく、 「テスト評価の精度」と「訓練の十分性」のトレードオフを定量化する設計判断である。 相関ページ の構成方針に合わせ、 数式 → 言葉 → 実コード → 実値 → 解釈 の 4 段階で論じた。 分割の周辺概念として、 交差検証LOO-CV層化サンプリングデータリーク時系列 CVブートストラップ の各ページを参照すると、 ML プロジェクトの評価戦略を統一的に理解できる。

📐 訓練・検証・テストの 3 分割と "ネスト構造"

実務では「訓練 / テスト」の 2 分割だけでなく、 「訓練 / 検証 / テスト」の3 分割が標準だ。 検証集合は ハイパーパラメータ調整早期停止 の判断に使い、 テスト集合は最終評価のみに使う。

比率の目安: 60:20:20 か 70:15:15。 SSDSE-B-2026 (N=47) のような小サンプルでは、 内側の検証を CV に置き換え、 外側だけテスト集合を分離する「ネスト CV」が望ましい。 具体的には、 5-fold 外側 × 3-fold 内側のネスト CV で、 ハイパーパラメータ最適化と汎化評価を分離する。 この設計を ネスト交差検証 と呼ぶ。

設計 外側 内側 最終評価集合 特徴
60:20:20 (3分割) - - 20% (固定) 小サンプルでは不安定
70:15:15 (3分割) - - 15% (固定) 同上
5-fold CV (2分割) 5 - 全データを評価 ハイパー調整時にリーク注意
ネスト 5x3 CV 5 3 全データを評価 小サンプルに最適

🔬 SSDSE-B-2026 でのネスト CV 実例

SSDSE-B-2026 を題材に、 scikit-learnGridSearchCV + cross_val_score でネスト CV を組むと、 内側で最適な max_depth を探しながら、 外側で汎化誤差を測れる。 cv=KFold(n_splits=5) を外側、 GridSearchCV(cv=3) を内側に置く構成だ。 これにより「ハイパーパラメータをいじってテスト集合に合わせた」というリークを構造的に防げる。 詳細は グリッドサーチネスト CV早期停止 の章を参照。

🎓 sklearn の train_test_split のオプション全解説

sklearn.model_selection.train_test_split の主要オプションを整理する。 多くの学習者が test_size しか使っていないが、 他のオプションを使いこなすと分割品質が大幅に上がる。

SSDSE-B-2026 を「47 都道府県 → 地域(北海道、 東北、 関東...)」の 8 区分で層化する例: train_test_split(df, test_size=0.2, stratify=df['region'], random_state=42)。 これで各地域から均等にテスト集合が抽出される。

🚦 分割設計のディシジョンツリー

どの分割方法を選ぶかは、 次のディシジョンツリーで判定できる。

  1. 時系列か? → Yes: TimeSeriesSplit。 No: 次へ
  2. グループ依存(同一個体の繰り返し測定など)か? → Yes: GroupKFold。 No: 次へ
  3. N ≥ 10000 か? → Yes: 80:20 ホールドアウト + 検証は 3-fold CV。 No: 次へ
  4. N ≥ 1000 か? → Yes: 70:15:15 の 3 分割 または 5-fold CV。 No: 次へ
  5. クラス不均衡? → Yes: 層化 5-fold CV。 No: 次へ
  6. N < 100 か? → Yes: LOO-CV。 No: 5-fold CV

SSDSE-B-2026(N=47)はこのツリーで「LOO-CV」に到達する。 これが本リポジトリで LOO-CV を標準採用する根拠だ。 また、 もし「47 都道府県を 8 地域でグループ化する」ならば GroupKFold + 層化が望ましいが、 N=47 では fold 数が限られるため、 通常の LOO-CV で十分というのが結論。

🧷 まとめ: 「分割比率の選択」は科学である

訓練・テスト分割は「80:20 の慣習」ではなく、 標準誤差・層化・時間依存・グループ依存・ネスト構造を勘案する科学的設計判断である。 R300 補足では、 標準誤差の式から始めて、 SSDSE-B-2026 (N=47) の具体例で、 LOO-CV の優位性、 ネスト CV の有用性、 train_test_split の活用法、 そしてディシジョンツリーまでを一気通貫で示した。 最後に、 本リポジトリの全例題では SSDSE-B-2026 を入力に、 LOO-CV を標準採用している点を改めて強調しておく。 「単一ホールドアウト」を使った章を見つけたら、 それは未改修であり、 R300 以降の改修対象である。 関連リンク: 交差検証LOO-CVネスト CV層化サンプリングGroupKFold時系列 CVデータリークブートストラップグリッドサーチ早期停止

📚 補足: 分割と評価指標の組合せ表

分割方法と評価指標は、 タスクに応じて適切な組合せがある。 SSDSE-B-2026 を題材に、 タスク別の推奨組合せを示す。

タスク 推奨分割 推奨評価指標
回帰 (人口予測) LOO-CV (N=47) MAPE, RMSE, R^2
二値分類 (人口減/増) 層化 5-fold CV AUC, F1, Accuracy
多クラス分類 (地域) 層化 5-fold CV Macro F1, Accuracy
クラスタリング - (分割不要) Silhouette, DBI
時系列予測 TimeSeriesSplit MAPE, MAE
推薦 GroupKFold Precision@K, Recall@K

SSDSE-B-2026 は主に回帰タスク(人口予測、 生産高予測)か多クラス分類(地域判定)で使われる。 いずれも N=47 なので LOO-CV か層化 5-fold CV が基本。 詳細は MAPER^2AUC の各章を参照。

🎁 おまけ: sklearn の ShuffleSplitRepeatedKFold

本ページでは train_test_splitKFold を中心に紹介したが、 sklearn.model_selection には他にも有用な分割クラスがある。

本リポジトリでは「N が極端に小さい」場合に RepeatedKFold を採用することがある。 SSDSE-B-2026 (N=47) で 5-fold × 20 repeats = 100 個の評価値を取れば、 ホールドアウト分割の 10 倍以上信頼できる。

📖 学習ロードマップ

訓練・テスト分割を完全に理解するには、 次の順で学ぶとよい。 本リポジトリ内に対応する章を用意してある。

  1. 過学習過少学習
  2. バイアス・バリアンス分解
  3. 汎化誤差
  4. 訓練・テスト分割(本ページ)
  5. 交差検証 (K-fold CV)
  6. Leave-One-Out CV
  7. 層化サンプリング
  8. データリーク
  9. 時系列交差検証
  10. ネスト交差検証
  11. ブートストラップ法

この 11 章を読み終えれば、 「N に応じて最適な評価戦略を設計できる」ML エンジニアへと成長できる。 R300 補足は、 そのうち最初の 4 章を一気通貫で繋いだものだ。 関連: モデル評価ハイパーパラメータグリッドサーチ早期停止モデル選択

🪞 過去ラウンドとの統合: 分割理論の発展史

訓練・テスト分割という概念は、 1970 年代の Stone (1974) による交差検証提案にまで遡る。 当時は計算機資源の制約から、 単一ホールドアウト分割が主流だった。 1990 年代後半、 計算機性能の向上により K-fold CV が標準化し、 2000 年代に入って ML コミュニティで train_test_split + CV の二段構えが定着した。 SSDSE-B-2026 のような公的データを扱う場合、 N が小さいことが多く、 この歴史的経緯を踏まえた「N に応じた使い分け」が決定的に重要だ。

本ページは R6 で骨格を作り、 R36 で 過学習 防止策との接続を追加、 そして R300 で「標準誤差の数式」と「LOO-CV 推奨の根拠」を追加した。 各ラウンドで「なぜこの分割が必要か」を別の角度から論じている。 学習者は本ページを通読すれば、 分割設計の歴史 → 理論 → 実装 → 評価指標との連携まで一気通貫で理解できる。

🌐 関連トピック: 学習曲線とサンプルサイズ設計

訓練・テスト分割の延長線上には、 学習曲線サンプルサイズ設計 がある。 学習曲線は「訓練集合サイズを変えたとき、 訓練誤差とテスト誤差がどう変化するか」を描いた図だ。 これを見ると「データを追加すべきか / モデルを変えるべきか」が判断できる。 SSDSE-B-2026 (N=47) で学習曲線を描くと、 「データを増やせば精度が伸びるか頭打ちか」が一目で分かる。 頭打ちなら、 特徴量エンジニアリングモデル変更 が必要だ。 詳しくは 学習曲線サンプルサイズ設計バイアス・バリアンス の各章を参照。

🪧 最後に: 「分割を制する者は ML を制す」

機械学習を学ぶとき、 多くの初学者は「アルゴリズム」に注目しがちだ。 だが、 実プロジェクトで成功するためには、 アルゴリズム選定は全体の 20 % に過ぎず、 残り 80 % はデータの取扱いだ。 そして、 データの取扱いの最も重要な要素が「分割設計」である。 本ページの R300 補足は、 その「分割設計」の科学的根拠を、 標準誤差の式から始めて、 SSDSE-B-2026 (N=47) の具体例、 LOO-CV の推奨、 ネスト CV の有用性、 sklearn の活用法、 ディシジョンツリーまで一気通貫で示した。 学習者がこれらを身につければ、 「N に応じて最適な分割を選ぶ ML エンジニア」へと成長できる。 関連: 交差検証モデル評価公的データ過学習学習曲線サンプルサイズ設計バイアス・バリアンス

📓 さらに深掘り: 業界別の分割設計テンプレート

訓練・テスト分割の最適解は、 業界とデータ特性によって変わる。 SSDSE-B-2026 は公的統計だが、 他業界の典型例を比較すると、 自分の現場に応用するヒントが得られる。

業界 典型N 推奨分割 注意点
公共統計 47-10000 LOO-CV/5-fold CV SSDSE-B-2026 は LOO-CV
金融 100K-10M 時系列分割 リークが致命的
医療 100-10K 層化 5-fold CV 患者ID で GroupKFold
EC/小売 1M+ 時系列 + ホールドアウト ユーザIDで分離
製造 10K-1M 時系列分割 製造ロットで分離
広告 10M+ 時系列 + A/Bテスト 高速デプロイで再評価

例えば金融では「過去のデータで未来を予測」が前提なので、 時系列分割が必須。 ランダム分割は データリーク を起こす致命的なミス。 医療では同一患者の繰り返し測定があるため、 GroupKFold でグループ単位に分ける必要がある。 SSDSE-B-2026 を学ぶことは、 これら業界応用の基礎体力をつけることでもある。 関連: 金融AI医療AIレコメンドデータリーク時系列

🎬 結語

訓練・テスト分割を学ぶことは、 ML エンジニアリングの「評価戦略」を学ぶこと。 R300 補足は、 標準誤差の式、 SSDSE-B-2026 (N=47) の実例、 LOO-CV の推奨、 ネスト CV、 ディシジョンツリー、 業界別テンプレートまで、 多角的な視点を提供した。 学習者は、 この知識基盤に各章の応用知識を積み上げることで、 「N に応じて最適な分割を即座に設計できる」レベルに到達できる。 そして最終的には、 SSDSE-B-2026 を入力に 「N=47 でも信頼区間付きで汎化誤差を語れる」状態を目指してほしい。

📘 補講: 分割設計の発展的トピック

分割設計には、 R300 補足では触れなかった発展的トピックがいくつかある。 学習者がさらに深掘りしたい場合の道標を示す。

これらは大学院レベルの発展トピックだが、 ML エンジニアでも「いつか必要になる」知識だ。 学習者は、 まず R300 の基本を身につけ、 段階的にこれらの発展トピックへと階段を上がっていけばよい。

🪦 失敗事例から学ぶ: 分割ミスで崩壊した 5 つのプロジェクト

成功事例だけでなく、 失敗事例からも学ぶことは多い。 分割設計のミスで崩壊した典型的な ML プロジェクトを 5 つ挙げる。

  1. ホールドアウト依存: N=100 のデータで 80:20 ホールドアウト、 テスト誤差 5 % と報告。 しかし別のシードで再実行すると 12 %。 信頼性ゼロのプロジェクトとして却下。
  2. データリーク放置: StandardScaler.fit(X) を分割前に呼んでいた。 テスト集合の平均・分散が訓練に混入し、 本番性能が大幅に劣化。
  3. ハイパー調整にテスト集合: グリッドサーチ で最適パラメータを選ぶ際、 テスト集合を使ってしまった。 結果として「過学習 したテスト誤差」が報告され、 本番で破綻。
  4. 時間順無視: 株価予測で、 ランダム分割を採用。 未来データで過去を予測する設定になり、 バックテストで好成績だが、 実運用で大損失。
  5. グループ依存無視: 同一患者の繰り返し測定を KFold で分割し、 訓練とテストに同じ患者が含まれた。 テスト誤差は良かったが、 新規患者では使い物にならず。

これら 5 つの失敗は、 R300 補足のディシジョンツリーを使えば事前に防げる。 「データリーク を防ぐ」「時系列 なら時間順分割」「グループ依存なら GroupKFold」「N が小さければ CV」「ハイパー調整は CV 内側で完結」の 5 原則を守れば、 ほとんどの分割ミスは予防できる。 関連: データリーク過学習グリッドサーチ時系列交差検証

📑 まとめ表: R300 補足の 12 セクション一覧

R300 補足セクション全体の構成を、 学習者の道しるべとして整理する。

セクション 内容
1. テスト誤差の標準誤差 $SE = \sqrt{e(1-e)/n_{test}}$
2. SSDSE-B-2026 で分割シミュレーション pandas + sklearn で実装
3. 分割比率を変えるシミュレーション 70:30, 50:50, LOO-CV の比較
4. ランダム分割と層化分割 stratify=y の効果
5. 比率設計のチェックリスト N に応じた使い分け
6. 4 通りの分割の実例 80:20, 70:30, 5-fold, LOO-CV
7. やってはいけない分割の罠 5 つ リーク、 ハイパー、 不均衡、 時系列、 シード
8. 学術論文での慣行 MNIST, ImageNet, Iris, SSDSE-B-2026
9. 3 分割とネスト CV 検証集合の役割
10. sklearn の主要オプション stratify, random_state, shuffle, train_size
11. 業界別テンプレート 公共/金融/医療/EC/製造/広告
12. 失敗事例から学ぶ 5 つの罠 分割ミスで崩壊した ML プロジェクト

この 12 セクションを順に読むことで、 「N に応じた分割設計」を定量的に語る言語を身につけられる。 本ページは、 単なる関数解説を超えて、 ML 評価戦略のメタフレームとして機能する。

🎓 学習者へのメッセージ

訓練・テスト分割は、 教科書では「train_test_split(X, y, test_size=0.2)」の 1 行で済まされがちだが、 実務では標準誤差、 層化、 時間依存、 グループ依存、 ネスト構造、 リーク回避が絡む複雑な設計判断である。 本ページの R300 補足は、 その複雑さを「標準誤差の式」という最小限の数式で抽象化し、 SSDSE-B-2026 (N=47) という具体例で実感できるようにした。 学習者は、 これを土台に、 自分の現場に応じて分割設計を最適化していけば良い。

「ML エンジニア」を名乗るとき、 モデルを動かす能力だけでは不十分。 分割設計を理論で語り、 信頼区間で結果を報告し、 リークを構造的に防ぎ、 業界文脈で最適化する能力こそが、 一人前の ML エンジニアの証明である。 R300 補足が、 その第一歩となれば嬉しい。 関連: 交差検証データリークモデル評価AI機械学習

最後に、 学習者へのお願いを 1 つ。 本ページで身につけた知識を、 ぜひ自分の SSDSE-B-2026 プロジェクトで実践してほしい。 「読んで分かる」と「実装して動かせる」の間には、 ML エンジニアリングの最大の壁がある。 その壁を越える唯一の方法は、 手を動かすことだ。 SSDSE-B-2026 (N=47) を読み込み、 train_test_split で 80:20 分割し、 SE を計算し、 5-fold CV と比較し、 LOO-CV まで試してみよう。 SE が 0.126 → 0.058 と縮む様を自分の目で確かめれば、 「N=47 では LOO-CV」という結論が腑に落ちる。 それが、 訓練・テスト分割を学ぶ最大の目的であり、 報酬である。

本ページの内容を全て吸収すれば、 学習者は「N に応じた分割設計」「リーク回避」「層化」「時系列対応」「ネスト CV」「業界別最適化」のすべてを自在に語れる ML エンジニアへと成長できる。 R300 補足が、 その成長の第一歩となることを願っている。 SSDSE-B-2026 のような公的データを素材にしたハンズオン教材として、 本ページは現場感と理論的厳密さを両立させた。 これからの学習が、 充実したものになることを願っている。 最後まで読んでくれた学習者に感謝。 一緒に、 「N=47 でも信頼区間付きで汎化誤差を語れる」ML エンジニアへと、 階段を上がっていきましょう。

📊 分割後に確認する図

train/testの散布比較
train/testで特徴量の関係が極端にずれていないか見る。
分割後の分布比較
目的変数や主要特徴量の分布差を確認する。
外れ値の偏り
外れ値が片側の集合だけに偏らないか確認する。

🧾 発表前の最終確認

訓練・テスト分割では、 分割比率、乱数種、層化の有無、時系列順序を必ず明示します。 前処理や特徴量選択を分割前に行うと情報漏洩になるため、 評価用データは最後まで触らないことが重要です。

🧮 SSDSE-B-2026 実値で計算してみる

SSDSE-B-2026 の 2023 年 47 都道府県データで、「高齢化率(中央値以上 / 未満)」を予測する 2 値分類タスクを設定します。4 つの分割方式を比較します。

データ出典:SSDSE-B-2026(独立行政法人統計センター)

STEP 1:データ準備(47 県、2 値ラベル)

$高齢化率 = 65歳以上 / 総人口$。中央値は約 0.318。これ以上を「ラベル 1」、未満を「ラベル 0」に分けます。結果は 0/1 ≒ 23/24 件

STEP 2:4 つの分割方式の比較

方式特徴SSDSE での挙動(test 比率 30%)
ランダムシャッフル → 70/30 で分けるtrain n=33 (13/20), test n=14 (10/4) ※不均衡発生
層化 (stratify)ラベル比率を保ったまま分けるtrain n=32 (16/16), test n=15 (7/8) ※比率維持
時系列 (TimeSeriesSplit)古い年 → 新しい年複数年データ前提。2018-2021 で学習、2022-2023 で評価
グループ (GroupKFold)県をグループに、漏れなく分ける同じ県は train か test 片方のみ。47 県 ÷ 5fold ≒ 9 県/fold

STEP 3:n=47 への推奨

サンプル 47 でホールドアウト test=15 は分散が大きすぎ。実務的には:

STEP 4:再現性確保

random_state=0(または任意固定値)を必ず指定。違うシードで分割すると結果が再現されない。論文・レポートでは「random_state=42 を使用」のように明記します。

🧮 数式に値を入れて手で計算する: 8:2 と 7:3 分割比較

合成 1000 件で代表的な分割比のサイズを計算する。

Step 1: 分割パターン

traintest
8:2800200
7:3700300
6:2:2600 + 200 val200

Step 2: テスト誤差の信頼区間

test=200 で 95% CI 半幅 ≈ 0.07 (精度 0.85 で) test=300 で半幅 ≈ 0.058 test 多いほど精度安定

🐍 Python で再現

1
2
3
4
5
6
import numpy as np
N = 1000
ratios = {'80:20': 0.8, '70:30': 0.7}
for label, r in ratios.items():
    train, test = int(N*r), int(N*(1-r))
    print(f"{label}: train={train}, test={test}")

📤 実行結果

80:20: train=800, test=200 70:30: train=700, test=300

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

🐍 Python 実装

SSDSE-B-2026 で 4 種類の分割方式を実装します。コピペで動きます。

スニペット① ランダム分割(最も基本)

🎯 目的:SSDSE-B-2026 の 47 都道府県データを 70/30 でランダム分割し、ラベル分布を確認する。
📥 入力:data/raw/SSDSE-B-2026.csv の 2023 年データ(n=47)と、目的変数「高齢化率_2値」。
📤 出力:train (n=32) と test (n=15) のラベル平均値 2 つ(sklearn は test 側を切り上げ)。シャッフルあり、random_state=0。
💬 ひとこと:47 件と少ないと、ランダム分割でラベル比率が大きく崩れる。次の StratifiedSplit と比較すべし。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
df = df[df['年度'] == 2023].reset_index(drop=True)
df['高齢化率'] = df['65歳以上人口'] / df['総人口']
df['高齢化率_2値'] = (df['高齢化率'] >= df['高齢化率'].median()).astype(int)

X = df[['総人口', '日本人人口']].values
y = df['高齢化率_2値'].values

X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)
print(f"train n={len(X_tr)}, label mean={y_tr.mean():.3f}")
print(f"test  n={len(X_te)}, label mean={y_te.mean():.3f}")
🎯 目的:ラベル比率を保つ層化分割 (StratifiedSplit) で、train/test のクラスバランスを揃える。
📥 入力:同じ 47 県データと「高齢化率_2値」ラベル。test_size=0.3, stratify=y。
📤 出力:train (n=32) と test (n=15) のラベル分布が、元データ(≈50%)にほぼ一致。
💬 ひとこと:分類問題では stratify=y を必ず指定。指定なしで小データを分けるとクラス比率が大きく崩れる。

スニペット② 層化分割(クラス比率を保つ)

 1
 2
 3
 4
 5
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.3, random_state=0, stratify=y)
print(f"train n={len(X_tr)}, label mean={y_tr.mean():.3f}")
print(f"test  n={len(X_te)}, label mean={y_te.mean():.3f}")
print("→ ラベル比率がほぼ均等になっているはず")
🎯 目的:時系列データに対して TimeSeriesSplit を使い、古い年度で学習・新しい年度で評価する。
📥 入力:SSDSE-B-2026 全多年データ(564 行、年度 2012-2023)。
📤 出力:5-fold 時系列分割の各 fold の train_yrs と test_yrs の年度分布。
💬 ひとこと:ランダム分割は時系列で意味なし。未来の情報で過去を予測してしまい、性能が過大評価される。

スニペット③ 時系列分割 (TimeSeriesSplit)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from sklearn.model_selection import TimeSeriesSplit

df_all = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
df_all = df_all.sort_values(['年度', '都道府県']).reset_index(drop=True)
print(f"全データ n={len(df_all)}, 年度範囲={df_all['年度'].min()}-{df_all['年度'].max()}")

tss = TimeSeriesSplit(n_splits=4)
for i, (tr_idx, te_idx) in enumerate(tss.split(df_all)):
    tr_years = sorted(df_all.iloc[tr_idx]['年度'].unique())
    te_years = sorted(df_all.iloc[te_idx]['年度'].unique())
    print(f"Fold {i+1}: train_yrs={tr_years[-3:]}, test_yrs={te_years}")
🎯 目的:同じ県のレコードが train/test に分かれないように GroupKFold を使う。
📥 入力:SSDSE-B-2026 全多年データ。group=都道府県 を指定。
📤 出力:5-fold グループ分割の各 fold の train_n と test_n、testに含まれる県名(先頭 5 つ)。
💬 ひとこと:都道府県をまたぐリーケージを防ぐ。同一県の異なる年データが train/test 両方にあるとデータ漏洩の典型。

スニペット④ グループ分割 (GroupKFold)

 1
 2
 3
 4
 5
 6
 7
 8
 9
from sklearn.model_selection import GroupKFold

groups = df_all['都道府県'].values
gkf = GroupKFold(n_splits=5)
for i, (tr_idx, te_idx) in enumerate(gkf.split(df_all, groups=groups)):
    test_prefs = df_all.iloc[te_idx]['都道府県'].unique()[:5]
    print(f"Fold {i+1}: train n={len(tr_idx)}, test n={len(te_idx)},"
          f" test 県(先頭5)={list(test_prefs)}")
    if i >= 2: break
🎯 目的:k 分割交差検証 (KFold) で n=47 のような小データの分割を安定化する。
📥 入力:47 県の 2023 年データ、StratifiedKFold(n_splits=5)、scoring='accuracy'。
📤 出力:5-fold 各 fold の accuracy と全 fold 平均。score の標準偏差で安定性を評価。
💬 ひとこと:n=47 では単一の train_test_split より、5-fold CV のほうが推定が安定。論文では平均と SD を併記。

スニペット⑤ Stratified K-Fold CV

 1
 2
 3
 4
 5
 6
 7
 8
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
model = RandomForestClassifier(n_estimators=100, random_state=0)
scores = cross_val_score(model, X, y, cv=skf, scoring='accuracy')
print(f"5-fold CV accuracy: {scores.mean():.3f} ± {scores.std():.3f}")
print(f"各 fold: {scores}")
🎯 目的:ネステッド CV(外側 CV で評価 + 内側 CV でハイパラ探索)でモデル選択の偏りを防ぐ。
📥 入力:47 県データ、外側 5-fold、内側 3-fold、GridSearchCV で n_estimators を探索。
📤 出力:外側 5-fold の accuracy 平均 + SD。同じデータでハイパラ探索と評価を兼ねるとオーバーフィット。
💬 ひとこと:ハイパラ探索のために CV を 1 度回すと、最終評価が楽観的にずれる。ネステッド CV で防ぐのが正攻法。

スニペット⑥ Nested Cross-Validation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from sklearn.model_selection import GridSearchCV, StratifiedKFold
import numpy as np

outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=1)

param_grid = {'n_estimators': [50, 100, 200]}
outer_scores = []
for tr_idx, te_idx in outer_cv.split(X, y):
    X_o_tr, X_o_te = X[tr_idx], X[te_idx]
    y_o_tr, y_o_te = y[tr_idx], y[te_idx]
    grid = GridSearchCV(RandomForestClassifier(random_state=0),
                       param_grid, cv=inner_cv, scoring='accuracy')
    grid.fit(X_o_tr, y_o_tr)
    outer_scores.append(grid.score(X_o_te, y_o_te))

print(f"Nested CV accuracy: {np.mean(outer_scores):.3f} ± {np.std(outer_scores):.3f}")

🐍 Python 実装(応用編 II)

🎯 目的:RepeatedStratifiedKFold で複数シードの平均を取り、評価の安定性を高める。
📥 入力:47 県データ、n_splits=5 × n_repeats=10 = 50 個のスコア。
📤 出力:50 個のスコア集合と、その平均・SD・95%CI。少データの不安定さを補強。
💬 ひとこと:n=47 だと 1 シードの結果は不安定。10 回繰り返して平均する RepeatedKFold が論文標準。
 1
 2
 3
 4
 5
 6
from sklearn.model_selection import RepeatedStratifiedKFold

rskf = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=0)
scores = cross_val_score(pipe, X, y, cv=rskf, scoring='roc_auc')
print(f"5x10 RepeatedSKF AUC: {scores.mean():.3f} ± {scores.std():.3f}")
print(f"95% CI: [{np.percentile(scores, 2.5):.3f}, {np.percentile(scores, 97.5):.3f}]")
🎯 目的:StratifiedGroupKFold(sklearn 0.24+)で「ラベル比率を保ちつつ、グループは分離」する。
📥 入力:全多年 SSDSE-B データ(n=564)、group=都道府県、ラベル=高齢化率 2 値。
📤 出力:5 fold の各 fold で train/test の n とラベル平均。両方とも均等に分かれている。
💬 ひとこと:「同一県は片側のみ」 + 「ラベル比率も保持」の両立技。医療データで重宝。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from sklearn.model_selection import StratifiedGroupKFold

df_all = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
df_all['高齢化率'] = df_all['65歳以上人口'] / df_all['総人口']
df_all['ラベル'] = (df_all['高齢化率'] >= df_all['高齢化率'].median()).astype(int)

X_all = df_all[['総人口', '日本人人口', '15歳未満人口']].values
y_all = df_all['ラベル'].values
groups = df_all['都道府県'].values

sgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=0)
for i, (tr_idx, te_idx) in enumerate(sgkf.split(X_all, y_all, groups=groups)):
    print(f"Fold {i+1}: train n={len(tr_idx)} (ラベル平均={y_all[tr_idx].mean():.3f}),"
          f" test n={len(te_idx)} (ラベル平均={y_all[te_idx].mean():.3f})")
    if i >= 2: break

🎯 サイズ別の分割選択フロー

データサイズと特性に応じた分割選択の意思決定フローです。

データサイズ推奨分割理由
n < 100LOOCV or Repeated 5-foldtest が極小で分散が大きすぎる
100 ≤ n < 1,0005-fold or 10-fold CV推定の安定性確保
1,000 ≤ n < 10,0005-fold CV or train/val/testどちらでも OK
10,000 ≤ n < 100,000train/val/test (60/20/20)CV はコスト過剰
n ≥ 100,000train/val/test (80/10/10)test 10% でも十分な精度

特性別の追加考慮

🚫 アンチパターン集

「やってはいけない」分割の例を、現実の事故と結びつけて解説します。

アンチパターン 1: テストセットを何度も使う

「モデル A の test score 0.85、モデル B の test score 0.87 だから B 採用」→ B を改善し続けると、test に対する overfitting。「test スコアで最良のモデルを選ぶ」を 10 回繰り返すと、test score の楽観バイアスが 5% 以上に。

アンチパターン 2: random_state を変えて「良い結果」を採用

「random_state=0 で AUC=0.78、=1 で 0.82、=2 で 0.85 → 0.85 を採用」はシード過適合。論文では却下されます。複数シードの平均で報告するか、最初に決めたシードに固定。

アンチパターン 3: test 性能でハイパラ調整

GridSearch の対象に test を含めるのは禁忌。validation セットを別に用意するか、Nested CV で外側 / 内側を分離。

アンチパターン 4: 分割前のスケーリング

「全データで StandardScaler を fit → 分割」は test の平均・分散が漏れる。Pipeline 内で fit を train だけに限定。

アンチパターン 5: 同じデータで学習と評価

「全データで学習、同じ全データで評価」は test set がない状態。当然 train accuracy=100% に近づく。初心者向け解説で「training accuracy」を性能と誤解する事故が頻発。

アンチパターン 6: 分割比率の感度なし

「test_size=0.3 で AUC=0.80、=0.2 で AUC=0.85」のように分割比率を変えてスコアが大きく変わるなら、サンプル不足の警告。複数比率で結果を確認するロバスト性チェックを行う。

🔍 上級者向けの落とし穴

特徴量エンジニアリング時のリーケージ

Target encoding(カテゴリ変数を「そのカテゴリの目的変数平均」で置き換え)は典型的にリーケージを起こす。CV の fold 内で計算するか、smoothing を加えて回避。

サンプリング時のリーケージ

SMOTE などのオーバーサンプリングを分割前に行うと、合成サンプルが train/test に分かれる。必ず train だけに適用。

早期停止 (Early Stopping) のリーケージ

XGBoost/LightGBM で eval_set=test として早期停止すると、test 情報が学習に漏れる。validation セットを別途用意するか、CV の inner fold を validation に使う。

外れ値除去のリーケージ

「全データで Z-score > 3 を除外」は test 情報を使った除外になる。train 統計で閾値を決めて test に適用。

複数モデル選択時のオーバーフィット

「20 個のモデルを比較し、CV best を採用」は CV に対する overfitting。Bonferroni 補正のような多重比較対策が理想だが、実務では 1 ベース + 1 アブレーション程度に絞る。

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

機械学習エンジニアが必ず一度はハマる落とし穴です。すべて本番事故に直結します。

❌ random_state を指定しない
実行のたびに分割が変わり、結果が再現しない。論文・レポート・チーム共有が破綻。必ず固定値(例:42, 0, 2024)を指定する。デフォルトの None は禁忌。
❌ クラス不均衡で stratify=None
陽性 1% の問題でテストに陽性が 0 件 → 精度測定不能。stratify=y を必ず指定。クラス不均衡 ページも参照。
❌ 時系列を普通の train_test_split で割る
未来の情報で過去を予測 → 当たって当然。性能が過大評価。必ず TimeSeriesSplit で時間順を保つ。Walk-forward validation も検討。
❌ 顧客 ID・県をまたいで分割
同じユーザ・同じ県のレコードが train/test 両方に存在 → 事実上のリーケージ。GroupKFold で防ぐ。本番では「新規ユーザ」「新規県」が来るので、それを再現する分割が必要。
❌ 分割前にスケーリング・特徴量選択
test の情報が train の前処理に漏れるリーケージ。スケーリング・選択は分割fit_transform を train だけで、test には transform のみ。Pipeline を使うのが安全。

🗺 概念マップ

「訓練・テスト分割」を中心に置いた関連概念のツリー。

概念関係
外側モデル選択 / ハイパラ探索 / Kaggle 戦略分割の応用領域
中核train_test_split / KFold / StratifiedKFold / TimeSeriesSplit / GroupKFold本ページの主役
前提教師あり学習 / 標本抽出 / ランダム性分割を可能にする基礎

🎓 深掘り:産業事例・比較表・演習

産業事例 6 件

事例 1:Kaggle コンペでの分割戦略
住宅価格予測コンペでは、コンペ主催者が事前に train/test を分けて配布。Local CV(手元の k-fold CV)と Public LB(コンペ side のテスト一部)の相関を見ながら戦略を立てる。「LB スコアが急上昇したら overfitting の疑い」を Kaggle ベテランは経験則で持つ。
事例 2:医療画像診断 AI
X 線画像の肺癌検出で「同じ患者の複数画像が train/test 両方に混在」する事故が多発。GroupKFold で患者 ID をグループにし、「新規患者で正しく診断できるか」を厳密に評価する設計が標準化。
事例 3:金融時系列予測
銀行の与信モデルや株価予測で TimeSeriesSplit が必須。「過去のリーマンショック期で学習 → 未来のコロナショック期で評価」のような分布シフトを評価。Walk-forward validation で月次再学習を模擬。
事例 4:レコメンドシステム
Netflix・Spotify では「同一ユーザの最新行動を test に、過去を train に」する Leave-One-Out 風の時系列分割。「未来の好み」を予測する task として正しく評価する。
事例 5:自動運転車
「同じ道路区間の異なる時刻のデータが train/test に混在」を防ぐため、道路セグメントをグループにした GroupKFold。「未知の道路でも安全に走るか」を評価する設計。
事例 6:自然言語処理(医療カルテ)
カルテ文書から疾患を予測する task で、「同一患者の複数カルテ」が train/test に混在する事故。患者 ID を group に GroupKFold + さらに「異なる病院」での評価で分布外汎化を測定。

比較表:5 つの分割方式

分割方式用途長所短所
train_test_split大規模データの初期検証超高速、シンプル1 回の分割で性能推定が不安定
KFold (k=5/10)中規模データの安定評価推定が安定、SD 報告可k 倍の計算コスト
StratifiedKFold分類問題(特に不均衡)クラス比率が保たれる回帰には bin 化が必要
TimeSeriesSplit時系列・パネルデータ時間順を保てるtrain データが古いほど少ない
GroupKFold同一エンティティ複数レコード事実上のリーケージを防げるグループ定義が必要

演習問題 5 問

問 1: 47 県データで陽性 5 件、陰性 42 件のクラス不均衡。train_test_split(test_size=0.3, random_state=0) で test に陽性が含まれない可能性があるか?
答えを表示
十分にあり得る。期待値で test 陽性 = 5×0.3=1.5 件だが、特定 random_state では 0 件もあり。stratify=y を指定 すれば確実に test に陽性 1-2 件入る。
問 2: 多年データ 564 行を普通の train_test_split で 70/30 に分けた。どの時系列リーケージが起きうるか?
答えを表示
2023 年データが train に、2018 年データが test に入る可能性。未来情報で過去を予測する形になり、性能過大評価。TimeSeriesSplit で防ぐ。
問 3: 同一県の 6 年分データ(北海道 2018-2023 など)。train_test_split で「北海道 2020 が train、北海道 2023 が test」になっても問題ないか?
答えを表示
問題あり。県固有の特性(地理・気候・産業構造)が train から漏れて test で活用される事実上のリーケージ。GroupKFold(groups=都道府県) を使う。
問 4: n=47 でホールドアウト 70/30 → test n=14。これで AUC=0.85 と出た。信頼できるか?
答えを表示
test n=14 では AUC の SD が ±0.10 程度。「AUC=0.85 ± 0.10」と幅広い。5-fold CV で平均と SD を併記する方が信頼できる。
問 5: グリッドサーチで CV=5 を回し、ベストパラメータ + ベストスコアを得た。これを論文に「最終モデルの性能」として書いてよいか?
答えを表示
ダメ。ハイパラ探索と評価が同じ CV を使うと最終性能が楽観的にずれる。ネステッド CV で外側 CV を「最終評価」に使う。

失敗例:分割を巡る本番事故

失敗例(実例):
大手 EC で「商品レコメンドモデル」を train_test_split で構築。Local CV では AUC=0.92 と高評価。本番デプロイ後、AUC=0.65 まで急落。原因は「同一ユーザの行動履歴が train/test に混在」のリーケージ。GroupKFold(groups=user_id) に変更して再学習すると Local CV AUC=0.71 へ低下したが、本番でも 0.69 でほぼ一致。「本番分布を模した分割」が現実的な性能を測れる教訓。

用語辞典(10 語)

訓練・テスト分割:データを学習用と評価用に分ける機械学習の基本作法。
ホールドアウト:データを 1 度だけ分ける方法(k-fold ではない)。
k-fold CV:データを k 等分し、各 fold で順に test にする方法。
stratify:層化抽出。ラベル比率を train/test で保つ。
random_state:乱数シード。固定して再現性確保。
リーケージ:test 情報が train に漏れる事故。性能過大評価の原因。
GroupKFold:同一グループが train/test 両方に存在しない分割。
TimeSeriesSplit:時間順を保つ時系列向け分割。
Nested CV:外側 CV で評価、内側 CV でハイパラ探索する 2 重 CV。
Out-of-Bag (OOB):bootstrap で抽出されなかったレコードを test に使う方法。

参考文献

📈 分割の可視化テクニック

分割の妥当性を目で確認するためのプロット 4 種。

① index プロット

x 軸にレコードインデックス、y 軸に fold 番号をプロット。「fold ごとに散らばっているか」を目視チェック。sklearn.model_selection.LearningCurveDisplay や自作で可。

② ラベル分布プロット

各 fold の train/test でラベル比率を棒グラフで比較。stratify の効果を確認できる。

③ 時系列タイムラインプロット

TimeSeriesSplit の場合、x 軸に時間、各 fold を別の色帯で表示。「train が test より前にあるか」を目視。

④ Learning curve(学習曲線)

サンプル数を変えながら train/test スコアをプロット。「サンプル増加でスコアが収束するか」を確認。データ不足の兆候を発見。

✨ ベストプラクティス

1. 分割は前処理より前に

スケーリング、欠損補完、特徴量選択はすべて分割後に train だけで学習し、test には適用するだけ。Pipeline で自動化が安全。

2. random_state は固定

論文や再現性確保のため、必ず固定値(42, 0, 2024 など)を明示。デフォルトの None は禁止。複数シードで結果が安定するか感度分析も実施。

3. ホールドアウト + CV のハイブリッド

大規模データ:「train=60%、val=20%、test=20%」の 3 分割が安全。中規模:「train=80% で 5-fold CV」+「test=20% を最終評価」。ネステッド CV は計算コストが高いがゴールド標準。

4. test セットには絶対に手を加えない

スケーラーは train で fit して test に transform。fit_transform を test に使わない。同様にデータクリーニング・補完も train ベース。

5. 本番分布を模した分割

「本番で何が来るか」を分割設計で再現。新規ユーザ → GroupKFold、新規地域 → 地域別 split、新規期間 → TimeSeriesSplit。

✅ 実務チェックリスト

① 分割前

② 分割実行

③ モデル学習・評価

🎯 このページのまとめ

📌 1 ページまとめ

訓練・テスト分割:機械学習の最も基本的な作法。「学習に使わなかったデータで性能を測る」ための手続き。

  • 典型比率: train:test = 70:30 〜 80:20
  • 必須項目: random_state 固定、stratify 指定(分類)
  • 5 方式の選択: random / stratify / time / group / nested
  • n が少なければ: ホールドアウトより k-fold CV
  • SSDSE n=47 の推奨: 5-fold StratifiedKFold
  • 禁忌: 分割前の前処理、test の漏洩、未来データの学習

次のステップ: 交差検証過学習データリーケージ を続けて学ぶと、ML の評価設計が体系的に理解できます。

📜 歴史と発展

「データを分けて評価する」という発想は古く、1930 年代には Larson が「サブサンプル法」として提唱しました。本格的に普及したのは 1970 年代、Stone (1974) と Geisser (1975) が独立に「交差検証 (cross-validation)」を体系化してから。両者は別々に「leave-one-out」の理論基盤を整え、計算機性能の向上とともに 1990 年代に標準手法となりました。

scikit-learn 以前

2000 年代までは、研究者が自分でデータ分割関数を書くのが普通でした。R では caret パッケージ (2008) が k-fold CV を体系化し、Python では scikit-learn 0.10 (2011) が train_test_split を導入。これにより「データ分割は 1 行で書く」が標準化されました。

深層学習時代の変化

2010 年代に深層学習が台頭し、データセットが極端に大規模化(ImageNet 1.3M 画像など)すると、k-fold CV は計算コストで現実的でなくなりました。代わりに「train/val/test の 3 分割」「事前分割された競技用データセット」がデファクトに。Kaggle は「public LB / private LB の 2 段階分割」を採用。

分布シフトの課題

2020 年代に入り、「ランダム分割は本番分布を再現しない」という批判が高まりました。Domain Generalization、Distributional Robust Optimization (DRO)、Out-of-Distribution (OOD) Detection といった新領域では、「明示的に分布が違う test セット」での評価が重視されています。SSDSE のような全国均質データではこの問題は薄いですが、医療・金融・自動運転ではこの課題が中心です。

❓ よくある質問 (FAQ)

Q1. test_size はいくらが正解?
A. データ量による:n>100,000 なら 0.1(10%)、n=10,000-100,000 なら 0.2-0.3、n<10,000 なら k-fold CV へ。SSDSE n=47 では 5-fold CV が推奨。
Q2. random_state は何の数字でもよい?
A. はい、固定であれば何でも OK。慣習的に 42, 0, 2024 が使われます。「42」は『銀河ヒッチハイク・ガイド』からの引用。複数シードで結果が大きく違うなら、サンプル不足の兆候。
Q3. k-fold CV の k はいくつ?
A. デフォルト k=5 または 10。Kohavi (1995) のシミュレーションで「k=10 がバイアス・分散のバランス最適」と示唆。n が極小なら LOOCV (k=n)、大規模なら k=3-5 で十分。
Q4. shuffle=True と shuffle=False の使い分け?
A. 原則 True。データに時系列・空間順序があるとき以外は必ずシャッフル。CSV ファイル順がアルファベット順や日付順だと、シャッフルなしで明確な偏りが発生。
Q5. test スコアと CV スコアが乖離する場合?
A. 主な原因 3 つ:(1) test が train と分布が違う、(2) test が小さすぎてばらつき、(3) リーケージ。まず test n を確認し、分布の可視化(密度プロット)で偏りをチェック。
Q6. 回帰問題で StratifiedKFold は使えない?
A. 直接は使えませんが、目的変数を bin 化(pd.cut で 5 等分)してから StratifiedKFold で間接的に使えます。極端な分布の回帰問題で有効。
Q7. test セットを 1 度しか使ってはダメ?
A. 厳密にはダメ(理想は 1 度)。同じ test を何度も使ってモデルを比較すると、test に対する overfitting が起きる。多モデル比較は validation セットで行い、最終モデルだけ test で評価するのが理想。

💼 実務ストーリー

ストーリー A:Kaggle の初参戦

Titanic コンペで「Local CV=0.85, Public LB=0.78」と乖離。原因を調べると、Local では random_state を変えて 5 シード平均すると 0.79 ± 0.02 に。1 シードだけの CV は楽観的と痛感し、以降は必ず複数シードでロバスト性を見る習慣に。

ストーリー B:医療画像 AI の本番事故

皮膚癌診断 AI を train_test_split で構築。Test AUC=0.94。本番デプロイ後、AUC=0.71 に急落。患者単位の GroupKFold で再学習すると Local AUC=0.73 に低下したが、本番でも 0.72 でほぼ一致。「同一患者の異なる時刻の画像」が train/test に混在していた典型的リーケージ。

ストーリー C:金融時系列予測

株価予測モデルを random shuffle で評価し AUC=0.85。実取引で大損失。TimeSeriesSplit で再評価すると AUC=0.55 まで低下。「未来の価格情報で過去を予測」していた究極のリーケージ。同社では分割監査担当を設置し、再発防止策を実装。

ストーリー D:47 県データの卒研

大学院生が SSDSE-B-2026 で「都道府県の人口移動」を予測。train_test_split(test=0.3) で n_test=14。AUC が試行ごとに 0.65-0.90 と大きく振れる。指導教員のアドバイスで 5-fold StratifiedKFold に変更すると 0.78 ± 0.05 と安定。サンプル少のときは k-fold という基本ルールを実体験。

⚙️ Pipeline で安全に分割

sklearn.pipeline.Pipeline を使うと、前処理と分類器を 1 つにまとめて、リーケージを自動回避できます。

🎯 目的:前処理(標準化 + 欠損補完)と分類器(RandomForest)を Pipeline で連結し、CV と組み合わせる。
📥 入力:47 県データ、StandardScaler + SimpleImputer + RandomForestClassifier。
📤 出力:5-fold CV のスコア配列。前処理は fold ごとに train で fit、test で transform。
💬 ひとこと:Pipeline を使えば、CV の各 fold で前処理が train だけで fit され、test に漏れない。手動でやるとミスりがちなので必須。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score, StratifiedKFold

pipe = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler()),
    ('clf', RandomForestClassifier(n_estimators=100, random_state=0)),
])
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
scores = cross_val_score(pipe, X, y, cv=skf, scoring='roc_auc')
print(f"CV ROC-AUC: {scores.mean():.3f} ± {scores.std():.3f}")
print(f"各 fold: {scores.round(3)}")
🎯 目的:GridSearchCV で Pipeline 内のハイパーパラメータを fold ごとに最適化する。
📥 入力:Pipeline と、探索パラメータ n_estimators=[50, 100, 200]、CV=5 fold。
📤 出力:最適パラメータと、それで再学習したモデルのテストスコア。
💬 ひとこと:Pipeline + GridSearchCV の組み合わせは scikit-learn の鉄板パターン。前処理パラメータも一緒に探索できる。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from sklearn.model_selection import GridSearchCV

param_grid = {
    'clf__n_estimators': [50, 100, 200],
    'clf__max_depth': [3, 5, 10, None],
}
grid = GridSearchCV(pipe, param_grid, cv=skf, scoring='roc_auc', n_jobs=-1)
grid.fit(X, y)
print(f"Best params: {grid.best_params_}")
print(f"Best CV score: {grid.best_score_:.3f}")
訓練・テスト分割 stratify / random_state KFold / GroupKFold data leakage / 汎化性能 test data / validation TimeSeriesSplit (時系列) Nested CV / pipeline

🔗 隣接手法への橋渡し

訓練・テスト分割は機械学習 pipeline の出発点で、 上流のデータ性質と下流の評価手法の両方に直結する。

SSDSE-B-2026 の 47 県 × 12 年データを単純に train_test_split(test_size=0.2) すると「東京 2015 train、 東京 2020 test」のリークが発生。 GroupKFold で県をグループに使うのが正解。

🌳 手法選択フロー

分割方法はデータの構造で 4 通りに分岐する。

  1. iid サンプル (47 県の独立な観測)? Yes → 単純 train_test_split(test_size=0.2, random_state=42)、 クラス不均衡なら stratify=y
  2. 同一エンティティ複数回 (47 県 × 12 年)? Yes → GroupKFold(groups=県名)、 県をまたぐリーク防止
  3. 時系列? Yes → TimeSeriesSplit、 「2015-2020 train、 2022 test」のように未来をテスト
  4. サンプル少 (n < 200)? Yes → 5-fold or 10-fold CV、 分割なしで CV ループ平均評価

SSDSE-B-2026 の場合: 単年なら hold-out OK、 パネル (47 県 × 12 年) なら GroupKFold、 県別人口予測なら TimeSeriesSplit。 適切な分割が「テスト精度の信頼性」を決める。

🧭 直感をさらに深める:なぜ分けるのか、なぜ3つに分けるのか

分割のたった一つの目的は、「まだ見ぬ未知データに出会ったときの性能を、手元のデータだけで前もって推定する」ことです。学習に使ったデータで測った点数は「答えを配ってから受けた試験」の点数であり、実力ではありません。だから学習に一切触らせなかったデータ ── テストセット ── で答え合わせをします。

テストは「最後の一回だけ」開ける封筒

初学者が最も誤解するのが「テストを何度見てよいか」です。答えは一度だけ。テストの点数を見ながら次数・特徴量・ハイパラを選び直した瞬間、テストは「第二の訓練データ」に変質し、点数は現実より甘くなります(データリーケージの一種)。テストセットは封をした封筒で、すべての意思決定が終わったあとに一度だけ開けるものだと考えてください。

だから「訓練 / 検証 / テスト」の 3 分割

では、モデルやハイパラの比較・調整は何で行うのか。そのために検証(validation)セットを別に切り出します。役割は明確に分かれます。

典型比率は 60:20:2070:15:15。データが少なくて 3 分割すると各セットが痩せすぎる場合は、train と validation の役割を交差検証で回して代用し、テストだけを外に取り置く(=CV + 最終ホールドアウト)のが定石です。SSDSE-B-2026 の 47 県のように n が小さいと、まさにこの形が現実的です。

🕳 落とし穴を深掘り(分割で壊れる7パターン)

分割は「2つに割るだけ」に見えて、事故のほとんどはここで仕込まれます。いずれも本番でこっそり性能が落ちるタイプで、Local の数字だけ見ていると気づけません。

① テストで何度もチューニング(テストへの過剰適合)
テスト誤差を指標に次数やハイパラを選ぶと、テストが実質「訓練の一部」になり、報告値が甘くなります。Kaggle でいう「Public LB に過学習」と同じ現象。調整は検証セットCVで行い、テストは最後だけ。
② 分割の前処理リーク
標準化・欠損補完・特徴量選択・エンコーディングを分割前に全データで行うと、テストの統計量(平均・分散・上位特徴)が訓練の前処理に漏れます。標準化の平均・分散は必ず train だけで fit、test には transform のみ。実務では Pipeline に前処理を封じ込め、fold 内で fit_transform させるのが唯一安全な方法です。詳しくはデータリーケージ
③ 時系列をランダム分割(未来漏れ)
時間順のあるデータをシャッフルして割ると「未来を学習して過去を当てる」未来リークになり、当たって当然の高スコアが出ます。必ず時間で切り、TimeSeriesSplit(expanding / rolling window)を使います。SSDSE-B-2026 のパネル(47 県 × 12 年)を年度予測に使うなら「2018–2021 で学習 → 2022–2023 で評価」の向きを守ること。
④ グループリーク(同一個体が両側に)
同じ顧客・患者・県のレコードが train と test の両方に入ると、個体固有のクセが漏れます。本番では「新規の個体」が来るのに、評価ではそれを再現できていない。GroupKFold / StratifiedGroupKFold で個体単位に分離します。後述の実測のとおり、SSDSE パネルを素朴に行単位でランダム分割すると、ほぼ全県が両側に漏れます。
⑤ 不均衡データで層化忘れ
陽性 1% の問題を素で分けると、テストに陽性が 0 件 → 指標が計算不能・無意味になります。分類は stratify=y を付け、fold でも StratifiedKFoldクラス不均衡ページも参照。
⑥ 小データで分割が不安定
n が小さいと、たまたまの分け方でスコアが大きく振れます。1 回のホールドアウトの数字を結論にしないこと。k-fold CV(+ 複数シードの RepeatedKFold)で「平均 ± SD」を報告するのが誠実です。後述の実測で、n=47 の単一分割がどれだけ不安定かを数字で示します。
⑦ 分割比の選び方
test を大きくすると評価は安定するが学習に使えるデータが減り(バイアス増)、小さくすると学習は充実するが評価が不安定(分散増)。データが潤沢なら 80:20〜90:10、少なければ test を切らずに CV。「test に最低 100、できれば 1,000+」が経験則で、これを下回るなら CV に切り替える合図です。

🚀 発展:分割方式の使い分けとホールドアウトの限界

「単純ランダム分割」は出発点にすぎません。データの構造に応じて分割方式を選ぶことが、信頼できる評価の核心です。

状況推奨狙い
分類・クラス比率を保ちたい層化分割 / StratifiedKFold各 fold のラベル比率を母集団に一致
時間順があるTimeSeriesSplit過去→未来の向きを固定、未来漏れ防止
同一個体が複数レコードGroupKFold個体を分離、新規個体への汎化を測る
n が小さいk-fold CV(k=5,10)全データを評価に使い分散を下げる
ハイパラ探索 + 公正な評価ネステッド CV選択用の内側 CV と評価用の外側 CV を分離

ホールドアウトの限界と CV への移行

1 回のホールドアウトは「テスト集合という 1 つの標本」で性能を推定するに過ぎず、その推定値自体に大きなばらつき(分散)があります。k-fold CV は全データを一度ずつテストに回して平均を取るため、同じデータ量でも推定の分散を下げられます。バイアス・分散の観点では k=5〜10 がスイートスポット(Kohavi 1995)、k=n の LOOCV はバイアス最小だが各 test が 1 点で分散が大きく計算も重い、という位置づけです。

なぜネステッド CV が要るか

グリッドサーチなどでハイパラを CV スコアで選ぶと、その CV スコアはもはや「選択に使った量」なので、そのまま最終性能として報告すると楽観的になります。外側 CV(評価)の中に内側 CV(選択)を入れ子にするネステッド CVで、選択と評価を分離するのが論文品質の設計です。モデル選択も同じ論理で組みます。

リーク検知の実務チェック

🧪 SSDSE-B-2026 実測:再現性と層化の効果

ここからは合成値ではなく SSDSE-B-2026(2023 年・47 都道府県)の実測値です。$高齢化率 = A1303(65歳以上) / A1101(総人口)$ を求め、中央値 0.3178 以上を「ラベル 1」、未満を「0」とした 2 値分類を題材にします(実測で 1:2 が 24:23、全体の陽性率 0.511)。

① 再現性:同じ random_state なら分割は同一

train_test_split(..., random_state=0) を 2 回呼ぶと、テストに入る 15 県は完全に一致しました(実測 True)。逆に random_state を指定しなければ実行のたびに変わり、論文・レポートが再現不能になります。「random_state=0 を使用」のように必ず値を明記すること。

② 層化の効果:test の陽性率のブレを消す(実測)

n=47 を test_size=0.3(テスト 15 県)で分けたとき、シード 0〜19 の 20 通りで テストの陽性率がどれだけ動くかを実測しました。

分割方式test 陽性率 平均範囲(min–max)SD
ランダム0.5300.333 – 0.7330.111
層化(stratify=y)0.5330.533 – 0.5330.000

母集団の陽性率 0.511 に対し、素のランダム分割ではシード次第でテストの陽性率が 33% 〜 73% まで揺れます(SD 0.111)。stratify=y を付けると どのシードでも 0.533 で一定(SD 0.000)。層化は「ラベル比率のブレ」という不確実性を丸ごと除去してくれる、というのが数字で確認できます。

③ 小データの不安定さ

②のランダム分割の SD 0.111 は、まさに「n=47 の単一ホールドアウトが当てにならない」ことの実測です。テスト構成すらシードで大きく動くのだから、そこで測った精度 1 つを結論にするのは危険。だから 47 県では 5-fold(層化)CV で「平均 ± SD」を報告する、という STEP 3 の推奨につながります。

④ グループリーク:素朴な行分割はほぼ全県が漏れる(実測)

パネル全体(564 行 = 47 県 × 12 年)を行単位で素朴に 70/30 ランダム分割すると、シード 0〜19 の平均で 46.7 / 47 県が train と test の両方に出現しました(min 45・max 47)。つまりほぼ全県の情報が漏れています。県単位の汎化を測るなら GroupKFold(groups=県) で分離が必須、という④の落とし穴の裏付けです。

この実測を再現するコード

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) 北海道 5,092,000 1,681,000 東京都 14,086,000 3,205,000 沖縄県 1,468,000 350,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd, numpy as np
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True)     # 47 県
df['aging'] = df['A1303'] / df['A1101']                          # 65歳以上 / 総人口
y = (df['aging'] >= df['aging'].median()).astype(int).values       # 24 vs 23

# ① 再現性: 同じ random_state は同じ分割
a = train_test_split(np.arange(47), test_size=0.3, random_state=0)[1]
b = train_test_split(np.arange(47), test_size=0.3, random_state=0)[1]
print('同一シードで一致:', np.array_equal(a, b))                    # True

# ② 層化の効果: 20 シードで test の陽性率を比較
rnd   = [train_test_split(np.arange(47), y, test_size=0.3, random_state=s)[3].mean()
         for s in range(20)]
strat = [train_test_split(np.arange(47), y, test_size=0.3, random_state=s, stratify=y)[3].mean()
         for s in range(20)]
print('random  平均%.3f%.3f-%.3f' % (np.mean(rnd),   min(rnd),   max(rnd)))    # 0.530 0.333-0.733
print('stratify平均%.3f%.3f-%.3f' % (np.mean(strat), min(strat), max(strat)))  # 0.533 0.533-0.533

※ 数値は SSDSE-B-2026(2023 年 47 県)の実測。乱数シードと sklearn のバージョンにより端数は変わり得ますが、「ランダムは揺れ、層化は一定」という傾向は再現します。パネルの合成 1000 件など本ページ内の合成例は「架空」と明記しています。