機械学習モデルのパラメータ最適化 - 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での最適化は、特定のデータセットやモデルに対して最良のハイパーパラメータを見つけるプロセスです。したがって、最適化の結果を解釈する際には、それぞれのケースや条件を考慮する必要があります。
この記事が気に入ったらサポートをしてみませんか?