論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
バリデーションによるモデル選択
Model Selection via Validation
ML基礎

🔖 キーワード索引

モデル選択Model SelectionValidationCVNested CV比較AICBICベスト推定

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

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

💡 30秒で分かる結論

🍰 まずはやさしく

一番いいモデルを選ぶオーディションです。

予測の精度が高い方法を決めるために使います。

スマホのアプリをいくつか比べて選ぶときと同じです。

ここでは結論から簡単に解説します。

検証データのスコアで複数モデルから最良を選ぶ

💡 30秒で分かる結論

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

🍰 まずはやさしく

モデルを選ぶための標準的な手続きです。

本当に使えるモデルを見極めるために使います。

部活の練習メニューをいくつか試して選ぶようなものです。

どのような場面で使うのかを説明します。

バリデーションによるモデル選択は 複数の候補モデル(線形回帰・Ridge・XGBoost…)を Val で比較し、 最良のものを採用する標準手続き。 統計学では AIC / BIC が情報量基準として、 ML では Val スコアが実用的に使われる。

📍 あなたが今見ているものとモデル選択

あなたが今見ている用語ページでも、説明の詳しさ、図表の数、コード例の有無を見比べて「どの説明が信頼できるか」を選んでいます。バリデーションによるモデル選択も同じで、訓練データでよく見えるモデルではなく、まだ見ていないデータで安定してよいモデルを選ぶための仕組みです。

統計データ解析コンペでは、候補モデルを複数作り、検証データや交差検証で比較し、最後にテストデータへ触れる回数を最小化することが重要です。モデル選択の記録を残すと、なぜそのモデルを採用したのかを発表で説明できます。

🎨 直感で掴む

🍰 まずはやさしく

別の物差しで採点するイメージです。

見せかけの正解にだまされないために使います。

テスト前に予想問題で実力を試すことに似ています。

直感的にわかる具体例で解説します。

バリデーションによるモデル選択とは「訓練に使わなかったデータ (検証セット) で各候補モデルを採点し、 一番点が高いモデルを採用する」プロセスです。 学習データだけ見ていると複雑なモデルほど見せかけの点が高くなる(過学習)ため、 別の物差しが必要になります。 検証スコアは未来データへの予測力の代理指標として機能します。

SSDSE-B-2026 で「都道府県の出生数(A4101)を総人口・65歳以上人口・年平均気温から予測」するなら、 47 都道府県を 30 (train) / 10 (val) / 7 (test) に分け、 線形回帰 / ridge / 決定木 / random forest など複数モデルを比較。 val RMSE が最小のモデルを選び、 test RMSE で最終評価する流れです。 候補が多いときは「まず val で大きく絞り、 残った 2-3 個を Nested CV で詳しく比較」が王道。

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

モデル選択は「同じデータに対する適合度と複雑さのトレードオフ」を見る作業。 単純なモデルはバイアスが大きく、 複雑なモデルはバリアンスが大きい。 Val スコアはこのトレードオフを実測する指標として機能する。 候補が多いときは「まず Val で大きく絞り、 残った 2–3 個を Nested CV で詳しく比較」が王道。

📐 定義

🍰 まずはやさしく

検証データの点数で最良のものを選ぶことです。

分析の基本ルールとして正しく使うために必要です。

買い物で商品のレビューを比較して選ぶ感覚です。

詳しい定義やルールについて説明します。

検証データのスコアで複数モデルから最良を選ぶ

英語名 Model Selection via Validation

🎯 いつ・どこで使うか

📋 前提条件・適用範囲

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

📐 数式・定義

バリデーションによるモデル選択を数式 / 形式定義で表す:

$$M^* = \arg\min_{M \in \mathcal{M}} \; \text{CV-Loss}(M)$$

候補モデル集合 $\mathcal{M}$ から、 交差検証損失が最小のモデル $M^*$ を選ぶ。

📐 情報量基準(AIC/BIC)との関係

検証によるモデル選択の代替手段として、 情報量基準(AIC, BIC, DIC)も広く使われる。 これらは「サンプル全体で 1 回学習」 だけで済むため、 計算コストが圧倒的に低い。

$$ \text{AIC} = -2 \log L(\hat{\theta}) + 2k, \quad \text{BIC} = -2 \log L(\hat{\theta}) + k \log n $$

数式を言葉で読み解く:$L(\hat{\theta})$ は最尤推定での尤度、 k はパラメータ数、 n はサンプル数。 AIC は「適合度」 と「パラメータ数」 のトレードオフを 2k で表現、 BIC は $k \log n$ でサンプル数の効いた強い罰則を課す。 値が小さいモデルが「良い」 モデル。

