Jubatusにcurlでアクセスするインタフェースについて

この記事はJubatus Advent Calendar 11日目の記事です。

Jubatusをとあるプロジェクトで使う為に問題になったことを手元で解決するために作った道具の話をします。

## 問題点1. JubatusにHTTP経由でアクセスしたい

Jubatusとはmsgpack-rpcプロトコルで通信するサーバプロセスである。
msgpack-rpcは高速なバイナリプロトコルで、それをjubatus-proxyで負荷分散することができる。

しかし、JubatusにHTTP経由でアクセスすることができればロードバランサで負荷を散らしたりnginxにhttpsを終端させたりBASIC認証を噛ませたりと様々なHTTPのノウハウを活かす事ができる。

## 問題点2. Jubatusプロセスを再起動せずにモデルを作り直したい

0.3までのJubatusは実は新しいモデルを `set_config` rpcで作ることができたが、一度に1プロセスに付き1モデルしか保持できない事から、実質初期化rpcに過ぎなかったので起動時オプションで与える形に変わった。
昔のJubatusの構想としてはrpc経由で複数のモデルを作ったりその出力を組み合わせたり多段にしたりアンサンブルしたりなどといろんなことができる構想が有ったようだが、そのインタフェースをどうしたら良いか確定しなかったのもあって実装されなかった。

Jubatusのrpcは第一引数に`name`という引数を取り、クラスタ内にある複数のモデルを呼び分ける事ができる。しかし1プロセス1モデルの構造の上なので複数のモデルを同時に使うためにはそれぞれ別のモデル名を与えたJubatusプロセスを立ち上げる必要がある。そしてZooKeeperとjubatus_proxyが必要となり管理が面倒になる。

# http_jubatus

これら２つの問題を同時に解決するために作ったのが、httpエンドポイントを持ち、複数のJubatusプロセスの死活を行い、そいつらとの通信を中継してくれる[http_jubatus](https://github.com/kumagi/http_jubatus)である。
慣れないGo言語でおっかなびっくり書いたのでイケてないコードはちょいちょいあると思う。

```
$ go run http_jubatus.go
```

と立ち上げる事ができる。

## モデル追加

新たにclassifierプロセスを立ち上げる際には

```
POST /classifier/
```

にjubatusのコンフィグファイルと`name`を投げつける。幸いにしてjubatusのコンフィグファイルはjsonなのでhttpと親和性が高い。classifier以外のプロセスを立ちあげたい場合はその名前がpathになっているので `POST /recommender/` なり何なりできる。

```
$ curl localhost:3000/classifier \
    -H 'Content-type: application/json' \
    -X POST \
    -d '{
          "name": "sample_classifier",
          "parameter": {
            "converter" : {
                "string_rules" : [
                    { "key" : "*", "type" : "str", "sample_weight" : "bin", "global_weight" : "bin" }
                ],
                "num_rules" : [
                    { "key" : "*", "type" : "num" }
                ]
            },
            "method" : "PA"
          }
        }'
```

という感じでcurlでPOSTすると、`sample_classifier`という`name`を持ったjubatusプロセスが新たにexecされ、コンフィグとしてJSONファイルの中身が渡される。

## 機械学習実行

`name`はそのままPATHの一部となる、そこの下にjubatusのrpc名をつなげた物が機械学習のHTTPエンドポイントになる。また、msgpackはJSONとおおよそ対応関係があるので、JSON形式でjubatusのパラメータを表現する事で機械学習が実行できる。例えば`train`を行いたい場合は以下のようになる。

```
curl localhost:3000/classifier/sample_classifier/train \
    -H 'Content-type: application/json' \
    -X POST \
    -d '[
        [
            [
                "bar",
                [
                    [],
                    [
                        ["fuga", 1.0]
                    ],
                    []
                ]
            ]
        ]
      ]'
```

## 反省点

良かったことと悪かった事がある。

### 良かったこと：モデルをRPCで作れるのは便利

ハイパーパラメータの調整のためなどでモデルを調整するにしてもクライアント側のプログラムを調整してリトライすればよいし、複数のハイパーパラメータで精度を比べたい場合でも全部とりあえず立ち上げるというクライアント側のプログラムがとても書きやすかった。サーバに食わせるJSONファイルを個別に編集してプロセスを立ちあげては殺していた日々は何だったのか。

### 悪かった事：JSONパラメータをcurlで記述するのは地獄

括弧の対応関係を追いかけるのが非常につらい。
HTTPで与えるオブジェクトを1つにしたかったのもあり、引数全体を１つの配列オブジェクトとして受け取るので配列1層、trainは複数の教師データを一度に与える事ができるように`paired datum`の配列を受け取る。`paired datum`はラベルと`datum`による長さ2の配列としてmsgpackの構造で表現されている。そして`datum`は長さ3の配列となっていて、`string_values`,`num_values`,`binary_values`でできている。それぞれの`values`はキーバリューペアの配列でありキーバリューペアはキーとバリューを含む長さ2の配列である。それらがすべて入れ子構造になっているので、それを表したJSON構造のデバッグは地獄だった。
もっとJSONライクに配列ではなくてオブジェクトでデータ構造を詰め込めばよかったのだけれど、RPC毎にJSON⇔msgpackの変換を手作業で書くのは現実的なコストではなく、いっそのことJubatus-idlから自動生成するよう`jenerator`に改変を加えたほうが良いと思う。

もう少し使ってみて良さそうであればJubatus本流でも同様のインタフェース経由で使えるようにできたらいいなと思う。