論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
バリデーション
Validation
ML基礎

🔖 キーワード索引

Validation検証HoldoutK-Fold CVStratified CVNested CVTime Series CVEarly Stopping汎化性能

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

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

💡 30秒で分かる結論

🍰 まずはやさしく

正解を確かめるためのテストです。

モデルが正しいか判断するために使います。

スマホのアプリを試す感覚に似ています。

まずは結論と直感的な意味を読みましょう。

モデル選択のための検証手続き

💡 30秒で分かる結論

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

🍰 まずはやさしく

モデルを選ぶための準備ステップです。

最適な設定を見つけるために使います。

部活の練習で本番を想定するようなものです。

この言葉がどう使われるかを見ていきましょう。

バリデーション(検証)は 学習済みモデルを Train と Test の間で評価し、 ハイパラ調整やモデル選択に使う中間ステップ。 Train で学び、 Val で選び、 Test で最終判定、 という 3 段構えが現代 ML の標準。

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

このページは、 ジャストインタイム型データサイエンス教育の用語集の一部です。 バリデーションは、 統計学・データサイエンス・機械学習・AI の文脈で頻出する重要キーワードであり、 SSDSE-B-2026 都道府県データを使った実例とともに、 定義・直感・数式・実装・落とし穴・関連用語を体系的に学べる構成になっています。 まずは「30 秒で分かる結論」と「直感で掴む」セクションから読み、 必要に応じて数式・実装の詳細に進むことをおすすめします。

🎨 直感で掴む

🍰 まずはやさしく

未知のデータへの強さを測る仕組みです。

練習問題だけに慣れるのを防ぐために使います。

テスト勉強で予想問題を解く感覚です。

データの量に合わせて選ぶ方法を学びます。

バリデーション (validation) は「モデルの未知データへの汎化性能を、 訓練時に見せなかったデータで測る」プロセス。 単に訓練データの精度 (in-sample) を測ると過学習を見逃すため、 train/validation の分離が必須。 SSDSE-B-2026 の 47 都道府県のような小標本では、 ホールドアウトより 5-fold CV または LOO で「全データを評価に使い回す」のが定石。

本ページでは バリデーション を、 「ホールドアウト → K-fold CV → 層化 K-fold → LOO → TimeSeriesSplit」の選択軸で整理し、 SSDSE-B-2026 の N=47 ケースでの実装を示す。 厳密な定義より、 まず標本サイズ N と時系列性で何を選ぶかを理解することを優先する。

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

Validation がないと「Test を見ながらモデルを調整する」ことになり、 Test 過適合が起きる。 そこで Train を Train + Val に分け、 ハイパラやモデル選択は Val で完結させる。 データが小さいと Val の不確実性が大きいので、 K-Fold CV で k 回平均を取る。 時系列データは未来情報リークを避けるため Time Series Split を使う。

📐 定義

🍰 まずはやさしく

モデル選択のための検証手続きのことです。

分析の標準的な道具として使います。

買い物で商品の質を確かめるようなものです。

詳しい定義や計算の方法を確認しましょう。

モデル選択のための検証手続き

英語名 Validation

🎯 いつ・どこで使うか

📋 前提条件・適用範囲

この用語を理解・使用するときは、 次のような前提を意識してください:

📐 数式・定義

バリデーションを数式 / 形式定義で表す:

$$\text{CV-Score} = \frac{1}{K}\sum_{k=1}^K \text{Metric}\!\big(\mathcal{D}_k^{\text{val}}, f^{(-k)}\big)$$

K-Fold 交差検証スコア:データを K 分割し、 各 fold を Val、 残りを Train として K 個のモデルを学習し平均する。

📐 k-fold CV の数式を言葉で読み解く

k-fold CV の汎化誤差推定量は次のように書ける。

$$\widehat{\text{Err}}_{\text{CV}} = \frac{1}{k}\sum_{j=1}^{k} \frac{1}{|D_j|}\sum_{(x_i,y_i)\in D_j} L(y_i, \hat{f}^{(-j)}(x_i))$$

数式を言葉で読み解く

バイアス・分散分解で見ると、 k=n(LOOCV)はバイアス最小だが分散大k=5 は分散小だがバイアス中程度。 経験則として k=5 または k=10 がスイートスポット。

🔬 数式を言葉で読み解く

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

