社内用として開発され、毎日2万回ダウンロードされるまで成長した「Optuna」

Hiroyuki Vincent Yamazaki氏:Optunaの開発をしています、Yamazakiです。ふだんは、Preferred Networksというところでエンジニアをしています。今日は「Optuna」の紹介をします。

今回はMeetupの1回目なので、Optunaの基本的なところで、どういった問題を解いているかと、これらの問題をどのように解いているかという基礎を紹介できればなと思います。よろしくお願いします。

いきなりですが、みなさんやみなさんの周りでこのような経験はありませんか? 例えば、「最新の研究を追従しようとしているけどうまく再現できない」とか「モデルは学習できたんだけれども思ったより少し遅い」とか。もしくは「そもそも学習がうまくいかない」など、こういったケースはありませんか?

Optunaの開発のきっかけがこれらになります。軽くOptunaの歴史をおさらいさせてください。

開発がスタートしたのが2018年です。Preferred Networksというところで、もともとは社内のインターナルなツールとして開発がスタートしました。同じ年に「Google」の画像のコンペティションで2位に入賞することができて、ここにも貢献しています。

さらに、ソフトウェアとしてどんどん成熟していったので、社内にとどまらずいろいろな方に使ってもらいたいという思いから、GitHub上でオープンソース化したのが、同じ年の2018年です。

2019年は、国内外のイベントにいろいろ出てOptunaの名前がちょっとずつ広まっていきました。2020年は、インターフェイスやパフォーマンスが安定してきたので、メジャーバージョンを切りました。

2020年は特に開発が活発で、2.0のメジャーも同じ年に出しています。

現在は、2.8が最新のバージョンになります。(※取材当時。記事公開時点での最新バージョンは2.9.1)新しい機能や、改善を随時みんなで開発している状況で、おかげさまで、現在は毎日2万回ほどダウンロードされるフレームワークに育ちました。

現在の開発の体制はこのようになっていて、継続的な開発者が10人以上います。ふだんは企業で働いている方や、大学などいろいろな機関にいる方が混ざっています。非同期のコミュニケーションを取ったり、こんなご時世なのでオンラインですが、定期的に口頭で議論したり、みんなでコミュニケーションを取りつつ、開発しているスタンスになります。

また、GitHub上のコントリビューターは今120人以上いて、この数字もどんどん増えているので、大変うれしいなと思っています。

「Optuna」のキーワードはハイパーパラメーターと最適化

ではちょっと話を戻して、Optunaがそもそもどういうフレームワークなのか、どういう問題を解いているのかをちょっと説明したいと思います。

キーワードとして出てくるのが「ハイパーパラメーター」や「ハイパーパラメーター最適化」です。

まず、ハイパーパラメーターは何かを説明させてください。これはモデルの品質を左右するとても重要なパラメーターです。

例えば機械学習だと、その学習のアルゴリズムや、学習のデータがしっかり整っていても、このパラメーターの調整を怠ると台無しになってしまうことがあり得ます。

例えば、この左の図の物体検出のモデルだとうまく予測できなかったりするのが、このパラメーターをうまくチューニングすれば、きちんと実用的なモデルが右のように得られます。

このハイパーパラメーターはそもそもアルゴリズムで学習できないのかというと、これはハイパーパラメーターの定義上、できないパラメーターで、アルゴリズムのそもそも挙動を制御するようなパラメーターなので、別の外の枠組みでチューニングする必要があります。

これを一般的にはみなさんどうやっているのかというと、「過去によかった値」を使うのがわりと一般的です。例えば、論文で言及されている値や、フレームワークだったらデフォルトの値を使うことが考えられます。

もう少しがんばっている方は、おそらくいろいろな値を数パターン試行しているかなと思います。ただ、探索の空間が大きかったり、そもそも連続的であるとあまり効率がよくなかったりというのがわかるかなと思います。

個人的には、自分が扱っている問題にこういうハイパーパラメーターが存在しているのをそもそも認識しておらず、それゆえに調整していないこともあるのかなと思っています。

なので、今日はこのあといろいろな応用の発表があるのですが、「自分の周りにはどういったハイパーパラメーターがあるんだろう」みたいなのをちょっと念頭に置きながら聞いてもらえるとおもしろいかなと思います。

では、もっと賢くハイパーパラメーターをチューニングしたい時はどうやるかというと、先ほどのハイパーパラメーター最適化の枠組みを使います。もっと広く言うと、ハイパーパラメーター最適化は「ブラックボックス最適化」という枠組みの中の一種と考えられます。

ここで急にいろいろな専門用語が出てきて、ちょっと恐縮なのですが、ご了承ください。

ブラックボックスが何かというと、中が解析できない関数だと思ってもらえればよいかなと思います。例えばニューラルネットワークだと、勾配を使って効率よく最適化できるのでブラックボックスには当てはまらない問題になります。

