Optunaとは?Pythonでベイズ最適化を実装する方法をわかりやすく解説

目次

イントロダクション:「パラメータ調整の苦労」から解放される

XGBoostのモデルを構築するとき、以下のような悩みを持ったことはありませんか?

「max_depth をいくつにしよう?」 「learning_rate は 0.05? 0.1?」 「subsample をどこまで下げるべき?」

XGBoostには調整すべきハイパーパラメータ(学習パラメータ)が10個以上あります。これらを手作業で「試行錯誤」しながら調整するのは、非常に時間がかかります。


従来の方法の問題点

方法①:グリッドサーチ(手動で値を決める)

max_depth: [3, 5, 7, 9]
learning_rate: [0.01, 0.05, 0.1, 0.2]
subsample: [0.5, 0.7, 0.9, 1.0]

→ 4 × 4 × 4 = 64通りをすべて試す

問題点

  • 組み合わせが爆発的に増える(5パラメータなら3,125通り)
  • 無駄な試行が多い(明らかに悪い組み合わせも試している)
  • 計算時間がかかりすぎる

方法②:ランダムサーチ(ランダムに値を決める)

指定範囲内からランダムに値を選んで試す。

問題点

  • 「良い領域」を見つけてもそこを集中的に探索しない
  • 運に左右される(たまたま良い組み合わせに当たるまで待つ)
  • 効率が悪い

Optunaの基本:「頭の良い最適化」

定義

Optuna は、Preferred Networks 社が開発した、ハイパーパラメータの自動最適化フレームワーク です。

単なる「試行錯誤」ではなく、ベイズ最適化 という統計的手法を使用して、効率的に最適なパラメータを探索します。

Optunaの基本的な仕組み:試行を繰り返す中で「学習」する

重要なポイント

  • Trial 1 の結果から「max_depth=7 の方が良さそう」と判断
  • Trial 2 の結果から「learning_rate は0.05~0.1の範囲が良さそう」と推定
  • Trial 3, 4 では、その「良さそうな領域」を重点的に探索

つまり、前の試行の結果を活かして、次の試行の値を決めている ということです。


ベイズ最適化(Bayesian Optimization)の基本

Optunaが使う「ベイズ最適化」とは何か、直感的に理解しましょう。

簡単な例:最高気温を予測する

ある地点で、毎日の最高気温を観測しているとします:

日付気温(℃)
1月1日5
1月15日8
2月1日10
3月1日15
4月1日22

この数日のデータから、「5月1日の気温は何℃くらい?」と予測する方法が、ベイズ最適化の基本的な考え方 です。

  • 観測したデータが少ないうちは、不確実性が大きい(予測の幅が広い)
  • データが増えるにつれ、予測が精密になる
  • 次にどこを観測すべきかを統計的に判断

XGBoostの最適化への応用

ベイズ最適化を XGBoost のハイパーパラメータ探索に応用すると:

【これまでの試行結果】
- max_depth=3 → 精度 85%
- max_depth=9 → 精度 92%
- max_depth=5 → 精度 90%

【統計的な推定】
「max_depth は大きいほど精度が上がる傾向だが、
  9を超えると過学習で精度が下がる可能性がある。
  次は max_depth=7 を試してみよう」

このように、次に試すべき値を統計的に判断 するのが、Optunaの強みです。


Optunaが使う最適化アルゴリズム:TPE(Tree-structured Parzen Estimator)

Optuna のデフォルトは TPE というアルゴリズムです。

TPEの基本的な流れ

  1. これまでの試行結果を分析
    • 「良かった試行」と「悪かった試行」を分類
  2. 確率分布を推定
    • 良い試行で共通する特徴(パラメータ値)を分析
    • 「この領域は良さそう」という確率分布を作成
  3. 有望な領域を集中探索
    • 次の試行の値を、その確率分布から選択
  4. 繰り返す
    • 新しい試行結果が得られたら、分析と推定を更新

グリッドサーチ との効率比較

【グリッドサーチ】
64通り全て試す
      ↓ ↓ ↓ ↓
  |精度| 検索順序 → 時間がかかる

【Optuna (TPE)】
最初の10試行で「良さそうな領域」を特定
次の10試行で、その領域に集中
      ↓
 |精度| 効率的に最適値に到達 → 時間が短い

グリッドサーチ・ランダムサーチ・ Optuna の比較

