📚 背景知識(読んでから問題へ)
🎯
Day 064 — XGBoost(Day 061)・LightGBM(Day 062)・CatBoost(Day 063)と学んだ3大GBDTを Optuna で自動チューニングするスキルを身につけます。手動で探していたハイパーパラメータを、ベイズ最適化で賢く自動探索します。
なぜハイパーパラメータのチューニングが必要か?
| 状況 | デフォルトで学習 | チューニング後 |
|---|---|---|
| Titanic(n=891)の典型 | CV Accuracy ≈ 0.80 | CV Accuracy ≈ 0.83〜0.85 |
| House Prices(回帰)の典型 | RMSE ≈ 30,000 | RMSE ≈ 22,000(改善率 27%) |
| コンペ上位との差 | LBスコア 0.80(中位) | チューニングでTop 20%に入ることも |
チューニング手法の比較
| 手法 | 仕組み | 問題点 | Kaggleでの使用 |
|---|---|---|---|
| グリッドサーチ | 全組み合わせを試す | 組み合わせ爆発(4パラメータ×5段階 = 625回) | ほぼ使わない |
| ランダムサーチ | ランダムに組み合わせを試す | 運任せ・非効率 | たまに使う |
| Optuna(ベイズ最適化) | 過去の結果を参考に次の点を選ぶ | 効率的・少ない試行数で高精度 | 上位入賞者の標準ツール |
🔄 Optuna の仕組み — study / trial / objective
Optuna の実行フロー
1
study = optuna.create_study(direction='maximize')最適化セッション(記録帳)を作成。最大化か最小化かを指定。
2
def objective(trial): ...1回の実験を定義する関数。
trial.suggest_*() でパラメータを提案させ、モデルを学習して評価値を返す。3
study.optimize(objective, n_trials=50)objective を n_trials 回呼び出す。内部でベイズ最適化アルゴリズム(TPE)が次の試行点を決める。
4
study.best_params / study.best_value全試行のうち最も良かったパラメータとスコアを取得。
study と trial の関係
🎛️ suggest_* メソッド一覧とパラメータ対応
trial.suggest_int(name, low, high)
整数型
木の本数・深さ・葉の最小サンプル数など整数値のパラメータに使用。
例:
例:
例:
例:
suggest_int('n_estimators', 100, 1000)例:
suggest_int('max_depth', 3, 10)例:
suggest_int('num_leaves', 20, 300)
trial.suggest_float(name, low, high)
浮動小数点型(線形スケール)
0〜1 の範囲のサンプリング率などに使用。
例:
例:
例:
例:
suggest_float('subsample', 0.5, 1.0)例:
suggest_float('colsample_bytree', 0.5, 1.0)例:
suggest_float('reg_alpha', 0.0, 1.0)
trial.suggest_float(name, low, high, log=True)
浮動小数点型(対数スケール)
learning_rate / 正則化係数などの小さい値に使用。
例:
例:
なぜlog? → 0.01と0.02の差も0.1と0.2の差も同等に扱うため
例:
suggest_float('learning_rate', 0.01, 0.3, log=True)例:
suggest_float('l2_leaf_reg', 1.0, 30.0, log=True)なぜlog? → 0.01と0.02の差も0.1と0.2の差も同等に扱うため
trial.suggest_categorical(name, choices)
カテゴリ選択型
離散的な選択肢のパラメータに使用。
例:
例:
例:
例:
suggest_categorical('booster', ['gbtree', 'dart'])例:
suggest_categorical('boosting_type', ['Ordered', 'Plain'])例:
suggest_categorical('grow_policy', ['SymmetricTree', 'Depthwise'])
🧠 ベイズ最適化の直感 — 賢く山の頂上を探す
3つの探索戦略の比較(横軸: learning_rate、縦軸: CV Accuracy)
📊 対数スケール vs 線形スケール — なぜ log=True が必要か
learning_rate = 0.001〜0.1 の範囲を8点で探索する場合の違い
💡
経験則:
learning_rate / l2_leaf_reg / reg_alpha / min_child_weight は log=True を使う。subsample / colsample_bytree のような 0〜1 の範囲は線形スケールで十分。チューニング効果の目安(Titanic Accuracy)
🗂️ データスキーマ(Titanic 風 n=5,000)
| 列名 | 型 | 値の範囲 | 説明 | チューニングでの重要度目安 |
|---|---|---|---|---|
Pclass | int | 1, 2, 3 | 旅客クラス | 中(GBDTが自動で扱う) |
Sex | str | male, female | 性別(最重要特徴量) | 高 |
Age | float | 1〜80 | 年齢 | 中〜高 |
SibSp | int | 0〜8 | 同乗兄弟/配偶者数 | 低〜中 |
Parch | int | 0〜6 | 同乗親/子供数 | 低〜中 |
Fare | float | 0〜∞ | 運賃 | 中 |
Embarked | str | S, C, Q | 乗船港 | 低 |
Cabin_type | str | A,B,C,D,E,None | キャビン種別 | 低〜中 |
Survived | int | 0 / 1 | 生存(目的変数) | — |
📝 問題
セットアップコード(最初に実行)
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
import warnings
warnings.filterwarnings('ignore')
try:
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING)
print("Optuna: OK")
except ImportError:
print("pip install optuna")
try:
import xgboost as xgb
print("XGBoost: OK")
except ImportError:
print("pip install xgboost")
try:
import lightgbm as lgb
print("LightGBM: OK")
except ImportError:
print("pip install lightgbm")
try:
from catboost import CatBoostClassifier
print("CatBoost: OK")
except ImportError:
print("pip install catboost")
# Titanic 風データ生成(n=5000)
np.random.seed(42)
n = 5000
data = pd.DataFrame({
'Pclass': np.random.choice([1, 2, 3], n, p=[0.24, 0.21, 0.55]),
'Sex': np.random.choice(['male', 'female'], n, p=[0.65, 0.35]),
'Age': np.random.normal(29.7, 14.5, n).clip(1, 80),
'SibSp': np.random.choice(range(9), n, p=[0.68,0.23,0.04,0.02,0.01,0.01,0.005,0.005,0.01]),
'Parch': np.random.choice(range(7), n, p=[0.76,0.13,0.09,0.005,0.005,0.005,0.005]),
'Fare': np.abs(np.random.normal(32, 50, n)),
'Embarked': np.random.choice(['S', 'C', 'Q'], n, p=[0.72, 0.19, 0.09]),
'Cabin_type': np.random.choice(['A','B','C','D','E','None'], n,
p=[0.05,0.08,0.12,0.1,0.07,0.58]),
})
data['Survived'] = (
(data['Sex'] == 'female') * 0.55 + (data['Pclass'] == 1) * 0.25 +
(data['Age'] < 16) * 0.2 + np.random.normal(0, 0.12, n)
) > 0.4
data['Survived'] = data['Survived'].astype(int)
cat_cols = ['Sex', 'Embarked', 'Cabin_type']
data_enc = data.copy()
for col in cat_cols:
data_enc[col] = LabelEncoder().fit_transform(data_enc[col])
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_type']
X_raw = data[features]
X_enc = data_enc[features].values
y = data['Survived'].values
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
def manual_cv_score(clf, X, y, cv):
scores = []
for tr_idx, val_idx in cv.split(X, y):
if isinstance(X, pd.DataFrame):
X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]
else:
X_tr, X_val = X[tr_idx], X[val_idx]
y_tr, y_val = y[tr_idx], y[val_idx]
clf.fit(X_tr, y_tr)
scores.append(accuracy_score(y_val, clf.predict(X_val)))
return float(np.mean(scores))
print("データ準備完了:", X_raw.shape)
問1 — XGBoost の Optuna チューニング(基本)
objective(trial)関数を定義し、以下のパラメータを最適化せよn_estimators: 100〜1000(整数)max_depth: 3〜10(整数)learning_rate: 0.01〜0.3(対数スケール浮動小数点)subsample: 0.5〜1.0(浮動小数点)colsample_bytree: 0.5〜1.0(浮動小数点)
study.optimize(objective, n_trials=30)で最適化を実行せよstudy.best_paramsとstudy.best_valueを表示せよ- デフォルトパラメータとの精度差を表示せよ
問2 — LightGBM の Optuna チューニング
- LightGBM 向けの
objective関数を定義し、以下を最適化せよn_estimators: 100〜1000(整数)num_leaves: 20〜300(整数)learning_rate: 0.01〜0.3(対数スケール)min_child_samples: 5〜100(整数)reg_alpha: 0.0〜1.0(浮動小数点)reg_lambda: 0.0〜10.0(浮動小数点)
- n_trials=30 で最適化し、best_params と best_value を表示せよ
- デフォルトパラメータとの精度差を表示せよ
問3 — CatBoost の Optuna チューニング
- CatBoost 向けの
objective関数を定義し、以下を最適化せよiterations: 100〜500(整数)depth: 4〜10(整数)learning_rate: 0.01〜0.3(対数スケール)l2_leaf_reg: 1.0〜30.0(対数スケール)
cat_features=['Sex','Embarked','Cabin_type']を使用すること- n_trials=20 で最適化(CatBoostは遅いため少なめ)し、結果を表示せよ
問4 — 最適化の履歴を可視化(matplotlib)
- 問1 の study オブジェクトを使い、試行ごとのスコア推移を折れ線グラフで描画せよ
- 各試行の累積最良スコア(cumulative best)も一緒にプロットせよ
- グラフのタイトルと軸ラベル、凡例を設定せよ
問5 — パラメータ重要度分析
optuna.importance.get_param_importances(study)で問1 のパラメータ重要度を取得せよ- 棒グラフで可視化せよ
- どのパラメータが XGBoost のスコアに最も影響を与えるか考察せよ
💡 ヒント
ヒント1(方向性)
pip install optunaでインストール。optuna.logging.set_verbosity(optuna.logging.WARNING)でログを抑制すること- CatBoost の objective 内では
manual_cv_scoreを使うこと(cross_val_scoreは cat_features を保持しないため) - XGBoost / LightGBM は
cross_val_scoreで直接評価できる direction='maximize'を忘れずに。デフォルトは minimize
ヒント2(アプローチ)
import optuna
def objective_xgb(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
'max_depth': trial.suggest_int('max_depth', 3, 10),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
'random_state': 42,
'eval_metric': 'logloss',
}
clf = xgb.XGBClassifier(**params)
score = cross_val_score(clf, X_enc, y, cv=cv, scoring='accuracy').mean()
return score
study_xgb = optuna.create_study(direction='maximize')
study_xgb.optimize(objective_xgb, n_trials=30)
print(f"Best: {study_xgb.best_value:.4f}")
print(study_xgb.best_params)
ヒント3(コード骨格)
# パラメータ重要度
from optuna.importance import get_param_importances
importances = get_param_importances(study_xgb)
print(importances)
# 可視化
import matplotlib.pyplot as plt
# 試行スコア推移
scores = [t.value for t in study_xgb.trials]
best_so_far = [max(scores[:i+1]) for i in range(len(scores))]
plt.figure(figsize=(10, 4))
plt.plot(scores, alpha=0.5, label='各試行スコア')
plt.plot(best_so_far, color='red', label='累積最良スコア')
plt.xlabel('試行番号')
plt.ylabel('CV Accuracy')
plt.title('XGBoost Optuna 最適化 — スコア推移')
plt.legend()
plt.tight_layout()
plt.show()
# 棒グラフ(重要度)
plt.figure(figsize=(8, 4))
plt.barh(list(importances.keys()), list(importances.values()))
plt.xlabel('重要度スコア')
plt.title('パラメータ重要度')
plt.tight_layout()
plt.show()
✅ 模範解答
import numpy as np
import pandas as pd
import optuna
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import accuracy_score
from optuna.importance import get_param_importances
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
optuna.logging.set_verbosity(optuna.logging.WARNING)
# ── データ準備 ──
np.random.seed(42)
n = 5000
data = pd.DataFrame({
'Pclass': np.random.choice([1, 2, 3], n, p=[0.24, 0.21, 0.55]),
'Sex': np.random.choice(['male', 'female'], n, p=[0.65, 0.35]),
'Age': np.random.normal(29.7, 14.5, n).clip(1, 80),
'SibSp': np.random.choice(range(9), n, p=[0.68,0.23,0.04,0.02,0.01,0.01,0.005,0.005,0.01]),
'Parch': np.random.choice(range(7), n, p=[0.76,0.13,0.09,0.005,0.005,0.005,0.005]),
'Fare': np.abs(np.random.normal(32, 50, n)),
'Embarked': np.random.choice(['S', 'C', 'Q'], n, p=[0.72, 0.19, 0.09]),
'Cabin_type': np.random.choice(['A','B','C','D','E','None'], n,
p=[0.05,0.08,0.12,0.1,0.07,0.58]),
})
data['Survived'] = (
(data['Sex'] == 'female') * 0.55 + (data['Pclass'] == 1) * 0.25 +
(data['Age'] < 16) * 0.2 + np.random.normal(0, 0.12, n)
) > 0.4
data['Survived'] = data['Survived'].astype(int)
cat_cols = ['Sex', 'Embarked', 'Cabin_type']
data_enc = data.copy()
for col in cat_cols:
data_enc[col] = LabelEncoder().fit_transform(data_enc[col])
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_type']
X_raw = data[features]
X_enc = data_enc[features].values
y = data['Survived'].values
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
def manual_cv_score(clf, X, y, cv):
scores = []
for tr_idx, val_idx in cv.split(X, y):
if isinstance(X, pd.DataFrame):
X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]
else:
X_tr, X_val = X[tr_idx], X[val_idx]
y_tr, y_val = y[tr_idx], y[val_idx]
clf.fit(X_tr, y_tr)
scores.append(accuracy_score(y_val, clf.predict(X_val)))
return float(np.mean(scores))
# ── 問1: XGBoost ──
print("=" * 65)
print("問1: XGBoost Optuna チューニング")
print("=" * 65)
default_xgb_score = cross_val_score(
xgb.XGBClassifier(random_state=42, eval_metric='logloss'),
X_enc, y, cv=cv, scoring='accuracy'
).mean()
print(f"デフォルト XGBoost CV: {default_xgb_score:.4f}")
def objective_xgb(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
'max_depth': trial.suggest_int('max_depth', 3, 10),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
'random_state': 42, 'eval_metric': 'logloss',
}
clf = xgb.XGBClassifier(**params)
return cross_val_score(clf, X_enc, y, cv=cv, scoring='accuracy').mean()
study_xgb = optuna.create_study(direction='maximize')
study_xgb.optimize(objective_xgb, n_trials=30)
print(f"最適化 XGBoost CV: {study_xgb.best_value:.4f}")
print(f"改善幅: {study_xgb.best_value - default_xgb_score:+.4f}")
print("最良パラメータ:")
for k, v in study_xgb.best_params.items():
print(f" {k}: {v}")
# ── 問2: LightGBM ──
print("\n" + "=" * 65)
print("問2: LightGBM Optuna チューニング")
print("=" * 65)
default_lgb_score = cross_val_score(
lgb.LGBMClassifier(random_state=42, verbose=-1),
X_enc, y, cv=cv, scoring='accuracy'
).mean()
print(f"デフォルト LightGBM CV: {default_lgb_score:.4f}")
def objective_lgb(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
'num_leaves': trial.suggest_int('num_leaves', 20, 300),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
'min_child_samples': trial.suggest_int('min_child_samples', 5, 100),
'reg_alpha': trial.suggest_float('reg_alpha', 0.0, 1.0),
'reg_lambda': trial.suggest_float('reg_lambda', 0.0, 10.0),
'random_state': 42, 'verbose': -1,
}
clf = lgb.LGBMClassifier(**params)
return cross_val_score(clf, X_enc, y, cv=cv, scoring='accuracy').mean()
study_lgb = optuna.create_study(direction='maximize')
study_lgb.optimize(objective_lgb, n_trials=30)
print(f"最適化 LightGBM CV: {study_lgb.best_value:.4f}")
print(f"改善幅: {study_lgb.best_value - default_lgb_score:+.4f}")
print("最良パラメータ:")
for k, v in study_lgb.best_params.items():
print(f" {k}: {v}")
# ── 問3: CatBoost ──
print("\n" + "=" * 65)
print("問3: CatBoost Optuna チューニング")
print("=" * 65)
default_cat_score = manual_cv_score(
CatBoostClassifier(iterations=200,
cat_features=['Sex','Embarked','Cabin_type'],
random_seed=42, verbose=0),
X_raw, y, cv
)
print(f"デフォルト CatBoost CV: {default_cat_score:.4f}")
def objective_cat(trial):
params = {
'iterations': trial.suggest_int('iterations', 100, 500),
'depth': trial.suggest_int('depth', 4, 10),
'learning_rate': trial.suggest_float('learning_rate', 0.01, 0.3, log=True),
'l2_leaf_reg': trial.suggest_float('l2_leaf_reg', 1.0, 30.0, log=True),
'cat_features': ['Sex', 'Embarked', 'Cabin_type'],
'random_seed': 42, 'verbose': 0,
}
clf = CatBoostClassifier(**params)
return manual_cv_score(clf, X_raw, y, cv)
study_cat = optuna.create_study(direction='maximize')
study_cat.optimize(objective_cat, n_trials=20)
print(f"最適化 CatBoost CV: {study_cat.best_value:.4f}")
print(f"改善幅: {study_cat.best_value - default_cat_score:+.4f}")
print("最良パラメータ:")
for k, v in study_cat.best_params.items():
print(f" {k}: {v}")
# ── 問4: スコア推移可視化 ──
print("\n" + "=" * 65)
print("問4: スコア推移可視化")
print("=" * 65)
scores = [t.value for t in study_xgb.trials]
best_so_far = [max(scores[:i+1]) for i in range(len(scores))]
plt.figure(figsize=(10, 4))
plt.plot(scores, alpha=0.4, marker='o', markersize=4, label='各試行スコア', color='#38bdf8')
plt.plot(best_so_far, color='#f0c419', linewidth=2, label='累積最良スコア')
plt.axhline(default_xgb_score, color='#ef4444', linestyle='--', linewidth=1,
label=f'デフォルト ({default_xgb_score:.4f})')
plt.xlabel('試行番号')
plt.ylabel('CV Accuracy (5-Fold)')
plt.title('XGBoost Optuna 最適化 — スコア推移 (n_trials=30)')
plt.legend()
plt.tight_layout()
plt.show()
# ── 問5: パラメータ重要度 ──
print("\n" + "=" * 65)
print("問5: パラメータ重要度分析")
print("=" * 65)
importances = get_param_importances(study_xgb)
print("パラメータ重要度:")
for k, v in importances.items():
bar = "█" * int(v * 40)
print(f" {k:20s}: {v:.4f} {bar}")
plt.figure(figsize=(8, 4))
params_list = list(importances.keys())
values_list = list(importances.values())
colors = ['#22c55e' if v == max(values_list) else '#38bdf8' for v in values_list]
plt.barh(params_list, values_list, color=colors)
plt.xlabel('重要度スコア')
plt.title('XGBoost ハイパーパラメータ重要度 (Optuna)')
plt.tight_layout()
plt.show()
max_param = max(importances, key=importances.get)
print(f"\n考察: 最重要パラメータは '{max_param}'")
print("→ このパラメータのチューニングに注力すると効率的にスコアが改善できる")
🪜 Step-by-Step 解説
1Optuna の基本概念 — study と trial
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING) # ログ抑制(必須)
# study = 1回の最適化セッション(記録帳)
study = optuna.create_study(direction='maximize') # スコアを最大化
# trial = 1回の試行(パラメータ1セットの実験)
# objective 関数内で trial.suggest_* を使う
study は「最適化の記録帳」、trial は「今回試す1つの実験」のイメージ。direction='maximize' でスコアを上げる方向に最適化。
2suggest_* の使い分け
def objective(trial):
# 整数: n_estimators, max_depth, num_leaves など
n = trial.suggest_int('n_estimators', 100, 1000)
# 浮動小数点(線形スケール): subsample, colsample_bytree など
sub = trial.suggest_float('subsample', 0.5, 1.0)
# 浮動小数点(対数スケール): learning_rate, l2 正則化係数
# → 0.001 〜 0.1 のような小さい値の探索に使う
lr = trial.suggest_float('learning_rate', 0.01, 0.3, log=True)
# カテゴリ選択
booster = trial.suggest_categorical('booster', ['gbtree', 'dart'])
3CatBoost の注意点 — cross_val_score が使えない問題
# NG: cross_val_score は cat_features を保持しない
score = cross_val_score(
CatBoostClassifier(cat_features=['Sex', ...], verbose=0),
X_raw, y, cv=cv
).mean() # エラーまたは精度が低い
# OK: 手動CVで DataFrame を iloc でスライスする
def manual_cv_score(clf, X, y, cv):
scores = []
for tr_idx, val_idx in cv.split(X, y):
X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]
y_tr, y_val = y[tr_idx], y[val_idx]
clf.fit(X_tr, y_tr)
scores.append(accuracy_score(y_val, clf.predict(X_val)))
return np.mean(scores)
4累積最良スコアの計算
scores = [t.value for t in study.trials]
# t.value = その試行のスコア
best_so_far = [max(scores[:i+1]) for i in range(len(scores))]
# i 番目まで(含む)のスコアの最大値
# → グラフにすると「何回目でベストが更新されたか」が一目でわかる
5パラメータ重要度の読み方
from optuna.importance import get_param_importances
importances = get_param_importances(study)
# {'learning_rate': 0.45, 'max_depth': 0.30, 'n_estimators': 0.15, ...}
# → 合計が1になる相対的な重要度
# → 重要度が高いほど「そのパラメータがスコアに与える影響が大きい」
📐 数学・統計の補足(文系向け)
ベイズ最適化を直感的に理解する
| 段階 | ランダムサーチ | Optuna(ベイズ最適化) |
|---|---|---|
| 1回目 | でたらめに試す | でたらめに試す(同じ) |
| 2〜5回目 | またでたらめ | 1回目の結果を参考に良さそうな場所を絞る |
| N回後 | N回ランダムに試した | 賢く良い場所に集中して試した |
💡
山探しの例え: ランダムサーチ = 地図なしで山をでたらめに歩く。ベイズ最適化 = 「今いる場所の高さ」を記録しながら、より高い方向へ賢く移動する。
対数スケールが必要な理由
| スケール | 0.001〜0.1 の8点探索例 | 問題点 |
|---|---|---|
| 線形スケール | 0.001, 0.014, 0.028, 0.042, 0.055, 0.069, 0.083, 0.100 | 0.001〜0.01 の重要な低領域が粗い |
| 対数スケール | 0.001, 0.002, 0.004, 0.008, 0.017, 0.037, 0.079, 0.100 | 各桁を均等に探索できる |
🏆 Kaggleでの実践的な使い方
| 場面 | Optuna の使い方 | ポイント |
|---|---|---|
| コンペ序盤のベースライン | n_trials=20〜30 で粗く探索 | 速度優先。LightGBM が最速 |
| コンペ中盤 | n_trials=50〜100 で精密探索 | 探索範囲を粗い探索の結果で絞る |
| 最終提出前 | best_params でアンサンブル | XGB + LGB + CatBoost の3モデルを多数決 |
| 制限時間がある場合 | early_stopping_rounds と組み合わせ | 各trial の学習を早期終了して n_trials を増やす |
# Kaggle 本番パターン: 二段階チューニング
# Phase 1: 粗い探索(n_trials=20)
study = optuna.create_study(direction='maximize')
study.optimize(objective_xgb, n_trials=20)
# Phase 2: best_params 周辺を精密探索
best_lr = study.best_params['learning_rate']
best_depth = study.best_params['max_depth']
def objective_fine(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 300, 800),
'max_depth': trial.suggest_int('max_depth',
max(3, best_depth-2),
min(12, best_depth+2)),
'learning_rate': trial.suggest_float('learning_rate',
best_lr*0.5, best_lr*2.0, log=True),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
'random_state': 42, 'eval_metric': 'logloss',
}
return cross_val_score(xgb.XGBClassifier(**params), X_enc, y,
cv=cv, scoring='accuracy').mean()
study2 = optuna.create_study(direction='maximize')
study2.optimize(objective_fine, n_trials=30)
print(f"最終 Best: {study2.best_value:.4f}")
🚫 よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
direction を指定しない |
デフォルトで動く気がする | デフォルトは 'minimize'。accuracy は最大化なので 'maximize' を明示 |
log=True なしで learning_rate を探索 |
線形の方が均等に見える | 小さい値のレンジには必ず log=True を使う |
| CatBoost で cross_val_score を使う | sklearn の標準 API だから | DataFrame の iloc でスライスする手動 CV が必要 |
| n_trials を大きくしすぎる | 多いほど良いと思う | CV × trials 回学習。コンペ制限時間に注意。30〜50 が現実的 |
| best_value がデフォルトより低い | チューニングしたのに… | 探索範囲が外れていることが多い。デフォルト値を探索範囲内に含める |
| ログを抑制しない | デフォルト確認不足 | optuna.logging.set_verbosity(optuna.logging.WARNING) を最初に呼ぶ |
🚀 次のステップ
- 発展:
optuna.integration.OptunaSearchCVで sklearn の GridSearchCV と同じ使い方ができる。early_stopping_roundsとの組み合わせも試すこと - 次回予告(Day 065): スタッキング(Stacking Ensemble)— ベースモデルのOOF予測をメタモデルで統合する
📋 自己評価(解いた後に記入)
✍️
理解度: [ ] 完全理解 [ ] おおむね理解 [ ] 要復習
自分の回答:
気づき・メモ: