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

🔖 キーワード索引

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

各章へ直接移動できる:

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

💡 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 モデルを選ぶ」 のではなく、 「複数モデルを組み合わせて精度を上げる」 のがアンサンブル戦略。 検証スコアでメタモデルの重みも 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 より高速データ消費

🔬 数式を言葉で読み解く

上の $M^* = \arg\min_{M \in \mathcal{M}} \text{CV-Loss}(M)$ に出てくる記号:

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

🔬 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
28
29
30
31
32
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
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

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

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

kf = KFold(n_splits=5, shuffle=True, random_state=42)
# 総人口(数百万)と気温(十数度)は桁が 5〜6 桁違う。そのままだと
# LinearRegression の最小二乗が気温の係数をほぼ 0 に潰すので、先に標準化する
candidates = {
    'LinearReg': make_pipeline(StandardScaler(), LinearRegression()),
    'Ridge(1)' : make_pipeline(StandardScaler(), 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 LinearReg 1487.49 Ridge(1) 2070.49 GBR 4136.64 最良: LinearReg

💬 2023 年度の 47 県で 5 分割 CV を回すと、標準化+線形回帰の RMSE が 1,487 人で最も小さく、Ridge(1) は 2,070 人、GBR は 4,137 人と線形回帰の約 2.8 倍悪い。人口から出生数への関係はほぼ直線なので、木を 100 本重ねる GBR は 37〜38 県の学習データに過剰に合わせてしまう。Ridge が線形回帰より悪いのは、総人口と 65 歳以上人口の相関が 0.99 を超えるほど強く、互いに逆向きに打ち消し合う 2 つの係数を alpha=1.0 の罰則が縮めてしまうためと考えられ、alpha も CV で選ぶのが筋。

▲ 上記コードはそのまま実行可能。 CP932 エンコーディング・skiprows=[1](2 行目の日本語名の行をスキップ)・列名の英数字コード(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,3668211,1606981,3061,270
Ridge (α=1)2,3878971,9241,1021,8191,626
RandomForest5,6621,3521,5661,4001,8542,367

結論:2023 年度の 47 都道府県を KFold(n_splits=5, shuffle=True, random_state=42) で分け、 線形回帰と Ridge は標準化してから当てはめた実測値(単位は人)。 3 モデルの中では線形回帰が CV スコア最小(1,270)で、 Ridge (α=1) は 1,626、 RandomForest は 2,367。 RandomForest は東京都を含む Fold1 だけ 5,662 と突出しており、 学習データの範囲を超える大きな県がテスト側に回ると木は外挿できず、 学習側の最大値付近で予測が頭打ちになる。 「都道府県人口 → 出生数」 はほぼ線形関係なので、 単純な関係なら単純なモデルが勝つ、 という Occam の剃刀の好例。 どの fold でも線形回帰が最小なので、 平均だけでなく fold ごとに見ても順位は崩れない。

同じ 5 分割で線形回帰・RandomForest・GradientBoosting の fold ごとの MAE を線で結んだ図。fold 1 で木の 2 モデルが約 5,660 人に跳ね上がり、線形回帰は全 fold で最小
上の表を fold 対応の線で描き、 GradientBoosting(random_state=42)も加えた。 CV 平均は線形回帰 1,270 人・RandomForest 2,367 人・GradientBoosting 2,401 人。 3 本の線はどの fold でも同じ順に並び、 線形回帰は 5 fold すべてで最小。 fold 1(秋田県・東京都・長野県・滋賀県・京都府・大阪府・兵庫県・香川県・福岡県・大分県)では木の 2 モデルがそろって約 5,660 人に跳ね上がる。 平均の差だけを見ると「どの分割でも負けているのか、 1 つの fold で大きく負けたのか」 が区別できないので、 同じ分割どうしを線で結んで確かめる。

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

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

Step 1: モデル別 fold スコア

モデルfold1fold2fold3fold4fold5平均±SD
M10.850.830.870.860.840.850±0.016
M20.880.820.900.850.790.848±0.044

Step 2: 選択判断

平均はほぼ同じ (M1 0.850、 M2 0.848) だが M1 の SD (0.016) は M2 (0.044) の約 1/3 → 安定な M1 を選択 M2 は fold 間のばらつき (variance) が大きく、 本番でもばらつくリスク

🐍 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

💬 Python の出力は M1 が平均 0.850・SD 0.0158、 M2 が平均 0.848・SD 0.0444 で、 Step 1 の表(ddof=1 の標本標準偏差を小数 3 桁に丸めた値)と一致する。 平均の差 0.002 は M2 の SD の 1/20 にも満たないので、 平均だけで順位を付けず、 ばらつきの小さい M1 を選ぶ根拠になる。

🐍 Python での扱い

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

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

📥 入力データ(SSDSE-B-2026 の 2023 年度・47 都道府県の人口データ):

df.head() 出力(2023 年度に絞った後): Prefecture A1101 A1303 A4101 0 北海道 5092000 1681000 24430 12 青森県 1184000 417000 5696 24 岩手県 1163000 407000 5432 36 宮城県 2264000 662000 12328 48 秋田県 914000 357000 3611 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
31
32
33
34
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
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

# SSDSE-B-2026 を読み込み、2023 年度の 47 都道府県に絞る
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]

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

# 5 モデルを 5-fold CV で評価
# Ridge / Lasso の罰則は係数の大きさに掛かるので、人口のような桁の大きい列は先に標準化する
models = {
    'LinearRegression': make_pipeline(StandardScaler(), LinearRegression()),
    'Ridge(α=1)': make_pipeline(StandardScaler(), Ridge(alpha=1.0)),
    'Lasso(α=0.1)': make_pipeline(StandardScaler(), 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})')

📤 実行例:

Lasso(α=0.1) 1270.255558 LinearRegression 1270.351689 Ridge(α=1) 1625.970421 RandomForest 2366.872244 GradientBoosting 2401.390226 dtype: float64 Best model: Lasso(α=0.1) (MAE=1270.256)

💬 結果の読み方:Lasso(α=0.1) の CV MAE 1,270.26 人が最小だが、 線形回帰の 1,270.35 人との差は 0.1 人にすぎず、 標準化後の係数(数千の大きさ)に対して α=0.1 の罰則がほとんど効いていないだけなので、 実質は同着と読む。 Ridge(α=1) が 1,626 人と 3 割近く悪いのは、 総人口と 65 歳以上人口がほぼ同じ動きをするため、 罰則が互いに打ち消し合う 2 つの係数を縮めてしまうから。 木系の RandomForest(2,367 人)と GradientBoosting(2,401 人)は、 学習用 37〜38 県の範囲を超える東京都などを外挿できず線形系の約 1.9 倍の誤差になった。

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

🎯 このコードでやること:Ridge 回帰の正則化パラメータ α を 0.001 〜 1000 の 7 候補から 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
24
25
26
27
28
29
30
31
import pandas as pd
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV, KFold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

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', 'A1303']]
y = df['A4101']

# 人口(数百万)のままだと α=1000 でも罰則が係数にほとんど効かないので、
# 標準化してから Ridge に渡す(標準化は fold ごとに学習側だけで fit される)
pipe = Pipeline([('sc', StandardScaler()), ('ridge', Ridge())])

# α を 7 個の候補から探索
param_grid = {'ridge__alpha': [0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0]}
kf = KFold(n_splits=5, shuffle=True, random_state=42)
gs = GridSearchCV(pipe, param_grid, cv=kf, scoring='neg_mean_absolute_error')
gs.fit(X, y)

print('Best α:', gs.best_params_['ridge__alpha'])
print(f'Best CV MAE: {-gs.best_score_:.1f}')

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

📤 実行例:

Best α: 0.1 Best CV MAE: 1239.0 alpha CV_MAE 0 0.001 1269.5 1 0.010 1261.8 2 0.100 1239.0 3 1.000 1626.0 4 10.000 2103.2 5 100.000 6799.9 6 1000.000 11135.6

💬 結果の読み方:α=0.1 で CV MAE が 1,239.0 人と最小になった。 α=0.001 の 1,269.5 人は罰則なしの線形回帰とほぼ同じで、 α=1 で 1,626.0 人、 α=100 で 6,799.9 人、 α=1000 では 11,135.6 人と、 係数が 0 に向かって縮むほど東京都のような大きな県の出生数を大きく外す。 ただし α=0.001〜0.1 の差は 30 人ほどで、 5 分割の選び方を変えれば順位が入れ替わりうる幅なので、 「0.1 以下ならどれでもほぼ同じ」 と読むのが安全。

🐍 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
28
29
30
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])
df = df[df['SSDSE-B-2026'] == 2023]   # 2023 年の 47 都道府県
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,
    # Ridge と Lasso の予測はほぼ同じ列になるので、制約なしの最小二乗だと
    # 巨大な正負の重みで打ち消し合う。スタッキングの重みは非負に制約する
    final_estimator=LinearRegression(positive=True),
    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: 1217.377 (±377.382) Ridge alone: 1270.352 (±590.552)