特性グリッドサーチランダムサーチOptuna
アルゴリズム全組み合わせを試すランダムに値を選ぶベイズ最適化(TPE)
効率性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
計算時間長い(最悪)中程度最短(通常)
調整パラメータが多い場合✗ 使えない△ 使えるが非効率◎ 推奨
学習能力✗ 前の試行を活かさない✗ 活かさない◎ 活かす
実装難度⭐⭐⭐⭐⭐ 簡単⭐⭐⭐⭐⭐ 簡単⭐⭐⭐ 中程度

Optuna の基本的な使い方

Step 1. 目的関数を定義

python

def objective(trial):
    # ハイパーパラメータの候補値を定義
    params = {
        'max_depth': trial.suggest_int('max_depth', 3, 10),
        'learning_rate': trial.suggest_loguniform('learning_rate', 0.001, 0.3),
        'subsample': trial.suggest_uniform('subsample', 0.5, 1.0),
        'colsample_bytree': trial.suggest_uniform('colsample_bytree', 0.5, 1.0),
    }
    
    # XGBoostでモデルを学習
    model = xgb.XGBClassifier(**params)
    model.fit(X_train, y_train)
    
    # テストデータで評価
    accuracy = model.score(X_test, y_test)
    
    # 精度を返す(Optunaはこの値を最大化しようとする)
    return accuracy

Step 2. Study(探索セッション)を作成

python

import optuna

# Study の作成(最大化方向で探索)
study = optuna.create_study(direction='maximize')

# 100回の試行で最適化
study.optimize(objective, n_trials=100)

Step 3. 最適なパラメータを取得

python

# 最良の試行を取得
best_trial = study.best_trial

print(f"最高精度: {best_trial.value:.4f}")
print(f"最適パラメータ: {best_trial.params}")

# 出力例:
# 最高精度: 0.9658
# 最適パラメータ: {'max_depth': 7, 'learning_rate': 0.0523, ...}

ハイパーパラメータの指定方法

Optuna では、以下のようなメソッドで探索範囲を定義します:

パラメータの種類と指定方法

パラメータ指定方法用途
整数値suggest_int()max_depth: 3~10木の深さ、n_estimators
実数値(線形)suggest_uniform()subsample: 0.5~1.0サンプリング比率
実数値(対数スケール)suggest_loguniform()learning_rate: 0.001~0.3学習率
カテゴリ値suggest_categorical()['gblinear', 'dart']ブースター種別

XGBoost の典型的なパラメータ範囲

python

params = {
    # 木の構造に関するパラメータ
    'max_depth': trial.suggest_int('max_depth', 3, 10),
    'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
    'gamma': trial.suggest_loguniform('gamma', 0.001, 1.0),
    
    # 学習に関するパラメータ
    'learning_rate': trial.suggest_loguniform('learning_rate', 0.001, 0.3),
    'subsample': trial.suggest_uniform('subsample', 0.5, 1.0),
    'colsample_bytree': trial.suggest_uniform('colsample_bytree', 0.5, 1.0),
    
    # L1/L2正則化
    'reg_alpha': trial.suggest_loguniform('reg_alpha', 0.001, 10.0),
    'reg_lambda': trial.suggest_loguniform('reg_lambda', 0.001, 10.0),
}

なぜ対数スケール(loguniform)を使うのか?

学習率のように「0.001 と 0.01 の差」と「0.1 と 1.0 の差」が同じくらい重要な場合、対数スケールを使います。

線形スケール:0.001, 0.002, 0.003, ... 0.3
           (0.001~0.01の間に1000個の値)

対数スケール:0.001, 0.002, 0.005, 0.01, 0.02, ... 0.3
           (全範囲に満遍なく分布)

製造業での実践的な活用例

例1:鋼板強度予測モデルの精度向上

背景:鋼板の工程条件から完成後の強度を予測するXGBoostモデルを構築。現在の精度は92%だが、より高精度化したい。

手作業での調整の場合

  • 試行回数:30~50回
  • 時間:2~3日
  • 結果:93.5%

Optuna を使った場合

python

import optuna
import xgboost as xgb
import pandas as pd

# データ準備(工程条件と強度データ)
# X_train, X_test, y_train, y_test

def objective(trial):
    params = {
        'max_depth': trial.suggest_int('max_depth', 3, 12),
        'learning_rate': trial.suggest_loguniform('learning_rate', 0.001, 0.3),
        'subsample': trial.suggest_uniform('subsample', 0.6, 1.0),
        'colsample_bytree': trial.suggest_uniform('colsample_bytree', 0.6, 1.0),
        'min_child_weight': trial.suggest_int('min_child_weight', 1, 5),
    }
    
    model = xgb.XGBRegressor(**params, n_estimators=100)
    model.fit(X_train, y_train)
    
    # 検証データで評価(R2スコア)
    score = model.score(X_test, y_test)
    return score

