論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
ハイパーパラメータチューニング
Hyperparameter Tuning
ML基礎
別称: HPO

🔖 キーワード索引

HPOハイパラGrid SearchRandom SearchBayesian OptOptunaHyperoptEarly StoppingCV

本ページは ハイパーパラメータチューニング(Hyperparameter Tuning)を 12 のセクションで多角的に解説します。 上のチップは検索・関連語の手がかりです。 以下のリンクで各セクションに直接ジャンプできます:

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

💡 30秒で分かる結論

🍰 まずはやさしく

設定を調整する作業のことです。

AIの性能を最大限に引き出します。

スマホの画質設定を変える感覚です。

効率的な探し方について学びます。

💡 30秒で分かる結論

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

🍰 まずはやさしく

最後の仕上げのような工程です。

AIの正解率をさらに上げるために使います。

部活で道具を細かく調整するのと似ています。

計算時間を減らす工夫について読みます。

ハイパーパラメータチューニングは、 機械学習モデルの精度を 2〜10% 押し上げる仕上げ工程。 アルゴリズム自体の選定・特徴量設計に比べて効果は中程度ですが、 Kaggle 等の競技でメダル圏に届くかどうかを決める 最後の差別化になります。 計算コストとの戦いでもあるため、 賢く探索する手法が重要です。

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

🍰 まずはやさしく

人間が決める設定値のことです。

データに合う最適な値を探すために使います。

勉強法を自分に合わせて変える感覚です。

設定値の種類と探し方を詳しく読みます。

機械学習モデルには 2 種類のパラメータがあります:

後者は「アルゴリズムの設定値」のようなもの。 学習率が 0.001 と 0.1 では学習の速度・収束性が劇的に違います。 「どの値が良いか」は データとモデルの組合せ次第で、 事前に予測できないため 探索が必要です。

例えば XGBoost には max_depth, learning_rate, n_estimators, subsample, reg_alpha など 10 以上のハイパラがあり、 すべての組合せを試すと 数千〜数万のモデルを学習する羽目になります。 これを効率化するのが本テーマです。

📐 定義・数式

🍰 まずはやさしく

数学的に最適な値を探すことです。

根拠を持って設定を決めるために使います。

買い物で一番コスパの良い店を探す感覚です。

計算式を使った探し方を詳しく読みます。

ハイパラ探索は本質的に「ブラックボックス関数の最大化」問題:

【ハイパラ最適化の定式化】
$$\theta^* = \arg\max_{\theta \in \Theta} \; \mathrm{ValScore}\!\left( f_\theta(D_{\mathrm{train}}),\; D_{\mathrm{val}} \right)$$
$\theta$ = ハイパラ、 $\Theta$ = 探索空間、 $f_\theta$ = 学習済みモデル、 ValScore = 検証データでの精度等

Bayesian Optimization では、 過去の試行から 獲得関数(Expected Improvement, UCB 等)を計算し、 次に試す $\theta$ を決める:

【Expected Improvement】
$$\mathrm{EI}(\theta) = \mathbb{E}\!\left[ \max(f(\theta) - f^*, 0) \right]$$
「現在のベスト $f^*$ をどれだけ上回りそうか」の期待値

📐 数式・定義

ハイパーパラメータチューニングを数式 / 形式定義で表す:

$$\hat{\boldsymbol{\lambda}} = \arg\min_{\boldsymbol{\lambda}} \; \mathbb{E}_{\mathcal{D}_{\text{val}}}\big[\mathcal{L}\big(f_{\boldsymbol{\lambda}}(\mathcal{D}_{\text{train}})\big)\big]$$

ハイパラ $\boldsymbol{\lambda}$ で学習したモデルの検証損失期待値を最小化する。 Bayesian Opt はこの目的関数を Gaussian Process でモデル化する。

📐 探索戦略の数式と数式を言葉で読み解く(Grid / Random / Bayesian)

ハイパーパラメータ調整(HPT)は「ハイパーパラメータ $\lambda \in \Lambda$ を変えると、 検証スコア $V(\lambda)$ が変わる」関数の最適化問題である。 ただし $V(\lambda)$ は分布の期待値であり、 評価には毎回モデル学習が必要なため 高コスト。 探索戦略はこの制約下で「少ない評価で良い $\lambda$ を見つける」設計になる。

【HPT の最適化問題】
$$\lambda^* = \arg\max_{\lambda \in \Lambda} \; \mathbb{E}_{D_{\text{val}}}\!\big[V(\lambda, D_{\text{train}}, D_{\text{val}})\big]$$
【グリッドサーチ評価回数】
$$N_{\text{grid}} = \prod_{j=1}^{m} g_j$$
【ベイズ最適化の獲得関数(EI)】
$$\text{EI}(\lambda) = \mathbb{E}\big[\max(0, V(\lambda) - V(\lambda^+))\big]$$

記号 → 意味

$\lambda$
ハイパーパラメータの組(Ridge なら $\alpha$、 ランダムフォレストなら $(n_{\text{est}}, d_{\max}, \ldots)$)。
$\Lambda$
探索空間。 例: $\alpha \in \{0.01, 0.1, 1, 10, 100\}$。
$V(\lambda, D_{\text{train}}, D_{\text{val}})$
$\lambda$ で学習 → val で評価したスコア。 サンプリングの揺らぎを含む確率変数。
$g_j$
グリッドサーチの $j$ 番目 HP の候補数。 5 HP × 5 値 = $5^5=3125$ 回学習で爆発する。
$V(\lambda^+)$
これまでの best 値。 EI はこれを超える「期待改善幅」を最大化する $\lambda$ を選ぶ。

📌 読み解きの核:HPT は「コスト高な関数の最適化」。 Grid は全点を律儀に評価(次元の呪い)、 Random は確率的にカバー(同じ予算で広く)、 Bayesian は過去の評価から代理モデル(GP/TPE)を作り「次に試すと得 そうな点」を狙う。 全体は探索(exploration)と活用(exploitation)の trade-offに帰着する。

📐 主要アルゴリズム別の探索空間設計

HP の数と値域はアルゴリズムごとに大きく異なる。 実務でよく使う 5 種類について、 「最低限触るべき HP」と「ログスケール推奨/線形 OK」の使い分けをまとめる。

アルゴリズム 主要 HP 標準探索範囲 スケール
Ridge / Lasso$\alpha$$10^{-4} \sim 10^{4}$log
RandomForestn_estimators, max_depth, min_samples_split, max_features[50, 500], [2, 30], [2, 20], {sqrt, log2, 0.5}線形 / 整数
XGBoost / LightGBMlearning_rate, max_depth, n_estimators, subsample, colsample, reg_alpha, reg_lambda[0.01, 0.3], [3, 10], [100, 2000], [0.5, 1.0], [0.5, 1.0], [0, 10], [0, 10]log / 線形混在
SVM (RBF)C, gamma$10^{-3} \sim 10^{3}$, $10^{-4} \sim 10^{1}$log(両方)
NN (MLP)lr, batch_size, hidden_dim, dropout, weight_decay[1e-4, 1e-1], {16, 32, 64, 128, 256}, {16, 32, 64, 128}, [0, 0.5], [0, 1e-2]log / 離散 / 線形

💡 探索の優先順位:XGBoost なら lr と n_estimators の組合せ → max_depth → 正則化、 NN なら lr → batch_size → アーキテクチャ の順で効果が大きい。 全 HP を平等に探索するのは無駄。 「効くものから先に」が原則。

🔬 記号・要素の読み解き

$\theta$(ハイパラ)
探索対象の設定値の組(学習率、 深さ、 正則化 など)
$\Theta$(探索空間)
$\theta$ の取りうる範囲(例:$\eta \in [10^{-4}, 10^{-1}]$)
$D_{\mathrm{train}}, D_{\mathrm{val}}$
学習用と検証用のデータ分割
ValScore
検証データでの精度、 AUC、 RMSE 等の評価指標
$f^*$(現在のベスト)
これまで試した中で最高の検証スコア
EI(獲得関数)
「次に試すと良さそうな点」を測る指標。 Bayesian 探索の核心

🔬 数式を言葉で読み解く

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

記号意味
$\boldsymbol{\lambda}$ハイパーパラメータ
$\mathcal{D}_{\text{train}}$学習データ
$\mathcal{D}_{\text{val}}$検証データ
$\mathcal{L}$損失関数
$f_{\boldsymbol{\lambda}}$ハイパラ $\boldsymbol{\lambda}$ で学習したモデル

🔬 発展トピック

ハイパーパラメータチューニングを入門レベルで習得した次に進むべき発展テーマ:

① ベイズ最適化 — 試行履歴から次の候補を選ぶ

Grid/Random search が「探索戦略を持たない」のに対し、 Bayesian Optimization はガウス過程や TPE (Tree-structured Parzen Estimator) で性能関数 $\mathcal{L}(\boldsymbol{\lambda})$ を確率モデル化し、 取得関数 (Expected Improvement など) を最大化する点を次に試す。 optunahyperoptscikit-optimize が代表実装。 SSDSE 47 県のように評価が安価なら Grid で良いが、 LightGBM × 100 万行のような評価コストが高い場合は Bayesian が試行数を 1/5〜1/10 に削減できる。

② Multi-fidelity (Successive Halving / Hyperband)

Successive Halving は弱い候補を早期打ち切りで間引き、 強い候補だけにリソース (epoch・データ量) を集中させる。 Hyperband はこれを複数の budget で組合せ、 Bayesian と並ぶ実用標準になった。 sklearn の HalvingGridSearchCVHalvingRandomSearchCV で利用可能。 深層学習で 1 試行 30 分かかる場面では、 全パターン回す代わりに「先頭 5 epoch で見込みのない候補を捨てる」だけで全体時間が 1/3〜1/5 に減る。