💬 結果の読み方:Stacking の CV MAE は 1,217 人で、 単独 Ridge の 1,270 人より 53 人(約 4%)小さい。 ただし fold ごとの MAE は Stacking が 756〜1,747、 Ridge が 698〜2,366 と散らばり、 標準偏差(±377 / ±591)が改善幅の 7 倍以上あるので、 47 県ではこの差を優劣とは言えない。 全データで当てはめ直したメタモデルの重みは ridge 0・lasso 0.68・rf 0.32 で、 ほぼ同じ予測を出す Ridge と Lasso のうち片方だけが残っている(重みを非負に制約しないと、 この 2 列が巨大な正負の重みで打ち消し合う)。

📊 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 のサンプル独立性を補正)などがある。

📜 歴史と理論的発展

年出来事主要人物
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-B-2026 は 2012〜2023 年度 × 47 都道府県 = 564 行の年次パネル。 年度の古い順に並べ替え、 1 fold = 1 年度(47 行)として 2019〜2023 年度を順に予測する。

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
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, KFold, cross_val_score

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
# CSV は県ごとに 2023 → 2012 の順で並ぶので、年度の古い順に並べ替える
df = df.sort_values(['SSDSE-B-2026', 'Code'], kind='stable').reset_index(drop=True)
X = df[['A1101', 'A1303']].values
y = df['A4101'].values
year = df['SSDSE-B-2026'].values