# 最適化実行(50回の試行)
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50)

# 最適パラメータを表示
print(f"最高R2スコア: {study.best_value:.4f}")
print(f"最適パラメータ:\n{study.best_trial.params}")

結果

  • 試行回数:50回(グリッドサーチより25~40%少ない)
  • 時間:約4時間(手作業より1日短い)
  • 精度:93.8% ← 手作業より0.3%向上

例2:半導体ウェーハ不良判定モデル

背景:成膜工程の条件から不良品を判定。正解率は現在88%。False Positive(良品を不良と誤判定)を減らしたい。

Optunaで重視すべき点:通常の精度ではなく、「正解率とFalse Positive のバランス」を考慮

python

from sklearn.metrics import precision_recall_curve, auc

def objective(trial):
    params = {
        'max_depth': trial.suggest_int('max_depth', 3, 10),
        'learning_rate': trial.suggest_loguniform('learning_rate', 0.01, 0.3),
        'scale_pos_weight': trial.suggest_loguniform('scale_pos_weight', 0.1, 10),
        # ↑ クラス不均衡を考慮(不良品が少ないため)
    }
    
    model = xgb.XGBClassifier(**params, n_estimators=100)
    model.fit(X_train, y_train)
    
    y_pred = model.predict_proba(X_test)[:, 1]
    
    # Precision-Recall AUC を指標とする(False Positive を抑える)
    precision, recall, _ = precision_recall_curve(y_test, y_pred)
    pr_auc = auc(recall, precision)
    
    return pr_auc

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=60)

結果

  • 正解率:89.2%
  • False Positive:2% に削減(従来5%)

例3:燃料電池の発電効率予測

背景:セル温度、ガス流量、圧力などから発電効率を予測。これまでの平均二乗誤差(MSE)は2.5。

python

from sklearn.metrics import mean_squared_error
import numpy as np

def objective(trial):
    params = {
        'max_depth': trial.suggest_int('max_depth', 4, 10),
        'learning_rate': trial.suggest_loguniform('learning_rate', 0.01, 0.2),
        'subsample': trial.suggest_uniform('subsample', 0.7, 1.0),
        'colsample_bytree': trial.suggest_uniform('colsample_bytree', 0.7, 1.0),
        'reg_alpha': trial.suggest_loguniform('reg_alpha', 0.001, 1.0),
    }
    
    model = xgb.XGBRegressor(**params, n_estimators=150)
    model.fit(X_train, y_train)
    
    y_pred = model.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    
    # Optunaは最小化できるので、MSEを直接返す
    return mse

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=80)

print(f"最小MSE: {study.best_value:.4f}")  # 例:1.87(従来2.5から0.63改善)

Pruning(枝刈り)機能:無駄な試行を早期に中止

Optuna には Pruning という機能があります。これは「見込みのない試行を途中で中止する」機能です。

Pruning の例

【Trial 1】
途中結果(10エポック時点):精度 85% → 有望 → 続行

【Trial 2】
途中結果(10エポック時点):精度 72% → 不振 → 中止 ← Pruning発動!

(このTrialはここで打ち切られ、次に進む)

Pruning の設定例

python

from optuna.pruners import MedianPruner

def objective(trial):
    params = {...}
    
    model = xgb.XGBClassifier(**params)
    
    # クロスバリデーションで途中結果をチェック
    for fold in range(5):
        train_data, val_data = split_fold(X_train, fold)
        model.fit(train_data[:1000])  # 途中学習
        
        val_score = model.score(val_data)
        
        # Pruning:他の試行と比べて大きく遅れていたら中止
        trial.report(val_score, fold)
        if trial.should_prune():
            raise optuna.exceptions.TrialPruned()
    
    return best_score

# Prunerを設定して Study を作成
study = optuna.create_study(
    direction='maximize',
    pruner=MedianPruner()
)
study.optimize(objective, n_trials=100)

効果

  • 計算時間が30~50%削減される
  • 無駄な試行が最小化される

Optuna の結果分析と可視化

Trial History の確認

python

# すべての試行結果を表示
trials_df = study.trials_dataframe()
print(trials_df[['number', 'value', 'params_max_depth', 'params_learning_rate']])