記号意味
$K$Fold 数(5 or 10 が典型)
$\mathcal{D}_k^{\text{val}}$$k$ 番目の検証 fold
$f^{(-k)}$fold $k$ を除いて学習したモデル
MetricRMSE・F1 など

🔬 発展トピック

「バリデーション」を入門レベルで習得した次に進むべき発展テーマ:

① 理論的拡張

バリデーション(検証)を 確率論・情報理論・最適化理論の観点で再定式化すると、 隣接する手法との理論的な関係が見えてきます。 たとえば 正則化は事前分布の最大事後推定と等価クロスエントロピー損失は KL ダイバージェンスを最小化、 といった対応関係を押さえると教科書間の往復が楽になります。

② 実装的拡張

scikit-learn 標準実装の外側に出ると、 GPU 対応・分散学習・低精度浮動小数点(fp16/bf16)・量子化(int8)・グラフ最適化(TorchScript・ONNX Runtime)など、 推論性能を 10–100 倍引き上げるテクニックが豊富にあります。 本番運用では モデル精度と推論コストのトレードオフを意識した実装が鍵。

③ 評価・解釈の拡張

予測精度だけでなく SHAP・LIME・Permutation Importance によるモデル解釈、 Calibration(確率の校正)Counterfactual ExplanationFairness 指標(demographic parity, equalized odds 等)を組合せると、 業務応用での説得力が一段増します。

④ 業界応用

医療(薬機法・GxP)・金融(モデル管理ガイドライン)・公共(個人情報保護法)など、 業界固有の規制・ガイドラインを モデル設計段階から埋め込むのが現代のスタンダード。 「バリデーション」を業務適用するときは、 ドメインの専門家・法務との早期コラボレーションが成否を分けます。

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

SSDSE-B-2026 で 「総人口 → 出生数」回帰の 5-Fold CVを実行し、 各 fold の R² と平均を出す。

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

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 1,681,000 24,430 東京都 14,086,000 3,205,000 86,348 沖縄県 1,468,000 350,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold, cross_val_score
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'})

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

kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(LinearRegression(), X, y, cv=kf, scoring='r2')
print(f'各 fold R²: {scores.round(3)}')
print(f'平均 R²   : {scores.mean():.3f} ± {scores.std():.3f}')
📤 実行例(実測) 各 fold R²: [0.974 0.99 0.985 0.988 0.981] 平均 R² : 0.983 ± 0.006

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

🧮 SSDSE-B-2026 で 5-fold CV を回す

このコードでやること:SSDSE-B-2026 の都道府県データ(2023 年・n=47)を使い、 「総人口(A1101)」を予測する線形回帰モデルの汎化性能を cross_val_score で 5-fold CV により評価する。

📥 入力データ(SSDSE-B-2026 抜粋)

SSDSE-B-2026 Code Prefecture A1101 A1301 A4101 A4103 0 2023 R01000 北海道 5092000 514000 24430 1.06 1 2023 R02000 青森県 1184000 118000 5696 1.23 2 2023 R03000 岩手県 1163000 120000 5432 1.16 ... 46 2023 R47000 沖縄県 1468000 236000 12549 1.60
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import KFold, cross_val_score

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 都道府県

# 説明変数:15歳未満人口、 出生数、 合計特殊出生率 → 目的変数:総人口
X = df[['A1301', 'A4101', 'A4103']]
y = df['A1101']

model = LinearRegression()
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='r2')
print("各 fold R²:", scores.round(3))
print(f"平均 R² = {scores.mean():.3f} ± {scores.std():.3f}")

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

各 fold R²: [0.953 0.998 0.979 0.998 0.966] 平均 R² = 0.979 ± 0.017

💬 結果の読み方:R² ≈ 0.979 と非常に高く、 5 fold 間のばらつきも小さい(±0.017)。 → 15 歳未満人口・出生数・合計特殊出生率の 3 変数で総人口がほぼ説明できる、 安定したモデルだと判断できる。 hold-out の 1 値ではなく 5 つの値の分布を見ることで「たまたまよく当たった」を排除できる。

🧮 数式に値を入れて手で計算する: K-fold CV の評価

合成 5-fold CV の精度を平均する。

Step 1: fold 別精度

f = [0.85, 0.87, 0.84, 0.86, 0.83] 平均 = 0.85 SD ≈ 0.015