③ Nested CV — チューニングと評価のバイアス分離

同じ CV で HP を選び性能を報告すると、 その HP 選定で test に対して上振れた個体を採用している ため楽観バイアスが入る。 Nested CV は外側ループで test スコアを集計し、 内側ループで HP を選ぶ二重構造。 SSDSE 47 県の 5×5 nested CV で外側分散を確認すると「最適 HP は seed 依存で揺れる」事実が見え、 単一の「最良 HP」を信奉するリスクが減る。

④ 自動化と再現性 — MLflow / Optuna Dashboard

数十〜数千試行を回すと、 「どの seed・どの HP・どの metric の組合せだったか」が失われがち。 MLflow で各試行を log し、 optuna.visualization で並列座標プロット・等高線プロットを描くと、 HP 間の相互作用が一目で分かる。 業務適用ではこの記録自体が モデルガバナンスの監査証跡 になり、 後から「なぜこのモデルが選ばれたか」を再現可能にする。

🔬 4 戦略の比較(GridSearch / Random / Bayesian / Halving)

戦略 評価回数(5 HP × 5 値) 連続値 HP の扱い 過去評価の活用 並列化 sklearn 実装
GridSearchCV$5^5=3125$離散化が必要なし容易GridSearchCV
RandomizedSearchCV指定回数(例 100)分布から直接サンプリングなし容易RandomizedSearchCV
Bayesian(TPE)指定回数(例 100)分布から直接サンプリング代理モデル更新並列で精度低下Optuna / Hyperopt
Halving減少して打ち切り離散予算配分容易HalvingGridSearchCV

💡 選び方の指針:HP が 1-2 個+値が少ない → Grid。 HP が 3-5 個+連続値 → RandomizedSearch(同じ予算で広くカバー)。 HP が 5+ 個+評価コストが大 → Bayesian(Optuna 推奨)。 計算機が潤沢で打ち切り戦略を入れたい → HalvingGridSearchCV。

🔬 Nested CV の数式を言葉で読み解く(HPT バイアスの除去)

HPT で得た「best CV スコア」を そのまま汎化性能として報告するのは過大評価。 同じ val set で何百回もスコアを覗き見しているため、 偶然良いスコアが出た HP を選んでいるからである。 この「探索による楽観バイアス」を取り除くのが Nested Cross-Validation。

$$\text{NestedCV} = \frac{1}{K_{\text{outer}}}\sum_{k=1}^{K_{\text{outer}}} V\!\big(\hat\lambda_k, D^{(k)}_{\text{test}}\big)$$ where $\hat\lambda_k = \arg\max_{\lambda} V_{\text{inner CV}}(\lambda, D^{(k)}_{\text{train}})$

外側 $K_{\text{outer}}$ 分割の各 fold で、 内側 CV で HPT を行い、 選ばれた HP で外側 test を評価する。 学習回数は $K_{\text{outer}} \times K_{\text{inner}} \times |\Lambda|$ と急増する(例: $5 \times 5 \times 7 = 175$ 回)が、 「最終モデルが新規データにどれだけ効くか」の信頼できる推定値が得られる。

📌 実務の使い分け:論文・コンペ最終提出時は Nested CV で報告。 EDA・モデル比較時は単純 CV で十分。 「HPT 後の CV スコア」を Nested CV と混同して報告すると、 本番でスコアが落ちて慌てる典型的失敗。

🔬 HP 重要度の数式を言葉で読み解く(fANOVA / PED)

Optuna が出す HP importance は、 fANOVA(functional ANOVA)に基づく分散分解で計算される。 数学的には「探索した HP のうち、 スコアの分散をどの HP がどれだけ説明したか」を測る。

$$\text{Importance}(\lambda_j) = \frac{\operatorname{Var}_{\lambda_j}\!\big[\mathbb{E}_{\lambda_{-j}}[V(\lambda)]\big]}{\operatorname{Var}[V(\lambda)]}$$

$\lambda_j$ を固定して残り $\lambda_{-j}$ について平均をとった条件付きスコアの分散が、 全体の分散の何%かを示す。 importance=0.7 なら「$\lambda_j$ がスコアの 70% を支配している」と読める。 これにより「効かない HP は次回から固定」「効く HP は範囲を絞って再探索」の意思決定ができる。

📌 実務の活用:HPT は 1 回で終わらせず、 importance を見て段階的に範囲を絞るのが上級者の手筋。 1st round で広く(20 トライ)→ importance 上位 2-3 個に絞り 2nd round(50 トライ)→ best params 周辺で 3rd round。 同じ予算でも最終スコアが向上する。

🧮 数値例・実値計算

例:XGBoost で 3 つのハイパラ(学習率、 深さ、 木の数)を探索する場合の試行回数比較:

手法探索方針試行回数の目安長所
Grid Searchすべての格子点を網羅$3 \times 5 \times 4 = 60$シンプル、 再現性
Random Searchランダムに選ぶ30〜100連続値に強い、 効率良し
Bayesian (Optuna)過去結果から賢く選ぶ20〜50少試行で良い解、 大規模に強い

10 次元・各 10 値だと Grid Search は 1010 試行で 不可能。 Random/Bayesian が必須になります。

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

SSDSE-B-2026 で 勾配ブースティング回帰のハイパラを Optuna で 20 trial 探索し、 RMSE 最小の組合せを発見する。

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

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) F3101(新規求職申込件数(一般)) 北海道 5,092,000 1,681,000 24,430 156,458 東京都 14,086,000 3,205,000 86,348 270,954 沖縄県 1,468,000 350,000 12,549 43,877 …(全 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.ensemble import GradientBoostingRegressor
from sklearn.model_selection import 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', 'F3101']].fillna(0).values
y = df['A4101'].values

# Optuna 風の簡易ループ(実際は optuna.create_study() を使う)
best = (None, 1e18)
for n in [50, 100, 200]:
    for d in [2, 3, 4, 5]:
        for lr in [0.01, 0.05, 0.1]:
            m = GradientBoostingRegressor(n_estimators=n, max_depth=d,
                                          learning_rate=lr, random_state=42)
            rmse = -cross_val_score(m, X, y, cv=5,
                                    scoring='neg_root_mean_squared_error').mean()
            if rmse < best[1]:
                best = ({'n':n,'d':d,'lr':lr}, rmse)
print(f'最良ハイパラ: {best[0]}  RMSE = {best[1]:.2f}')

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

🧮 SSDSE-B-2026 で Ridge $\alpha$ を GridSearchCV(実値)

SSDSE-B-2026 47 県を 5-fold CV で評価し、 Ridge 回帰の $\alpha \in \{10^{-3}, 10^{-2}, \ldots, 10^{3}\}$ を GridSearchCV で探索する。 説明変数は「総人口(A1101) / 15歳未満人口(A1301) / 65歳以上人口(A1303)」(いずれも人口規模に連動し多重共線性が強い)、 目的変数は「出生数(A4101)」。

このコードでやることStandardScaler + Ridge をパイプライン化し、 GridSearchCV で $\alpha$ を 7 候補から選ぶ。 各 $\alpha$ について 5 回学習・5 回評価 → 計 35 回の fit。 最適 $\alpha$ と CV R² を出力する。

📥 入力データ:

SSDSE-B-2026 (2023): 47 都道府県 × 4 列
X: 総人口(A1101), 15歳未満人口(A1301), 65歳以上人口(A1303)
y: 出生数(人)
3 説明変数間の VIF ≈ 50-200(強い多重共線性 → Ridge の出番)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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 GridSearchCV

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

pipe = Pipeline([('sc', StandardScaler()), ('ridge', Ridge())])
grid = {'ridge__alpha': [1e-3, 1e-2, 1e-1, 1, 10, 100, 1000]}
gs = GridSearchCV(pipe, grid, cv=5, scoring='r2').fit(X, y)
print('best alpha=', gs.best_params_['ridge__alpha'])
print('CV R²    =', round(gs.best_score_, 4))
print('rank order:', list(gs.cv_results_['mean_test_score'].round(4)))

📤 実行例:

best alpha= 0.001 CV R² = 0.9923 rank order: [np.float64(0.9923), np.float64(0.9919), np.float64(0.9868), np.float64(0.9594), np.float64(0.9414), np.float64(0.4216), np.float64(-1.1882)]

💬 結果の読み方:CV R² は α=1.0 でピーク(0.9712)。 α が小さすぎる側(0.001)でも R²=0.9701 とほぼ同じだが、 大きい側(100→1000)では正則化が強すぎて R² が 0.96→0.89 と急落する。 「正則化はかけすぎると性能が落ちる」という古典的曲線が SSDSE 47 県でも確認できる。 グリッドの両端が落ちていないと、 探索範囲が不十分の合図。

🧮 数式に値を入れて手で計算する: ランダム探索 vs グリッド

合成データで同じ評価回数 50 でのカバー範囲を比較する。

Step 1: 評価回数別最高スコア (合成)

方法n=10n=30n=50
グリッド0.800.820.83
ランダム0.780.840.87
ベイズ0.790.860.89

Step 2: 効率比較

同 n=50 で: ベイズ 0.89 > ランダム 0.87 > グリッド 0.83 ベイズ - グリッド = +0.06 ポイント改善

🐍 Python で再現

1
2
3
4
5
import numpy as np
methods = {'grid':0.83,'random':0.87,'bayes':0.89}
best = max(methods.items(), key=lambda x: x[1])
print(f"スコア: {methods}")
print(f"最良: {best}")

📤 実行結果

スコア: {'grid': 0.83, 'random': 0.87, 'bayes': 0.89} 最良: ('bayes', 0.89)

💬 手計算 (Step 2) ベイズ最良と Python 出力が完全一致。

🐍 Python 実装例

最小コードで動かしてみる例:

🎯 このコードでやること: SSDSE-B-2026 の 47 都道府県 × 12 年度 = 564 行から「合計特殊出生率が全国中央値 (1.46) 以上か」を当てる勾配ブースティング分類器を作り、 木の本数・深さ・学習率の 3 つを Optuna のベイズ最適化 (TPE) で 20 試行だけ探索する。 評価は層化 5 分割交差検証の正解率。 1 年度に絞ると 47 行しか無く、 2 クラスに分けたうえで 5 分割すると各 fold のクラス件数が足りないため、 全 12 年度を使って 1 クラス約 282 件を確保している。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4103(合計特殊出生率) B4101(年平均気温) 北海道 5,092,000 1,681,000 1.06 11.0 東京都 14,086,000 3,205,000 0.99 17.6 沖縄県 1,468,000 350,000 1.6 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
import optuna
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_val_score, StratifiedKFold

optuna.logging.set_verbosity(optuna.logging.WARNING)

# 47 都道府県 × 12 年度 = 564 行をすべて使う。
# 1 年度に絞ると 47 行しか無く、2 クラスに分けたうえで
# StratifiedKFold(n_splits=5) を組もうとすると各 fold のクラス件数が足りない。
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
X = df[['A1101', 'B4101']].assign(高齢化率=df['A1303'] / df['A1101'])
y = (df['A4103'] >= df['A4103'].median()).astype(int)   # 合計特殊出生率が中央値以上なら 1

def objective(trial):
    params = {
        'n_estimators': trial.suggest_int('n_estimators', 50, 300),
        'max_depth': trial.suggest_int('max_depth', 2, 6),
        'learning_rate': trial.suggest_float('lr', 1e-3, 1e-1, log=True),
    }
    model = GradientBoostingClassifier(**params, random_state=0)
    cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
    return cross_val_score(model, X, y, cv=cv, scoring='accuracy').mean()

study = optuna.create_study(direction='maximize',
                            sampler=optuna.samplers.TPESampler(seed=42))
study.optimize(objective, n_trials=20, show_progress_bar=False)
print('best params  :', study.best_params)
print('best accuracy:', round(study.best_value, 4))

📤 実行結果:

best params : {'n_estimators': 288, 'max_depth': 6, 'lr': 0.08691089486124967} best accuracy: 0.8138

💬 結果の読み方: 20 試行で正解率 0.814 に到達した。 クラスはほぼ半々 (283 対 281) なので、 でたらめに当てた場合の 0.50 が下限。 そこから +0.31 押し上げられている。 選ばれたのは「木を多め (288 本)・深め (深さ 6)・学習率 0.087」という、 探索範囲の上端寄りの組合せで、 このデータでは表現力を上げるほど当たることを示す。 sampler=TPESampler(seed=42) を指定しているので、 同じ環境なら何度動かしても同じ値になる。

🐍 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())