具体的には、何かの探索の空間からある入力を受け取った時に、それをもとに何かを出力する目的関数を定義して、最大化もしくは最小化する入力をこの空間から探します。これを効率よく行うのがブラックボックス最適化です。

若干抽象的だと思うので、先ほどの例で見てみると、物体検出の場合、バウンディングボックスと言うのですが、この四角が重複しているかを判断する閾値が0から1の値であって、例えば0.6だったら0.6の時に、どれぐらいよい予測モデルができるか、これをずらした場合にどう変わるか、このパラメーターを0から1の間でチューニングします。

この時のブラックボックスは、物体検出器の学習および評価になるわけです。

「探索空間」と「目的関数」でよいパラメーターを見つける

では、Optunaでこれをどうやって解くのかについて話をしたいと思います。

Optunaはいろいろな特徴があって、時間が無限にあったら全部お話ししたいのですが、今回は主にこれをどうやって効率よく解いているか、ユーザー目線ではどういったコードを書くのか、どういうふうに記述すればそれが実行できるのかに焦点を当てた内容を共有します。

まずは、後者です。どのように効率よく解いているかをざっくり説明します。

Optunaの入力は、大きく2つあります。1つは先ほどの「探索空間」。そして自分が最適化したい「目的関数」。これらをOptunaに渡すと、Optunaはなんらかのかたちで、よいハイパーパラメーターを見つけてくれます。だいぶフワッとしていますが、このような枠組みになっています。

中を見てみましょう。Optunaの中には、最適化のアルゴリズムというコアのコンポーネントがあります。このアルゴリズムが何をしているかというと、探索空間からあるハイパーパラメーターを選んできて、Optunaがそれを評価するのを何回も繰り返すことで、どんどんよい領域に集中していって、よいハイパーパラメーターを見つけます。ざっくり説明すると、こういう枠組みになっています。

さらに、試行の履歴を保存しておくので、ユーザーがあとからこれを分析したり、続行したりすることも簡単にできます。

実際、どういうアルゴリズムがOptunaで実装されているかというと、大きく分けてこのようなものがあるかなと思います。最初の2つがグリッド探索、ランダム探索です。図で言うと、左の2つの画像です。

等間隔で探索空間を網羅的にカバーしたり、ランダムな場合だと、一様にランダムに点を試していくナイーブなアルゴリズムになっています。探索空間が狭い場合は、これで十分なケースもあるとは思うのですが、直感的にも効率がよくないのはわかってもらえるかなと思います。

そこでOptunaでは、もう少し賢いアルゴリズムがいくつも実装されています。それが、これらのベイズ最適化や進化計算です。上の2つとどう違うかというと、試行を積み重ねていくごとに、今までの履歴をもとに、より有望な領域を優先的に探索するアルゴリズムで、例えばこの右の図のように最適な解にどんどん集中していく探索になります。

具体的なアルゴリズム名をいくつか挙げると、例えばTree Parzen Estimator(TPE)というものがあって、これはわりと幅広い問題で安定して動くので、Optunaの今はデフォルトになっています。

もちろん探索空間や目的関数によるのですが、例えば試行の数が数十、数百のオーダーだと、わりと使いやすいかなと思います。各試行にけっこう時間がかかって、数を抑えたい時はガウス過程を使うこともできます。

試行を何千とか回せる時は、下のCMA-ESなどの進化計算のアルゴリズムも備わっています。

最後のNSGA-IIは、上と若干違った特徴をもっています。先ほどのブラックボックスの出力は1つでしたが、複数の値を出力する問題も考えられます。こういう時に、複数の目的のトレードオフを考慮しながら効率よく最適化する特殊なアルゴリズムもOptunaには備わっています。

「Optuna」の概念 Storage、Study、Trial

実際にこれをどうやって実行するのかを軽く説明したいと思います。

まず、Optunaのドキュメンテーションやコードを読む時に、必ず出てくる概念がいくつかあるので、それを軽くおさらいしたいなと思います。

ベースとなるのが「Storage」です。実験の永続化、もしくはメモリー上でもいいのですが、実験の情報を永続化するための抽象レイヤーだと思ってもらえればよいかなと思います。永続化する場合は、例えばこの右にあるデータベースに接続してとっておくこともできるわけです。

このStorageの中には、複数の「Study」が存在します。1つのStudyは、1つの実験です。ある探索空間と、ある目的関数が紐付いていると思ってもらえればいいかなと思います。

おわかりのとおり、このStudyの中には「Trial」(試行)が、Trial 0、Trial 1、Trial nまで、どんどん実験を回していくとStudyに追加されていくイメージです。

さらに、先ほど出てきた最適化のアルゴリズムもStudyに紐付いていて、Pruningアルゴリズムというものも、Optunaには複数あります。これもStudyに紐付いています。

どういったものかというと、あまり有望でないハイパーパラメーターの場合で、Trialが長い時に、早期に試行を切り上げられないかを判断するアルゴリズムです。これによって、さらに効率よい最適化ができる仕組みがOptunaにはあります。

(次回へつづく)