# TimeSeriesSplit: 「過去 → 未来」 の順番で学習・予測
# test_size=47 で 1 fold = 1 年度(47 都道府県)ずつ、2019〜2023 年度を順に予測する
tscv = TimeSeriesSplit(n_splits=5, test_size=47)
kf = KFold(n_splits=5, shuffle=True, random_state=42)   # 比較用: 年度を無視して混ぜる

models = {'Ridge': Ridge(alpha=1.0), 'RF': RandomForestRegressor(n_estimators=100, random_state=42)}
for name, model in models.items():
    ts = -cross_val_score(model, X, y, cv=tscv, scoring='neg_mean_absolute_error')
    kfs = -cross_val_score(model, X, y, cv=kf, scoring='neg_mean_absolute_error')
    print(f'{name:5s}: TimeSeriesSplit MAE = {ts.mean():7.1f} (±{ts.std():6.1f})'
          f'   KFold MAE = {kfs.mean():7.1f}')

# 各 fold の train / test の年度を表示
for i, (tr, te) in enumerate(tscv.split(X)):
    print(f'Fold {i+1}: train={len(tr)} ({year[tr].min()}-{year[tr].max()}), '
          f'test={len(te)} ({year[te].min()})')

📤 実行例:

Ridge: TimeSeriesSplit MAE = 2156.9 (± 335.0) KFold MAE = 1547.4 RF : TimeSeriesSplit MAE = 1518.4 (± 132.3) KFold MAE = 991.1 Fold 1: train=329 (2012-2018), test=47 (2019) Fold 2: train=376 (2012-2019), test=47 (2020) Fold 3: train=423 (2012-2020), test=47 (2021) Fold 4: train=470 (2012-2021), test=47 (2022) Fold 5: train=517 (2012-2022), test=47 (2023)