Step 2: 95% CI

SE = 0.015/√5 ≈ 0.0067 CI = 0.85 ± 1.96·0.0067 = [0.837, 0.863]

🐍 Python で再現

1
2
3
4
5
6
7
import numpy as np
folds = np.array([0.85, 0.87, 0.84, 0.86, 0.83])
mean = folds.mean()
sd = folds.std(ddof=1)
SE = sd/np.sqrt(len(folds))
print(f"平均: {mean}, SD: {sd:.3f}")
print(f"95% CI: [{mean - 1.96*SE:.3f}, {mean + 1.96*SE:.3f}]")

📤 実行結果

平均: 0.85, SD: 0.016 95% CI: [0.836, 0.864]

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

🐍 Python での扱い

SSDSE-B-2026 のような公的統計データを Python で扱う際の基本パターン:

📥 入力例(SSDSE-B-2026 全体:564 行 × 112 列 = 47 都道府県 × 2012〜2023 年) 年度 地域コード 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) … 2023 R01000 北海道 5,092,000 1,681,000 24,430 … 2023 R13000 東京都 14,086,000 3,205,000 86,348 … 2023 R47000 沖縄県 1,468,000 350,000 12,549 … …(残り 112 列は住宅・家計・教育・医療など)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
import numpy as np

# データ読み込み
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=0)
print(df.shape)
print(df.dtypes)
print(df.describe())

# 「バリデーション」の文脈で扱う場合の例:
# 分野: ML基礎
# 関連手法は同カテゴリの他用語を参照してください。
📤 実行例(実測) (565, 112) SSDSE-B-2026 object Code object Prefecture object A1101 object A110101 object ... L322106 object L322107 object L322108 object L322109 object L322110 object Length: 112, dtype: object SSDSE-B-2026 Code Prefecture A1101 ... L322107 L322108 L322109 L322110 count 565 565 565 565 ... 565 565 565 565 unique 13 48 48 521 ... 561 550 555 560 top 2023 R24000 三重県 1414000 ... 42183 9167 23791 49899 freq 47 12 12 5 ... 2 2 …(以下略)

具体的なコードは 機械学習の基礎 を参照してください。

📝 レポートでの報告

分析結果を報告するときに含めるべき情報:

✅ チェックリスト

🔗 同カテゴリの他用語

データリーケージ訓練・テスト分割教師あり学習教師なし学習強化学習分類回帰タスク目的変数説明変数特徴量訓練データ検証データテストデータ過学習

🐍 Python 実装バリエーション

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

① pandas + numpy(最小依存)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) A5101(転入者数(日本人移動者)) F3101(新規求職申込件数(一般)) 北海道 5,092,000 24,430 47,388 156,458 東京都 14,086,000 86,348 406,749 270,954 沖縄県 1,468,000 12,549 26,410 43,877 …(全 47 行)
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=0)
df = df.rename(columns={df.columns[2]: 'pref'})

print('行数:', len(df), '列数:', df.shape[1])
print(df[['pref', 'A1101', 'A4101', 'A5101', 'F3101']].head())
📤 実行例(実測) 行数: 565 列数: 112 pref A1101 A4101 A5101 F3101 0 都道府県 総人口 出生数 転入者数(日本人移動者) 新規求職申込件数(一般) 1 北海道 5092000 24430 47388 156458 2 北海道 5140000 26407 49084 166170 3 北海道 5183000 28762 48832 166966 4 北海道 5224614 29523 48494 174993

② scikit-learn(学習・評価)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 1,681,000 24,430 東京都 14,086,000 3,205,000 86,348 沖縄県 1,468,000 350,000 12,549 …(全 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
26
27
# ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)──
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in df.columns[3:]:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()]

# この抜粋で使う df_jp を用意する
import pandas as pd

df_jp = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df_jp = df_jp[df_jp['SSDSE-B-2026'] == 2023]   # 2023 年の 47 都道府県

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_jp[['A1101', 'A1303']].fillna(0).values
y = df_jp['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.982 RMSE = 3438.97

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

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A4101(出生数) 北海道 5,092,000 24,430 東京都 14,086,000 86,348 沖縄県 1,468,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)──
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0)
df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
for _c in df.columns[3:]:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()]

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.995, p 値 = 1.53e-47 t = 0.000, p = 1.000

