tensorflowの学習結果をブラウザから使う

さて前回の続き。

tensorflowで学習が進んだのはわかった、さてある程度うまくいったあたりで、この学習結果で友達たちに見せて遊べるようにするには何があればいいか、ということを考えていた。どうしたらいいだろうか。

まず、tensorflowにはsaverというものがあり、学習結果などをファイルに書き出せ、そこから復帰もできる。これは便利で良いのだけれど、あとで利用するためだけにtensorflowをインストールしたサーバがどこかに必要になるけど、そんなものをメンテしたくない。もっとお気軽にいきたい。

tensorflowの学習結果マトリックスはevalするとnumpyのndarrayに変換できる。numpyにはnpyファイル形式というものがあり、ndarrayはこの形式で保存できる。まずはこれを保存するようにした。少なくともこうしておけばnumpyの入ったPython環境で利用ができるだろう。

ちょっと調べてみたらAppEngineからでもnumpyは使えそうなので(https://cloud.google.com/appengine/docs/python/tools/using-libraries-python-27)、まあ最悪それを使えばメンテコストはそれなりに低いものができるとは思う。あんまり面白くはないけれど。

もう一つ別な方法がある。ブラウザ内の処理環境に移植するという方法だ。


ブラウザ内というのはつまりはJavaScriptなのだけど、さすがにマトリックスの計算などをJavaScriptで書きたくない。性能も出ないだろう。そこでEmscriptenを使った。

EmscriptenはC/C++などのコンパイラツールチェインで、LLVMビットコードをasm.jsにコンパイルできる。asm.jsというのは……JSのサブセットで高速化などの最適化がしやすいもの。さらにwebassembly (wasm)にもコンパイルできるようだったが、そもそもwasmが手元のChromeではまだフラグなしでは使えないので今回は見送った。

さてEmscripten。Emscriptenにはem++というC++コンパイラがあり、バックエンドのclang++と連携してLLVMビットコードの生成やasm.jsの生成を行ってくれる。ようするに普通のc++/g++/clang++のところを置き換えてやれば.jsができるようだ。さらにはconfigureなどとうまく連携するためのものもあるようだが、今回は自分がいちから書くだけなので、適当にMakefileを書き、CXXを切り替えればコンパイルできるというやっつけ仕事にした。それにしても、イチからC++のコードを書くのも、白紙の状態からMakefile書くのも、けっこう久しぶりだな……。

行列計算については、C++ではEigenというライブラリが定番のようなのでこれを使った。tensorflowも裏ではEigenを使っているようだ。実装内容は……たんにtensorflowのBasicLSTMCellの実装を見ながら素直に移植した、とまあそれだけで、ここにはそれほどの苦労はない。

苦労はどちらかというとnpyファイルの扱いのほうがあった。npyファイル形式は公開されているが、C++のパーザなんてものはない。というか、検索したらcnpyというライブラリがあったのだけど、今回の自分の用途には向いていなかったようなので、利用しなかった。けっきょくやっつけパーザを自分で書いてEigenのマトリックスにした。

とりあえず一通りのものが動いたところで、手元の環境でネイティブバイナリを動かして、これが動くか確認する必要がある……これはまあ動いた。

とするとEmscriptenでコンパイルされた.jsも同じく動作するはずだ。はずだが……そのためにはnpyファイルをどうにかして生成したJSのコードに渡さないといけない。元はどうあれコンパイルされたJSファイルはふつうのJSと同じ実行環境でしかない。ただファイルを開けばいいというものではない。

外部ファイルはどうやってEmscriptenのC++に読ませたらいいか。これにはいくつか方法があるが、一番手軽なのはコンパイル時にデータファイルを指定する方式だ。

em++に –preload-file というコマンドラインフラグを渡すと、コンパイラはファイルをまとめたパッケージを作り、また生成されたJSファイルではメイン処理が動く前にまずこのパッケージデータを読み込む処理が走る(参考: http://kripken.github.io/emscripten-site/docs/porting/files/packaging_files.html#packaging-files)。そしてC++側でファイルを読み込むコードによってこのパッケージデータ上のデータが読めるようになる。これに必要なnpyをパッケージしておけば、C++側では単にファイルを読めばいいだけになってかなりお手軽だ。

が、試してみた感じではメモリ消費の問題があるようだった。マトリックスはさほど巨大なものではないはずなのだが、そうはいっても全部合わせればそれなりの規模ではあるし、こうしたコードにすることでファイルシステムのエミュレーションも必要になる。試しにやってみたらメモリ消費が急増してまったく動かなかったので、この方向性は諦めた。

ほかにもいろいろあったのだが、けっきょくお手軽なのは

  • JS側で必要なデータをfetchし、
  • その結果のArrayBufferをC++に渡す

ということのようだ。embindを使うとArrayBufferはC++のstd::stringとして入ってくるので、そこから先はnpyパーザを使えば完成、という次第。

ただしこれも、たかが数十MBのデータでもデフォルトでは大きすぎるということで呼び出しに失敗してしまう。これを何とかするにはリンク時に -s TOTAL_MEMORY= でそれなりの大きさのバッファを作る(今回は256MBとした)ことで解決した。これはまああまり賢くはないアプローチである気はする(たとえば、ほんとうはもう少し巨大なデータでも試した結果があったのだけど、そっちはこの方式だと破綻してしまったので試していない)。常識的に考えれば、fetchのストリーミングAPIでちょっとずつC++にデータを渡してやれば良いのだろうけれど。


にしてもEmscripten / asm.js、思ってたよりずっと高速に動作していてけっこうすごい。動作も当然なんの問題もない。いまどき、ブラウザのなかで行列計算したくなったらEmscriptenで一発なのだなあ。

良い時代になりましたね。web assemblyも楽しみになってきました。

tensorflowの学習結果をブラウザから使う” への1件のフィードバック

コメントは受け付けていません。