💬 結果の読み方:学習データは 2012〜2018 年度の 329 行から 2012〜2022 年度の 517 行へ増えていくが、 Ridge の fold 別 MAE は 2019 年度の 1,733 人から 2023 年度の 2,718 人へむしろ悪化する。 全国の出生数が 2012 年度の 103.7 万人から 2023 年度の 72.7 万人へ減り続けており、 過去の「人口あたり出生数」 で学んだモデルは新しい年度ほど多めに外すため。 年度を混ぜる KFold だと Ridge 1,547 人・RF 991 人と、 未来の年度を学習に使える分だけ 3 割前後甘く出る。 RF の方が良いのは、 同じ県の過去年度をほぼそのまま覚えて返せるからで、 この差がそのまま「未来の県」 に通用するとは限らない。

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

「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 件)

「バリデーションによるモデル選択」を実務・試験で扱うときに頻発する典型的なミスです。

❌ 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 スコアの差が標準偏差より小さければ、 統計的には同等。 解釈性・推論速度・メンテナンス性を加味した総合判断が必要。

✅ 理解度チェック(本ページの実測値で考える)

  1. Q1. 🐍 の 5 モデル比較では、 CV MAE が Lasso(α=0.1) 1,270.26 人、 線形回帰 1,270.35 人だった。 「Lasso が最良」 と報告してよいか。
    解答

    よくない。 差は 0.1 人で、 同じデータの Stacking の比較では fold ごとの MAE の標準偏差が ±377〜±591 人ある。 この差は分割を変えれば入れ替わる幅なので「同着」 と読み、 罰則の要らない線形回帰を選ぶ方が説明しやすい。

  2. Q2. GridSearchCV で Ridge の α を 7 候補から選び、 α=0.1 の Best CV MAE 1,239.0 人を得た。 これを「新しいデータでの予想誤差」 として報告してよいか。
    解答

    そのままでは不十分。 1,239.0 は 7 候補の中で最も良かった値を拾ったものなので、 たまたま良く出た分だけ楽観的になりうる。 選択と評価を分けるにはネスト CV の外側の値を報告する。 本ページのネスト CV では内側 best 0.9751 と外側平均 0.9786 でほとんど差が無かったが、 これは候補が少なく、 どの α でも R² がほぼ同じだったためで、 候補を増やすほど差は開きやすい。

  3. Q3. 2012〜2023 年度の 564 行で、 Ridge の MAE は TimeSeriesSplit で 2,156.9 人、 KFold で 1,547.4 人だった。 「来年度の県別出生数を予測する」 モデルの性能として報告すべきはどちらか。
    解答

    TimeSeriesSplit の 2,156.9 人。 KFold は未来の年度を学習に使えてしまうため、 1,547.4 / 2,156.9 ≈ 0.72 と約 3 割甘く出る。 出生数は 2012 年度から 2023 年度まで減り続けているので、 過去だけで学んだモデルは新しい年度を多めに外す。 報告する数値は、 実際に使う場面(過去から未来を予測)と同じ分け方で測る。

  4. Q4. 総人口から出生数を予測する Ridge(α=1)を 70/30 のホールドアウトで 1 回評価したら、 R² = 0.80 だった。 このモデルは使えないと結論してよいか。
    解答

    1 回だけでは判断できない。 図の 500 通りの分割では R² の中央値は 0.986 で、 最小は 0.791、 500 回中 11 回は 0.90 を下回った。 0.80 は分割の運が悪かった可能性が高い。 5-fold 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最小と、これら規準の最小が「だいたい一致し、時にズレる」ことを見比べると、モデル選択の全体像がつながります。関連: モデル選択。