- はじめに
- 従来のフレームワークにおける課題
- Optunaの設計思想
- optunaの理論
- 最後に
はじめに
optunaといえば、予測モデルのハイパーパラメータチューニングで大変お世話になっている人がとても多くいると思います。グリッドサーチのようなハイパーパラメータの最適化手法はどのようなことが行われているかがとても、簡単で解釈している人は多いと思いますが、ことoptunaに関してはどうでしょうか?内部的にどのようなことが行われているかわかっていながら使用している人がどの程度いるでしょうか?個人的な感覚ですが、大体こんなことをやっているのは知っているが、詳しくは知らないといった人が大半なのではないでしょうか?ということで今回はoptunaの理論に関して記事を書いていきたいと思います。
従来のフレームワークにおける課題
従来のハイパーパラメータ最適化フレームワークには、主に三つの重要な課題が存在していました。
最初の課題は、パラメータ探索空間の静的な定義です。従来のフレームワークでは、最適化を開始する前にすべてのパラメータ空間を明示的に定義する必要がありました。これは特に、複数のモデルタイプや条件付きパラメータを扱う大規模な実験において大きな制約となっていました。例えば、ニューラルネットワークの層数に応じて各層のユニット数を探索したい場合、その依存関係を静的に記述することは非常に煩雑でした。
二つ目の課題は、効率的な探索戦略と枝刈り戦略の両立です。多くのフレームワークは、パラメータの探索戦略は持っているものの、計算リソースを効率的に活用するための枝刈り(プルーニング)機能が不十分でした。これにより、有望でないパラメータの組み合わせの評価に多くの時間を費やすことになっていました。
三つ目の課題は、システムアーキテクチャの柔軟性です。従来のフレームワークの多くは、特定の使用環境や計算規模を想定して設計されており、小規模な実験から大規模な分散計算まで、様々なスケールの最適化タスクに対して柔軟に対応することが困難でした。
Optunaの設計思想
これらの課題を解決するため、Optunaは三つの革新的な設計基準に基づいて開発されました。
まず、Define-by-run APIの導入です。これは深層学習フレームワークの世界で普及した概念を、ハイパーパラメータ最適化の文脈に適用したものです。このアプローチにより、パラメータ探索空間を動的に構築することが可能となり、複雑な依存関係を持つパラメータ空間でも直感的に記述できるようになりました。
次に、効率的なサンプリングアルゴリズムと枝刈りアルゴリズムの統合です。Optunaは、Tree-structured Parzen Estimator(TPE)やCovariance Matrix Adaptation Evolution Strategy(CMA-ES)などの高度なサンプリングアルゴリズムと、Asynchronous Successive Halving(ASHA)などの効率的な枝刈りアルゴリズムを組み合わせることで、限られた計算リソースで効果的な最適化を実現しています。
optunaの理論
今回参考にした論文は以下です。
Define-by-run APIの理論と実装
Define-by-run APIは、Optunaの最も特徴的な革新の一つです。この概念の理解を深めるため、まず従来のdefine-and-run方式との比較から始めましょう。 従来のdefine-and-run方式では、最適化の対象となるパラメータ空間を以下のように静的に定義する必要がありました:
一方、Optunaのdefine-by-run方式では、パラメータ空間を実行時に動的に構築します。この方式は、以下のような確率的な生成プロセスとして形式化できます:
わかりやすく説明すると、これらの式は、パラメータ探索の「従来の固定的な方法」と「optunaの柔軟な方法」の本質的な違いを表現しています。従来の方式(最初の式)では、例えば「層数は1から10、各層のユニット数は10から100」というように、すべてのパラメータの取りうる値を前もって決めておく必要がありました。これは、食事の注文で例えると、「前菜、主菜、デザートをそれぞれこの中から一つ選んでください」という固定のコース料理のようなものです。
一方、optunaの方式(二番目の式)は、まるで「お客様の好みをお聞きしながら、次の料理を提案させていただきます」というようなア・ラ・カルト方式です。パラメータは一つ一つ順番に、それまでの結果を見ながら柔軟に決めていきます。「層数を3にしたから、各層のユニット数はこの程度が良さそうだ」といった具合に、パラメータ間の自然な依存関係を表現できます。この柔軟性により、より効率的で直感的なパラメータ探索が可能となっています。
この方式の実装は、以下のような直感的なPythonコードとして表現されます:
def objective(trial): # 層数を動的に決定 n_layers = trial.suggest_int('n_layers', 1, 4) # 各層のユニット数を動的に決定 layers = [] for i in range(n_layers): n_units = trial.suggest_int(f'n_units_{i}', 4, 128) layers.append(n_units)
この実装の特徴は、パラメータの生成が実行時のコンテキストに応じて行われることです。これにより、以下のような複雑な依存関係も自然に表現することが可能となります:
・パラメータ間の階層的な依存関係
・条件付きパラメータの動的な生成
・可変長のパラメータ構造
サンプリングアルゴリズムの理論
Optunaにおけるサンプリングアルゴリズムは、探索と活用のトレードオフを効果的に制御するように設計されています。中でも特に重要なのが、Tree-structured Parzen Estimator(TPE)です。
TPEは、以下のような条件付き確率モデルに基づいています:
もっとわかりやすく述べると、この式は、TPEがパラメータの「良い領域」と「悪い領域」を学習する方法を表現しています。具体的には、これまでの試行結果を上位25%(γ = 0.25)とそれ以外に分け、それぞれの領域でパラメータの出現分布を推定します。l(x)は「良い結果を出したパラメータはこんな値を取っていた」という分布、g(x)は「あまり良くない結果だったパラメータはこんな値だった」という分布を表しています。この二つの分布を比較することで、「良いパラメータっぽい領域」を特定し、そこを重点的に探索することができます。これは、人間が試行錯誤する際の「うまくいった設定の周辺をもっと詳しく調べてみよう」という直感的な戦略を、確率的にモデル化したものと考えることができます。
TPEにおける改善期待値(Expected Improvement, EI)は、以下の式で定式化されます:
わかりやすく説明してみましょう。TPEの仕組みは、経験豊富なデータサイエンティストの思考プロセスを数学的にモデル化したものと考えることができます。最初の式は「良い結果が出たパラメータと悪い結果が出たパラメータを分類する」という基本的な考え方を表しています。これは、例えば機械学習コンペティションで「上位25%に入った提出と、そうでない提出で使ったパラメータを分けて考える」というような戦略に相当します。
二番目の式(EI)は「次にどのパラメータを試すべきか」を決める方法を表現しています。これは、ポーカーでいえば「このカードを引いたらどれくらい手が良くなるか」を計算するようなものです。現在の最良値y*からどれだけ改善が見込めるかを、確率的に計算しています。
最後の二つの式は、「良いパラメータの分布」と「悪いパラメータの分布」をデータから具体的に推定する方法を示しています。これは、点在するデータ点から滑らかな分布を推定する作業で、例えば「この辺りのパラメータはうまくいきやすい」という連続的な傾向を捉えようとしています。カーネル関数Khは、データ点の周りに山のような形を作り、それらを重ね合わせることで全体の分布を表現します。これは、例えば「このパラメータの近くも似たような結果が出るだろう」という自然な仮定を数学的に表現したものです。
一方で、パラメータ間の相関関係を考慮したい場合、CMA-ES(Covariance Matrix Adaptation Evolution Strategy)も重要な選択肢となります。CMA-ESは、以下の多変量正規分布に基づくサンプリングを行います:
わかりやすく説明すると、これらの式は、CMA-ESが「パラメータ間の相互関係を学習しながら探索する」方法を表現しています。直感的に理解するために、地図上で宝探しをする状況を想像してみましょう。最初の式は「次にどこを探すか」を決める方法を表しています。中心m(今までで最も有望そうな場所)を基準に、ある範囲σ(探索範囲の大きさ)で、C(地形の特徴)を考慮しながら次の探索位置を決めます。
二番目の式は「地形の特徴Cをどう更新するか」を表しています。例えば、「北に行くと同時に東に行くと良い結果が得られやすい」といった、パラメータ間の相関関係を学習していきます。過去の経験(Ck)を活かしつつ、新しい発見(y_i)を適度な重み(w_i)で取り入れ、さらに長期的な探索の方向性(p_c)も考慮します。これにより、「ただランダムに探索する」のではなく、パラメータ空間の構造を理解しながら効率的に探索を進めることができます。
効率的な枝刈り(Pruning)メカニズム
Optunaの重要な特徴の一つが、効率的な枝刈りメカニズムです。特に、Asynchronous Successive Halving(ASHA)アルゴリズムは、分散環境での効率的な最適化を可能にする革新的な手法です。
ASHAの基本的なアイデアは、以下の数式で表現される評価基準に基づいています:
ASHAの重要な特徴は、その非同期性にあります。従来の同期的な手法では、すべてのトライアルが特定のチェックポイントに到達するまで待つ必要がありましたが、ASHAではそのような同期化は不要です。これは以下の確率モデルとして形式化できます:
ここまでを分かりやすく説明してみましょう。ASHAの仕組みは、効率的な運動選手の育成プログラムに似ています。最初の式は「いつ、誰を、どのように評価するか」という評価スケジュールを決める方法を表しています。rungは選手の成長段階のようなもので、各段階で評価を行い、次のステップに進むかどうかを判断します。ここで、rは最初の評価までに必要な最小トレーニング量、ηは選手を絞り込む際の厳しさ、sは最初の評価をどれくらい早く始めるかを決めるパラメータです。
二番目の式は「誰を次の段階に進ませるか」を決める基準を表しています。従来の方式では、例えば「100m走で全員が走り終わるまで待って、その後で評価する」という同期的な評価でしたが、ASHAでは「誰かが走り終わるたびに、その時点での相対的な順位で判断する」という非同期な評価を行います。具体的には、各段階で上位1/η(例えば上位3分の1)の選手だけが次の段階に進めます。これは、有望でない試行を早めに発見して打ち切ることで、計算資源を有望な試行により多く配分する効率的な戦略です。
実際の実装では、以下のようなコードとなります:
def objective(trial): for step in range(100): # 現在の性能値を計算 current_value = compute_intermediate_value() # 中間結果を報告 trial.report(current_value, step) # 枝刈りの判定 if trial.should_prune(step): raise TrialPruned()
この枝刈りメカニズムにより、計算リソースを効率的に活用することが可能となります。有望でないトライアルを早期に停止することで、より多くのリソースを有望なトライアルに振り分けることができます。
ASHAの理論的な収束性は、以下の不等式で保証されます:
分散最適化の理論とアーキテクチャ
Optunaは分散環境での最適化を効率的に行うために、独自のアーキテクチャを採用しています。この分散システムは、以下の理論的なフレームワークに基づいています。
分散最適化における各ワーカーの振る舞いは、以下の確率過程としてモデル化できます:
実際のシステムアーキテクチャでは、この理論的なフレームワークを実現するために、以下の要素が実装されています:
ストレージバックエンド
ストレージシステムは、以下の一貫性モデルに基づいて設計されています:
Optunaは、この一貫性モデルを満たすように、複数のストレージバックエンドをサポートしています:
# RDBを使用する場合 storage = optuna.storages.RDBStorage( "postgresql://user:password@host/database" ) # SQLiteを使用する場合 storage = optuna.storages.RDBStorage( "sqlite:///example.db" ) # インメモリストレージを使用する場合 storage = optuna.storages.InMemoryStorage()
トライアルの同期と非同期実行
分散環境での最適化効率は、以下の理論的な上限で特徴付けられます:
実際のユースケースと性能評価
Optunaの実践的な性能を理解するため、代表的なベンチマークと実際のユースケースについて解説します。
ベンチマーク評価の理論的枠組み
性能評価は、以下の理論的な枠組みに基づいて行われます:
TPEとCMA-ESの組み合わせによる性能向上
OptunaではTPEとCMA-ESを組み合わせることで、より効率的な探索を実現しています。この組み合わせの理論的な根拠は、以下の期待改善度の式で説明できます:
わかりやすく解説すると、この式は、TPEとCMA-ESを組み合わせた時の「探索と活用のバランス取り」の仕組みを表現しています。宝探しに例えると、σ(x)は「この場所にどれくらい不確実性があるか」(まだよく調べていない場所ほど大きな値)、γは「今までの最高の発見に比べてどれくらい良い発見が期待できるか」を表します。Φ(γ)は「より良い結果が得られる確率」、φ(γ)は「どれくらい良い結果が得られそうか」を表現しています。
これらを掛け合わせることで、「まだ調べていない場所(探索)」と「良さそうな場所の周辺(活用)」のどちらを優先すべきかをバランス良く判断します。具体的には、不確実性が高い場所(大きなσ(x))でも改善の期待が低ければ探索を控え、逆に不確実性が低い場所でも大きな改善が期待できれば重点的に調べる、という賢い探索戦略を実現しています。
実世界での応用例
実際の応用例として、Deep Learningモデルの最適化における性能を見てみましょう。この場合、目的関数は以下のように定式化されます:
実験結果は以下のような収束特性を示しました:
システムの拡張性と実装の詳細
Optunaのシステムアーキテクチャは、高い拡張性を持つように設計されています。この拡張性は以下の理論的枠組みに基づいています。
カスタムサンプラーの実装
カスタムサンプラーを実装する際の理論的な基礎は、以下のベイズ最適化の枠組みに基づいています:
実装は以下のような形で行われます:
class CustomSampler(BaseSampler): def __init__(self, seed=None): self._rng = np.random.RandomState(seed) def sample_independent(self, study, trial, param_name, param_distribution): return self._sample_from_distribution(param_distribution)
カスタム枝刈り手法の実装
カスタム枝刈り手法の理論的基礎は、以下の条件付き確率モデルで表現されます:
実装上の最適化とパフォーマンスチューニング
Optunaのシステム実装には、様々な最適化技術が組み込まれています。これらの最適化は、理論的な性能保証と実用的な効率性のバランスを取るように設計されています。
データベースアクセスの最適化
データベースアクセスのパフォーマンスは、以下のキャッシュヒット率モデルに基づいて最適化されています:
メモリ使用量の最適化
メモリ使用量の制御は、以下の制約付き最適化問題として定式化されます:
並列処理の効率化
並列処理の効率化は、以下のアムダールの法則に基づいて設計されています:
ハイパーパラメータ探索の最適化
探索空間の効率的なサンプリングは、以下のエントロピー最大化問題として定式化されます:
実践的な使用方法とベストプラクティス
Optunaを実務で効果的に活用するためには、理論的な理解に基づいた適切な使用方法が重要です。ここでは、実践的な側面から詳細に解説していきます。
探索空間の設計
探索空間の設計は、以下のような理論的な考慮に基づいて行われるべきです。探索空間の表現力と探索効率のトレードオフは、以下の式で定量化されます:
パラメータのスケーリングについては、以下の対数変換が有効です:
目的関数の設計
目的関数の設計は、以下の複合的な評価指標に基づいて行われます:
計算リソースの最適配分
計算リソースの配分は、以下の最適化問題として定式化されます:
実装例
import optuna import lightgbm as lgb import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split # 再現性のため乱数シードを設定 np.random.seed(42) # サンプルデータの準備 print("データの読み込みと前処理...") data = load_breast_cancer() X_train, X_test, y_train, y_test = train_test_split( data.data, data.target, test_size=0.2, random_state=42 ) def objective(trial): # パラメータ探索空間の定義 param = { # 基本設定 'objective': 'binary', 'metric': 'binary_logloss', 'verbosity': -1, 'boosting_type': 'gbdt', # 学習率(対数スケールで探索) 'learning_rate': trial.suggest_float( 'learning_rate', 1e-4, 1e-1, log=True ), # 木の深さと葉の数 'max_depth': trial.suggest_int('max_depth', 3, 12), 'num_leaves': trial.suggest_int( 'num_leaves', 2, 4096, log=True ), # 特徴量とサンプルのサブサンプリング 'feature_fraction': trial.suggest_float( 'feature_fraction', 0.6, 1.0 ), # 過学習制御パラメータ 'min_child_samples': trial.suggest_int( 'min_child_samples', 5, 100 ), # 正則化パラメータ 'lambda_l1': trial.suggest_float( 'lambda_l1', 1e-8, 10.0, log=True ), 'lambda_l2': trial.suggest_float( 'lambda_l2', 1e-8, 10.0, log=True ), } # データセットの作成 dtrain = lgb.Dataset(X_train, label=y_train) # 早期停止を含むコールバックの設定 callbacks = [ lgb.early_stopping(stopping_rounds=50), lgb.log_evaluation(period=0) # ログ出力を抑制 ] # クロスバリデーションの実行 cv_results = lgb.cv( params=param, train_set=dtrain, num_boost_round=1000, nfold=5, stratified=True, callbacks=callbacks, seed=42 ) # cv_resultsのキーを確認して適切なメトリクスを取得 metric_name = 'binary_logloss-mean' best_score = float('inf') if metric_name in cv_results: best_score = min(cv_results[metric_name]) best_iteration = len(cv_results[metric_name]) trial.set_user_attr('best_iteration', best_iteration) return best_score # 最適化の実行 print("Optunaによる最適化を開始...") study = optuna.create_study( direction='minimize', sampler=optuna.samplers.TPESampler(seed=42) ) study.optimize( objective, n_trials=20, timeout=600 # 10分でタイムアウト ) # 結果の表示 print("\n=== 最適化結果 ===") print(f"最良の試行:") print(f" Trial number: {study.best_trial.number}") print(f" Loss: {study.best_trial.value:.4f}") if 'best_iteration' in study.best_trial.user_attrs: print(f" Best iteration: {study.best_trial.user_attrs['best_iteration']}") print("\n最適なパラメータ:") for key, value in study.best_trial.params.items(): print(f" {key}: {value}")
高度な機能とカスタマイズ
Optunaの高度な機能とカスタマイズについて、理論的な背景とともに解説していきます。
マルチ目的最適化
マルチ目的最適化は、以下のパレート最適性の概念に基づいて実装されています:
実際のマルチ目的最適化では、以下のような支配関係に基づくランキングが使用されます:
条件付きパラメータ空間
条件付きパラメータ空間は、以下のような条件付き確率モデルとして表現されます:
カスタム分布の実装
カスタム分布の実装は、以下の確率分布族に基づいて行われます:
実装例
import optuna import lightgbm as lgb import numpy as np from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, f1_score, roc_auc_score import time # 再現性のため乱数シードを設定 np.random.seed(42) # サンプルデータの準備 print("データの読み込みと前処理...") data = load_breast_cancer() X_train, X_test, y_train, y_test = train_test_split( data.data, data.target, test_size=0.2, random_state=42 ) def multi_objective(trial): """ マルチ目的最適化の目的関数 目的1: AUC最大化(予測性能) 目的2: モデルサイズ最小化(計算リソース効率) 目的3: 推論時間最小化(実行効率) """ # パラメータ探索空間の定義 param = { # 基本設定 'objective': 'binary', 'metric': 'auc', 'verbosity': -1, 'boosting_type': 'gbdt', # 条件付きパラメータの実装 # まず木の深さ制御方法を選択 'depth_control': trial.suggest_categorical('depth_control', ['max_depth', 'num_leaves']), } # 条件付きパラメータの設定 if param['depth_control'] == 'max_depth': # max_depthを使用する場合の設定 param['max_depth'] = trial.suggest_int('max_depth', 3, 12) param['num_leaves'] = 2 ** trial.suggest_int('max_depth', 3, 12) # max_depthに応じて設定 else: # num_leavesを使用する場合の設定 param['num_leaves'] = trial.suggest_int('num_leaves', 16, 4096, log=True) param['max_depth'] = -1 # num_leavesを優先 # 共通パラメータの設定 param.update({ # 学習率(小さいほど細かい学習が可能) 'learning_rate': trial.suggest_float('learning_rate', 1e-4, 1e-1, log=True), # 特徴量のサブサンプリング(過学習抑制とモデルサイズ削減に効果) 'feature_fraction': trial.suggest_float('feature_fraction', 0.6, 1.0), # 正則化パラメータ(過学習抑制) 'lambda_l1': trial.suggest_float('lambda_l1', 1e-8, 10.0, log=True), 'lambda_l2': trial.suggest_float('lambda_l2', 1e-8, 10.0, log=True), # 最小データポイント数(過学習抑制) 'min_child_samples': trial.suggest_int('min_child_samples', 5, 100), }) # 早期停止のためのコールバック callbacks = [ lgb.early_stopping(stopping_rounds=50), lgb.log_evaluation(period=0) ] # データセットの作成 dtrain = lgb.Dataset(X_train, label=y_train) dvalid = lgb.Dataset(X_test, label=y_test, reference=dtrain) # モデルの学習 model = lgb.train( param, dtrain, num_boost_round=1000, valid_sets=[dvalid], callbacks=callbacks ) # テストデータでの予測と実行時間計測 start_time = time.time() y_pred = model.predict(X_test) inference_time = (time.time() - start_time) * 1000 # ミリ秒単位 # 二値分類の予測値を生成 y_pred_binary = (y_pred > 0.5).astype(int) # 目的関数1: AUC(最大化) auc = roc_auc_score(y_test, y_pred) # 目的関数2: モデルサイズ(最小化、KBサイズ) model_size = len(model.model_to_string()) / 1024 # 目的関数3: 推論時間(最小化、ミリ秒) # 学習過程の情報をロギング trial.set_user_attr('accuracy', accuracy_score(y_test, y_pred_binary)) trial.set_user_attr('f1_score', f1_score(y_test, y_pred_binary)) trial.set_user_attr('n_estimators', model.current_iteration()) return auc, -model_size, -inference_time # 2番目と3番目の目的は最小化したいので負の値を返す # マルチ目的最適化の実行 print("マルチ目的最適化の開始...") study_multi = optuna.create_study( study_name="lightgbm_multi_objective", directions=['maximize', 'maximize', 'maximize'] # すべて最大化問題として扱う ) # 最適化の実行 study_multi.optimize( multi_objective, n_trials=20, timeout=600 # 10分でタイムアウト ) # 結果の表示 print("\n=== パレート最適解 ===") trials = sorted(study_multi.best_trials, key=lambda t: t.values[0], reverse=True) for i, trial in enumerate(trials): print(f"\n解 {i+1}:") print(f" AUC: {trial.values[0]:.4f}") print(f" モデルサイズ: {-trial.values[1]:.2f} KB") print(f" 推論時間: {-trial.values[2]:.2f} ms") print(f" Accuracy: {trial.user_attrs['accuracy']:.4f}") print(f" F1 Score: {trial.user_attrs['f1_score']:.4f}") print(f" 使用した木の数: {trial.user_attrs['n_estimators']}") print(" パラメータ:") for key, value in trial.params.items(): print(f" {key}: {value}") # パレートフロントの可視化 import plotly # 最適化履歴の可視化 fig = optuna.visualization.plot_pareto_front( study_multi, target_names=["AUC", "Model Size (KB)", "Inference Time (ms)"] ) fig.show()
このような発展的な最適化も可能というわけです。
おまけ:optunaとベイズ最適化との関連性
optunaの中核を成すTPE(Tree-structured Parzen Estimator)は、ベイズ最適化の一種として捉えることができます。この章では、optunaがなぜベイズ最適化と呼ばれるのか、その理論的背景について解説します。
ベイズ最適化の基本的フレームワーク
ベイズ最適化は、以下の確率的なフレームワークに基づいています:
TPEによるベイズ最適化の実装
TPEは、従来のガウス過程に基づくベイズ最適化とは異なるアプローチを取ります。TPEでは、条件付き確率を以下のように直接モデル化します:
獲得関数の確率的解釈
TPEにおける期待改善量(EI)は、以下のベイズ的な期待値計算として解釈できます:
要は、このEI(Expected Improvement)の式は、「新しいパラメータを試してみた時に、どれだけ良い結果が得られそうか」を数学的に表現したものです。具体的には、現在の最良値y*よりも良い値が得られる可能性を、確率的に重み付けして計算しています。式の中のp(y|x)は、あるパラメータxを選んだ時に得られる値yの確率分布を表していますが、これを事前に正確に知ることは不可能です。そこでTPEでは、これまでの試行結果からカーネル密度推定(KDE)という手法を使って、この確率分布を柔軟に推定します。これにより、パラメータ空間の形状に関する強い仮定を置くことなく、データから確率分布を学習することができます。
マルチフィデリティ最適化との関連
optunaの枝刈り機能は、ベイズ最適化の文脈では以下のような条件付き確率モデルとして解釈できます:
要は、この式は、optunaの賢い「途中打ち切り」の仕組みを確率的に表現したものです。途中経過の値y_tから、最終的な評価値y_Tがどうなりそうかを予測し、継続するかどうかを判断します。具体的には、「現在の中間評価値y_tが与えられた時に、最終的な評価値y_Tが既存の解よりも良くなる確率」を計算しています。この確率が低い場合、つまり途中経過から見て良い結果が期待できない試行は早めに打ち切ることで、計算リソースを有望な試行により多く割り当てることができます。これは、限られた計算リソースを「勝ち目のある試行」に賢く集中投資する戦略といえます。
ハイパーパラメータの階層ベイズモデル
optunaのハイパーパラメータ最適化は、以下のような階層ベイズモデルとして表現することができます:
要は、この式は、optunaによる最適化の「二重の学習」の仕組みを表現しています。最適化の対象となるモデルのハイパーパラメータθと、それを最適化するためのTPE自体の設定パラメータηが、階層的な関係を持っています。簡単に言えば、「モデルの学習」と「学習の仕方の学習」が同時に行われているのです。データDから、モデルのハイパーパラメータθの良し悪しを学習しながら、同時にTPEのメタパラメータηも調整されていきます。この二段階の学習構造により、個々のモデルの特性や問題の難しさに応じて、最適化の方法自体も適応的に変化していくことが可能となります。
最後に
つらつらとoputunaの理論から関係なくはないがあまり関係ない話まで色々扱ってみましたがどうだったでしょうか?正直oputunaの理論に関してのみでいえば、前半部分のみさえ理解できれば十分だと思います。後半部分はおまけだと思ってください。概念的説明で分かりやすくしようとした結果、少しというか結構読みにくくなった気がしますが、そんな記事を最後までお読みいただきありがとうございました。今回の記事では、数式ごとに概念的な説明を付属するという私の記事では目新しい挑戦をしてみましたがどうだったでしょうか?
わたしもまだ初学者の域を出ないので、なにか間違いなどありましたらご指摘いただけると幸いです。概念的な説明でかぶってる部分、TPEとかは特にかぶっていると思いますが、記事を最初から一貫して書いてるというよりも、行ったり戻ったりして書いているため、こんな感じになてしまいました。読みにくかったら申し訳ないです。