# 出力例:
#    number    value  params_max_depth  params_learning_rate
# 0       0  0.9201                 5              0.0523
# 1       1  0.9341                 7              0.0847
# 2       2  0.9283                 6              0.0634
# ...

可視化(グラフで傾向を理解)

python

import matplotlib.pyplot as plt

# Trial の進行状況をプロット
fig, ax = plt.subplots()
trials_values = [t.value for t in study.trials]
ax.plot(trials_values, marker='o')
ax.set_xlabel('Trial')
ax.set_ylabel('Accuracy')
ax.set_title('Optimization Progress')
plt.show()

重要なパラメータの特定

python

# パラメータと目的関数値の関係を可視化
optuna.visualization.plot_param_importances(study).show()
# → どのパラメータが最も精度に影響を与えているかが視覚的にわかる

実装時の注意点

① 探索範囲の設定

範囲を広すぎると、最適値を見落とすリスクがあります。

python

# ❌ 悪い例:範囲が広すぎる
'learning_rate': trial.suggest_loguniform('learning_rate', 0.0001, 1.0)

# ✅ 良い例:ドメイン知識に基づいた範囲
'learning_rate': trial.suggest_loguniform('learning_rate', 0.01, 0.2)

② 試行回数(n_trials)の決定

データ量やパラメータ数に応じて決めます。

状況推奨試行回数理由
パラメータ 3~5個、データ大量50~100回探索空間が比較的小さい
パラメータ 10個以上200~500回探索空間が広い
学習に時間がかかる場合30~50回計算時間とのバランス

③ 早期打ち切り(Early Stopping)との組み合わせ

XGBoostの早期打ち切り機能と Optuna を組み合わせると、さらに効率化できます。

python

def objective(trial):
    params = {...}
    
    model = xgb.XGBClassifier(**params, n_estimators=10000)
    
    # 早期打ち切りを有効化
    model.fit(
        X_train, y_train,
        eval_set=[(X_val, y_val)],
        early_stopping_rounds=50,  # 50回精度が改善しなかったら打ち切り
        verbose=False
    )
    
    score = model.score(X_test, y_test)
    return score

④ 計算リソースの管理

多くの試行をすると時間がかかります。

python

# 並列実行で速度向上(複数プロセスで同時実行)
study.optimize(objective, n_trials=100, n_jobs=4)
# n_jobs=4:4つのプロセスで同時実行

Optuna 、XGBoost、SHAP の統合フロー

実務では、以下のように3つのツールを組み合わせます:

Step 1. Optuna で最適パラメータを探索

python

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
best_params = study.best_trial.params

Step 2. 最適パラメータで最終モデルを構築

python

final_model = xgb.XGBClassifier(**best_params, n_estimators=200)
final_model.fit(X_train, y_train)

Step 3. SHAP で予測結果を解釈

python

import shap

explainer = shap.TreeExplainer(final_model)
shap_values = explainer.shap_values(X_test)

# 個別予測の理由を可視化
shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])

# 全体的な傾向を把握
shap.summary_plot(shap_values, X_test)

このように、最適化 → 学習 → 解釈 というフローで、高精度かつ説明可能なモデルが実現できます。


Optuna のメリット・デメリット

✅ メリット

  • 計算時間が短い:グリッドサーチの 1/3~1/2 の時間
  • 自動学習:前の試行を活かして、賢く探索
  • 実装が簡単:数十行のコードで実装可能
  • 柔軟性:パラメータ数が多い場合でも対応
  • Pruning で無駄を削減:見込みのない試行を早期中止
  • 結果の可視化:試行の進行状況が簡単に可視化できる

❌ デメリット

  • ハイパーパラメータの知識が必須:探索範囲を適切に設定する必要がある
  • 試行回数が必須:少なすぎると最適値を見落とす
  • 計算環境に依存:マシンスペックが低いと時間がかかる
  • 因果性がない:パラメータと精度の「因果関係」ではなく、「相関」を探しているだけ

よくある質問

Q1. グリッドサーチとOptunaの計算時間の違いは?

A. 具体例:8個のパラメータを調整する場合

グリッドサーチ(3値 × 8パラメータ):
3^8 = 6,561通り
→ 1試行あたり1分なら、約110時間必要

Optuna(100回の試行):
→ 約1.5~2時間で完了

効率化率:50~70倍!

Q2. 試行回数は多いほど良いのか?

A. ある程度までは良いですが、報酬逓減の法則があります。

試行回数    精度の改善
50回:    +2.3%
100回:   +3.1%(+0.8%の追加改善)
200回:   +3.4%(+0.3%の追加改善)

