こんにちは.この記事は Jubatus Advent Calendar の5日目の記事です。
この記事では,Jubatus 1.0.0にあわせてJubatusコミュニティからリリースされたembedded-jubatus (pypi)の紹介をしたいと思います.
作るきっかけ
私はJubatusを使ってデータ分析をしているのですが,パラメータの調整をしようとするとパラメータ毎にJubatusサーバの起動と終了を繰り返さなければなりませんし,マルチコアを生かしたパラメータ調整を行おうとすると,Jubatusサーバを同時に複数起動させる必要があり,ポートが競合しないように立ち上げたり,RPCタイムアウトの調整…と,気を使うことが多く面倒に感じていました.(※この当時はJubakitも無かったため,似たようなJubatusサーバ管理の仕組みを独自に作っていました)
また,パラメータ調整は限られたデータ量で実施するため,Jubatusを分散実行させる必要もありません.
そこで,Pythonからjubatus-coreを直接呼び出すことができれば,サーバの起動終了やポートに気を使うこともなくなりますし,RPCを経由しない分,より高速にパラメータ調整が出来るようになると思い,Pythonからjubatus-coreを直接呼び出すモジュール embedded-jubatus を作りました.
embedded-jubatusの使い方
embedded-jubatusはJubatusのPythonクライアントのインタフェースと同じインタフェースを持っていますので,ClassifierやAnomalyといったクラスの名前空間をjubatusからembedded_jubatusに変更するだけで使えるようになります.
同じインタフェースを持っているので,試行錯誤や調整段階ではembedded_jubatusのClassifierをimportするルートを通るようにし,実際の分析を行う際はjubatusのClassifierをimportするルートを通るようにするだけで,他のコードに変更を加えること無く切り替えることが出来ます.
(ここらへんは先日のハッカソンのJubatus 1.0の紹介の中で紹介されていました)
以下のようにインスタンス化のところを変更するだけです.
embedded-jubatusのClassifier等の引数には下記の例のようにサーバに指定する様なコンフィグファイルのパスを指定してもいいですし,
Pythonのdict形式で定義したコンフィグをそのまま渡すことも出来ます.
if use_embedded:
from embedded_jubatus import Classifier
classifier = Classifier('/usr/share/jubatus/example/config/classifier/default.json')
else:
from jubatus import Classifier
classifier = Classifier('127.0.0.1', 9199, '', 10)
現時点ではJubatus 1.0のモデル形式変更への追従が漏れていたので,上手く動かないのですが次のバージョンからはJubatusが作るモデルとも互換性がありますので,embedded-jubatusで作ったモデルをjubatusにデプロイすることも出来るようになります(issue#17).
embedded-jubatusの性能
では,テキストや数値データを利用した場合,RPC経由でjubatusを利用する場合と,embedded-jubatusを用いて直接jubatus-coreを利用する方法に,どの程度の性能差があるのか見てみましょう.
実験に使ったデータとソースコードはGitHubのこのリポジトリにあります.
まず,Jubatusサーバを起動します.この時にタイムアウトオプションを大きめに指定する必要があります.(十数秒間RPCを発行しない時間があるため,Jubatusサーバよりタイムアウトと判断され切断されるのを防ぐためです)
$ jubaclassifier -f /usr/share/jubatus/example/config/classifier/default.json -t 600
続いてベンチマークスクリプトを実行すると,以下のようにどのぐらいの時間がかかったかが出力されます.
$ python ./benchmark.py
# 20news
= train (1 rows/call) =
server=78.6512610912323[s]
embedded=37.97922086715698[s]
= train (10 rows/call) =
server=79.29068160057068[s]
embedded=38.570820808410645[s]
= train (100 rows/call) =
server=62.23877835273743[s]
embedded=37.912595987319946[s]
= train (18828 rows/call) =
server=54.572771072387695[s]
embedded=37.742305517196655[s]
# dorothea
= train (1 rows/call) =
server=2.9357399940490723[s]
embedded=1.1047742366790771[s]
= train (10 rows/call) =
server=2.9662089347839355[s]
embedded=1.0966050624847412[s]
= train (100 rows/call) =
server=2.267573595046997[s]
embedded=1.0598268508911133[s]
= train (800 rows/call) =
server=2.025496482849121[s]
embedded=1.1467664241790771[s]
20newsはテキストデータ,dorotheaはスパースな数値データです.
このようにembedded-jubatusはRPCを経由するよりも半分以下の時間で同じ処理が実行できていることが解ります.
RPC経由だと一度に大量のデータをJubatusサーバに送信すると,応答が返ってくるまでに長い時間がかかる場合があります.そのため通常はdatumを小分けにして送信するのですが,そうするとRPCのオーバーヘッドによりserver版とembedded版の差が大きくなっていきます.
(タイムアウトのお話)
Jubatusにはサーバ起動時に指定するタイムアウトと,クライアント側で指定するタイムアウトの2種類があります.これはそれぞれ以下のような意味になりますので,適宜設定する必要があります.タイムアウトが発生したらリトライするといった処理は必要ですが,学習時など冪等にならない場合もありますので,なるべくタイムアウトが起こらないように使い方を工夫するかタイムアウト時間を調整したほうが良いです.
なおこれはPythonクライアントの話でそれ以外の言語のバインディングは使ったことがないのでわかりません.
参考:http://jubat.us/ja/tips_faqs/faq_rpc_err_workaround.html
-
サーバ側のタイムアウト: サーバ側で指定したタイムアウトの時間が経過してもクライアントが何も要求を送ってこない場合は,サーバ側でTCPを切断します.その後,クライアント側で送信しようとするとタイムアウトエラーになります
-
クライアント側のタイムアウト:クライアント側で指定したタイムアウト時間がたっても,サーバから応答が帰ってこなかったら,タイムアウトエラーを発生させ,TCPを切断します.大量のDatumをJubatusサーバに送った場合や,Jubatusサーバ側が高負荷状態の場合にタイムアウトエラーとなる場合がありますので,一度に送るデータ量は上手く調整してあげたほうが良いです.
おわりに
以上,簡単ですが embedded-jubatus を作るきっかけから,使い方,性能評価を紹介しました.
他にも embedded-jubatus には numpyのndarray や scipy の csr_matrixなどの疎行列を直接受け取ることの出来る scikit-learn互換APIも一部用意されています.
明日も再び私(@k_oi)で,とある発表に対抗してJubatusのハッシュ探索性能を高速化した話をしようと思います.