Fri Aug 14 2020

Hake 構想

動機

機械学習の一連の実験、データセットを取ってき、様々な前処理を試し、様々な学習コードに適用して、テストを行うという行為は、ただ単にコードを設計するのよりもはるかに複雑だ. 初めから正解と思われるコードがあるわけではなく、外延的に見れば同じことをやっているようにしか関数を何種類も書いて、それらの組み合わせを試して動かしてみるしか、正しさは分からない. 従って、これらの行為自体を支援し、管理するツールが欲しくなる. 行為の管理とはつまり、これらは結局データの処理を行うパイプラインだと言えるから、このパイプライン自体を何かコードとして書いてファイルとして保存しておくことだ. そしてそのファイルは実行可能で、違う環境に持ってきてもまたそれを実行すれば同じ結果を得ることが出来る、これが理想だ.

ところで機械学習の各ユニットはハイパーパラメータ(ここでいう「ハイパー」は「メタ」みたいな意味)を取り、その値で挙動が大きく変わったりする. これもまたどういう値を取るのが正解とかではなく、やっぱり色々試してみるしかない. これもやっぱり同様に管理させたい. どうせなら、例えば「1以上10以下の整数全部で試す」とか簡単にやらせたいし、ハイパーパラメータが複数ある場合にはその組み合わせは膨大になるから、何か効率的に狭いパラメータ部分空間の中で、良いスコアを出すようなパラメータの組み合わせを探索させたくなる. 実際に動かしてみてテストデータセットに適用してみてスコアの数値を出してみることが、普通のソフトウェア開発の「テスト」に相当する. データセットを取得してから、テストをさせるまでが、一連のパイプラインだろう.

"機械学習 パイプライン フレームワーク" とかで調べるとそれらしいものがたくさん出てくる.

いくつかは、例えば各処理をPythonで書くことを前提にして、パイプライン処理自体をPythonで書かせるものがある. しかしデータセットの取得なんて、シェルなら wget 一個を使うだけだったりする. そんなのまでPythonで書きたくはないし、というかPythonで全ての処理をするとは全然限らない.

なんと言っても、UNIX哲学に反している!

いくつかは git と連携して、git とほとんど同様のコマンド体系を覚えて使わせるものがある. ソースコードは git で、データセットは彼らのツールで管理する. パイプラインの管理の方法もあるが直接書くことを前提にしたソースコードというものが無かったので満足するものではなかった.

ところで各処理の依存関係を管理して、勝手にゴールまで実行させるような、パイプライン管理&実行ツールがある. GNU Make だ. そしてそのソースコードは Makefile と呼ばれる. ファイル自体の管理はやはり git がある. Make も git も機械学習を前提にしてるわけではないが、それはそれだけ汎用的に作られていることを意味しているし、普通に有用だからエンジニアの基礎教養に既に成っているものと思われる. わざわざ既にあるものを再発明して新しい体系を学習したくはない. Makefile+git が最強なのではないだろうか.

足りていないのは、パラメータ探索をさせる機構と、テスト結果(スコア)の管理だ.

Hake

名前を Hake (ヘイク) とした. 南アフリカでは普通に市場に出回るお魚らしい. 刷毛ではない. QWERTYキーボード上で make っぽく入力できる文字列なら何でもいいと思った. だから nake でも良かったわけだ.

開発構想

コンセプト

名前を make に似せたように使い心地も make とほとんど同じになるようにしようと思っている.

Makefile と全く同じ文法で記述された Hakefile を読み、ターゲットを指定して hake <TARGET> を実行する. 何を隠そう、内部では単に make <TARGET> -f Hakefile を実行するだけなので、この点において何も新しいことはしていない. しかも Hakefile がない場合は Makefile を読みに行く. Hake を知らない人がこれを見ても Makefile を見て普通に make を叩いて使えるようにしたいと思っている.

パラメータ

Make は次のようにパラメータを渡せる.

# Makefile
run:
    echo $(X)

これに対して X を環境変数として渡せばそれを読むことが出来る:

$ X=1 make
echo 1
1

これは誤った使い方で

$ make X=1
echo 1
1

このように make への引数として渡すのが正しい. これをやるともうちょっと便利に使える:

# Makefile

X := 3  # これがデフォルト値として使える

run:
    echo $(X)
$ make
echo 3
3

$ make X=999
echo 999
999

このように、Makefile内部で定義した値を上書きしてくれる. デフォルト値を中で定義して必要なときだけパラメータを上書きするという使い方が出来る.

実験に必要なパラメータはこれを使うことにしよう.

一個のパラメータの組み合わせで実行するだけならただのMakeでも出来る. 勝手に組み合わせを作ってくれて、良い感じに探索をさせよう. 各パラメータについて「範囲」を指定する:

$ hake X=1..999

これは

$ make X=1
$ make X=2
$ make X=3
  :
$ make X=999

という 999 回の実行を意味する. 複数指定出来る:

$ hake X=1..999 Y=a,b,c

これは X=1 から X=999Y=a から Y=c までの \(999 \times 3\) 通りを全て実行する.

$ make X=1 Y=a
$ make X=2 Y=a
$ :
$ make X=999 Y=a
$ make X=1 Y=b
$ :
$ make X=999 Y=b
$ make X=1 Y=c
$ :
$ make X=999 Y=c

ロギング

実験に於いてはログが命で、とにかくなんでも保存しておくのが良い. コードは git が管理するから、commit hash でも置いておけばいいか. make に渡す先のパラメータは Hake が持ってるはずだからこれも吐けばいい.

実験名を与えることは重要だと思っている. 本気で. 要は、人間フレンドリーな実験IDを与えることだ. 実験を開始した日付時刻とかパラメータから半自動で作ることはできる. 日付時刻を付け加えることは実際、ファイル名が被りにくくなるくらいの貢献しかないものだ. これ昨日やった実験だから、確かああいう方式を採用してたはずだ、みたいな人間の記憶に頼る実験をするか? もちろんすべきではない.

先人に学ぼう. docker container というものがある. image を実行して作るプロセス相当が container だ. container を停止させたり再起動させるために名前を与える必要がある. docker はデフォルトで自動で名前付けを行ってくれる.

これを頂こうと思う. 今後例えば実験結果の比較とかをするツールを作ることになる. そのときにこの名前で指定したりすると便利そう. ただの数字列に過ぎない日付時刻やIDだけで指定することを想像したらぞっとする. git commit hash みたいなの(短縮可能なハッシュ値)でもいいとは思うけどね、ちゃんと作れれば.

パラメータ最適化

ここまでに説明したことはほとんど自明に実装可能な機能で、実際ほとんど実装し終えた. もう少し賢いことも、させたくなる. パラメータ最適化だ.

あり得るパラメータの組み合わせ全てを試すのは簡単だが、組み合わせ数が爆発したら全部は試していられなくなる. 最終的なテストフェーズで出てくるスコアの最大化(或いは最小化)という最適化問題だと見做せるから、それをやればいい. ブラックボックス最適化の手法もアプリケーションも既にごまんとあるから、特に困らない. 簡単に実装できるものをとりあえず使える状態にして、満足できなかったらまた考えようと思う. 最初の方法としては 差分進化 をさせようと思っている. パラメータからスコアまでの写像にある種の連続性を仮定しなければいけないので、場合によっては嘘になってしまうが、とりあえずとしてね.