100回で十分な場合が多い

Q3. パラメータの探索範囲は手作業で設定するのか?

A. はい。ドメイン知識や過去の経験に基づいて設定します。

推奨値の参考:

python

# 一般的な推奨範囲
max_depth: 3~10
learning_rate: 0.001~0.3
subsample: 0.5~1.0

ただし、広めの範囲を設定して Optuna に任せるのも良い手です。

Q4. Optuna はどの機械学習ライブラリでも使えるのか?

A. はい。XGBoost、LightGBM、scikit-learn、ニューラルネットワーク、すべてに対応しています。


競合ツールとの比較

ツール学習性実装難度スピード柔軟性人気度
グリッドサーチ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
ランダムサーチ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Optuna✓ TPE⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Hyperopt✓ TPE⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Ray Tune✓ 複数対応⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

結論:実装難度と実用性のバランスが最高。Optunaが最もおすすめ


実務的なステップ:プロジェクト適用フロー

段階1:準備(1日)

  1. データ収集・前処理
  2. XGBoast 基本モデルの構築
  3. 手作業で良さそうなパラメータ範囲を設定

段階2:最適化(1~3日)

  1. Optuna で自動探索(50~200回の試行)
  2. 最適パラメータを確認
  3. 検証データで最終精度を確認

段階3:本番適用(1~2日)

  1. 全データで最終モデルを再学習
  2. 本番環境にデプロイ
  3. 定期的な再学習スケジュール構築

全体所要時間:3~6日


実装例:完全なコード

python

import optuna
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import pandas as pd

# ========== データ準備 ==========
data, target = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    data, target, test_size=0.2, random_state=42
)

# ========== 目的関数定義 ==========
def objective(trial):
    # ハイパーパラメータの候補値を提案
    params = {
        'max_depth': trial.suggest_int('max_depth', 3, 10),
        'learning_rate': trial.suggest_loguniform('learning_rate', 0.001, 0.3),
        'subsample': trial.suggest_uniform('subsample', 0.5, 1.0),
        'colsample_bytree': trial.suggest_uniform('colsample_bytree', 0.5, 1.0),
        'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
        'gamma': trial.suggest_loguniform('gamma', 0.001, 1.0),
    }
    
    # XGBoost でモデル構築
    model = xgb.XGBClassifier(**params, n_estimators=100, random_state=42)
    model.fit(X_train, y_train, verbose=0)
    
    # 精度を評価
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    
    return accuracy

# ========== 最適化実行 ==========
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, show_progress_bar=True)

# ========== 結果確認 ==========
print(f"\n最高精度: {study.best_value:.4f}")
print(f"\n最適パラメータ:")
for key, value in study.best_trial.params.items():
    print(f"  {key}: {value}")

# ========== 最終モデル構築 ==========
final_model = xgb.XGBClassifier(**study.best_trial.params, n_estimators=200)
final_model.fit(X_train, y_train)

print(f"\n本番用モデルの精度: {final_model.score(X_test, y_test):.4f}")

# ========== 結果の可視化 ==========
import matplotlib.pyplot as plt

# Trial 履歴をプロット
trials_df = study.trials_dataframe()
plt.figure(figsize=(10, 5))
plt.plot(trials_df['number'], trials_df['value'], marker='o')
plt.xlabel('Trial')
plt.ylabel('Accuracy')
plt.title('Optimization Progress')
plt.grid(True)
plt.show()

# パラメータ重要度をプロット
optuna.visualization.plot_param_importances(study).show()

グラフのイメージ図


まとめ

Optuna の核になる考え方

Optuna は、ベイズ最適化(TPE) という統計的手法を使って、XGBoost のハイパーパラメータを効率的に探索するツールです。

主なメリット

計算時間が大幅に短い(グリッドサーチの 1/3~1/2)
前の試行を活かす(賢い探索)
実装が簡単(数十行のコード)
Pruning で無駄削減(見込みのない試行を早期中止)
結果の可視化が容易(パラメータ重要度の自動作成)

製造業での活用

  • 品質予測モデルの精度向上:92% → 93.8%
  • 不良判定の False Positive 削減:5% → 2%
  • 発電効率予測の精度向上:MSE 2.5 → 1.87

推奨フロー

  1. Optuna で最適パラメータを自動探索(数時間)
  2. 最適パラメータで本番モデルを構築
  3. SHAP で予測結果を解釈・説明

このサイクルを回すことで、高精度かつ説明可能な予測モデルが実現できます。


関連記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次