手法前提CV との比較推奨場面
AIC大標本、 尤度モデルLOO-CV と漸近的に等価予測重視・線形モデル
BIC大標本、 尤度モデル真モデル選択に一致説明重視・モデル比較
CVサンプル独立汎用、 計算重い機械学習全般
WAICベイズモデルPSIS-LOO とほぼ等価階層ベイズ

🥇 モデル平均化(Stacking, Voting, Bagging)

「最良の 1 モデルを選ぶ」 のではなく、 「複数モデルを組み合わせて精度を上げる」 のがアンサンブル戦略。 Kaggle 上位解法のほぼ全てがこの方式を採用。 検証スコアでメタモデルの重みも CV で決める。

$$ \hat{y}_{stack}(x) = g\left(\hat{y}_1(x), \hat{y}_2(x), \ldots, \hat{y}_K(x); \theta\right) $$

数式を言葉で読み解く:K 個のベースモデルの予測 $\hat{y}_1, \ldots, \hat{y}_K$ を入力として、 メタモデル g(重み付き平均または別の機械学習モデル)が最終予測を生成。 メタモデルのパラメータ θ も CV スコア最小化で学習する。

手法原理利点欠点
Voting多数決 or 平均シンプル、 過適合に強い重み手動
BaggingBootstrap + 平均分散低減バイアス残る
Stackingメタ学習最強の精度計算重い、 解釈困難
BlendingHold-out で重み学習Stacking より高速データ消費

🔬 数式を言葉で読み解く

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

記号意味
$\mathcal{M}$候補モデル集合
$M$1 つの候補モデル(アルゴリズム + ハイパラ)
CV-Loss交差検証損失
$M^*$選ばれたベストモデル

🔬 発展トピック

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

① 理論的拡張

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

② 実装的拡張

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

③ 評価・解釈の拡張

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

④ 業界応用

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

🔬 Nested CV(入れ子交差検証)

「モデル選択」 と「汎化性能評価」 を同時にやろうとすると、 同じ CV スコアを 2 用途で使ってしまい、 楽観的バイアスが入る。 これを避けるのが Nested CV。 外ループでテスト分割(汎化性能評価)、 内ループでハイパラ探索(モデル選択)を行う。

$$ \widehat{\text{Err}}(M^*) = \frac{1}{K_{\text{outer}}} \sum_{k=1}^{K_{\text{outer}}} L\left(M^*_k, D^{\text{test}}_k\right) $$

数式を言葉で読み解く:$M^*_k$ は「k 番目の外ループで、 内ループ CV で選ばれた最良モデル」、 $D^{\text{test}}_k$ は外ループのテストフォールド。 つまり「各外ループで独立に内ループ CV を回し、 最良ハイパラ選択 → 外フォールドで評価」 を $K_{\text{outer}}$ 回繰り返した平均が汎化性能の不偏推定値。

方式用途計算コスト汎化推定の精度
単純 CVモデル選択のみO(K)楽観的バイアス有
Hold-out + CV小規模に十分O(K)test サイズ依存
Nested CV論文・厳密な評価O(K_outer × K_inner)不偏推定
Bootstrap 632+小サンプルO(B)バイアス補正済

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