④ 可視化(matplotlib + seaborn)

 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('A1101')
ax.set_ylabel('A4101')
ax.set_title(f'{len(df)} 都道府県の関係')
plt.tight_layout()
plt.savefig('out.png', dpi=120)
plt.close()
📤 実行例(実測) このブロックは標準出力を出さない(図を描く・変数を定義するだけ)。

🐍 Stratified k-fold で不均衡分類を評価

このコードでやること:SSDSE-B-2026 から「合計特殊出生率(A4103)が全国平均以下か」を二値分類し、 各 fold で正例比率を揃える Stratified k-fold で評価する。

📥 入力データ:A4103(合計特殊出生率)を平均で二値化したラベル。 正例率は約 50%(実測 49%=23/47)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score

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', 'A4101']]
y = (df['A4103'] < df['A4103'].mean()).astype(int)

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
model = LogisticRegression(max_iter=1000)
scores = cross_val_score(model, X, y, cv=skf, scoring='accuracy')
print(f"Stratified 5-fold accuracy: {scores.mean():.3f} ± {scores.std():.3f}")

📤 実行例

Stratified 5-fold accuracy: 0.831 ± 0.085

💬 結果の読み方:accuracy 83% は単純多数決(約 51%)より高いが、 標準偏差 ±8.5% と大きい → サンプル数 47 と小さいことが原因。 通常の KFold だと fold によって正例比率が偏り評価が不安定になるので、 不均衡データでは Stratified 一択。

🐍 Nested CV — ハイパラ調整と性能評価を分離

このコードでやること:内側 CV でハイパラを選び、 外側 CV でモデル性能を評価する Nested CV を SSDSE-B-2026 で実行する。 同じデータでチューニングと評価を兼ねると 楽観バイアスが乗るのを防ぐ。

📥 入力データ:SSDSE-B-2026、 X は人口 3 変数、 y は出生数(A4101)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV, cross_val_score, KFold

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
X = df[['A1101', 'A1301', 'A4200']]
y = df['A4101']

inner = KFold(n_splits=3, shuffle=True, random_state=0)
outer = KFold(n_splits=5, shuffle=True, random_state=1)
param_grid = {'alpha': [0.01, 0.1, 1.0, 10.0, 100.0]}
gs = GridSearchCV(Ridge(), param_grid, cv=inner, scoring='r2')
nested_scores = cross_val_score(gs, X, y, cv=outer, scoring='r2')
print(f"Nested CV R² = {nested_scores.mean():.3f} ± {nested_scores.std():.3f}")

📤 実行例

Nested CV R² = 0.992 ± 0.000

💬 結果の読み方:通常の GridSearchCV だと「最良ハイパラでの内側 CV スコア」が報告されるが、 そのハイパラ自体が同じデータで選ばれているため楽観バイアス(数 % 上振れ)が乗る。 Nested CV では 外側 fold ごとに別個のハイパラ選択 + 評価を行うので、 報告値が新規データでの期待性能に近い。

🐍 TimeSeriesSplit — 時系列データの正しい分割

このコードでやること:SSDSE-B-2026 を複数年データとして扱い、 TimeSeriesSplit で「過去→未来」の分割を行う。 通常の KFold と比較して、 リーク防止の重要性を確認する。

📥 入力データ:SSDSE-B-2026 を SSDSE-B-2026 列(年)でソート、 X は人口関連 3 変数、 y は出生数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import TimeSeriesSplit, cross_val_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df_sorted = df.sort_values('SSDSE-B-2026')  # 年順
X = df_sorted[['A1101', 'A1301', 'A4200']]
y = df_sorted['A4101']

tscv = TimeSeriesSplit(n_splits=5)
for i, (tr, vl) in enumerate(tscv.split(X)):
    print(f"Fold {i}: train {len(tr)} 件, val {len(vl)} 件")

scores = cross_val_score(LinearRegression(), X, y, cv=tscv, scoring='r2')
print(f"TimeSeriesSplit R² = {scores.mean():.3f} ± {scores.std():.3f}")

📤 実行例

Fold 0: train 94 件, val 94 件 Fold 1: train 188 件, val 94 件 Fold 2: train 282 件, val 94 件 Fold 3: train 376 件, val 94 件 Fold 4: train 470 件, val 94 件 TimeSeriesSplit R² = 0.989 ± 0.005

