コード生成器の利用¶
Jubatus フレームワークを利用した機械学習アルゴリズムの開発では、まず IDL と呼ばれる RPC インタフェース定義ファイルを作成する。
Jubatus に付属するコード生成器 jenerator
を使用することで、IDL から各部品 (サーバ, Proxy, 各言語版のクライアント) を生成することができる。
これらの生成器を利用することで、フレームワークの利用者は機械学習アルゴリズムの実装に集中することができる。
開発の流れ¶
- サービスが持つべきRPCインターフェースを IDL で定義する。
jenerator
を用いて、IDL から サーバー、Proxy のコード、共通のデータ構造、各言語(C++/Python/Ruby/Java)のクライアントを生成する。- RPC毎にサーバーが利用するユーザ定義クラスのインターフェースの実体、および必要に応じてmix操作を作成する。
スケルトンプロジェクト を利用すると、容易に開発を開始できる。
IDL を使用する理由¶
Jubatusは機械学習などのアルゴリズムをモジュール化し、容易に追加できることを目的にしているが、公開されている実装に対してrecommenderを追加しようとした場合、それぞれのRPCインターフェースをクライアントのヘッダと実装、Proxyのヘッダと実装、サーバー本体のヘッダと実装の6箇所に定義する必要があった。さらにpficommonのMPRPC_GEN, MPRPC_PROC等、サーバーへの関数登録などで合計7箇所に記述を繰り返す必要があることが明らかになった。 このような設計では、新しい学習アルゴリズムを追加する度に同じRPC定義を7回繰り替えさなければならず、APIの仕様を変更するたびに同じような修正を繰り返さなくてはならないためバグが入り込む温床となっており、機械学習を分散環境で実装するためのフレームワークとして容易に追加できると言いがたい。さらに、C++のマクロおよびテンプレートを多用しているため、コンパイルエラーが複雑なものとなり、Jubatusを用いて機械学習を実装するにはJubatusの深い知識が必要となっていた。
IDL を利用することで、上記のフローで一連のシステムを作成することができることを確認した。 実際に RPC 定義をするのは、7箇所から3箇所に削減された。これを用いて、recommender, classifier, regression, stat, graph が構成出来ることを確認した。
ファイルの構成¶
Jubatus フレームワークを利用した機械学習システムは、以下のファイルで構成される (NAME はサービスの名称である)。
- NAME_serv.cpp: 機能を実装するソースファイル (
jenerator
で生成されるテンプレートを編集) - NAME_serv.hpp:
NAME_serv.cpp
に対応するヘッダファイル (jenerator
で生成されるテンプレートを編集) - NAME_impl.cpp: サーバの main 関数と RPC インタフェースの定義、RPC メソッドの登録 (
jenerator
で自動生成) - NAME_proxy.cpp: Proxy の実装 (
jenerator
で自動生成) - NAME_client.hpp: サーバー間通信で利用するクライアントの実装 (
jenerator
で自動生成) - NAME_types.hpp: RPC で使用する構造体や型の情報 (
jenerator
で自動生成)
jenerator
: コード生成器¶
RPC インターフェースは MessagePack-IDL により定義する。 ただし、そのままJubatusのコードを生成するためには、MessagePack-IDL の文法とは別に、RPCサービスの各メソッドにアノテーションをつける必要がある。
アノテーションは Jubatus のコードジェネレータである jenerator
では解釈されるが、MessagePack-IDL ではコメントとして無視される。
このため、同じ IDL ファイルで各種クライアントも生成できる。
各メソッドに付与するアノテーションの文法は以下の通りである。
- 各メソッドには
#@
で始まる 3 つのアノテーションを付与する必要があり、順番に "ルーティング", "ロック種類", "結合方法" を指定する。 - "ルーティング" には、Proxy がどのようにリクエストをプロキシするかを定義する。
cht
,broadcast
,random
の 3 種類が用意されており、これによって、典型的だと思われる機械学習の分散方式をカバーすることができる。cht
は Consistent Hashing によるリクエストの分散を意味する。cht
アノテーションがあるメソッドは、少なくとも 1 つの引数(cht のキーとなる string)を取る必要がある。 更新データのレプリケーション多重度 (冗長度) はデフォルトでは 2 である。#@cht(1)
のように、更新データのレプリケーション多重度を指定することもできる。broadcast
は全サーバーへリクエストをブロードキャストを意味する。random
はランダムに選択されたいずれか 1 台のサーバーへリクエストを送信することを表す。
- "ロック種類" には、リクエストのread/writeを
analysis
,update
,nolock
のいずれかで定義する。analysis
では、サーバ側で read ロックされることになり、複数のスレッドからの同時アクセスが可能となる。update
では、サーバー側で write ロックされることになり、安全にデータを更新することができる。nolock
ではロックは行われない。
- "結合方法" には、API 呼び出しに対する複数のサーバからの結果を結合する方法を定義する。 利用可能なアグリゲータは aggregators.hpp に掲載されている。
なお、メソッドの戻り値型に void
は利用できない。
返り値が必要ない場合は、意味のない int
や bool
型などを指定する必要がある。
以下は、アノテーション付きの IDL の例である。
message entry {
0: string key
1: string value
2: int version
}
service kvs {
#@cht(2) #@update #@pass
int put(0: string key, 1: string value)
#@cht(2) #@analysis #@pass
entry get(0: string key)
#@cht(2) #@update #@pass
int del(0: string key, 1: int version)
#@broadcast #@update #@pass
int clear()
}
以下のサーバー向けのRPCメソッドは jenerator
によって、自動的に各サービスに追加される。
#@random #@analysis #@pass
string get_config()
#@broadcast #@analysis #@all_and
bool save(0: string id)
#@broadcast #@update #@all_and
bool load(0: string id)
#@broadcast #@analysis #@merge
map<string, map<string, string> > get_status()
jenerator
のビルド¶
jenerator
のビルドには OCaml 4.02.1 以上(findlib あり) 、extlib および OMake および OUnit および ppx_deriving が必要である。
OCaml環境の構築には、 OPAM を利用するのが楽である。
なお jenerator
が依存するモジュールのインストールには OPAM のバージョン1.2以降を利用する必要がある。
OPAMをソースから入れる場合は、 ocamlbrew を利用するとよい。
$ opam switch 4.02.1
$ eval `opam config env`
$ opam install ocamlfind extlib omake ounit ppx_deriving
$ cd jubatus/tools/jenerator
$ omake
$ sudo omake install
omake install
を行うと jenerator
が /usr/local/bin/jenerator
としてインストールされる (環境によりパスは異なる場合がある)。
インストールパスを変える場合は、
$ PREFIX=/path/to/install omake install
でインストールパスを変えることができる。
インストールを行わずに、ビルドされた jenerator
のバイナリを直接使用してもよい。
サーバの実装¶
kvs_impl.cpp
は、 kvs_serv
クラスを利用してサーバーを構成する。
このクラスを kvs_serv.{cpp,hpp}
に定義する必要がある。
生成されたテンプレート (kvs_serv.tmpl.{cpp,hpp}
) をリネームして利用することができる。
kvs_impl.cpp
の中では main
関数も実装されており、ユーザは main
を実装する必要はない。
コマンドライン引数の仕様は Jubatus フレームワークを使用しているサーバの間ですべて共通である。
オプションは --help
で参照することができる。
Mixable クラス¶
TBD.
Proxy の実装¶
Proxy に関しては、実装をする必要はない。 jenerator
が生成した kvs_proxy.cpp
をコンパイルすると Proxy が得られる。
kvs_proxy.cpp
には main
関数の実装だけがあり、各 RPC メソッドごとにリクエストをプロキシし、レスポンスを集約するためのファンクタを登録する。