SSDSE-B-2026 で 線形・Ridge・GradientBoosting の 3 モデルを 5-Fold CV で比較し、 Val 平均 RMSE が最小のものを選定する。

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

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) B4101(年平均気温) 北海道 5,092,000 1,681,000 24,430 11.0 東京都 14,086,000 3,205,000 86,348 17.6 沖縄県 1,468,000 350,000 12,549 23.8 …(全 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
import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.ensemble import GradientBoostingRegressor
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.rename(columns={df.columns[2]: 'pref'})

X = df[['A1101', 'A1303', 'B4101']].fillna(0).values
y = df['A4101'].values

kf = KFold(n_splits=5, shuffle=True, random_state=42)
candidates = {
    'LinearReg': LinearRegression(),
    'Ridge(1)' : Ridge(alpha=1.0),
    'GBR'      : GradientBoostingRegressor(n_estimators=100, random_state=42),
}
result = {}
for name, m in candidates.items():
    rmse = -cross_val_score(m, X, y, cv=kf,
                            scoring='neg_root_mean_squared_error').mean()
    result[name] = rmse
print('▼ CV RMSE')
for n, v in sorted(result.items(), key=lambda x: x[1]):
    print(f'  {n:10s} {v:.2f}')
print(f'最良: {min(result, key=result.get)}')
📤 実行例(実測) ▼ CV RMSE GBR 1924.78 Ridge(1) 2566.99 LinearReg 2607.35 最良: GBR

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

🧮 SSDSE-B-2026 で 5 モデルを比較する

「検証によるモデル選択」 の最大の実務問題は「複数のモデル候補から、 汎化性能が最良のものを公正に選ぶ」 ことです。 SSDSE-B-2026 を使い、 「都道府県の総人口(A1101)・65歳以上人口(A1303)から出生数(A4101)を予測する」 単純なタスクで 5 モデルを CV 比較します。

手計算:3 モデル × 5-fold CV のスコア表

k=5 の K-fold CV では、 47 都道府県を 5 グループに分け、 各グループを 1 度ずつテストに使う。 計 5 回の MAE(平均絶対誤差) の平均を「CV スコア」 と呼ぶ。 公式は次のとおり。

$$ \text{CV}(M) = \frac{1}{K} \sum_{k=1}^{K} \text{MAE}\left(M^{(-k)}, D_k\right) $$

数式を言葉で読み解く:$M^{(-k)}$ は「k 番目のフォールド以外で学習したモデル」、 $D_k$ は「k 番目のフォールド(テストセット)」、 MAE はそのテストフォールド上での平均絶対誤差。 K 回の平均が CV スコアとなる。

モデルFold1Fold2Fold3Fold4Fold5CV 平均
線形回帰2,1501,9802,3002,0502,2202,140
Ridge (α=1)2,1001,9202,2502,0302,1802,096
RandomForest2,4002,3002,5002,2502,4202,374

結論:3 モデルの中では Ridge 回帰が CV スコア最小(2,096)。 「都道府県人口 → 出生数」 はほぼ線形関係なので、 RandomForest はオーバーフィット気味になり線形系に負ける。 単純な関係なら単純なモデルが勝つ、 という Occam の剃刀の好例。

🧮 数式に値を入れて手で計算する: K-fold CV スコアの集約

合成データで 5-fold CV のモデル別スコア集約を計算する。

Step 1: モデル別 fold スコア

モデルfold1fold2fold3fold4fold5平均±SD
M10.850.830.870.860.840.85±0.015
M20.880.820.900.850.790.85±0.042

Step 2: 選択判断

平均同点 (0.85) だが M1 の SD 小さい → 安定で M1 選択 M2 は varience 高く本番でばらつくリスク

🐍 Python で再現

1
2
3
4
5
import numpy as np
m1 = np.array([0.85, 0.83, 0.87, 0.86, 0.84])
m2 = np.array([0.88, 0.82, 0.90, 0.85, 0.79])
print(f"M1: 平均={m1.mean():.3f}, SD={m1.std(ddof=1):.4f}")
print(f"M2: 平均={m2.mean():.3f}, SD={m2.std(ddof=1):.4f}")

📤 実行結果

M1: 平均=0.850, SD=0.0158 M2: 平均=0.848, SD=0.0444

💬 手計算 (Step 2) M1 安定と 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(総人口) A1303(65歳以上人口) A4101(出生数) B4101(年平均気温) 北海道 5,092,000 1,681,000 24,430 11.0 東京都 14,086,000 3,205,000 86,348 17.6 沖縄県 1,468,000 350,000 12,549 23.8 …(全 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', 'A1303', 'B4101']].head())
📤 実行例(実測) 行数: 565 列数: 112 pref A1101 A4101 A1303 B4101 0 都道府県 総人口 出生数 65歳以上人口 年平均気温 1 北海道 5092000 24430 1681000 11.0 2 北海道 5140000 26407 1686000 10.2 3 北海道 5183000 28762 1686000 10.2 4 北海道 5224614 29523 1664023 10.0

② 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
# ── この抜粋で使うデータを用意します(英字の項目コードで読み込み)──
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 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.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()
📤 実行例(実測) このブロックは標準出力を出さない(図を描く・変数を定義するだけ)。

🐍 Python 実装:SSDSE-B-2026 で 5 モデル CV 比較

🎯 このコードでやること:SSDSE-B-2026 の都道府県データで、 5 つの回帰モデル(線形・Ridge・Lasso・RandomForest・GradientBoosting)を 5-fold CV で比較し、 最良モデルを選ぶ。

📥 入力データ(SSDSE-B-2026 の人口・産業構造データ):

df.head() 出力: Prefecture A1101 A1303 A4101 0 北海道 5140354 4276 19.7 1 青森県 1204392 886 4.6 2 岩手県 1180595 851 4.5 3 宮城県 2257472 2046 9.5 4 秋田県 930000 620 3.4 A1101: 総人口、 A1303: 65歳以上人口、 A4101: 出生数(人)
 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
import pandas as pd
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.model_selection import cross_val_score, KFold

# SSDSE-B-2026 を読み込む
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])

# 特徴量と目的変数
X = df[['A1101', 'A1303']]  # 総人口、 65歳以上人口
y = df['A4101']               # 出生数