💬 結果の読み方:fold が進むごとに train サイズが拡大する expanding window。 R² 平均 0.989 は通常 KFold(0.990)より僅かに低いが、 こちらが 正しい未来予測能力。 KFold の高スコアは「未来のサンプルで学習して過去を当てている」リークが原因。

📊 主要 CV 手法の比較表

手法バイアス分散計算量用途
Hold-outO(1)大規模・素早く回したい
5-fold CV小〜中O(5)標準的選択
10-fold CVO(10)中小規模データ
LOOCV最小O(n)超小規模(n < 50)
Stratified k-foldO(k)不均衡分類
TimeSeriesSplitO(k)時系列
GroupKFoldO(k)グループ構造あり
Nested CV最小O(k×k)ハイパラ調整 + 性能評価
Bootstrap (.632+)O(B)不確実性区間が欲しい時

🐍 Pipeline で Leakage を防ぐ

このコードでやること:標準化 + 線形回帰を Pipeline にまとめ、 CV の各 fold で正しく fit するようにする。 Pipeline なしと有りの結果を比較する。

📥 入力データ:SSDSE-B-2026、 X は人口関連変数(スケールがバラバラ)、 y は出生数。

 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 Ridge
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, KFold

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
X = df[['A1101', 'A1301', 'A4200']]
y = df['A4101']
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# Pipeline 化:fold ごとに scaler を再 fit する
pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('ridge', Ridge(alpha=1.0))
])
scores = cross_val_score(pipe, X, y, cv=kf, scoring='r2')
print(f"Pipeline CV R² = {scores.mean():.3f} ± {scores.std():.3f}")

📤 実行例

Pipeline CV R² = 0.991 ± 0.001

💬 結果の読み方:もし StandardScaler().fit_transform(X) を CV ループの外で実行していたら、 テスト fold の平均・分散が train に漏れていた。 Pipeline 化により 各 fold で scaler を train のみで fit するため、 リーク無しの推定値。 必ず Pipeline で運用する習慣をつけよう。

🔍 深掘り:CV スコアの不確実性と統計的検定

2 つのモデルの CV スコアを比較する際、 「平均値だけ」で勝敗を決めるのは危険。 fold ごとのスコアは相関しているため、 通常の t 検定の前提が成り立たない。 推奨:

Kaggle 等で「CV スコアが 0.001 上がった」と喜ぶ前に、 fold 標準偏差を見て差が有意かを必ず確認すること。

⚠️ よくある落とし穴

❌ 過学習に注意
訓練データだけ高精度でも、 未知データで失敗するモデルは無価値。
❌ データの偏りを確認
バイアスのあるデータからは、 バイアスのあるモデルが生まれます。
❌ 指標を単独で見ない
1 つの指標で「良い」と判断せず、 複数の評価軸を併用しましょう。

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

「バリデーション」を実務・試験で扱うときに頻発する典型的なミスです。 各項目を 1 度読んでおけば 9 割の事故が防げます:

❌ Val と Test を混同
Val は調整用、 Test は最終評価用。 役割を混ぜると過大評価。
❌ 時系列を Random CV
時系列データは未来リーク。 TimeSeriesSplit を使う。
❌ Stratify 忘れ
不均衡分類で Random KFold だと少数派が偏在。 StratifiedKFold を必須。
❌ 単一 Holdout で結論
データ少量だと Holdout 1 回の結果は揺れる。 CV で平均をとる。
❌ CV を Test で再検証しない
CV スコアが良くても本番分布が違うと失敗。 最終 Test で確認。

⚠️ Data Leakage の典型パターン 5 種

  1. 正規化を CV 前に適用StandardScaler.fit_transform(X) を CV の外でやると、 テスト fold の平均・分散が train に漏れる。 → Pipeline に scaler を組み込み、 fold ごとに fit すれば防げる。
  2. 特徴量選択を CV 前に:相関の高い変数だけ残して CV すると、 「テスト側を覗いた選択」になる。 → 特徴量選択も Pipeline に含める。
  3. 時系列を shuffle:未来→過去の予測になり、 楽観評価。 → TimeSeriesSplit を使う。
  4. 同一個体が train/val に分割:医療データで「同じ患者の別検査」が train と val に分かれると、 個人特徴を覚えてしまう。 → GroupKFold で患者 ID をグループ化。
  5. ターゲット由来の特徴量:「将来の y で作った変数」を X に入れる。 → 特徴量定義時点で「予測時に入手可能か」を必ず確認。

