【ハイパーパラメータの自動最適化!】Preferred NetworksさんのOptunaを触ってみた!
ハイパーパラメータ。モデルを支配するパラメータ。
調整するの苦戦しますよね?
ということで、今回は先日Preferred Networksさんが公開したPython上のハイパーパラメータ自動最適化ツールOptunaをご紹介します!
(※この記事の内容をご自身で試される場合は自己責任でお願いします)
はじめに
昨今のディープラーニング・AIなどを扱う際に重要な要素の1つがハイパーパラメータ。
多層ニューラルネットワークを例に取れば、ニューロンの数・層の数・学習率やドロップアウトの割合など数えれば数えるほど切りがありません。 もちろん、先見の知識を扱うことができる対象であれば、どのようなハイパーパラメータがいいのかなどはある程度は知られてはいます。
しかし、自分が初めて行う対象やあまり知られていない対象に対して適用したいという場合は往々にしてあると思います。 そんな場合には自分自身でチューニングしてよいハイパーパラメータを探す必要が出てきます...
そんなときに朗報です!
2018年12月2日にPreferred Networksさんが自動でハイパーパラメータを最適化してくれるツールを公開してくださいました!
その名も“Optuna”
https://research.preferred.jp/2018/12/optuna-release/research.preferred.jp
ということで、今回はこのOptunaを触ってみたという記事になります!
Optunaの導入
嬉しいことにOptunaの導入の方法は公式ドキュメントに書いてあります。
https://optuna.readthedocs.io/en/stable/installation.htmloptuna.readthedocs.io
Pythonの環境としてはv2.7か>=v3.4が必要みたいです。
ぱっと公式ドキュメントを見る限り、
$ pip install optuna
だけでいいみたいです! というわけで早速入れてみましょう!
Optuna仮想環境の構築
私はAnacondaさんを使っていますので、Optunaさん用に環境を整えます。
仮想環境
$ conda create -n optuna_env pip python=3.4 $ conda activate optuna_env $ conda install scikit-learn matplotlib $ pip install cython $ conda install pandas $ pip install optuna $ conda deactivate
でOK!
ここで注意してほしいのが4行目のcythonと5行目のpandasです!
まず4行目からいきますが、Cythonが入っていないと、Cython is required to compile pandas from a development branch.
と怒られてしまいました💦
また、5行目のPandasに関しても、pandasが入っていないと、Failed building wheel for pandas
と怒られてしまいました💦
ま、よくわかりませんが、conda
とpip
を使ってそれぞれ入れるとうまくいきましたので、良しとしましょう!
Optunaの利用
というわけで、簡単な例でOptunaさんの力を借りてみましょう!
ライブラリ
今回はoptunaに加えて、次のライブラリを使います
# 今回の主役 import optuna # サブのライブラリ import numpy as np import matplotlib.pyplot as plt import sklearn from sklearn.linear_model import LinearRegression
二次関数の最適化(公式チュートリアル)
公式ドキュメントにてチュートリアルが書かれておりますので、私もチュートリアルにトライしてみます!
公式チュートリアルで紹介されている基本問題は、このような最適化問題です。
つまり、中学生のときにやった二次関数の最適化ですね! これはあっという間に、最適解はで最小値0となることがわかります。
ということで、これをOptunaさんの力を借りてやってみましょう!
まずは目的関数の定義です。
# objective function def objective(trial): x = trial.suggest_uniform('x', -10, 10) return (x-2)**2
Optunaさんでは"trial"とは、一回目的関数を計算してみることを指しているようです。 つまり、-10から10の間で候補のを一様に1つ生成してからを計算してみることを指しているっぽい?
次はの最適化
study = optuna.create_study()
study.optimize(objective, n_trials=100)
[I 2018-12-15 04:35:37,229] Finished a trial resulted in value: 61.2878584158245. Current best value is 61.2878584158245 with parameters: {'x': 9.828656233085248}. [I 2018-12-15 04:35:37,273] Finished a trial resulted in value: 3.7371480273689595. Current best value is 3.7371480273689595 with parameters: {'x': 0.06682954001232488}. [I 2018-12-15 04:35:37,290] Finished a trial resulted in value: 134.86042888109552. Current best value is 3.7371480273689595 with parameters: {'x': 0.06682954001232488}. [I 2018-12-15 04:35:37,305] Finished a trial resulted in value: 27.434594542295343. Current best value is 3.7371480273689595 with parameters: {'x': 0.06682954001232488}. 〜中略〜 [I 2018-12-15 04:35:44,502] Finished a trial resulted in value: 24.603222898235302. Current best value is 0.0018931986286930766 with parameters: {'x': 2.0435109024118447}. [I 2018-12-15 04:35:44,583] Finished a trial resulted in value: 6.377461997831239. Current best value is 0.0018931986286930766 with parameters: {'x': 2.0435109024118447}.
実はこれで目的関数の最適化が終わります!
簡単すぎる...!
また、Optunaさんの言い回しですが、"study"とは一回の試行であるtrialをまとめた最適化のセッションのようです。
そして、この出力をみる限り、徐々にCurrent best valueが小さくなって、最適解に近づいている様子を確認することができます!
では、得られている解を確認しましょう!
print("opt_x : ", study.best_params) print("opt_f : ", study.best_value)
opt_x : {'x': 2.0435109024118447} opt_f : 0.0018931986286930766
study
の中にbest_params
とbest_value
がありますので、そこで最適解と最適値を確認できます!
確かに、2に近い最適解を見つけることができてますね!
線形回帰のモデル選択
というわけで、もう少しデータ解析に近い問題を考えてみましょう!
次のような問題を考えてみます!
今、データが得られているとし、周期的っぽいなってことで、そのデータに対してモデル
を仮定しているとします。ここでは互いに独立な標準正規分布に従う確率変数です。 そして、考えるべき問題はデータからFourier係数を推定するという問題です。
ここで、モデルに含まれているはハイパーパラメータでモデルの形を支配しています。
この問題では、Optunaを使ったcross-validationによってこのハイパーパラメータを自動で決定します!
まずはデータを生成しましょう!
ここでは次のようにデータを作ります。
def f(t): return np.sin(3*t) + 0.5*np.cos(5*t) T = 4500 t_learn = np.random.randn(T) * np.pi x_learn = f(t_learn) + np.random.randn(T) Tval = 500 t_val = np.random.randn(Tval) * np.pi x_val = f(t_val) + np.random.randn(Tval)
すなわち、
ですね! このデータからハイパーパラメータを5と決定できて、
と推定できれば成功です!
ちなみにデータはこんな感じ。
plt.scatter(t_learn, x_learn, c="k", alpha=0.1) plt.show()
うん。何か構造は見えるけど、難しそう。
というわけでチャレンジ!
# feature function # 今回は線形回帰とか言いつつ、実は非線形な特徴関数をかました上での線形回帰です。 # そこで特徴関数を定義します。 def phi(t, M): ''' arguments: t : 説明変数(numpy 1d-array) M : Fourier次数(ハイパーパラメータ)(int) return: 1,cos(t),cos(2t),...,cos(Mt),sin(t),...,sin(Mt)を並べたもの ''' if M==0: return np.ones((len(t),1)) return np.hstack([ np.ones((len(t),1)), np.array([np.cos(i*t) for i in range(1,M+1)]).T, np.array([np.sin(i*t) for i in range(1,M+1)]).T, ]) # Objective function def objective(trial): # Fourier order optunaM = trial.suggest_int('Fourier_order', 0, 15) # linear model lm = LinearRegression().fit(phi(t_learn,M=optunaM), x_learn) # Vailidation dataに対する決定係数(1に近ければ近いほど当てはまりがよい) R2 = lm.score(phi(t_val, M=optunaM), x_val) return 1-R2 study = optuna.create_study() study.optimize(objective, n_trials=100)
[I 2018-12-15 05:11:46,482] Finished a trial resulted in value: 1.000597154371967. Current best value is 1.000597154371967 with parameters: {'Fourier_order': 1}. [I 2018-12-15 05:11:46,545] Finished a trial resulted in value: 0.6039560086065457. Current best value is 0.6039560086065457 with parameters: {'Fourier_order': 9}. [I 2018-12-15 05:11:46,598] Finished a trial resulted in value: 0.5993651892410196. Current best value is 0.5993651892410196 with parameters: {'Fourier_order': 5}. [I 2018-12-15 05:11:46,665] Finished a trial resulted in value: 0.6041404708784243. Current best value is 0.5993651892410196 with parameters: {'Fourier_order': 5}. 〜中略〜 [I 2018-12-15 05:11:57,031] Finished a trial resulted in value: 0.6993435745409307. Current best value is 0.5993651892410196 with parameters: {'Fourier_order': 5}. [I 2018-12-15 05:11:57,178] Finished a trial resulted in value: 0.6005207158780083. Current best value is 0.5993651892410196 with parameters: {'Fourier_order': 5}.
さて、これでの最適化ができました! ここではの範囲で探しています。
print("opt_M : ", study.best_params)
opt_M : {'Fourier_order': 5}
実際にとなって、期待していたとおりのハイパーパラメータになりました!
また、モデルの当てはまり具合も確かめてみましょう!
opt_linear_model = LinearRegression().fit(phi(t_learn,M=study.best_params["Fourier_order"]), x_learn) predicted_x = opt_linear_model.predict(phi(t_val,M=study.best_params["Fourier_order"])) plt.scatter(t_val, x_val, c="k", alpha=0.5, label="validation data") plt.scatter(t_val, predicted_x, c="r", alpha=0.5, label="prediction") plt.plot(np.linspace(-10,10,1000), f(np.linspace(-10,10,1000)), "b-", label="genuine model") plt.xlim(-5,5) plt.legend() plt.show()
いいですね!(なお、なんでの最適化にValidation data用いているのに、それと同じデータを用いて当てはまり具合を見ているのかというと、単なるサボりです...ご勘弁を!💦)
推定されるパラメータはどうかというと...
print("cosine Fourier coef.:",opt_linear_model.coef_[:study.best_params["Fourier_order"]+1]) print(" sine Fourier coef.:",opt_linear_model.coef_[study.best_params["Fourier_order"]+1:])
cosine Fourier coef.: [ 0. 0.00150789 -0.01555043 0.00190459 -0.04628791 0.48007748] sine Fourier coef.: [-0.03093966 0.01295187 1.00363006 -0.03495288 0.01748351]
確かに欲しかった、
に近い値になって、それ以外は0に近くなってますね!めでたしめでたし!
まとめ
ということで、今回は自動でハイパーパラメータを最適化するツール
Optuna
をご紹介いたしました。 このツールはさすがPreferred Networksさんとしか言いようがありません! 今まで苦戦していたチューニングが楽になりそうです!
また、こちらのOptuna、
Chainer
scikit-learn
XGBoost
LightGBM
などとも併用できるようですね! ますます夢が膨らんでしまいます。
というわけで、もう少しOptunaさんと遊んでみたいと思います!
間違いのご指摘やご意見・ご質問等ございましたら、ぜひコメントのほどよろしくお願いします!