Tue Aug 18 2020

Hake 構想

パラメータチューニング機能を入れてとりあえず当初考えていた機能は実装した.

Hake はパラメータチューニングの機能だけをMakeに上乗せしただけのもの.

機械学習のテストまでの流れは大雑把には次のようなもの:

  1. データセットの取得
  2. 訓練
  3. テスト

この流れは用意にMakefileで記述出来る

dataset:
    wget ... > dataset

train:
    python ./train.py > model

test:
    python ./test.py model

「機械学習の実験」とは主にはこの train の部分を何度も書き換えて様々なパラメータで実行させる行為のことを言う. 従って ./train.py はUNIX的にCLIコマンドとして定義しておくのが便利. 内部で用いるパラメータをコマンドラインから受け取るようにしておく.

train:
    python ./train.py --alpha $(ALPHA) --beta $(BETA) > model

これで次のように make を呼べばパラメータを渡せる:

$ make train ALPHA=1 BETA=2

これはただ一通りのパラメータを与えただけだが、実際の実験では、考えられる組み合わせを全探索するとか、適当な指標でもって良さそうなパラメータを探索させるとかそういったことをさせたい. Hake はこれを提供する:

$ hake train ALPHA=1..3 BETA=2..4

コマンドの makehake に置き換えた以外は全てそのまま流用できる. ファイルも Makefile をそのまま使っていい. ただし普通の Makefile と区別させるために Hakefile という名前にして別物として管理した方がいいのではないかと今は思っている.

上のように 1..3 とするとこれは 1 以上 3 以下という整数の閉区間を表し, Hake はこのパラメータ範囲から作れる全組み合わせで make を呼び出す.

Hake は内部ではただ単に、make プロセスを作って実行するに過ぎない. その中から出力された標準出力は全て Hake の監視対象になり、ログとして保存される. それから、次のようなスキーマを持つJSONだった場合、メトリックとして特別な監視対象になる:

{
  "metric": "<metric-name>",
  "value": float-value
}

ついでに言うと実行した make のどれかから出力されればよくて、誰が出力したかまでは監視していない. 例えば Makefile もとい Hakefile で train と test のルールが別々に用意されていて、このようなメトリックを出力できるのはおそらく test の中でだが、Hake は特に気にしない. あなたが

$ hake train test

のように複数のターゲットを同時に指定した場合、Hake はこれらを順に実行するが、全体としては一個の make を実行したに過ぎない. この中のどこかで上記のフォーマットでメトリックを標準出力してくれれば構わない. Hake は本当に何も難しいことをしない.

単にメトリックを出力するだけでは無意味で、Hake にどのメトリック(その名前)を監視させて、そしてそれを最大化したいか最小化したいかまでを指定すると、Hake は最適化モードに入り、パラメータの全組み合わせを試す代わりに、ブラックボックス最適化を行う. ここのアルゴリズムには現在、差分進化を使っている.

$ hake train test --max acc ALPHA=0..10 BETA=100..200

こうすると, {"metric": "acc", "value": ...} を監視対象としてその value を最大化させるように, ALPHA, BETA を変えながら make train test を試す.

ふりかえり

まだ致命的に足りていない機能(主に並列化)もありつつ、一応は、こんなのが欲しいと思ったものは実装した. そもそもHakeを実装し始めた最大の動機は、何でもかんでもPythonの中でやりたくない、というのがある. 今どきは何でもPythonの中でデータセットのダウンロードからテストまでの一本のパイプラインを全て行い、パラメータの最適化までもそこにPythonコードとして埋め込んでしまう.

そして私は、根本的にPythonが好きじゃない. 確かにたいていPythonを使う. ライブラリが充実していて一極集中してしまっているから. それでも隙あらば違う言語を使おうと私は目論んでいるし、特定の言語やツールにロックオンされているツールなんてまっぴらごめんだと思っている.

その点 GNU Make は最高のツールだ. Make という名前はビルドツールであることを指しているが、別に使い方はビルドに限定されていない. C/C++ にフレンドリーに機能が作られているが別に何の言語でもいい. ダウンロードしてきたソースコードに Makefile が無かったら誰だって不安になるし、あれば、とりあえず make すればいいんだとわかって安心するだろう. 私は機械学習の実験という営みにもこれを取り入れたかった. Makefile があり、とりあえず make を叩けば実験が全て再現される. これが理想だ.

Hake は Make の薄い薄いラッパーとなっており、Makefileを書けるならHakefileを書くこともできるし、make コマンドを叩けるなら hake コマンドを叩くこともできる. そういう風になっています.

問題点

現実的には一個のプロセスで全てをやってしまうのがメモリ効率がよいこと.

Hakeの目指す理想の形は、パイプラインの各過程を違うプロセスにすることで、 データセットをダウンロードするだけのスクリプトがあり、 訓練スクリプトはデータセットを毎回読み込んで学習したモデルを毎回ディスクに保存し、 テストスクリプトはモデルをディスクから読んで、テスト用のデータセットも読み込んでテストする. Hake はこれらをそれぞれ別のプロセスとして読んで実行するから、データセットもモデルもメモリ上で共有しない. 甚だ非効率だ.

これらを通して実行する一個のスクリプトにした方が良い. データセットを読むのが時間がかかるなら、それを大事にして、パラメータ最適化もそのスクリプトの中でやったほうが本当は良い.

解決策としては、

  1. データセットはできるだけすぐ使える最小の形にまで変換してから保存する
  2. 訓練とテストは一つのプロセスにする(モデルをディスクに保存するのは不要なら避ける)
  3. 並列化

最後の並列化は、まだ実装していない.