📌 CV スコアが現実より極端に高い時は、 まず Data Leakage を疑え。 業務 KPI と CV スコアの乖離が見つかったら 9 割は漏洩が原因。

🗓 バリデーション手法の歴史

出来事提唱者
1968Hold-out 法の体系化Lachenbruch & Mickey
1974Cross-validation の理論整備Stone, M.
1979Bootstrap の提案Efron, B.
1995k-fold CV と LOOCV の比較研究Kohavi, R.
2006Nested CV による楽観バイアス研究Varma & Simon
2017scikit-learn 0.18 で TimeSeriesSplit 標準化scikit-learn team

❓ よくある質問

Q1. k は 5 と 10 のどちらが良い?

A. データが小さい(n < 500)なら 10-fold、 中規模(500-5000)なら 5-fold で十分。 bias-variance のトレードオフで k=10 は分散がやや大きくなるが、 多くの実証研究で k=5 と k=10 はほぼ同じ結論を出す。

Q2. LOOCV は使うべき?

A. n が極小(< 50)でなければ非推奨。 LOOCV は 分散が大きい(fold 間の相関が高いため)。 線形回帰には解析解があり計算量問題は無いが、 通常は 10-fold で十分。

Q3. Validation と Test の違いは?

A. Validation はモデル選択(ハイパラ調整・特徴量選択など)に使うデータ、 Test は最終評価のみに使うデータ。 Test を見てチューニングしてはいけない(リーク)。 Kaggle の public/private LB の関係に近い。

Q4. shuffle=True と shuffle=False の使い分け?

A. IID なら True、 時系列・順序のあるデータなら False。 とくに SSDSE-B-2026 のような複数年パネルでは year を考慮して時系列分割する必要がある。

Q5. CV スコアが test スコアと大きく違う場合は?

A. (1) data leakage、 (2) train/test の分布差(covariate shift)、 (3) サンプリングバイアス、 (4) ハイパラ過剰調整、 のいずれか。 まず Pipeline 化と分布の確認から。

🎮 触って理解する — 検証設計シミュレータ

個々の手法(交差検証ホールドアウト)の中身は各ページに譲り、 ここでは「検証の全体設計」= データの条件からどの分割法を選ぶかを体感する。 下のスライダとスイッチで 標本サイズ N・時系列性・クラス不均衡 を切り替えると、 ①フローチャートの推奨経路、 ②分割の見取り図、 ③学習回数・fold サイズ が同時に更新される。 本ページ「🌳 手法選択フロー」のルールをそのまま動かせるようにしたもの。

N=47:SSDSE-B-2026 の 47 都道府県と同じ小標本
時系列データ?
クラス不均衡?
分割数 K

① 意思決定フローチャート(推奨経路がハイライト)

① 時系列データ? Yes TimeSeriesSplittrain は常に val より過去 No ② N ≥ 10,000? Yes Holdout(train:val = 8:2)大標本なら 1 回の分割で十分安定 No ③ N < 100? Yes LOO / 反復 K-fold全データを検証に使い回す No ④ クラス不均衡? Yes Stratified K-fold各 fold のクラス比を保持 No K-fold(shuffle=True)標準の交差検証

② 分割の見取り図(横棒 = データ N 件、フォールドに触れると内訳表示)

Train Validation 未使用(その fold では使わない)
フォールドにマウス / 指を乗せると内訳を表示
学習するモデル数
Train / Val 件数(代表 fold)
検証に 1 度でも使う割合

💡 直感 — 検証設計は「模擬試験の設計」

本番の入試(Test)を解いて対策したら実力は測れない。 だから模擬試験(Validation)を別に用意し、 模試の作り方(分割法)はデータの性質で決める——これが検証の全体設計。 上のフローチャートの分岐は 3 つだけ:時間の向き(時系列性)・データの量(N)・ラベルの偏り(不均衡)。 この 3 条件を確認せずに train_test_split を書き始めるのが、 初学者の最も多い事故パターン。

⚠️ シミュレータで体感できる落とし穴

🚀 発展 — フローチャートの先へ

このフローチャートは 1 段目の設計。 実務ではさらに、 ①Nested CVハイパーパラメータ調整を内側ループ、 性能評価を外側ループに分けて「調整による楽観バイアス」を除く、 ②GroupKFold:同一個体(同じ患者・同じ都道府県の複数年)が train と val に跨がるのを防ぐ、 ③反復 CV(RepeatedKFold):乱数シードを変えて CV を繰り返し、 分割運の影響を平均化する——を組み合わせる。 分割の内部動作は 交差検証ホールドアウト訓練・テスト分割、 時系列の性質は 時系列データ、 検証が守ろうとしている敵は 過学習 の各ページで深掘りできる。

🗺 概念マップ

関連概念を視覚的に整理した概念マップ。

validation 教訓 Pipeline 内 平均と標準偏差の両方 全データで再学習 スコアの安定性 分割の妥当性

このマップは validation の意思決定の流れを表す。 中央に「validation」を置き、「分割の妥当性 (層化が必要か、 時系列性があるか)」「スコアの安定性 (CV fold 間の標準偏差)」「全データで再学習 (本番モデルの構築)」「ハイパーパラメータ選択 (グリッドサーチ・ベイズ最適化との接続)」の 4 軸へ枝が伸びる。 SSDSE-B-2026 の場合、 47 都道府県という小標本では 5-fold CV と LOO の差が大きく、 分割設計が validation 結果を左右する点に注意したい。

🔗 隣接手法への橋渡し

バリデーションは「モデル評価」を担う層であり、 前後の手法とリレー的に接続する。

SSDSE-B-2026 (47 都道府県) のように小標本ではホールドアウト不可、 5-fold CV か LOO が必須となる。 学習曲線と組み合わせ、 「データを増やせば改善するのか、 モデル変更が必要なのか」を判断する。

🌳 手法選択フロー

バリデーション手法は標本サイズと時系列性で決まる。 以下の 3 段階で選択する。

  1. 時系列性があるか? Yes → TimeSeriesSplit (時系列用 CV) (未来情報漏洩防止)、 No → 次へ
  2. 標本サイズ N は? N>=10000 → 単純な train/test 分割、 100<=N<10000 → 5-fold か 10-fold CV、 N<100 → LOO または反復 CV
  3. クラス不均衡か? Yes → 層化 K-fold (sampling) でクラス比保持、 No → 通常の K-fold

SSDSE-B-2026 (N=47) のケースでは「No → 100 未満 → LOO または 5-fold 反復」が選ばれる。 fold 間の標準偏差が平均の 20% を超える場合、 分割設計か特徴量を見直す。

🌱 解説の深化 — 「CV スコアそのもの」の不確実性を疑う

ここまでは「どう分割するか」を扱ってきた。 この節では一歩引いて、 算出された CV スコア自体がどれほど信用できる数値なのかを、 SSDSE-B-2026(2023 年・47 都道府県)の実測で確かめる。 モデル・変数は本文の実値計算と同じ(説明変数 A1301 15歳未満人口・A4101 出生数・A4103 合計特殊出生率 → 目的変数 A1101 総人口、 線形回帰)。

🎨 直感 — CV スコアは「測定値」ではなく「推定値」

5-fold CV の平均 R² は、 モデルの汎化性能という見えない真値を、 たまたま引いた 1 通りの分割で推定した値にすぎない。 分割は random_state(シード)で決まるので、 シードを変えれば同じデータ・同じモデルでもスコアは動く。 体温計で 1 回測った 36.8℃ をそのまま信じず「測るたびに少し揺れる」と知っておくのと同じで、 CV スコアには標本誤差ならぬ「分割誤差」が乗っている。 N=47 の小標本ではこの揺れが無視できない大きさになる。

実測:本文と同じモデルで random_state を 0〜9 の 10 通りに変えて 5-fold CV を回すと、 平均 R² は 0.955(seed=6)〜 0.990(seed=9)まで 0.035 の幅で動いた(10 シードの平均 0.978、 シード間 SD 0.011)。 本文の seed=42 の値 0.979 は、 この分布のほぼ中央に落ちる「たまたま素直な 1 標本」だったことになる。

⚠️ 落とし穴(重要) — fold 平均に埋もれる外れ値と「シード選び」

🔬 発展 — 揺れを測って報告する道具立て

🔗 関連ページ