見出し画像

機械学習モデルのパラメータ最適化 - Optunaのアルゴリズム

いままでなんとなく使っていたOptunaのパラメータ最適化。グリッドサーチのような総当たりアプローチではなく、効率的なアルゴリズムで計算コストを抑えているとのこと。最適化の効果を最大限にするために、Optunaの仕組みやサンプラー、プルーナーの使い分けを理解しよう。


グリッドサーチとOptunaの違い

グリッドサーチは、ハイパーパラメータの候補値をあらかじめ設定し、そのすべての組み合わせを試して最適なパラメータを見つける手法です。網羅的なアプローチであるため、最適なパラメータを確実に見つけることができますが、組み合わせの数が多くなると計算コストが指数的に増加します。

一方、Optunaは、ハイパーパラメータ最適化を効率的に行うためのライブラリです。Optunaの特徴は、試行錯誤による最適化プロセスを効率的に進める点にあります。Optunaは、過去のトライアル結果をもとに有望なパラメータ領域を優先的に探索するため、グリッドサーチに比べて計算コストを大幅に削減できます。

Optunaの効率的な最適化の仕組み

Optunaは、グリッドサーチのような総当たり的アプローチではなく、効率的な最適化を可能にするためのアルゴリズムを備えています。以下にその仕組みを簡単に紹介します。

  • TPE(Tree-structured Parzen Estimator)サンプラー: 過去のトライアル結果をもとに、有望なハイパーパラメータの領域を推定します。これにより、最適化プロセスを効率的に進められます。

  • プルーナー: Optunaのプルーナーは、無駄なトライアルを早期に終了させる仕組みです。MedianPrunerやSuccessiveHalvingPrunerなど、さまざまなプルーナーがあり、計算コストを削減できます。

  • スタディ: Optunaのスタディは、最適化プロセス全体を管理するオブジェクトです。方向性やストレージの設定、トライアル管理などを通じて、効率的な最適化が可能です。

サンプラー、プルーナー、スタディの種類

Optunaでは、サンプラー、プルーナー、スタディにさまざまな種類があります。それぞれの特徴と用途を説明します。

サンプラー

  • TPESampler: デフォルトのサンプラーで、効率的な最適化に適しています。

  • RandomSampler: ランダムにパラメータを生成するサンプラーです。広範囲の探索に適しています。

  • GridSampler: グリッドサーチのように、指定したパラメータの組み合わせを試すサンプラーです。

プルーナー

  • MedianPruner: 過去のトライアルのメディアンと比較して、進捗が劣っている場合にトライアルを終了させるプルーナーです。

  • SuccessiveHalvingPruner: ステージごとにトライアルを減らしていく手法です。

  • HyperbandPruner: SuccessiveHalvingPrunerの拡張版で、効率的なプルーニングを実現します。

スタディ

  • スタディ名: スタディ名を設定することで、再開や継続が可能です。

  • ストレージ: SQLiteやPostgreSQLなど、スタディの結果をデータベースに保存できます。

サンプルデータでの検証

LightGBMを使用してOptunaでハイパーパラメータ最適化を行うサンプルコードを示します。まずは標準的な設定で最適化を行い、次にサンプラーやプルーナーを変えて結果を検証します。

!pip install optuna
import optuna
import lightgbm as lgb
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import matplotlib.pyplot as plt

# シードを固定
random_seed = 42
np.random.seed(random_seed)  # NumPyの乱数シードを固定
random.seed(random_seed)  # Pythonの標準乱数シードを固定

# サンプルデータの準備
data = pd.DataFrame({
    'Feature1': np.random.rand(1000),
    'Feature2': np.random.rand(1000),
    'Target': np.random.rand(1000)
})
X = data[['Feature1', 'Feature2']]
y = data['Target']

# トレーニングセットとテストセットに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

デフォルト設定(TPESampler、MedianPruner)

# OptunaのObjective Functionを定義
def objective(trial):
    param = {
        'objective': 'regression',
        'metric': 'rmse',
        'boosting_type': 'gbdt',
        'random_state': 42,
        'num_leaves': trial.suggest_int('num_leaves', 20, 300),
        'learning_rate': trial.suggest_float('learning_rate', 0.005, 0.2, log=True),
        'max_depth': trial.suggest_int('max_depth', 3, 12),
        'min_child_samples': trial.suggest_int('min_child_samples', 5, 100),
        '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),
        'subsample': trial.suggest_float('subsample', 0.4, 1.0),
        'colsample_bytree': trial.suggest_float('colsample_bytree', 0.4, 1.0)
    }

    train_data = lgb.Dataset(X_train, label=y_train)
    valid_data = lgb.Dataset(X_test, label=y_test)

    model = lgb.train(
        param,
        train_data,
        valid_sets=[valid_data],
        callbacks=[lgb.early_stopping(50, verbose=True)]
    )

    preds = model.predict(X_test, num_iteration=model.best_iteration)
    rmse_score = mean_squared_error(y_test, preds, squared=False)

    return rmse_score  # Optunaは最小化を目指すため、RMSEを返す

# Optunaのスタディを作成
study = optuna.create_study(direction='minimize')  # 最小化を目指す
study.optimize(objective, n_trials=10) # トライアル数を指定

# 最適化された結果を表示
print("Best RMSE:", study.best_value)
print("Best Parameters:", study.best_params)

Best RMSE: 0.29461292652402526

プルーナーの変更(SuccessiveHalvingPruner)

from optuna.pruners import SuccessiveHalvingPruner

pruner = SuccessiveHalvingPruner()  # SuccessiveHalvingPrunerを設定
study = optuna.create_study(direction='minimize', pruner=pruner)
study.optimize(objective, n_trials=10)

# 最適化された結果を表示
print("Best RMSE:", study.best_value)
print("Best Parameters:", study.best_params)

Best RMSE: 0.29487046039153714

サンプラーの変更(RandomSampler)

from optuna.samplers import RandomSampler

random_sampler = RandomSampler()  # RandomSamplerのインスタンスを作成
study = optuna.create_study(direction='minimize', sampler=random_sampler)
study.optimize(objective, n_trials=10)

# 最適化された結果を表示
print("Best RMSE:", study.best_value)
print("Best Parameters:", study.best_params)

Best RMSE: 0.2947970296324155

実験結果を見ると、RMSEの値は設定によってわずかに異なっていますが、これは各最適化プロセスが異なるアプローチを取るためです。ただし、最適化には多くのランダム要素が含まれているため、データセットや実行条件に応じて結果が変わる可能性があります。また、試行回数が少ないと結果のばらつきが大きくなるため、これが常に最良の設定とは限りません。
(今回のサンプルでは試行回数も少なく設定しています)

Optunaでの最適化は、特定のデータセットやモデルに対して最良のハイパーパラメータを見つけるプロセスです。したがって、最適化の結果を解釈する際には、それぞれのケースや条件を考慮する必要があります。


この記事が気に入ったらサポートをしてみませんか?