け日記

最近はPythonでいろいろやってます

Python: BayesianOptimizationによるベイズ最適化

お仕事で、時間のかかる学習のパラメータ選定に、ベイズ最適化を用いる機会がありましたので、備忘録として整理します。

ベイズ最適化

ベイズ最適化 (Bayesian Optimization) は、過去の実験結果から次の実験パラメータを、確率分布から求めることで最適化する手法です。機械学習では、可能な限り少ない試行回数で筋の良いハイパーパラメータを選定するために用いられます。

キーとなる概念は2つです。

  • 未知の関数fは、ガウス過程に従うと仮定する
  • 次に試行するパラメータを、獲得関数で選択する

ガウス過程を事前分布として導入することで、観測済みのパラメータから、未知のパラメータxにおけるf(x)の期待値・分散をほとんどコストなしで計算できるようになります (ガウス過程についてはよく理解できなかったので、改めて整理して記事を書くと思います)。

各点の期待値・分散が明らかになったところで、次にどこのパラメータを使うのかを決めるのが、獲得関数 (acquisition function) です。
期待値と分散をいかにミックスさせるかがキモとなります。期待値が高い点ほど、既知のデータからf(x)の値が大きくなることが予想できますが、局所解に陥っている可能性もあります。適度に、分散が大きい点、つまり周りが探索されていないためにf(x)がどういった値になるのかわからない点を調べる必要もあります。
この獲得関数にはいくつか種類がありますが、代表的なものは以下の3つです。このあと紹介するBayesianOptimizationでもこの3つが実装されています。

  • PI (Probability of Improvement)
    • 現在の最大値を超える確率が最も高い点を選ぶが、局所解に陥りやすい
  • EI (Expected Improvement)
    • 現在の最大値との差の期待値が最も高い点を選ぶ
  • UCB (Upper Confidence Bound)
    • 上側信頼区間が最も高い点を選ぶ

参考

ベイズ最適化の理論的な話は、こちらが参考になります。

www.slideshare.net

www.slideshare.net

qiita.com

BayesianOptimization

Pythonでも、ベイズ最適化を行うライブラリはいくつかあります。今回はBayesianOptimizationを使ってみます (他にはGPyOptscikit-optimizeなどがあります) 。

github.com

事前にインストールしておきます。

$ pip install bayesian-optimization

1変数関数の最適化

簡単な例として、以下の1変数関数のベイズ最適化を行います。


f(x)=-(x^4 - 20x^2 + 10x) + 300

2つの極大値を持つ関数となっています。

f:id:ohke:20180804104551p:plain

BayesianOptimizationで最適化する実装は以下です。
BayesianOptimizationを生成し、maximizeメソッドで最適化します。

  • BayesianOptimizationでは最適化する関数fと各パラメータの値域pboundsを渡しています
  • maximizeメソッドには4つの引数を渡します
    • init_pointsは、最初に取得する関数f(x)の数で、ランダムに選択されます (デフォルトは5)
      • この値が少ないと、局所解に陥りやすくなります
    • n_iterは、試行回数です(デフォルトは25)
    • acqは、獲得関数の選択です (デフォルトは"ucb"で、"ei"と"poi"があります)
    • kappa は、ucbの場合のみ必要な引数で、値が大きいほど分散を重視した積極的な探索が行われます
from bayes_opt import BayesianOptimization

# 1変数関数の定義
def f(x):
    return -(x**4 - 20 * x**2 + 10 * x) + 300

# 最適化するパラメータの下限・上限 (xのみ)
pbounds = {
    'x': (-5.0, 5.0)
}

# 関数と最適化するパラメータを渡す
optimizer = BayesianOptimization(f=f, pbounds=pbounds)

# 最適化
optimizer.maximize(init_points=3, n_iter=10)

上のコードを実行すると、例えばこんな結果となります。

  • Step 1〜5で、初期値5点を計算してます
  • Step 6〜15で、パラメータ (x) を選択して試行を繰り返してます
    • x=-3.2807のとき最大値432.22484となっていることがわかります (Step 14)
Initialization
-----------------------------------------
 Step |   Time |      Value |         x | 
    1 | 00m00s |  416.37121 |   -3.8321 | 
    2 | 00m00s |  331.24223 |   -1.0481 | 
    3 | 00m00s |  415.17122 |   -3.8511 | 
    4 | 00m00s |  322.92473 |    1.4443 | 
    5 | 00m00s |  403.84389 |   -4.0018 | 