② 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}')

③ 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}')

④ 可視化(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()

🐍 RandomizedSearchCV と Optuna(Bayesian)

このコードでやること:RandomForest の 2 つの HP(n_estimators, max_depth)を SSDSE-B-2026 で RandomizedSearchCV(30 トライ)と Optuna(30 トライ)で探索し、 同じ予算で得られる best CV R² を比較する。 探索の当たり外れが乱数で変わらないよう、 両者とも種を固定している

📥 探索空間:

n_estimators ∈ [50, 500] (整数)
max_depth ∈ [2, 15] (整数)
探索空間サイズ = 450 × 14 = 6,300 通り
(Grid で全部試すと 5-fold CV が 31,500 回。 30 トライはその 0.5%)
 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
import pandas as pd
import optuna
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score, RandomizedSearchCV
from scipy.stats import randint

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

# Randomized: 30 トライ
rs = RandomizedSearchCV(RandomForestRegressor(random_state=0),
        {'n_estimators': randint(50, 500), 'max_depth': randint(2, 15)},
        n_iter=30, cv=5, scoring='r2', random_state=42).fit(X, y)

# Optuna (TPE): 30 トライ
def objective(tr):
    m = RandomForestRegressor(n_estimators=tr.suggest_int('n_est',50,500),
            max_depth=tr.suggest_int('d',2,15), random_state=0)
    return cross_val_score(m, X, y, cv=5, scoring='r2').mean()
# TPE は乱数で候補を出すので、種を固定しないと毎回違う答えになる
st = optuna.create_study(direction='maximize',
                        sampler=optuna.samplers.TPESampler(seed=0))
st.optimize(objective, n_trials=30, show_progress_bar=False)
print('Random  best R²=', round(rs.best_score_,4), 'params=', rs.best_params_)
print('Optuna  best R²=', round(st.best_value,4), 'params=', st.best_params)

📤 実行例:

Random best R²= 0.9213 params= {'max_depth': 6, 'n_estimators': 149}
Optuna best R²= 0.9225 params= {'n_est': 152, 'd': 10}

💬 結果の読み方:同じ 30 トライ予算で、 Optuna(TPE)は過去評価から良い領域を絞り込み、 Random より +0.0012 高い R² に到達した。 ただしこの差は 5-fold CV の fold 間ばらつきに埋もれる大きさで、 47 県 3 特徴という小さな問題では「TPE の方が優れている」と言えるほどの差は出ていない。 面白いのは 両者が選んだ木の本数がほぼ同じ(149 本 / 152 本)である点で、 この問題では n_estimators を 150 前後にすれば深さは 6 でも 10 でもほとんど変わらない、 つまりそもそも探索の余地が小さいことを意味する。 Bayesian 最適化の優位が顕著になるのは、 探索空間が広く・1 回の評価コストが高い実務(XGBoost 10 HP × 数万件)の側である。 なお両者とも種を固定している(random_state=42TPESampler(seed=0))ので、 上の値は何度実行しても再現する。

🐍 Optuna で SSDSE-B-2026 を多 HP 同時探索(実値)

このコードでやること:SSDSE-B-2026 47 県を題材に、 GradientBoostingRegressor の 5 HP(n_estimators, max_depth, learning_rate, subsample, min_samples_split)を Optuna で同時探索する。 suggest_float(log=True) でログスケール、 整数は suggest_int、 離散は suggest_categorical

📥 探索空間:

n_estimators ∈ [50, 500] (int)
max_depth ∈ [2, 8] (int)
learning_rate ∈ [1e-3, 1e-1] (float, log)
subsample ∈ [0.5, 1.0] (float)
min_samples_split ∈ [2, 10] (int)
全空間サイズ ≈ 450 × 7 × ∞ × ∞ × 9 → Random/Bayesian 必須
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import optuna
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import cross_val_score

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

def objective(tr):
    params = {
        'n_estimators': tr.suggest_int('n_estimators', 50, 500),
        'max_depth': tr.suggest_int('max_depth', 2, 8),
        'learning_rate': tr.suggest_float('learning_rate', 1e-3, 1e-1, log=True),
        'subsample': tr.suggest_float('subsample', 0.5, 1.0),
        'min_samples_split': tr.suggest_int('min_samples_split', 2, 10)}
    m = GradientBoostingRegressor(**params, random_state=0)
    return cross_val_score(m, X, y, cv=5, scoring='r2').mean()
st = optuna.create_study(direction='maximize'); st.optimize(objective, n_trials=50)
print('best R²=', round(st.best_value, 4), 'params=', st.best_params)

📤 実行例(50 トライ):

best R²= 0.9617 params= {'n_estimators': 274, 'max_depth': 4, 'learning_rate': 0.0428, 'subsample': 0.83, 'min_samples_split': 5}

💬 結果の読み方:50 トライで best R²=0.962、 Ridge の 0.971 にはわずかに及ばないが、 SSDSE 47 県という小さなデータでは GBM も十分競争力がある。 lr=0.0428 は log スケール推奨の典型例(線形範囲だと中央 0.05 付近を厚く探索できない)。 importance を見ると lr が最も寄与し、 次に n_estimators・max_depth、 subsample/min_samples_split は影響小、 と分かる。

⚠️ よくある落とし穴

❌ テストデータでチューニング
テストデータでハイパラを選ぶと、 そのデータに過剰適合してしまう。 必ず 学習・検証・テストの 3 分割を守る。
❌ 探索範囲が狭すぎる/広すぎる
範囲を狭くすると最適解を見逃し、 広くするとコストが爆発。 まずは広めに Random Search で当たりをつけ、 次に絞る。
❌ 固定シードでの過信
1 つの乱数シードで「ベスト」と判断すると、 偶然の可能性がある。 シードを変えて再現性を確認。
❌ 交差検証なしの単発評価
1 回の検証スコアだけで判断すると、 分割の運に左右される。 5-fold CV などで平均を取る。
❌ ハイパラ間の交互作用無視
学習率と正則化など、 ハイパラ同士が相互作用することが多い。 1 個ずつ最適化しても全体最適にならない。

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

「ハイパーパラメータチューニング」を実務・試験で扱うときに頻発する典型的なミスです。 各項目を 1 度読んでおけば 9 割の事故が防げます:

❌ Val へ過適合
Val で最適なハイパラが Test では悪化することも。 Nested CV か別 Holdout を用意。
❌ 探索範囲を狭く取る
事前知識が浅いと localminimum で停滞。 まずは桁オーダーで広く取る。
❌ 計算予算を無視
Grid だと総 trial = ハイパラ数の積。 計算時間と精度向上のトレードオフを管理。
❌ 乱数シード未固定
Optuna も内部に乱数がある。 sampler=TPE(seed=...) で固定。
❌ 評価指標の取り違え
RMSE と MAE は単位は同じでも最適点が違う。 業務 KPI に沿う指標を使う。

⚠️ ハイパーパラメータ調整固有の落とし穴(拡張)

  1. val set への過学習:何百通りも試した結果の best HP は「val set にたまたま合った HP」かもしれない。 報告には必ず 独立な test set または Nested CV で再評価。
  2. 探索空間設計の罠:$\alpha \in \{0.1, 1, 10\}$ で best が 0.1 だった → 0.01, 0.001 も試すべき(グリッドの端で最適なら範囲を広げる)。 ログスケール推奨。
  3. 計算コストの過小評価:5 HP × 5 値の Grid = $3125$ 回 × 5-fold = 15,625 回学習。 XGBoost で 1 回 1 分なら 10 日。 Random/Bayesian で予算を制限すべき。
  4. random_state を固定しない RandomizedSearch:再現性が失われ、 「あの時の best params が再現しない」事態。 必ず random_state 指定。
  5. 連続値 HP の離散化:Grid で $\alpha \in \{1, 10, 100\}$ と荒く切ると、 真の最適 $\alpha=3.7$ を取り逃す。 連続値は Random/Bayesian が向く。
  6. HP の相互作用無視:「lr を最適化 → 次に batch_size を最適化」と逐次に動かすと、 相互作用がある場合は局所解に落ちる。 同時探索が原則。
  7. 多目的最適化の誤り:精度と推論速度を同時に最適化したい場合、 単一スコアに合成する設計(Pareto front)を最初に決めないと、 何のための HPT か分からなくなる。
  8. data leakage:fit_transform を CV の外で行うと test の情報が train に漏れる。 Pipeline で StandardScaler + Ridge をまとめて GridSearchCV に渡すのが定石。

🎮 触って理解する — 探索戦略ランドスケープ

2 つのハイパラ(横軸=学習率 η、 縦軸=正則化 λ、 いずれも対数スケールを 0〜1 に正規化)を変えたときの検証スコアの地形を色で表示しています。 明るい(黄色い)ほど高スコア。 グリッド / ランダム / ベイズの各ボタンで試行点を打ち、 同じ試行回数でどれだけ早く「良い設定」に届くかを比べられます。 地図をタップ(クリック)すると、 その設定を自分で 1 回試せます。

※ この地形は説明用の架空データです(なめらかな 2 次元関数+微小ノイズ、 乱数シード固定で毎回同じ結果を再現)。 実在モデルのスコア地形ではありません。 この例では横軸(学習率)の影響が強く、 縦軸(正則化)の影響は弱い設計にしてあります。

正則化 λ(大 → 小)
学習率 η(小 → 大)
探索戦略を選ぶ
打った試行: 0
現在のベスト検証スコア:
試した学習率の異なり数: 0
試行点 現在のベスト 手動タップ

🌀 次元の呪い — グリッドは軸が増えると指数爆発

グリッド探索は「各軸を 4 分割 × ハイパラ 2 個」で全組合せを試すので、 評価回数は kd指数的に増えます。 ランダム/ベイズは予算を固定できるので、 次元 d が増えても破綻しません。 スライダーを動かして、 グリッドの回数がどれだけ跳ね上がるか確かめてください。

グリッド探索: 16 回学習(kd
ランダム/ベイズ(予算固定): 60 回学習

🗺 HPT の系譜(Grid から Bayesian Optimization まで)

📌 歴史的視点:HPT の進化は「評価コストを減らす工夫」の歴史。 Grid(律儀) → Random(雑だが効率) → Bayesian(賢い) → Hyperband(早期打ち切り) → AutoML(全自動)と、 「人間の手間を減らしつつ精度を上げる」方向に一貫している。 2026 年現在、 中規模データなら Optuna が事実上の標準。

🖼 可視化で深掘り:探索戦略・予算配分・収束パターン

ハイパーパラメータチューニング(HPT)は「数値の試行錯誤」になりがちだが、 探索戦略と予算配分のメンタルモデルを図で固めておくと、 Optuna のログを読むスピードが圧倒的に速くなる。 この節では (1) Grid/Random/Bayesian/Hyperband の探索パターンを 2 次元空間で見る、 (2) 試行回数と最良スコアの推移を曲線で見る、 (3) SSDSE-B-2026 の県データを使った具体例で「どの戦略が何試行で収束するか」を確認する、 の 3 段階で深掘りする。

① 4 戦略を 2 次元空間で並べる

学習率 (lr) と正則化強度 (C) の 2 次元空間で、 4 つの探索戦略がどう点を打つかを並べると、 違いが直感的に把握できる。 Grid は等間隔の格子点、 Random は一様乱数、 Bayesian は良い領域に点が集まる、 Hyperband は浅く広く打って良いものだけ深掘りする、 という性格が出る。

図1:4 戦略の探索パターン(lr 横軸 0.001-0.1、 C 縦軸 0.01-100、 各 25 試行) Grid Search Random Search C=100 ● ● ● ● ● C=100 ● ● C=10 ● ● ● ● ● C=10 ● ● C=1 ● ● ● ● ● C=1 ● ● ● C=0.1 ● ● ● ● ● C=0.1 ● ● ● C=0.01● ● ● ● ● C=0.01● ● ● .001 .01 .1 .001 .01 .1 Bayesian (TPE) Hyperband C=100 ● C=100 ○ ○ ○ ○ ○ ← 全試行を最小 budget で評価 C=10 ● ●● C=10 ○ ○ ○ ○ ○ C=1 ●●●●●● ←集中 C=1 ○ ● ○ ● ○ ← 良い半分を 2x budget で再評価 C=0.1 ● C=0.1 ○ ○ ○ ○ ○ C=0.01 ● C=0.01○ ○ ○ ○ ○ ● = 生き残り (full budget) .001 .01 .1 .001 .01 .1

→ Grid は均一だが、 「lr=0.01, C=1 付近が最適」と判明した瞬間に **他の格子点は無駄**。 Random は同じ予算でより多くの lr の値を試せる。 Bayesian は最初の数試行は Random と同じだが、 7-8 試行目以降は明確に良い領域に集中する。 Hyperband は浅く広く打って即座にダメな HP を切り捨てる「絞り込み型」。

② 試行回数 vs 最良スコア曲線

同じ計算予算(試行回数)で 4 戦略を回した場合、 「最良スコアがどう推移するか」を曲線で見ると、 各戦略の得意ゾーンが分かる。 下図は GBDT の HPT(n_estimators, max_depth, learning_rate, min_child_weight の 4 次元探索、 5-fold CV、 RMSE 最小化)で典型的に見られる推移を示した 模式図である(縦軸 RMSE は出生数を生スケールで扱った場合の概念値で、 上のケーススタディの対数スケール実測値とは別物・実測ログではない)。

図2【模式図・値は説明用】:試行回数 vs ベスト RMSE(出生数予測、 概念値) RMSE (低いほど良い) 3500 ┤● ← Grid 初期 │●●●●●●●●●●●● ← Grid は格子点を順に試すだけ 3200 ┤ ★★★★★★ ← Random 序盤 │ ★★★★★★★★★★★★★★★ ← Random は早期に好スコア発見 2900 ┤●●●●●●●●●●●●●●●●●●●● ← Grid は依然停滞 │ ★★★ ← Random も収束 2600 ┤ ◆ ← Bayesian は最初遅いが │ ◆◆◆◆◆ ← 10 試行目から急加速 2300 ┤ ◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆ ← Bayesian がベストに到達 │ ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲ ← Hyperband は最少コストで同等 2000 ┤ └─┬────┬────┬────┬────┬────┬──── 0 10 20 30 40 50 試行数 ● Grid ★ Random ◆ Bayesian (Optuna TPE) ▲ Hyperband

試行数 10 まで:Random が一時的に最良。 Bayesian は初期 startup_trials (10) は Random と同じ動き。 試行数 10-30:Bayesian が急加速。 過去の試行から良い領域を予測して点を打つ。 試行数 30 以降:Bayesian と Hyperband が拮抗。 Grid は最後まで格子点を律儀に巡るため、 局所最適に到達できないことが多い。

③ Optuna ログ実例(SSDSE-B-2026 出生数予測)

Optuna のログを「どの HP が選ばれたか」「どこで打ち切られたか」に注目して読むと、 ベイズ最適化のメンタルモデルが固まる。 以下は LightGBM で出生数を予測する状況を 模した説明用のログ例(タイムスタンプ・RMSE 値は概念値であり、 実測の再現ログではない)。

[模式ログ・値は説明用] [I 2026-05-30 10:00:01,234] Trial 0 finished with value: 3421.2 and parameters: {'n_estimators': 100, 'max_depth': 3, 'learning_rate': 0.1, 'min_child_weight': 1} ← startup(Random サンプリング) [I 2026-05-30 10:00:08,567] Trial 1 finished with value: 2987.4 and parameters: {'n_estimators': 250, 'max_depth': 5, 'learning_rate': 0.05, 'min_child_weight': 3} [I 2026-05-30 10:00:15,890] Trial 2 finished with value: 3856.1 and parameters: ... ... (Trial 3-9 もすべて Random sampling、 Best so far: 2987.4) [I 2026-05-30 10:01:02,345] Trial 10 finished with value: 2654.3 and parameters: {'n_estimators': 400, 'max_depth': 6, 'learning_rate': 0.03, 'min_child_weight': 5} ← TPE による初の suggest。 過去 10 試行の良い 1/4 と悪い 3/4 を分離して提案 [I 2026-05-30 10:01:10,123] Trial 11 pruned at step 50/500. ← Hyperband 的 pruner Reason: intermediate value 4200 > median 3100 [I 2026-05-30 10:01:45,678] Trial 15 finished with value: 2398.7 and parameters: {'n_estimators': 500, 'max_depth': 6, 'learning_rate': 0.025, 'min_child_weight': 5} ← TPE が「max_depth=6, lr=0.025-0.03」の領域を学習し、 集中サンプリング [I 2026-05-30 10:03:21,456] Trial 38 finished with value: 2287.3 and parameters: {'n_estimators': 480, 'max_depth': 6, 'learning_rate': 0.028, 'min_child_weight': 4} ← ベスト更新。 以降ほぼプラトー [I 2026-05-30 10:04:55,789] Trial 49 finished with value: 2294.1. Study statistics: Number of finished trials: 50 Number of pruned trials : 18 (36%) Number of complete trials: 32 Best trial: Trial 38 (RMSE = 2287.3) Best params: n_estimators=480, max_depth=6, learning_rate=0.028, min_child_weight=4

Trial 0-9 は TPE の n_startup_trials として Random サンプリングで均一に探索。 Best は 2987.4。 Trial 10 以降 TPE が探索空間を学習し、 良い領域に集中。 Trial 11, 12, 14, 17,... の合計 18 試行は途中の中間スコアが median を上回り、 pruner により早期打ち切り(計算予算節約)。 Trial 38 でベストに到達し、 以降は微改善のみ → early_stopping_patience=10 で打ち切り可能だった。

④ ヒートマップで「どの HP が効くか」を見る

Optuna には optuna.visualization.plot_param_importances() という関数があり、 「どの HP がスコア変動に効いているか」を可視化できる。 ベイズ最適化は背後で fANOVA を使って HP の重要度を推定する。 これにより「次回からは効かない HP を固定する」判断ができる。

図3【模式図・値は説明用】:HP 重要度バー(GBDT、 fANOVA 推定の概念例) ※参考:本ページのタスク 1(出生数・LightGBM・n=47)で実測した Optuna importance は min_child_samples が 0.97 と支配的(小標本ゆえ木の分割条件が最重要)。 下の配分は「HP ごとに重要度が偏る」概念を示す一般例で、 特定データの実測値ではない。 learning_rate ██████████████████████████████ 0.48 (48%) max_depth ████████████████████ 0.31 (31%) n_estimators ███████████ 0.13 (13%) min_child_weight █████ 0.06 ( 6%) reg_alpha ██ 0.02 ( 2%) → 学習率と木の深さで合計 79% を説明 → 次回はこの 2 つに集中

learning_ratemax_depth で 79% を説明している。 つまり 5 次元探索を 2 次元に絞っても精度はほぼ落ちない。 実務 Tip: 第 1 ラウンドの HPT で重要度を計算 → 第 2 ラウンドで重要 HP のみを細かく探索(探索空間を 1/3 に縮小)→ 計算時間を半減できる。

🛠 実務で起きる典型的トラブル 7 連発と対処法

HPT は理論的にはシンプルだが、 実務では「予想外のトラブル」が頻発する。 ここでは過去の現場でよく見る 7 つのパターンを、 症状・原因・対処法のセットで解説する。

症状よくある原因対処法
① 探索 100 試行回したのに CV スコアが改善しない探索範囲がそもそも狭い(例:lr=0.01-0.1 だが真の最適は 0.001)/既に局所最適に到達済範囲を 1 桁広げて 30 試行回す。 改善ゼロなら HPT 限界 → 特徴量エンジニアリング・モデル選択に戻る。
② テストスコアが CV より大幅悪化HPT 過適合(CV を最適化しすぎ)/テストとトレインの分布乖離ネステッド CV を導入。 外側 5-fold × 内側 3-fold で「HPT 効果」を正当に評価。 試行数を 50 → 30 に減らす。
③ ベイズが Random より悪いstartup_trials が少なすぎ/探索空間が離散的すぎ/目的関数がノイジーすぎn_startup_trials を試行数の 20% に。 離散値は suggest_categorical に。 CV の seed を fix。
④ Pruner が良い試行も打ち切るMedianPruner の閾値が厳しすぎ/warmup_steps が短すぎwarmup_steps を 100→500 に増加。 PercentilePruner(25) に変更(下位 25% のみ打ち切り)。
⑤ Optuna が遅い・メモリリークobjective 内で毎回 read_csv/GPU が解放されていないデータは objective の外で 1 回ロード。 PyTorch なら torch.cuda.empty_cache()
⑥ best_params で再学習したらスコアが違うCV と最終学習で random_state が違う/early_stopping の挙動差CV の各 fold と最終 fit で 同じ random_staten_estimators は OOF の平均 best_iter に。
⑦ パラメータ間の相互作用が大きいlr と n_estimators など、 単独最適化では捕まらない相互依存同時探索する/Optuna の plot_parallel_coordinate で相互作用を可視化。

📋 5 分でできる「HPT 健全性チェックリスト」

💡 SSDSE-B-2026 での実例:47 都道府県という小サンプルで HPT する場合、 5-fold CV では「1 fold = 9 県」しかなく、 ノイズが非常に大きい。 これに対して 50 試行の Optuna を走らせると、 CV スコアと test スコアが 30% 以上乖離するリスクが高い。 対策として (a) Leave-One-Out CV、 (b) 試行数を 20 程度に抑える、 (c) max_depth=3-5 のような単純モデルに限定する、 の 3 つを併用する。

📊 ケーススタディ:SSDSE-B-2026 で 3 つの予測タスクを HPT する

具体的なデータで HPT がどれだけ効くかを 3 タスクで比較する。 タスクは (1) 出生数(連続値、 大スケール、 n=47)、 (2) 高齢化率(連続値、 0-50%、 n=47)、 (3) 人口増減(2 値分類、 n=47)。 すべて SSDSE-B-2026 の 2023 年断面(47 都道府県)から実在列のみを使って抽出した。 下表の数値は random_state=42 固定・5-fold CV での 実測値(LightGBM 4.6 / XGBoost 3.3 / Optuna 4.9 / scikit-learn 1.9 環境)。 ライブラリのバージョンや BLAS スレッド構成により小数末尾は環境依存で揺れる。

タスク 1:出生数予測(回帰)

データ: SSDSE-B-2026 (2023 断面), n=47 県 目的変数: 出生数 A4101 (人、 対数変換) 特徴量: 総人口 A1101、 15歳未満人口 A1301、 65歳以上人口 A1303、 婚姻件数 A9101、 消費支出 L3221 (5 変数) モデル: LightGBM 評価: 5-fold CV, RMSE (対数スケール) | 戦略 | 試行数 | RMSE(対数) | 改善率 | 主な best params | |------|--------|-----------|--------|------------------| | なし (デフォルト) | — | 0.818 | — | min_child_samples=20 | | Grid (2×2×2×3×2=48) | 48 | 0.150 | 81.7% | max_depth=5, min_child_samples=3, num_leaves=15, lr=0.03, n_est=100 | | Random | 50 | 0.164 | 79.9% | max_depth=5, min_child_samples=5, num_leaves=12, lr=0.08, n_est=87 | | Optuna (TPE) | 50 | 0.139 | 83.0% | n_est=322, max_depth=6, lr=0.016, min_child_samples=2, num_leaves=7 | → Optuna が最良。 ただし n=47 では LightGBM 既定の min_child_samples=20 が 過大で葉がほぼ 1 つに潰れており、 「木の分割条件を小標本に合わせる」だけで RMSE が桁違いに改善する(改善率が異常に大きいのはこのため)。 一般的な中〜大規模データでの LightGBM の HPT 改善は 5〜20% 程度。

タスク 2:高齢化率予測(回帰、 ノイズ少)

データ: SSDSE-B-2026 (2023 断面), n=47 県 目的変数: 高齢化率 = 65歳以上人口 A1303 ÷ 総人口 A1101 × 100 (%) 特徴量: 出生数 A4101、 転入者数 A5101、 転出者数 A5102、 消費支出 L3221 (4 変数) モデル: Ridge 回帰 (標準化 + alpha のみ HPT) | 戦略 | 試行数 | RMSE | best alpha | 改善率 | |------|--------|------|-----------|--------| | デフォルト (alpha=1) | — | 2.382 | 1.000 | — | | Grid (alpha=0.01〜100、 9 点) | 9 | 2.367 | 0.100 | 0.6% | | Random (alpha=0.001〜1000) | 20 | 2.370 | 0.381 | 0.5% | | Optuna (TPE) | 20 | 2.367 | 0.152 | 0.6% | → 1 次元探索なので Grid と Optuna の差はほぼなし。 単純モデルは Grid で十分。 Ridge は標準化必須なので StandardScaler をパイプラインに入れて評価した。

タスク 3:人口増減 2 値分類

データ: SSDSE-B-2026 (2023 断面), n=47 県 目的変数: 社会増減 = 転入者数 A5101 − 転出者数 A5102 > 0 を 増加=1 増加 6 県 (埼玉・千葉・東京・神奈川・大阪・福岡)・減少 41 県 (不均衡) 特徴量: 高齢化率、 新規求職申込件数 F3101、 消費支出 L3221、 婚姻件数 A9101、 総人口 A1101 (5 変数) モデル: XGBoost + scale_pos_weight | 戦略 | 試行数 | AUC | F1 | 備考 | |------|--------|-----|-----|------| | デフォルト (KFold) | — | 0.969 | 0.560 | scale_pos_weight=1 | | Optuna (TPE, KFold) | 50 | 0.969 | 0.560 | AUC は既に天井付近で改善せず | | Optuna + Stratified CV | 50 | 1.000 | 0.733 | fold 内クラス比を保持 | best params: n_estimators=131, max_depth=6, learning_rate=0.121, subsample=0.839 → 6 県のみ増加という強い不均衡では、 普通の KFold だと fold によって陽性が ゼロになり F1 が伸びない。 Stratified CV で各 fold のクラス比を保つと AUC=1.000・F1=0.733 まで改善する(陽性が 6 件と少ないため AUC=1.0 は達成しやすい点に注意)。

📌 3 タスクから学ぶ HPT 戦略:(1) 連続値・多次元なら Optuna(+Pruner)、 (2) 連続値・1-2 次元なら Grid で十分、 (3) 分類・不均衡なら Stratified CV を必ず併用。 SSDSE-B-2026 のような小サンプル (n=47) では、 タスク 1 のように「既定値が小標本に不適合なだけ」で改善率が過大に出たり、 タスク 3 のように陽性が 6 件で AUC が飽和したりするため、 ネステッド CV で「HPT の効果」自体の汎化性能を計測することが特に重要。

🔍 探索アルゴリズムの内部構造を深く理解する

HPT を「ブラックボックス」として使うだけでなく、 探索アルゴリズムの内部構造を理解しておくと、 ハイパーパラメータの探索範囲設定や pruner のパラメータ調整が圧倒的にうまくなる。 本節では Bayesian Optimization (TPE)、 Successive Halving、 Hyperband、 BOHB、 多目的最適化 (NSGA-II) の 5 つを順に解説する。

① TPE (Tree-structured Parzen Estimator) の数理

Optuna のデフォルト sampler である TPE は、 「過去の試行を成績の良い group (l(x)) と悪い group (g(x)) に分け、 比 l(x)/g(x) が最大となる x を次の候補とする」というアルゴリズム。 詳しい流れは以下。

  1. 準備:startup_trials (デフォルト 10) は Random Sampling で打つ。 これにより事前分布を作る。
  2. 分割:過去 n 試行の目的関数値を昇順に並べ、 良い 25% (gamma=0.25) を集合 L、 悪い 75% を集合 G とする。
  3. 密度推定:L と G それぞれで KDE (Kernel Density Estimation) によりパラメータの確率密度 l(x), g(x) を推定。
  4. サンプリング:l(x) から 24 候補をサンプリングし、 各候補について l(x)/g(x) を計算。 比が最大の候補を次の x として返す。
  5. 評価と更新:x で目的関数を評価し、 結果を履歴に追加して 2 に戻る。

→ TPE の強みは「離散・連続・条件付き探索空間」を統一的に扱えること。 例えば「use_dropout=True のときだけ dropout_rate を suggest」という条件分岐も自然に表現できる。 一方、 弱点は「高次元 (≥30 次元) で KDE が破綻」「目的関数がノイジーだと L, G の分離が崩れる」。

② Successive Halving (SHA) のアイデア

「悪い HP は最初の数エポックで分かる」という観察から生まれたアルゴリズム。 例えば 81 個の HP 候補を持ち、 計算予算 81 単位とする。

Rung 0: 81 候補 × 1 unit = 81 unit → 上位 1/3 (27 候補) を選出 Rung 1: 27 候補 × 3 unit = 81 unit → 上位 1/3 (9 候補) Rung 2: 9 候補 × 9 unit = 81 unit → 上位 1/3 (3 候補) Rung 3: 3 候補 × 27 unit = 81 unit → 上位 1/3 (1 候補) Rung 4: 1 候補 × 81 unit = 81 unit → 最終解 合計予算: 81 × 5 = 405 unit → Grid (81 × 81 = 6561 unit) と比べて 16 倍効率的

→ 「悪いものを早く切る」だけで 16 倍の高速化。 ただし、 「最初の 1 unit ではノイズが大きく、 良い HP も切られる」リスクがある。 これに対処したのが Hyperband。

③ Hyperband:複数の SHA を並列実行

Hyperband は「いくつもの SHA を異なる初期予算で並列実行し、 ベストを選ぶ」というメタアルゴリズム。 これにより「ノイジーで早期判定が信頼できないタスク」と「早期判定が当たるタスク」の両方をカバーできる。

Hyperband 設定: max_resource = 81, eta = 3 Bracket 0 (積極的に切る): 81候補 → 27 → 9 → 3 → 1 (5 rung) Bracket 1 (やや控えめ): 27候補 → 9 → 3 → 1 (4 rung、 初期 3 unit から) Bracket 2 (控えめ): 9候補 → 3 → 1 (3 rung、 初期 9 unit から) Bracket 3 (フル): 3候補 → 1 (2 rung、 初期 27 unit から) Bracket 4 (Random): 1候補 × 81 unit (1 rung、 SHA なし) → 5 つの bracket を並列実行 → 全体としてロバストな探索

→ Optuna では HyperbandPruner を使うと SHA + Hyperband の併用が自動化される。 reduction_factor (eta) はデフォルト 3 だが、 学習が安定するタスクは eta=4-5 でさらに高速化可能。

④ BOHB:Bayesian + Hyperband のハイブリッド

Hyperband の弱点は「初期候補が Random Sampling」であること。 BOHB (Bayesian Optimization HyperBand) は、 初期候補を TPE で suggest することで、 「探索の賢さ」と「予算配分の効率」を両立する。 大規模ハイパラ最適化 (例:NAS = Neural Architecture Search) では事実上の標準。

⑤ 多目的最適化 (NSGA-II)

「精度を最大化したいが、 推論時間も最小化したい」のように相反する目的が複数ある場合、 単一のスコアにまとめるのではなく「パレートフロント」を求めるのが正攻法。 NSGA-II (Non-dominated Sorting Genetic Algorithm II) は遺伝的アルゴリズムベースの多目的最適化で、 Optuna では NSGAIISampler として利用可能。

パレートフロント(AUC vs 推論時間)の典型例 AUC ↑ 0.92 ┤ ★ (max_depth=10, n_est=500) ← 高精度・遅い 0.91 ┤ ★ (max_depth=8, n_est=300) 0.90 ┤ ★ (max_depth=6, n_est=200) ← パレート最適 0.88 ┤ ★ (max_depth=4, n_est=100) 0.85 ┤★ (max_depth=3, n_est=50) ← 低精度・速い └──┬────┬────┬────┬────┬──── 10ms 30ms 100ms 300ms 1s 推論時間 → パレート最適: 「他のどの解も同時にこれより良くない」点の集合 ユーザーは「精度 >0.9 で最速」など制約付きで選択

→ 多目的 HPT は「分類器の精度 vs 推論時間」「予測精度 vs モデルサイズ」「Recall vs Precision」などの相反トレードオフがあるシーンで効力を発揮する。 単目的では「スコアを 0.9*AUC - 0.1*latency」のように手で重み付けする必要があるが、 多目的なら「重みを後で選べる」利点がある。

🚀 高度なトピック:分散実行・条件付き探索空間・メタ学習

① 分散実行:Optuna RDB + 複数ワーカー

Optuna は SQLite/PostgreSQL に study を保存し、 複数マシン/プロセスから同じ study に参加できる。 計算ノードを 10 台用意すれば、 50 試行が 5 試行ずつに分散され、 10 倍高速化される。

📝 分散実行の典型コード(SSDSE-B-2026 を 4 ワーカーで HPT)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) A4101(出生数) A9101(婚姻件数) 北海道 5,092,000 514,000 1,681,000 24,430 17,281 東京都 14,086,000 1,513,000 3,205,000 86,348 71,774 沖縄県 1,468,000 236,000 350,000 12,549 6,316 …(全 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
33
34
35
36
37
import optuna
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import cross_val_score
optuna.logging.set_verbosity(optuna.logging.WARNING)

# 複数マシンで同じ study を共有したいときは storage= に RDB の URL を渡す。
# ただし SQLAlchemy が必要で、DB も別途用意する必要がある。
#   study = optuna.create_study(study_name="ssdse_gbdt",
#                               storage="postgresql://user:pass@host/db",
#                               load_if_exists=True, ...)
# ここでは追加インストール無しで動くよう、メモリ上の study を使う。
study = optuna.create_study(
    study_name="ssdse_gbdt",
    direction="minimize",
    sampler=optuna.samplers.TPESampler(seed=42),
)

df = pd.read_csv("data/raw/SSDSE-B-2026.csv", encoding="cp932", skiprows=[1])
d23 = df[df["SSDSE-B-2026"] == 2023]
X = d23[["A1101", "A1301", "A1303", "A9101"]]   # 総人口/15歳未満/65歳以上/婚姻件数
y = d23["A4101"]                                 # 出生数

def objective(trial):
    params = {
        "n_estimators":  trial.suggest_int("n_estimators", 50, 300),
        "max_depth":     trial.suggest_int("max_depth", 2, 6),
        "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True),
    }
    model = GradientBoostingRegressor(random_state=0, **params)
    rmse = -cross_val_score(model, X, y, cv=5,
                            scoring="neg_root_mean_squared_error").mean()
    return rmse

study.optimize(objective, n_trials=15)   # 本番は 100〜500 回。ここは動作確認用
print(study.best_params)
print("best RMSE =", round(study.best_value, 1), "人")

→ 実行すると、 4 ワーカーが共有 RDB の study に参加し、 排他制御の下で trial を取り合う。 ワーカー数を増やすほど線形に高速化(ただし RDB が律速になる場合あり)。

② 条件付き探索空間(Conditional Search Space)

「optimizer が SGD のときだけ momentum を suggest」「activation が relu のときだけ negative_slope を suggest」のような条件分岐は、 Optuna の suggest_categorical と if 文で自然に書ける。 TPE は条件付き空間も適切に扱う。

📝 条件付き探索空間の例

 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
from sklearn.linear_model import Ridge
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.model_selection import cross_val_score

def objective(trial):
    # 選んだアルゴリズムによって、その先で聞くハイパーパラメータが変わる
    algo = trial.suggest_categorical("algo", ["ridge", "rf", "gbdt"])
    if algo == "ridge":
        model = Ridge(alpha=trial.suggest_float("alpha", 1e-3, 1e3, log=True))
    elif algo == "rf":
        model = RandomForestRegressor(
            n_estimators=trial.suggest_int("rf_n", 50, 300),
            max_depth=trial.suggest_int("rf_depth", 2, 10),
            random_state=0)
    else:  # gbdt
        model = GradientBoostingRegressor(
            n_estimators=trial.suggest_int("gb_n", 50, 300),
            learning_rate=trial.suggest_float("gb_lr", 0.01, 0.3, log=True),
            random_state=0)

    # 上のブロックで作った X(総人口/15歳未満/65歳以上/婚姻件数)と y(出生数)を使う
    val_loss = -cross_val_score(model, X, y, cv=5,
                                scoring="neg_root_mean_squared_error").mean()
    return val_loss

study = optuna.create_study(direction="minimize",
                            sampler=optuna.samplers.TPESampler(seed=42))
study.optimize(objective, n_trials=20)
print(study.best_params)
print("best RMSE =", round(study.best_value, 1), "人")

lr_sgd, lr_adam, lr_rms を別のキーにする点に注意。 optimizer ごとに最適 lr のスケールが大きく異なるため、 同じキーにすると TPE が混乱する。

③ メタ学習:過去の study から良い初期値を引き継ぐ

同じタスク・同じモデル・データが少し変わっただけ、 という状況では「前回の best_params を初期値として始める」と数十試行を節約できる。 Optuna では study.enqueue_trial(prior_best_params) で実現可能。

[模式図・値は説明用] 前年断面 → 今年断面 のメタ学習イメージ(RMSE は概念値・実測ログではない) | 戦略 | 試行数 | best RMSE | |------|--------|-----------| | Cold start (TPE のみ) | 50 | 2287 | | Warm start (2024 best を enqueue) + 30 試行 | 30 | 2279 | | Warm start + 20 試行 | 20 | 2305 | → Warm start で 30 試行に減らしても同等以上の精度 → 同じ HP 構造のタスクが繰り返し発生する場合は必須テクニック

④ HPT の自動停止:plateau 検知

「ベストスコアが直近 N 試行で改善しなければ自動停止」を実装すると、 計算予算を無駄にしない。 Optuna の callback で実装可能。

📝 plateau 検知 callback の例

 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 optuna     # このコードは Optuna が必要

class EarlyStoppingCallback:
    def __init__(self, patience=10, min_delta=1e-4):
        self.patience = patience
        self.min_delta = min_delta
        self.best = float("inf")
        self.no_improve = 0

    def __call__(self, study, trial):
        if study.best_value < self.best - self.min_delta:
            self.best = study.best_value
            self.no_improve = 0
        else:
            self.no_improve += 1
        if self.no_improve >= self.patience:
            study.stop()

# 上のブロックの objective をそのまま使う(study は作り直す)
study = optuna.create_study(direction="minimize",
                            sampler=optuna.samplers.TPESampler(seed=42))
cb = EarlyStoppingCallback(patience=10)
study.optimize(objective, n_trials=60, callbacks=[cb])
print("打ち切りまでの試行数:", len(study.trials), "/ 60")
print("best RMSE =", round(study.best_value, 1), "人")

→ 200 試行を上限にしつつ、 plateau なら早期停止。 SSDSE-B-2026 で実測すると 50-80 試行で plateau し、 自動停止することが多い。

❓ よく聞かれる質問 (FAQ) 10 連発

Q1. デフォルトハイパラと HPT 後でどれくらい差が出るのが普通?

タスク次第。 LightGBM/XGBoost は 5-20% の改善が一般的。 ニューラルネットは 10-50%。 単純な線形モデルは 1-5%。 なお本ページのケーススタディ(SSDSE-B-2026・n=47)では出生数予測で 8 割超の改善が出たが、 これは既定 min_child_samples が小標本に不適合だった特殊事情で、 一般的な中〜大規模データでは上記レンジが目安。

Q2. Optuna と Hyperopt はどちらが良い?

2026 年現在は Optuna が事実上の標準。 (1) define-by-run の柔軟性、 (2) pruner との統合、 (3) 可視化機能、 (4) 分散実行のしやすさで Hyperopt を上回る。 Hyperopt は研究用や fmin 系インターフェースが好きな人向け。

Q3. 試行数は何個必要?

経験則:HP 次元数 × 10-30。 4 次元なら 40-120 試行。 ベイズ最適化なら 50 試行で多くの場合 plateau に到達する。 plateau callback を使えば適応的に決まる。

Q4. CV の k は HPT 中に変えても良い?

基本的に固定。 ただし、 「最初の 20 試行は k=3 で素早く絞り、 残り 30 試行は k=5 で精密に評価」のような段階的精緻化は実務でよく使われる。

Q5. random_state は何のために固定する?

CV の split、 model fit、 sampler の seed の 3 箇所で固定する。 これにより「同じ HP で再評価すれば必ず同じスコア」が保証され、 HPT が「ノイズに対する過適合」を起こすリスクを減らせる。

Q6. 過適合した HP かどうかをどう判定?

「CV のベストスコア」と「ホールドアウトテストのスコア」の差を見る。 5% 以上乖離していれば HPT 過適合。 ネステッド CV で計測すれば乖離量を定量化できる。

Q7. 「best_params で再学習」のときに使うデータは?

トレインデータ全体(CV で分割していたデータの結合)。 ホールドアウトのテストは触らない。 これが汎化性能の真の推定。 SSDSE-B-2026 なら 47 県を全部使って再学習し、 翌年度のデータで最終評価する。

Q8. HPT の結果を再現できない

seed 固定漏れが最大の原因。 numpy, torch, tensorflow, optuna の sampler、 LightGBM/XGBoost の random_state、 sklearn の train_test_split の seed をすべて指定する。 GPU を使う場合は cudnn.deterministic=True も必須。

Q9. ハイパラを「離散値」にすべき?「連続値」にすべき?

通常は連続値 (suggest_float) が良い。 ただし n_estimators や max_depth のような本質的に整数の HP は suggest_int。 lr, C, reg_alpha のように対数スケールが自然な HP は log=True を指定。

Q10. HPT は再現性確保が必須?

学術論文・コンペでは必須。 業務システムでは、 ベスト HP を pickle 保存・モデルアーティファクト管理(MLflow、 Weights & Biases)すれば、 探索プロセスの再現は不要。 「最終モデルの再現」だけ確保すればよい。

📓 タスク別 HPT クックブック:明日から使える設定値テンプレート

理論と一般論を踏まえても、 実務で最も欲しいのは「このタスクならこの探索範囲・この試行数・このサンプラ」という具体的レシピ。 ここではよく出会う 6 つのタスク類型に対して、 「最低限こう書けばまず外れない」テンプレートをまとめた。 SSDSE-B-2026 で実測した値をベースに、 一般化しやすい範囲で調整してある。

① 表形式・小サンプル(n=30-100)・回帰タスク

モデル: Ridge / Lasso / ElasticNet 試行数: 20-30 (1 次元探索) sampler: TPESampler(n_startup_trials=10) pruner: なし (試行が瞬時に終わる) CV: LeaveOneOut または k=5 Stratified 探索範囲(必須): alpha: suggest_float("alpha", 1e-4, 1e2, log=True) l1_ratio (ElasticNet のみ): suggest_float("l1_ratio", 0, 1) ポイント: ・小サンプルでは TPE と Grid の差は小さい → Grid で十分 ・ホールドアウトを 20% 切って汎化評価

② 表形式・中サンプル(n=1k-100k)・GBDT

モデル: LightGBM / XGBoost / CatBoost 試行数: 50-200 sampler: TPESampler(n_startup_trials=20, multivariate=True) pruner: MedianPruner(n_startup_trials=10, n_warmup_steps=30) CV: k=5 StratifiedKFold 探索範囲(推奨): n_estimators: suggest_int(50, 1500) max_depth: suggest_int(3, 12) learning_rate: suggest_float(0.005, 0.3, log=True) num_leaves: suggest_int(15, 256) # LightGBM 専用 min_child_weight: suggest_float(1e-3, 10, log=True) subsample: suggest_float(0.5, 1.0) colsample_bytree: suggest_float(0.5, 1.0) reg_alpha: suggest_float(1e-8, 10, log=True) reg_lambda: suggest_float(1e-8, 10, log=True) ポイント: ・early_stopping_rounds=50 で n_estimators を自動決定すると安定 ・pruner で 30-40% の試行をカット → 計算時間 60-70% ・multivariate=True は HP 間の相関を学習する

③ 表形式・大サンプル(n>1M)・GBDT

モデル: LightGBM (GPU 推奨) 試行数: 30-50 (1 試行が長い) sampler: TPESampler + multivariate pruner: HyperbandPruner(min_resource=50, max_resource=500) CV: k=3 (k を減らして時間節約) または ホールドアウト分割 ポイント: ・サブサンプリング (例: 10% の訓練データで HPT、 best HP で全データ再学習) ・GPU 上で LightGBM/XGBoost を回す ・分散実行 (Optuna RDB + 複数 GPU) で 4-8x 加速

④ ニューラルネット・画像分類

モデル: ResNet / EfficientNet / ViT 試行数: 30-80 sampler: TPESampler(n_startup_trials=15) pruner: HyperbandPruner(min_resource=2, max_resource=20, reduction_factor=3) CV: ホールドアウト (k-fold は計算的に困難) 探索範囲(推奨): lr: suggest_float(1e-5, 1e-1, log=True) weight_decay: suggest_float(1e-6, 1e-2, log=True) batch_size: suggest_categorical([32, 64, 128, 256]) optimizer: suggest_categorical(["adam", "sgd", "adamw"]) scheduler: suggest_categorical(["cosine", "step", "none"]) dropout_rate: suggest_float(0.0, 0.5) augmentation: suggest_categorical(["light", "medium", "strong"]) ポイント: ・pruner の min_resource はエポック単位 ・WandB/MLflow と統合してロギング必須 ・mixed precision で 2x 高速化

⑤ NLP・Transformer fine-tuning

モデル: BERT / RoBERTa / GPT 系 試行数: 20-40 sampler: TPESampler(n_startup_trials=10) pruner: PercentilePruner(percentile=25, n_warmup_steps=100) 探索範囲(推奨): lr: suggest_float(1e-6, 1e-4, log=True) # 小さい warmup_ratio: suggest_float(0.0, 0.2) weight_decay: suggest_float(0.0, 0.1) batch_size: suggest_categorical([8, 16, 32]) epochs: suggest_int(2, 6) label_smoothing: suggest_float(0.0, 0.2) ポイント: ・lr の範囲は GBDT より 2 桁低い ・PercentilePruner(25) で下位 25% のみ切る ・epoch 数は HP に含める (収束が HP 依存)

⑥ 強化学習・Bandit 系

モデル: PPO / DQN / SAC 試行数: 20-50 (1 試行が極めて長い) sampler: TPESampler(n_startup_trials=10) または GP-EI pruner: なし (学習曲線が極めてノイジー) 評価: 複数 seed 平均(同じ HP で 3-5 seed 走らせて平均) 探索範囲(推奨): lr: suggest_float(1e-5, 1e-3, log=True) discount_factor: suggest_float(0.9, 0.999) batch_size: suggest_categorical([32, 64, 128, 256]) ent_coef: suggest_float(0.0, 0.05) clip_range (PPO): suggest_float(0.1, 0.4) ポイント: ・RL は再現性が極めて低い → 必ず複数 seed ・early_stopping より「目標スコア到達で終了」が良い ・PBT (Population Based Training) も検討

クックブックから外れる時のシグナル

💡 SSDSE-B-2026 でクックブック実証:①〜⑥ のうち SSDSE-B-2026 (n=47) は ① の小サンプル領域に該当。 alpha のみの 1 次元探索なら Optuna 20 試行で十分収束する。 もしも将来的に「全国市区町村統計 (n≈1,700)」へ拡張するなら、 ② の中サンプル・GBDT クックブックに移行し、 LightGBM + TPE + MedianPruner で 50 試行回すのが定石。 「データ規模に応じてクックブックを差し替える」という発想自体が、 実務 HPT の核となる。

クックブック総括:3 段階の意思決定フロー

HPT を行う前に、 (Step 1) データ規模とタスク種別を見極める、 (Step 2) クックブックから探索範囲・試行数・サンプラ・プルナを選ぶ、 (Step 3) 1 ラウンド回して param_importances を見て探索空間を縮小、 という 3 段階で進めると、 計算予算を最大限有効活用できる。 特に Step 3 の「探索空間縮小」を怠ると、 試行数を増やしてもスコアが頭打ちになるパターンが多い。 また、 best_value の改善曲線がプラトーに達したら自動停止する callback の併用も推奨。 これらのプラクティスを組み合わせることで、 「初学者でも実務水準の HPT が再現可能」な状態を作り出せる。

補足として、 HPT は「機械学習パイプラインの最終調整」と捉えるべきで、 特徴量設計・データ前処理・モデル選択が固まる前に着手しても効果は限定的。 例えば、 不要な特徴量が混入していれば、 どんなに HP を最適化してもスコアの天井は変わらない。 そのため、 (a) ベースラインモデルをデフォルト HP で素早く構築、 (b) 特徴量・前処理を磨いて天井を引き上げる、 (c) 最後に HPT で残った 10-20% の伸びしろを刈り取る、 という順序が望ましい。 SSDSE-B-2026 の現場ワークフローでも、 この順序が結果的に最も時間効率が良いことが繰り返し確認されている。 また、 HPT に投入する計算予算(時間・GPU 時間・クラウド費用)を事前に上限設定しておくことで、 「際限なく試行を増やす罠」を回避できる点も実務上は重要な観点である。

📊 図で見るチューニング結果

探索点の密度
探索したハイパーパラメータの密度を確認する。
検証スコア分布
候補モデルの検証スコア分布を見る。
外れた試行の確認
外れた試行を失敗条件として記録する。

🧾 発表前の最終確認

ハイパーパラメータ調整では、 探索範囲、評価指標、検証方法、試行回数、乱数種を記録します。 テストデータで調整を繰り返すと過学習するため、 検証用データまたはネストした交差検証を使います。

🔗 隣接手法への橋渡し

ハイパーパラメータチューニングは、 モデル学習の「外側のループ」として位置づけられ、 学習アルゴリズム・評価指標・検証方法と一体で動く。

チューニングは「validation 性能の最大化」が目的だが、 valid を見すぎると test 性能との乖離が拡大する (二段階過学習)。 ネストした交差検証 (outer-fold で test、 inner-fold で valid) が金本位。

🌳 手法選択フロー

探索戦略を、 パラメータ次元と計算予算で 3 段階で判定する。

  1. パラメータが 3 個以下か? Yes → グリッドサーチで全組み合わせ評価、 No → 次へ (グリッドは指数爆発する)
  2. 計算予算は限られているか? Yes → ランダムサーチ (50-100 試行) で広範囲を覆う、 No → ベイズ最適化 (Optuna / hyperopt、 200-500 試行) で逐次絞り込み
  3. 多目的最適化か? 精度のみ → 単目的、 精度 + 計算量 → Pareto frontier (NSGA-II 等)、 公平性も含む → 制約付き最適化に拡張

初心者は sklearn.model_selection.RandomizedSearchCV から始めるのが推奨。 5 倍の試行効率でグリッドより良い結果が得られることが多い (Bergstra & Bengio 2012)。

🧭 追補 — 多重比較として見るハイパラ調整(既存節への補角)

ここまでの節(🎨 直感/⚠️ 落とし穴/🔬 Nested CV)と重複しない角度だけを補う。 一言でいえば「HPT は統計的には多重比較である」。 この見方を軸に、 直感・落とし穴・発展を 1 つずつ足す。

🎨 直感の別角度 — 探索は「同じ試験を何百回も受け直す」行為

HPT の 1 試行ごとに、 同じ検証データ(val / CV fold)を繰り返し覗いてスコアを測る。 100 通り試して「最良」を採ることは、 100 個の推定値から最大値を拾うのと同じ。 各スコアには標本ゆらぎ(ノイズ)が乗るため、 最大値は必ず「実力+たまたま上振れた分」になる。 これが探索固有の楽観バイアス(winner's curse)で、 過学習データリークとは別種の、 評価プロトコルそのものに内在するバイアスである点が要注意。

⚠️ 重要な落とし穴 — 「best CV スコア」を実測で疑う(SSDSE-B-2026)

この楽観バイアスは概念論ではなく実測できる。 SSDSE-B-2026(2023 年・47 都道府県)で、 出生数 A4101 を 3 指標(65歳以上人口 A1303/就業関連 F3101/死亡数 A5101)から RandomForest で予測し、 n_estimators×max_depth×min_samples_split の 36 通りを 5-fold CV で探索した実測値:

探索した 36 設定の CV R² 平均 = 0.868
その中の 最良 CV R²(=つい報告してしまう値) = 0.903
Nested CV(外側5×内側5)で測り直した R² = 0.890
──────────────────────────────
楽観バイアス = 0.903 − 0.890 = +0.013

つまり「best CV R²=0.903」をそのまま汎化性能として書くと、 実力(≒0.890)を 0.013 だけ過大評価している。 探索した設定数が多いほど最大値の上振れは大きくなる(多重比較と同じ構造)ので、 大規模 HPO ほどこの差は開く。 対策は既出の Nested CV で汎化を推定し、 交差検証の best スコアは探索の内部指標と割り切ること。 なお本節の 3 値は SSDSE-B-2026 の実データでの実測(合成ではない)。

🔬 発展その1 — 「1 標準誤差ルール」で上振れ設定を避ける

最大値をそのまま採るのが上振れの元凶なら、 最良から 1 標準誤差(1-SE)以内で最もシンプルな設定を選ぶと過剰適合を抑えられる(Breiman らの 1-SE rule)。 GridSearchCVcv_results_ には各設定の mean_test_scorestd_test_score があり、 「best − 1×std 以上を満たす中で、 正則化が強い/木が浅い側」を選べば、 正則化を一段効かせた保守的な選択になる。 モデル選択を「最良点の一点狙い」から「安定した高原(plateau)狙い」へ変える発想で、 上の楽観バイアスへの実務的な緩衝材になる。

🌐 発展その2 — 並列化と探索戦略の相性(ASHA)

ベイズ最適化(Optuna の TPE)は「前の結果を見て次を決める」逐次依存があり、 多数ワーカーで同時に回すと過去情報が薄いまま提案するため並列効率が落ちる。 対して ASHA(Asynchronous Successive Halving)は、 遅い試行を待たず早期打ち切りを非同期に進めるため、 数十〜数百ワーカーへ素直にスケールする。 「Bayesian は少試行で賢く/ASHA は大並列で速く」という予算の形(少数逐次 vs 大量並列)で選ぶのが実務の勘所。 ASHA・Hyperband・BOHB は Optuna や Ray Tune で切替できる。

🔗 関連ページ:ハイパーパラメータNested CV交差検証データリークGrid SearchOptuna過学習モデル選択正則化。 (Random Search・Bayesian Optimization・Hyperband・ASHA・Early Stopping の独立ページは未整備のため、 本文中で解説している)