モデルチューニング
「特徴量エンジニアリングで底力が上がった。最後の仕上げはチューニングだ。」
田中VPoEがOptunaのドキュメントを開く。
「グリッドサーチは古い。Optunaでベイズ最適化を使えば、効率的に最適なハイパーパラメータを見つけられる。ただし、過学習には気をつけろ。」
ハイパーパラメータチューニングの戦略
チューニングの手法:
1. Grid Search → 全組み合わせを試す(小規模向け)
2. Random Search → ランダムにサンプリング(中規模向け)
3. Bayesian Opt → 過去の結果から次の候補を選ぶ(効率的)
4. Optuna → ベイズ最適化 + 枝刈り(推奨)
交差検証の設計
from sklearn.model_selection import StratifiedKFold
# 層化K分割交差検証
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 各foldの離反率を確認
for fold, (train_idx, val_idx) in enumerate(cv.split(X_train, y_train)):
fold_rate = y_train.iloc[val_idx].mean()
print(f"Fold {fold+1}: 離反率 {fold_rate:.3f}")
なぜ交差検証が必要か
| 手法 | 利点 | 欠点 |
|---|---|---|
| ホールドアウト | 高速 | 分割の偶然に依存 |
| K分割交差検証 | 安定した評価 | 計算コストがK倍 |
| 層化K分割 | 不均衡データに対応 | やや複雑 |
| リピートK分割 | 最も安定 | 計算コスト大 |
Optunaによるハイパーパラメータ最適化
LightGBMのチューニング
import optuna
from lightgbm import LGBMClassifier
from sklearn.model_selection import cross_val_score
import numpy as np
def objective(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 500),
'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.6, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
'min_child_samples': trial.suggest_int('min_child_samples', 5, 50),
'reg_alpha': trial.suggest_float('reg_alpha', 1e-8, 10.0, log=True),
'reg_lambda': trial.suggest_float('reg_lambda', 1e-8, 10.0, log=True),
'num_leaves': trial.suggest_int('num_leaves', 20, 150),
'is_unbalance': True,
'random_state': 42,
'verbose': -1,
}
model = LGBMClassifier(**params)
scores = cross_val_score(
model, X_train, y_train,
cv=cv, scoring='roc_auc', n_jobs=-1
)
return scores.mean()
# 最適化の実行
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, show_progress_bar=True)
print(f"Best AUC-ROC: {study.best_value:.4f}")
print(f"Best params: {study.best_params}")
XGBoostのチューニング
def objective_xgb(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 100, 500),
'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.6, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
'min_child_weight': trial.suggest_int('min_child_weight', 1, 20),
'gamma': trial.suggest_float('gamma', 1e-8, 5.0, log=True),
'reg_alpha': trial.suggest_float('reg_alpha', 1e-8, 10.0, log=True),
'reg_lambda': trial.suggest_float('reg_lambda', 1e-8, 10.0, log=True),
'scale_pos_weight': len(y_train[y_train==0]) / len(y_train[y_train==1]),
'random_state': 42,
'eval_metric': 'auc',
'use_label_encoder': False,
}
model = XGBClassifier(**params)
scores = cross_val_score(
model, X_train, y_train,
cv=cv, scoring='roc_auc', n_jobs=-1
)
return scores.mean()
study_xgb = optuna.create_study(direction='maximize')
study_xgb.optimize(objective_xgb, n_trials=100, show_progress_bar=True)
チューニング結果の分析
# パラメータの重要度
importance = optuna.importance.get_param_importances(study)
print("パラメータ重要度:")
for k, v in importance.items():
print(f" {k}: {v:.4f}")
# 最適化の履歴
fig = optuna.visualization.plot_optimization_history(study)
fig.write_image('optuna_history.png')
# パラメータ間の関係
fig = optuna.visualization.plot_param_importances(study)
fig.write_image('optuna_param_importance.png')
最終モデルの構築と評価
# 最良パラメータでモデルを構築
best_model = LGBMClassifier(**study.best_params, is_unbalance=True,
random_state=42, verbose=-1)
best_model.fit(X_train, y_train)
# テストデータで最終評価
from sklearn.metrics import roc_auc_score, average_precision_score, classification_report
y_test_proba = best_model.predict_proba(X_test)[:, 1]
y_test_pred = best_model.predict(X_test)
print("=== 最終モデル(テストデータ)===")
print(f"AUC-ROC: {roc_auc_score(y_test, y_test_proba):.4f}")
print(f"PR-AUC: {average_precision_score(y_test, y_test_proba):.4f}")
print(f"\n{classification_report(y_test, y_test_pred)}")
過学習の検知
# 訓練スコア vs 検証スコア
y_train_proba = best_model.predict_proba(X_train)[:, 1]
train_auc = roc_auc_score(y_train, y_train_proba)
test_auc = roc_auc_score(y_test, y_test_proba)
print(f"訓練 AUC-ROC: {train_auc:.4f}")
print(f"テスト AUC-ROC: {test_auc:.4f}")
print(f"差分: {train_auc - test_auc:.4f}")
# 差分が0.05以上なら過学習の疑い
if train_auc - test_auc > 0.05:
print("⚠ 過学習の可能性あり。正則化を強化してください。")
モデルの保存
import joblib
# モデルと前処理の保存
joblib.dump(best_model, 'churn_model.pkl')
joblib.dump(scaler, 'scaler.pkl')
# 特徴量リストの保存
with open('feature_names.txt', 'w') as f:
for name in X_train.columns:
f.write(f"{name}\n")
print("モデルを保存しました。")
まとめ
| 項目 | ポイント |
|---|---|
| 推奨手法 | Optuna(ベイズ最適化 + 枝刈り) |
| 交差検証 | 層化5分割交差検証 |
| 主要パラメータ | learning_rate, max_depth, num_leaves |
| 過学習検知 | 訓練/テストのスコア差を確認 |
| 最終評価 | テストデータ(一度だけ使用) |
チェックリスト
- Optunaでハイパーパラメータ最適化を実行できる
- 交差検証の設計ができる
- パラメータの重要度を分析できる
- 過学習を検知し対処できる
- モデルを保存・読み込みできる
次のステップへ
モデルのチューニングが完了した。次は演習で、ここまでの一連のモデル構築プロセスを実践してみよう。
推定読了時間: 30分