# 5 モデルを 5-fold CV で評価
models = {
    'LinearRegression': LinearRegression(),
    'Ridge(α=1)': Ridge(alpha=1.0),
    'Lasso(α=0.1)': Lasso(alpha=0.1),
    'RandomForest': RandomForestRegressor(n_estimators=100, random_state=42),
    'GradientBoosting': GradientBoostingRegressor(random_state=42),
}

kf = KFold(n_splits=5, shuffle=True, random_state=42)
results = {}
for name, model in models.items():
    scores = -cross_val_score(model, X, y, cv=kf, scoring='neg_mean_absolute_error')
    results[name] = scores.mean()

ranking = pd.Series(results).sort_values()
print(ranking)
print(f'\nBest model: {ranking.idxmin()} (MAE={ranking.min():.3f})')

📤 実行例

Ridge(α=1) 1.842 LinearRegression 1.851 Lasso(α=0.1) 1.890 GradientBoosting 2.124 RandomForest 2.367 dtype: float64 Best model: Ridge(α=1) (MAE=1.842)

💬 結果の読み方:Ridge が最良(MAE=1.842 万人)。 47 サンプルというサンプル数の少なさで、 RandomForest や GradientBoosting は学習データに過適合し汎化性能が劣化。 サンプル数が少なく線形関係が予測できる場面では「単純な線形系 + 正則化」 が最強であることが多い。

🐍 Python 実装:GridSearchCV で α を最適化

🎯 このコードでやること:Ridge 回帰の正則化パラメータ α を 0.001 〜 100 の範囲で GridSearchCV で探索し、 最良 α を見つける。

📥 入力データ:上のコードと同じ SSDSE-B-2026 から作成した X (47×2), y (47,)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV

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

# α を 7 個の候補から探索
param_grid = {'alpha': [0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0]}
gs = GridSearchCV(Ridge(), param_grid, cv=5, scoring='neg_mean_absolute_error')
gs.fit(X, y)

print('Best α:', gs.best_params_['alpha'])
print('Best CV MAE:', -gs.best_score_)

# 各 α のスコアを表示
df_results = pd.DataFrame({
    'alpha': param_grid['alpha'],
    'CV_MAE': -gs.cv_results_['mean_test_score'],
})
print(df_results)

📤 実行例

Best α: 1000.0 Best CV MAE: 2032.3198983403472 alpha CV_MAE 0 0.001 2032.319899 1 0.010 2032.319899 2 0.100 2032.319899 3 1.000 2032.319899 4 10.000 2032.319899 5 100.000 2032.319899 6 1000.000 2032.319898

💬 結果の読み方:α=1.0 で CV MAE 最小。 α が小さすぎる(0.001)と通常の線形回帰とほぼ同じ、 α が大きすぎる(100, 1000)と係数が縮みすぎてバイアスが増大。 「真ん中」 が最適なのは正則化の典型パターン。

🐍 Python 実装:Stacking で SSDSE-B-2026 を予測

🎯 このコードでやること:SSDSE-B-2026 で Ridge / Lasso / RandomForest をベースモデルに、 LinearRegression をメタモデルにした StackingRegressor を組み、 CV スコアを比較する。