Bayesian Optimization
-----------------------------------------
 Step |   Time |      Value |         x | 
    6 | 00m02s |  431.97998 |   -3.3539 | 
    7 | 00m02s |  432.22002 |   -3.2909 | 
    8 | 00m04s |  432.22471 |   -3.2823 | 
    9 | 00m06s |  432.22484 |   -3.2803 | 
   10 | 00m05s |  432.22470 |   -3.2824 | 
   11 | 00m07s |  432.22482 |   -3.2813 | 
   12 | 00m05s |  432.22468 |   -3.2787 | 
   13 | 00m06s |  432.22477 |   -3.2818 | 
   14 | 00m05s |  432.22484 |   -3.2807 | 
   15 | 00m06s |  432.22459 |   -3.2829 | 

結果はresプロパティが持っています。

optimizer.res['max'] 
# {'max_val': 432.2248419599048, 'max_params': {'x': -3.280735302927517}}

# optimizer.res['all']で全ての試行結果が取得できます

SVMのハイパーパラメータの最適化

ベイズ最適化は、パラメータの数が多かったり、値域が広かったりするときに、真価を発揮します。

もうちょっと実用的な例として、irisデータセットをSVMで学習する場合のハイパーパラメータの最適化を行います。

与えられたパラメータ(Cとgamma)で10-Foldの交差検証を行ったときの平均スコアを返す関数を定義します。

  • Cは、誤分類によるペナルティで、値を大きくすればペナルティは大きくなります (→過学習しやすい)
  • gammaは、ガウスカーネルのパラメータで、値を大きくすれば決定境界が複雑になります (→過学習しやすい)
from sklearn import datasets, svm, model_selection

# irisデータセットのロード
iris = datasets.load_iris()

# 独立変数・従属変数の分離
data = iris.data
target = iris.target

def validate(C, gamma):
    # 与えられたパラメータでSVMのモデルを初期化
    model = svm.SVC(C=C, gamma=gamma, degree=1, random_state=0)
    
    # 10-Foldで交差検証
    result = model_selection.cross_validate(model, data, target, cv=10)

    # 精度を返す
    return np.mean(result['test_score'])

Cとgammaの値域をそれぞれ0.0001〜10000に設定して、最適化を行います。
もしグリッドサーチでパラメータチューニングするとなった場合、10n (0.0001, 0.001, ... , 1000, 10000) に絞ったとしても、82=64パターンの試行が必要となります。

# 最適化するパラメータの下限・上限 (Cとgamma)
pbounds = {
    'C': (0.0001, 10000),
    'gamma': (0.0001, 10000)
}

# 関数と最適化するパラメータを渡す
optimizer = BayesianOptimization(f=validate, pbounds=pbounds)

# 最適化
optimizer.maximize(init_points=5, n_iter=10, acq='ucb')

Step 12で最高スコア0.98が得られていることがわかります (データ数が150なので誤分類はわずか3つです)。
たった12回の試行で (おそらく) 最高スコアを出せるパラメータに到達できました。

Initialization
-----------------------------------------------------
 Step |   Time |      Value |         C |     gamma | 
    1 | 00m00s |    0.60000 | 8961.1186 |  119.0645 | 
    2 | 00m00s |    0.36667 | 3397.2021 | 3041.1179 | 
    3 | 00m00s |    0.36667 | 1494.0017 | 2237.3522 | 
    4 | 00m00s |    0.36667 | 5771.8602 | 7315.8688 | 
    5 | 00m00s |    0.36667 | 1126.9929 | 3907.0304 | 
Bayesian Optimization
-----------------------------------------------------
 Step |   Time |      Value |         C |     gamma | 
    6 | 00m03s |    0.36667 | 9999.7367 | 3193.1401 | 
    7 | 00m02s |    0.40000 |    0.0001 | 10000.0000 | 
    8 | 00m02s |    0.95333 | 9985.2013 |    3.1480 | 
    9 | 00m03s |    0.36667 | 10000.0000 | 10000.0000 | 
   10 | 00m03s |    0.36667 | 4126.7382 | 10000.0000 | 
   11 | 00m03s |    0.90667 |    0.0001 |    0.0001 | 
   12 | 00m03s |    0.98000 | 2836.3260 |    0.0001 | 
   13 | 00m03s |    0.41333 |    0.0001 | 7060.3988 | 
   14 | 00m03s |    0.98000 | 4626.0169 |    0.0001 | 
   15 | 00m03s |    0.36667 | 10000.0000 | 6978.0628 | 

まとめ

今回はPythonでベイズ最適化を行うBayesianOptimizationについて使い方を整理しました。