ベイズ最適化(skopt)によるハイパーパラメータ探索

機械学習のハイパーパラメータの値によってモデルの精度が大きく変わることがある。
SVMのハイパーパラメータに対して、グリッドサーチ(Grid Search)とベイズ最適化(Bayesian Optimization)の精度を比較した。
ベイズ最適化には、ライブリ[scikit-optimize](https://scikit-optimize.github.io/stable/auto_examples/sklearn-gridsearchcv-replacement.html)(skopt)を用いた。

作成したコードはGoogle colab上で実装し、GitHubにあります。
<https://github.com/JunJun100/BayesianOptimization>

# SVMのハイパーパラメータ
SVMにはハイパーパラメータである、gammaとCの2つある。
ハイパーパラメータの値によって精度が低い、あるいは過学習が起きる。
以下の図では、(gamma=10^-1, C=10^0)が最も精度がよい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/981627/b42f1a70-23bb-a154-357b-f113d6f9315d.png" width=95%>
(出典:[RBF SVM parameters](https://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html))

# グリッドサーチ
グリッドサーチは、探索範囲を格子状(グリッド)にする。
予めパラメータの探索候補値を与え、全ての組み合わせに対して計算する。
シンプルなアルゴリズムだが、計算時間がかかる。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/981627/7ff8f03d-589e-db8a-36ea-9bb7c30761fc.png" width=80%>

# ベイズ最適化
グリッドサーチはシンプルなアルゴリズムだが、探索に時間がかかる。
より効率的に探索するのがベイズ最適化である。
ベイズ最適化では、正答率が高そうなエリアを中心に探索する。
しかし、局所解に陥らないよう定期的に他のエリアも探索する。
人間の感覚のように、それっぽいエリアの探索をベイズ最適化は実現できる。

# 実装

### 設定
```
# 乱数のシードを設定
import random
import numpy as np

np.random.seed(1234)
random.seed(1234)
```

```
# Breast cancer dataset for binary classification
import pandas as pd
from sklearn.datasets import load_breast_cancer
import time

from sklearn.model_selection import GridSearchCV
from sklearn.metrics import f1_score
from sklearn.svm import SVC
```

```
# Bayesian Optimization
!pip install scikit-optimize

from skopt import BayesSearchCV
from skopt.space import Real, Categorical, Integer
```

### データの読み込み
```
# set x and y
dataset = load_breast_cancer()
X = pd.DataFrame(dataset.data, columns = dataset.feature_names)
y = pd.Series(dataset.target, name = 'y')
X.join(y).head(3)
```

### Grid Search
```
# パラメータグリッドの設定
param_grid_svc = {
    'C' : [0.1, 1, 10, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500],
    'gamma' : [1e-8, 1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1.0, 2.0]
}
print('探索空間: ',  param_grid_svc)
```

探索回数としては、13*10 = 130となる。

```
# Grid Search
t1 = time.time()   # 時間計測開始
gs_svc = GridSearchCV(estimator = SVC(random_state = 1),
                      param_grid = param_grid_svc,
                      scoring = 'f1',
                      cv = 5,
                      return_train_score = False
                      )
gs_svc.fit(X, y)
t2 = time.time()   # 時間計測終了
print('{:.2f}秒かかった'.format(t2 - t1))

# 実行結果:10.53秒かかった
```


```
# テストスコアの確認
df_gs_tmp = pd.DataFrame(gs_svc.cv_results_)
df_gs = df_gs_tmp.sort_values('rank_test_score', ascending = True)
df_gs.head()
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/981627/061d276c-18e9-3eb3-c4a8-17f7de805029.png" width=100%>


上位3つのmean_test_scoreは0.966832と同順位である。
gammaは1e-05,Cは[400, 500]の間に最適解がある。



```
# Best Hyperparameter
gs_svc.best_params_

# 実行結果:{'C': 400, 'gamma': 1e-05}
```

### Bayesian Optimization
```
# 探索範囲の設定
param_bayes_svc = {
    'C' : Real(0.1, 500.0, prior = 'log-uniform'),
    'gamma' : Real(1e-8, 2.0, prior = 'log-uniform')
}
print('探索空間: ',  param_bayes_svc)
```

今回の探索は30回(n_iter=30)に設定した。

```
# Bayes Search
t1 = time.time()   # 時間計測開始
bo_svc = BayesSearchCV(estimator = SVC(random_state = 1),
                      search_spaces = param_bayes_svc,
                      scoring = 'f1',
                      cv = 5,
                      n_iter = 30,
                      return_train_score = False
                      )
bo_svc.fit(X, y)
t2 = time.time()   # 時間計測終了
print('{:.2f}秒かかった'.format(t2 - t1))
# 実行結果:24.83秒かかった
```

↑Grid Searchの2倍以上の時間がかかった。
(n_iterを増やすと精度は上がるが、計算時間が大きく増える)

```
# テストスコアの確認
df_bo_tmp = pd.DataFrame(bo_svc.cv_results_)
df_bo = df_bo_tmp.sort_values('rank_test_score', ascending = True)
df_bo.head()
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/981627/27ece4bb-2fe3-c0f9-05c0-6c6805ae7bfd.png" width=100%>

mean_test_scoreのベストスコアを見ると、0.966836
>Grid Searchののベストスコアは、0.966832


```
# Best Hyperparameter
bo_svc.best_params_

# 以下、実行結果:
# OrderedDict([('C', 499.99999999999994), ('gamma', 9.368957868762131e-06)])
```

### 結果
|手法   |計算時間[sec]|探索回数|(C, gamma)|f1スコア|
|------|-----------|--------|-------|-------|
|グリッドサーチ|10.53|130|(400~500, 1e-05)|0.966832|
|ベイズ最適化|24.83|30|(500, 9.4e-06)|0.966836|

# まとめ
ベイズ最適化の方が少ない探索回数で最適解を得られたが、計算時間はグリッドサーチの2倍以上かかった。
ベイズ最適化のライブラリ*scikit-optimize*は、グリッドサーチと同様に実装することができるので、
実装が容易である。
他のベイズ最適化のライブラリ(GPyOptやBayesianOptimization等)を使えば、
計算時間が改善されるかもしれない。

# 参考文献
[機械学習のためのベイズ最適化入門](https://www.slideshare.net/hoxo_m/ss-77421091)
[ベイズ最適化とグリッドサーチの比較](https://qiita.com/koji-murakami/items/b7887f1cef11ddc443a4)