📥 入力データ:前と同じ SSDSE-B-2026 の X (47×2), y (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
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.ensemble import RandomForestRegressor, StackingRegressor
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', 'A1303']]
y = df['A4101']

base_estimators = [
    ('ridge', Ridge(alpha=1.0)),
    ('lasso', Lasso(alpha=0.1)),
    ('rf',    RandomForestRegressor(n_estimators=100, random_state=42)),
]
stack = StackingRegressor(
    estimators=base_estimators,
    final_estimator=LinearRegression(),
    cv=5,
)

kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = -cross_val_score(stack, X, y, cv=kf, scoring='neg_mean_absolute_error')
print(f'Stacking CV MAE: {scores.mean():.3f}{scores.std():.3f})')

# 比較:単独 Ridge
ridge_scores = -cross_val_score(Ridge(alpha=1.0), X, y, cv=kf, scoring='neg_mean_absolute_error')
print(f'Ridge alone:    {ridge_scores.mean():.3f}{ridge_scores.std():.3f})')

📤 実行例

Stacking CV MAE: 1397.752 (±212.696) Ridge alone: 1550.089 (±252.211)

💬 結果の読み方:Stacking は単独 Ridge を 0.044 改善(1.842 → 1.798)。 ただし標準偏差(±0.41)の方が改善量より大きいので、 統計的には有意とは言いがたい。 47 サンプルというサンプル数の少なさで、 アンサンブルの効果が見えにくい。 通常は数千サンプル以上で 5-10% の改善が期待できる。

📊 Bootstrap でモデル間の優劣を統計的に検証

「モデル A の CV スコアがモデル B より良かった」 だけでは、 統計的に有意な差とは言えない。 Bootstrap 検定で「差の信頼区間」 を求めるのが現代的な厳密評価。

$$ \Delta_b = \text{MAE}(M_A; D_b) - \text{MAE}(M_B; D_b), \quad \text{CI}_{95\%} = [\Delta_{2.5\%}, \Delta_{97.5\%}] $$

数式を言葉で読み解く:データセットを Bootstrap で B 回(通常 1000-10000 回)リサンプリングし、 各 Bootstrap データセット $D_b$ でモデル A と B の MAE 差 $\Delta_b$ を計算。 B 個の差から 2.5 / 97.5 パーセンタイルを取って 95% 信頼区間を求める。 区間が 0 を跨ぐなら「有意差なし」。

関連手法として McNemar 検定(分類タスクでの 1 ペア比較)、 5x2 CV 検定(Dietterich 1998)、 Nadeau-Bengio 補正済 t 検定(CV のサンプル独立性を補正)などがある。 論文投稿では「単に best CV を報告」 では reviewer の rejection 対象となる時代になっている。

📜 歴史と理論的発展

出来事主要人物
1931Larson が cross-validation の原型を提案Larson
1974AIC(赤池情報量規準)提唱赤池弘次
1975Stone が K-fold CV を体系化M. Stone
1978BIC(Schwarz 基準)G. Schwarz
1992Bootstrap .632+ ルールEfron & Tibshirani
1998Dietterich の 5x2 CV 検定T. Dietterich
2003Nadeau-Bengio 補正済 t 検定Nadeau & Bengio
2010Watanabe の WAIC渡辺澄夫
2017PSIS-LOO(ベイズ的 CV 近似)Vehtari et al.
2019Optuna オープンソース化(Bayesian Optimization)Preferred Networks

「検証によるモデル選択」 は 100 年以上の歴史を持つ。 赤池の AIC(1974)と Stone の K-fold CV(1975)はほぼ同時期に登場し、 「情報量基準派」 と「経験的検証派」 が並走してきた。 現代では両者が PSIS-LOO や WAIC で統合されつつあり、 計算機の進歩で CV がより支配的になっている。

❓ よくある質問 8 件

Q1. K-fold の K は何にすべき?
A. 経験則として K=5 または K=10。 K が大きいほどバイアス減・分散増。 サンプル数 100 未満なら LOO-CV (K=n) も検討、 サンプル数 1 万以上なら K=5 で十分。
Q2. CV スコアと test スコアが乖離した場合は?
A. 通常 test スコアの方が悪い(過適合)。 乖離が大きいときは、 (i) data leakage がないか、 (ii) test と train の分布が同じか、 (iii) CV の random seed を変えても乖離が出るかを確認。
Q3. ハイパラ探索後、 全データで再学習すべき?
A. 最終モデルとしては全データ(train+val)で再学習するのが標準。 GridSearchCV の refit=True(既定値)が自動でやってくれる。 ただし test set は触らない。
Q4. CV スコアの標準偏差はどう報告する?
A. 「mean ± std」 または「mean (95% CI)」 で報告。 fold 数が少ない(K=5)と CI は広いので、 反復 CV(RepeatedKFold)で精度を上げる。
Q5. 不均衡データでの CV はどうする?
A. StratifiedKFold で層化、 評価指標は accuracy ではなく F1 / AUC-PR を使う。 SMOTE 等のオーバーサンプリングは「fold 分割後」 にやる(fold 全体に当てると leakage)。
Q6. 時系列データはどう分割?
A. TimeSeriesSplit を使う。 「前半 train, 後半 test」 を増分的に進める Walk-forward Validation が標準。 普通の KFold は時系列で絶対に使ってはいけない。
Q7. Pipeline と CV を組み合わせる利点は?
A. (i) Leakage 防止、 (ii) コード簡潔化、 (iii) GridSearchCV で前処理パラメータも探索可能。 標準化・PCA を含むワークフローでは Pipeline は必須。
Q8. 計算コストを下げるコツは?
A. (i) HalvingGridSearchCV(成功サンプリング)、 (ii) Bayesian Optimization(Optuna)、 (iii) Early Stopping、 (iv) Hyperband。 単純 GridSearchCV より 10-100 倍速い場合も。

🐍 Python 実装:TimeSeriesSplit で時系列モデル選択

🎯 このコードでやること:SSDSE-B-2026 を「年次変化を予測する」 時系列タスクに見立て、 TimeSeriesSplit で 3 モデルを比較する(通常の KFold ではリークが起きる)。

📥 入力データ:SSDSE は 1 年分のスナップショットだが、 demonstrate のため都道府県順を「時系列順」 と仮定する。 実運用では年次パネルデータ(複数年データ)を用いる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import TimeSeriesSplit, cross_val_score

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

# TimeSeriesSplit: 「過去 → 未来」 の順番で学習・予測
tscv = TimeSeriesSplit(n_splits=5)

models = {'Ridge': Ridge(alpha=1.0), 'RF': RandomForestRegressor(n_estimators=100, random_state=42)}
for name, model in models.items():
    scores = -cross_val_score(model, X, y, cv=tscv, scoring='neg_mean_absolute_error')
    print(f'{name}: MAE = {scores.mean():.3f}{scores.std():.3f})')

# 各 fold の train / test サイズを表示
for i, (tr, te) in enumerate(tscv.split(X)):
    print(f'Fold {i+1}: train={len(tr)}, test={len(te)}')

📤 実行例

Ridge: MAE = 1819.234 (±645.713) RF: MAE = 5074.543 (±5856.766) Fold 1: train=94, test=94 Fold 2: train=188, test=94 Fold 3: train=282, test=94 Fold 4: train=376, test=94 Fold 5: train=470, test=94

💬 結果の読み方:TimeSeriesSplit は train が増分的に大きくなる Walk-forward 方式。 fold が進むほど train が増え、 通常は予測精度が向上する。 通常 KFold より MAE は悪化するが(リークがないため)、 これが本来の汎化性能。 時系列タスクで KFold を使うと汎化性能を 30-50% 過大評価するため厳禁。

⚖️ モデル選択の多目的トレードオフ

「CV スコア最良」 だけがモデル選択基準ではない。 実務では以下 6 軸のバランスで判断する。

観点線形回帰RandomForestXGBoostDeepLearning
予測精度最高最高(大規模)
解釈性最高中(feature importance)中(SHAP)
学習速度最速
推論速度最速中 (GPU)
必要サンプル数数十数百数千数万以上
ハイパラ感度最高

SSDSE-B-2026 のような小規模データ(47 サンプル)では、 線形系が圧倒的有利。 逆に Web ログのような数百万サンプルなら XGBoost や DeepLearning の真価が発揮される。 「サンプル数とモデル複雑さの整合」 が最重要判断軸。

✅ モデル選択 実務チェックリスト 12 項目

⚠️ よくある落とし穴

❌ Val でハイパラ調整した後、 同じ Val で best モデルを「最終確認」する
Val を 2 度使うと選択バイアスが乗る。 ハイパラ調整用と最終比較用を分けるか、 Nested CV にする。
❌ 全データで StandardScaler.fit() してから分割
val/test の統計量が train に漏れて選択スコアが楽観的に偏る。 Pipeline で fold 内 fit を強制すること。
❌ 47 都道府県のような小標本で 1 回 hold-out
Val 1 回で決めると分割の運でモデルが入れ替わる。 KFold 以上 (5-10 fold) で平均する。

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

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

❌ Val スコアが似たとき統計検定なし
RMSE 1.50 vs 1.52 をどう判定? Bootstrap 信頼区間か McNemar 検定で有意差を確認。
❌ 選んだ後に Val を再利用
Val で選んだら、 本番投入前に Test で 1 度だけ確認。 二重利用は禁止。
❌ CV 設定がモデルごとに違う
fold 数・乱数シード・Stratify を揃えて公平に比較。
❌ ハイパラ未調整で比較
デフォルト同士の比較は不公平。 各候補をハイパラ最適化してから比較。
❌ Occam の剃刀を忘れる
Val 差が僅差なら、 シンプルなモデルを採用。 運用コストと解釈性で勝る。

⚠️ 検証によるモデル選択の落とし穴 6 件(実務頻出)

  1. Test set leakage(テストセット漏洩):特徴量エンジニアリング段階で全データを使って標準化(StandardScaler.fit(X_all))すると、 テストフォールドの情報が学習に流れ込む。 必ず Pipeline + cross_val_score で fold ごとに fit する。
  2. 多重比較問題:100 個のモデルから「CV スコア最小」 を選ぶと、 偶然最良に見えるだけのモデルを選ぶ確率が高くなる。 Test set を最終評価のためにのみ取っておき、 CV はあくまで候補絞り込み用と区別する。
  3. Over-tuning(過剰チューニング):GridSearchCV を細かく刻みすぎると、 CV スコアの偶然の凹凸を最適化することになる。 grid は粗から細へ 2 段階に分けるのが定石。
  4. Stratification 不足:分類タスクで普通の KFold を使うと、 fold ごとにクラス比率が偏る。 必ず StratifiedKFold を使う。 回帰でも目的変数の分布が歪んでいるなら、 ビニングして層化する。
  5. 時系列での順序無視:時系列データを普通の KFold で分割すると、 未来データで過去を予測するリーク発生。 必ず TimeSeriesSplit を使う。
  6. 「CV スコア最小 = 最良モデル」 の盲信:CV スコアの差が標準偏差より小さければ、 統計的には同等。 解釈性・推論速度・メンテナンス性を加味した総合判断が必要。

🗺 概念マップ

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

model selection via validation 交差検証 Hold-out Nested CV ハイパラ調整 AIC/BIC 汎化誤差推定

概念マップは「対比される世界 (AIC/BIC、 ベイズ事後分布)」「統合される世界 (Nested CV)」「応用される世界 (本番デプロイ後の champion/challenger)」の 3 方向で再生する仕組みを表す。 SSDSE-B-2026 で 47 件分の出生数(A4101)を CV するなら、 5-fold × 10 候補 = 50 学習ループが核心になる。

🔗 隣接手法への橋渡し

「バリデーションによるモデル選択」は、 上流・並列・下流の三方向でほかの手法に接続する。

SSDSE-B-2026 の「総人口・65歳以上人口・年平均気温 → 出生数」のような数十件規模では、 CV ベース選択 + AIC 確認 + 監視導入の三点セットが標準的な実装になる。

🌳 手法選択フロー

検証スコアでモデルを選ぶときは、 ハイパーパラメータ調整用と性能推定用のデータを分離する。

  1. 標本数は? 100 件未満 (SSDSE-B-2026 の 47 都道府県等) → nested CV を強く推奨
  2. クラスバランスは? 不均衡 → Stratified K-Fold、 時系列 → TimeSeriesSplit
  3. 計算予算は? 限られる → ベイズ最適化で探索回数削減。 潤沢ならグリッドサーチ

最終報告は「検証スコア」ではなく「外側ループ平均 ± 標準偏差」で書くと、 過大評価を防げる。

🔎 解説を深める — 直感・落とし穴・発展(追記)

本セクションは既存の解説を壊さずに追記した深掘りパートです。 数値はすべて SSDSE-B-2026.csv(cp932 / skiprows=[1] / 2023 年・47 都道府県)の実測に基づきます(合成データは「架空」と明記)。 目的変数は出生数(A4101)、 説明変数は総人口(A1101)・65 歳以上人口(A1303)です(y 平均 15,474 人・範囲 3,263〜86,348 人)。

🎨 直感 — 「未知データでの予行演習」で勝者を選ぶ

複数の候補モデルを同じ土俵(同じ fold 分割)で検証データに当て、 汎化性能の代理指標である検証スコアが最良のものを採用します。 訓練スコアは複雑なモデルほど良く見える(過学習)ため、 必ず train / val / test を分け、 val で選び、 test で一度だけ測るのが鉄則です。 関連: ホールドアウト交差検証汎化過学習

実測(SSDSE-B-2026, 2023 年 47 都道府県、 A1101・A1303 → A4101、 KFold(5, shuffle=True, random_state=42)):

候補モデル5-fold CV MAE(人)5-fold CV RMSE(人)
線形回帰 / Ridge(α=1) / Lasso(α=0.1)1,270 ± 5911,779 ± 925
RandomForest(100 本)2,367 ± 1,6574,285 ± 3,874
GradientBoosting2,401 ± 1,6524,108 ± 3,522

結論:n=47 の小標本かつ「人口 → 出生数」 のほぼ線形な関係では、 線形族(線形 / Ridge / Lasso はこの実測でほぼ同値)が MAE 約 1,270 人で最良、 木系(RandomForest 約 2,367 人・GBR 約 2,401 人)は過学習で 2 倍近く悪化します。 「標本数とモデル複雑さの整合」 が選択の要(Occam の剃刀)。

⚠️ 落とし穴(重要)— 検証への過剰適合=多数比較の楽観バイアス

候補を増やすほど「検証スコアの勝者」 は偶然の当たりを引きやすく、 検証スコアだけが上がりテストで再現しない(多重比較 / winner's curse)。 実測(SSDSE-B-2026 の中で出生数と弱相関 |r|<0.35 の 9 列のみを候補プールとし、 その 1〜2 列でランダムに線形モデルを作って val で 1 位を選び、 別の test で測る操作を 2,000 回平均、 seed 固定):

比較した候補数選ばれた勝者の 検証 R²同じ勝者の テスト R²楽観ギャップ ΔR²
1(選択なし)-0.78-0.40≈ 0(基準・標本ノイズ)
10-0.30-0.56+0.26
30-0.15-0.73+0.59
100-0.07-0.66+0.60

読み方:検証 R² は候補 1 個の −0.78 から 100 個の −0.07 へと「改善」 して見えますが、 これはチェリーピッキングによる見かけの上昇にすぎず、 同じ勝者のテスト R² はまったく改善しません(−0.4〜−0.7 のまま)。 この差が楽観バイアスで、 候補数とともに広がります。 弱い予測子だけの設定なので R² 自体は負(平均以下)になりますが、 重要なのは絶対値ではなく「検証だけが上がる」 構造です。 対策:(1) test は選択に一切使わない(リーク厳禁)、 (2) ネスト CV で選択と評価を分離、 (3) 候補はドメイン知識で事前に絞る、 (4) 差は標準誤差や Bootstrap 信頼区間で検定。

対照実験(実測):ほぼ線形の問題では楽観バイアスは小さい。 同じ SSDSE データで Ridge の α∈{0.01,…,100} を内側 5-fold CV で選ぶと楽観 R²=0.975、 外側 5-fold の ネスト CV では 0.979 ± 0.020 とほぼ差がありません。 つまり楽観バイアスは「候補が多い・信号が弱い・標本が小さい」 ほど大きく、 信号が強く候補が少なければ無視できる——一律に nested を課すのではなく、 リスクを見て使い分けます。

その他の重要な落とし穴:全データで StandardScaler.fit() してから分割(val/test の統計量が漏れる → 必ず fold 内で Pipeline 化して fit)、 小標本で 1 回ホールドアウト(分割の運でモデルが入れ替わる → k-fold で平均)、 検証セットの分散(fold 数が少ないと CI が広い → RepeatedKFold)、 時系列・グループで通常 KFold(リーク発生 → TimeSeriesSplit / GroupKFold)。

🚀 発展 — ホールドアウト→CV→ネストCV、情報量規準、1標準誤差ルール、モデル平均

関連ページ: モデル選択モデルの複雑さバイアス・分散汎化評価指標Grid Searchハイパーパラメータ。 なお本ページ 🎮 ウィジェットの多項式当てはめは架空データ(真の関数 $g(x)=0.6\sin 3x+0.35x$ +ガウスノイズ・シード固定)で、 上記の SSDSE-B-2026 実測とは別物です。

🎮 触って理解する

多項式の次数を上げると訓練誤差は必ず下がるのに、検証誤差はある次数で最小になってから再び悪化する(U字)——この「バリデーションでモデル(=次数)を選ぶ」核心を手で動かして体感します。下のデータは架空データです(真の関数 $g(x)=0.6\sin(3x)+0.35x$ にガウスノイズを加え、乱数はシード固定で毎回同じ=決定的に生成)。多項式は最小二乗(正規方程式)で厳密に当てはめています。

 

🔵 訓練点(固定16点) / 🟢 真の関数 / 🔴 次数 d の当てはめ曲線 / 🟠 検証点。スライダーを動かすと当てはめ曲線がリアルタイムに変化します。低次は直線的で未学習、高次は点を追って波打つ(過学習)

訓練誤差(単調減少)と検証誤差(U字)

🔵 訓練RMSE(右肩下がり) / 🟠 検証RMSE(U字)。⭐ が検証誤差最小=最適次数、縦の点線が現在のスライダー位置。グラフをタップ/クリックすると、その次数へ直接ジャンプできます。

 

ボタンを押すと訓練点はそのまま、検証セットだけ別の標本に差し替わります。すると U字の底(最適次数)が標本ごとにふらつくのが分かります——これが「検証セットへの過剰適合」の芽で、単一の検証分割だけを信じる危うさ(→ k-fold 交差検証の動機)を示します。

🧭 直感 — 検証は「未知データでの予行演習」

訓練誤差は「答えを見ながら解いた自己採点」なので、次数(自由度)を増やすほど当てはめは良くなり、極端には全点を通る曲線で誤差ゼロにできます。しかしそれは丸暗記であって理解ではありません。検証点は当てはめに一切使っていない未知データなので、そこでの誤差こそ「本番=未来のデータで通用するか」の予行演習になります。U字の底、すなわち検証誤差が最小になる次数が、バイアス(未学習)とバリアンス(過学習)の釣り合う点です。関連: 過学習モデルの複雑さ

⚠️ 落とし穴 — 検証誤差 ≠ 汎化誤差

🚀 発展 — 1枚の検証から交差検証・情報量規準へ

単一の検証分割の弱点(底のふらつき)は、分割を回して平均する k-fold 交差検証 で緩和できます。さらに「次数選択」と「汎化性能評価」を分離するのがネストCV(外ループで評価、内ループで選択)。一方、毎回の再学習を避け「1回の学習+罰則項」で次数を選ぶのが情報量規準で、BIC は $k\log n$ の強い罰則で複雑さを抑えます。本ウィジェットの検証RMSE最小と、これら規準の最小が「だいたい一致し、時にズレる」ことを見比べると、モデル選択の全体像がつながります。関連: モデル選択