2012年12月26日水曜日

JubatusをOpenBlocksで動作させてみる

前回のエントリである「JubatusをOpenBlocks AX3へインストールしてみる」の続きとなります。
今回は、以下の3点を目標とします。

  • 1. OpenBlocks AX3 上のJubatusを、ZooKeeperのCライブラリとリンクして動作させる
  • 2. Jubatusの分類器jubaclassifierをOpenBlocks上で動作させて試す
  • 3. Jubatusの分類器jubaclassifierを分散動作させて試す。このとき、jubaclassifierをOpenBlocksとx86系サーバというヘテロジニアスな環境で組み合わせる

この3点を選んだ理由は、ZooKeeperのCライブラリの利用がOpenBlocksで実行可能であること、Jubatusのサンプ ルを利用して実際にJubatusの分類器を触ってみること、応用として異なるCPUアーキテクチャ間の環境でもJubatusが利用できることを確認 することができるからです。
その代わり、ZooKeeperのセットアップ・設定方法については解説しません。ZooKeeper本体を動作させなくても、1,2番目の内容でOpenBlocks上で動かすJubatusの分類器を触ることはできると思います。

1. 環境構築:ZooKeeper CクライアントライブラリのインストールとJubatusのインストール

JubatusをOpenBlocks AX3へインストールしてみる」 からの差分として、ZooKeeperのCクライアントライブラリをインストールします。また、Jubatusはconfigureオプションから ZooKeeperの利用を有効にしてからインストールします(既にJubatusをインストールしていても、再インストールする形となっても構いませ ん。ただし、Linuxの共有ライブラリのキャッシュが有効になっている場合は再インストール後の再構築を忘れないでください)。
ApacheのZooKeeperプロジェクトから ZooKeeper 3.4.5(stable)リリースのtarボールをダウンロードし、そこからCライブラリをインストールします。ZooKeeperのCライブラリはイ ンラインアセンブラを利用しており、そのままではARMプロセッサを用いているOpenBlocksで動作しないため $ patch -p0 < patch.diff のようなコマンドで以下のパッチをあてることでインストールできます。
diff src/mt_adaptor.c.orig src/mt_adaptor.c -u --- src/mt_adaptor.c.orig 2012-12-14 20:14:37.060018322 +0900 +++ src/mt_adaptor.c 2012-12-14 20:18:20.000018333 +0900 @@ -482,6 +482,8 @@ int32_t fetch_and_add(volatile int32_t* operand, int incr) { + return __sync_fetch_and_add(operand, incr); +#if 0 #ifndef WIN32 int32_t result; asm __volatile__( @@ -503,6 +505,7 @@ } return result; #endif +#endif } // make sure the static xid is initialized before any threads started
$ wget http://ftp.yz.yamagata-u.ac.jp/pub/network/apache/zookeeper/zookeeper-3.4.5/zookeeper-3.4.5.tar.gz $ tar -xvf zookeeper-3.4.5.tar.gz $ cd zookeeper-3.4.5/src/c/ $ ./configure $ make $ sudo make install
2012年11月リリースのJubatus 0.3.4利用します。--enable-zookeeperと--enable-debugオプションも加えてconfigureします。
$ git clone https://github.com/jubatus/jubatus.git $ cd jubatus $ git checkout jubatus-0.3.4 -b jubatus-0.3.4 $ ./waf configure --disable-re2 --enable-debug --enable-zookeeper Setting top to : /home/kashihara/repos/jubatus Setting out to : /home/kashihara/repos/jubatus/build Checking for 'g++' (c++ compiler) : ok Unpacking gtest : yes Checking for library pthread : yes Checking for library msgpack : yes Checking for library dl : yes Checking for program pkg-config : /usr/bin/pkg-config Checking for 'libglog' : yes Checking for 'libevent' : not found Checking for library event : yes Checking for 'pficommon' : yes Checking for header pficommon/network/mprpc.h : yes Checking for header unistd.h : yes Checking for header sys/types.h : yes Checking for header sys/wait.h : yes Checking for header sys/stat.h : yes Checking for header cxxabi.h : yes Checking for header sys/socket.h net/if.h : yes Checking for header sys/ioctl.h : yes Checking for header fcntl.h : yes Checking for header netinet/in.h : yes Checking for header arpa/inet.h : yes Checking for header dlfcn.h : yes Checking for header c-client-src/zookeeper.h : not found Checking for header zookeeper/zookeeper.h : yes Checking for library zookeeper_mt : yes Checking for header sys/socket.h net/if.h sys/ioctl.h : yes Checking for header netinet/in.h arpa/inet.h : yes Checking for compiler atomic builtins : no 'configure' finished successfully (12.197s)
$ ./waf $ sudo ./waf install

ZooKeeperでの動作確認


ZooKeeperのセットアップについては解説しないため、導入にあたっては以下の記事や本家ドキュメントをお勧めします。

管理が困難―分散処理の常識はZooKeeperで変わる (1/3) - @IT
http://www.atmarkit.co.jp/fjava/rensai4/bigdata_java08/01.html
Apache ZooKeeper へようこそ!
http://oss.infoscience.co.jp/hadoop/zookeeper/
Apache ZooKeeper - Home
http://zookeeper.apache.org

ZooKeeperを有効にしたJubatusの分類器jubaclassifierが動作するか確認しましょう。ZooKeeperのホスト、 ポート番号を-zオプションで指定し、-nオプションでJubatus内でのグループ名を指定します。ここではtestとしておきます。以下のように ZOO_INFOといったノードの作成のログが表示されていれば成功です。
$ jubaclassifier -z 192.168.0.102:2181 -n test I1225 01:51:17.177259 14603 server_util.cpp:137] starting jubaclassifier 0.3.4 RPC server at 192.168.0.101:9199 pid : 14603 user : kashihara mode : multinode mode timeout : 10 thread : 2 tmpdir : /tmp logdir : zookeeper : 192.168.0.102:2181 name : test join : false interval sec : 16 interval count : 512 2012-12-25 01:51:17,177:14603(0x2b44a6620d00):ZOO_INFO@log_env@712: Client environment:zookeeper.version=zookeeper C client 3.4.5 2012-12-25 01:51:17,177:14603(0x2b44a6620d00):ZOO_INFO@log_env@716: Client environment:host.name=pfisky 2012-12-25 01:51:17,177:14603(0x2b44a6620d00):ZOO_INFO@log_env@723: Client environment:os.name=Linux 2012-12-25 01:51:17,177:14603(0x2b44a6620d00):ZOO_INFO@log_env@724: Client environment:os.arch=2.6.32-71.29.1.el6.x86_64 2012-12-25 01:51:17,177:14603(0x2b44a6620d00):ZOO_INFO@log_env@725: Client environment:os.version=#1 SMP Tue May 10 17:35:18 CDT 2011 2012-12-25 01:51:17,178:14603(0x2b44a6620d00):ZOO_INFO@log_env@733: Client environment:user.name=kashihara 2012-12-25 01:51:17,178:14603(0x2b44a6620d00):ZOO_INFO@log_env@741: Client environment:user.home=/home/kashihara 2012-12-25 01:51:17,178:14603(0x2b44a6620d00):ZOO_INFO@log_env@753: Client environment:user.dir=/home/kashihara/repos/jubatus 2012-12-25 01:51:17,178:14603(0x2b44a6620d00):ZOO_INFO@zookeeper_init@786: Initiating client connection, host=192.168.0.102:2181 sessionTimeout=10000 watcher=(nil) sessionId=0 sessionPasswd=<null> context=(nil) flags=0 2012-12-25 01:51:17,178:14603(0x2b44a6a32700):ZOO_INFO@check_events@1703: initiated connection to server [192.168.0.102:2181] 2012-12-25 01:51:17,181:14603(0x2b44a6a32700):ZOO_INFO@check_events@1750: session establishment complete on server [192.168.0.102:2181], sessionId=0x13ad037d2ce00fd, negotiated timeout=10000 I1225 01:51:17.202198 14603 zk.cpp:109] create /jubatus I1225 01:51:17.235656 14603 zk.cpp:109] create /jubatus/supervisors I1225 01:51:17.237215 14603 zk.cpp:109] create /jubatus/jubakeepers I1225 01:51:17.238535 14603 zk.cpp:109] create /jubatus/actors I1225 01:51:17.239946 14603 zk.cpp:109] create /jubatus/actors/classifier I1225 01:51:17.241165 14603 zk.cpp:109] create /jubatus/actors/classifier/test I1225 01:51:17.242658 14603 zk.cpp:109] create /jubatus/actors/classifier/test I1225 01:51:17.243892 14603 zk.cpp:109] create /jubatus/actors/classifier/test/master_lock I1225 01:51:17.245112 14603 zk.cpp:109] create /jubatus/actors/classifier/test/nodes I1225 01:51:17.246489 14603 zk.cpp:109] create /jubatus/actors/classifier/test/nodes/192.168.0.101_9199 I1225 01:51:17.246520 14603 membership.cpp:79] actor created: /jubatus/actors/classifier/test/nodes/192.168.0.101_9199
例えば、ZooKeeperとうまく接続できなかったときは次のようにノード作成失敗のログを出力し、最終的にエラーが発生して起動に失敗します。
$ jubaclassifier -z 127.0.0.1:1234 -n test I1225 01:41:41.027243 11741 server_util.cpp:136] starting jubaclassifier 0.3.4 RPC server at 192.168.0.100:9199 pid : 11741 user : kashihara mode : multinode mode timeout : 10 thread : 2 tmpdir : /tmp logdir : zookeeper : 127.0.0.1:1234 name : test join : false interval sec : 16 interval count : 512 2012-12-25 01:41:41,028:11741(0x401ba430):ZOO_INFO@log_env@712: Client environment:zookeeper.version=zookeeper C client 3.4.5 2012-12-25 01:41:41,028:11741(0x401ba430):ZOO_INFO@log_env@716: Client environment:host.name=obsax3 2012-12-25 01:41:41,029:11741(0x401ba430):ZOO_INFO@log_env@723: Client environment:os.name=Linux 2012-12-25 01:41:41,029:11741(0x401ba430):ZOO_INFO@log_env@724: Client environment:os.arch=3.0.6 2012-12-25 01:41:41,029:11741(0x401ba430):ZOO_INFO@log_env@725: Client environment:os.version=#1 SMP Fri Nov 16 11:53:45 JST 2012 2012-12-25 01:41:41,030:11741(0x401ba430):ZOO_INFO@log_env@733: Client environment:user.name=kashihara 2012-12-25 01:41:41,032:11741(0x401ba430):ZOO_INFO@log_env@741: Client environment:user.home=/home/kashihara 2012-12-25 01:41:41,032:11741(0x401ba430):ZOO_INFO@log_env@753: Client environment:user.dir=/home/kashihara 2012-12-25 01:41:41,032:11741(0x401ba430):ZOO_INFO@zookeeper_init@786: Initiating client connection, host=127.0.0.1:1234 sessionTimeout=10000 watcher=(nil) sessionId=0 sessionPasswd=<null> context=(nil) flags=0 2012-12-25 01:41:41,033:11741(0x40ed6460):ZOO_ERROR@handle_socket_error_msg@1697: Socket [127.0.0.1:1234] zk retcode=-4, errno=111(Connection refused): server refused to accept the client E1225 01:41:41.033897 11741 zk.cpp:104] /jubatus failed in creation -4 connection loss 2012-12-25 01:41:41,034:11741(0x40ed6460):ZOO_ERROR@handle_socket_error_msg@1697: Socket [127.0.0.1:1234] zk retcode=-4, errno=111(Connection refused): server refused to accept the client E1225 01:41:41.034329 11741 zk.cpp:104] /jubatus/supervisors failed in creation -4 connection loss 2012-12-25 01:41:41,034:11741(0x40ed6460):ZOO_ERROR@handle_socket_error_msg@1697: Socket [127.0.0.1:1234] zk retcode=-4, errno=111(Connection refused): server refused to accept the client E1225 01:41:41.034677 11741 zk.cpp:104] /jubatus/jubakeepers failed in creation -4 connection loss 2012-12-25 01:41:41,034:11741(0x40ed6460):ZOO_ERROR@handle_socket_error_msg@1697: Socket [127.0.0.1:1234] zk retcode=-4, errno=111(Connection refused): server refused to accept the client E1225 01:41:41.035019 11741 zk.cpp:104] /jubatus/actors failed in creation -4 connection loss 2012-12-25 01:41:41,035:11741(0x40ed6460):ZOO_ERROR@handle_socket_error_msg@1697: Socket [127.0.0.1:1234] zk retcode=-4, errno=111(Connection refused): server refused to accept the client E1225 01:41:41.035387 11741 zk.cpp:104] /jubatus/actors/classifier failed in creation -4 connection loss 2012-12-25 01:41:41,035:11741(0x40ed6460):ZOO_ERROR@handle_socket_error_msg@1697: Socket [127.0.0.1:1234] zk retcode=-4, errno=111(Connection refused): server refused to accept the client E1225 01:41:41.035745 11741 zk.cpp:104] /jubatus/actors/classifier/test failed in creation -4 connection loss F1225 01:41:41.036495 11741 server_util.hpp:124] Dynamic exception type: jubatus::exception::runtime_error::what: Failed to prepare lock_service #0 [jubatus::exception::error_api_func_*] = lock_service::create #0 [jubatus::exception::error_at_file_*] = ../src/common/membership.cpp #0 [jubatus::exception::error_at_line_*] = 161 #0 [jubatus::exception::error_at_func_*] = void jubatus::common::prepare_jubatus(jubatus::common::lock_service&, const std::string&, const std::string&) *** Check failure stack trace: *** @ 0x402fa974 google::LogMessage::Fail() @ 0x402fe764 google::LogMessage::SendToLog() @ 0x402fdd18 google::LogMessage::Flush() @ 0x402fec54 google::LogMessageFatal::~LogMessageFatal() @ 0x1e174 jubatus::framework::run_server<>() @ 0x13934 main @ 0x40526538 (unknown) アボートしました

2. Jubatusの分類器jubaclassifierを試す

jubatus-exampleの将軍分類サンプルを見てみましょう。
https://github.com/jubatus/jubatus-example

jubaclassiferの起動

OpenBlocks側では引数なしで起動してみましょう
$ jubaclassifier I1225 01:49:25.317184 11751 server_util.cpp:136] starting jubaclassifier 0.3.4 RPC server at 192.168.0.100:9199 pid : 11751 user : kashihara mode : standalone mode timeout : 10 thread : 2 tmpdir : /tmp logdir : zookeeper : name : join : false interval sec : 16 interval count : 512

jubatus-example 将軍分類の実行

shogunは、徳川家、足利家、北条家の将軍の姓名をあらかじめ学習させ、分類時に名前を入力すると三家の名字を返すサンプルプログラムとなっています。
サンプルプログラムをx86系のLinuxかMac等で実行します。Rubyのサンプルを使うためjubatus ruby gemsをインストールします。
$ git clone https://github.com/jubatus/jubatus-example $ cd jubatus-example $ git checkout -b obs-example $ gem install jubatus $ cd shogun
OpenBlocks AX3に割り当てているIPアドレスを192.168.0.100とします。 shogun.rbのグローバル変数 $hostを書き換えてから、shogun.rbを実行します。以下のように結果が表示されたら成功です。
$ ruby shogun.rb 徳川慶喜 足利義昭 北条守時
shogun.rbのコードを見てみましょう。config, train, predictという3つのメソッドが定義されています。それぞれ、Jubatusの分類器の設定、学習、名前から名字を分類(予測)することに対応しています。
trainメソッドでは徳川・足利・北条の各家の代表的な将軍が記述され、これがJubatusの分類器で学習されます。predictメソッドで は、慶喜・義昭・守時を名前の入力としてJubatusに問いあわせ、Jubatusが返した結果からスコアの高いラベル(学習時の名字である徳川・足 利・北条に対応)を分類結果として表示しています。
入力した文字列(名前)をどのように機械学習で扱っているかについてはData Conversion — Jubatusを参照してください。configメソッドを見るとuni-gramを用いていることがわかります。
以上で、Jubatusの分類器をOpenBlocksで試すことが確認できました。shogun.rbファイルで学習・分類していましたが、実行 してみたい分類に応じてファイルを分割して分類、学習をJubatusで実行してみると面白いと思います。 注意事項として、学習前にset_configを使って必ず設定を与える必要があります。学習後はset_configを実行しないでください。なぜな ら、学習後にset_configを呼び出すと、学習済みのモデルがクリアされてしまうからです(2013年の次のリリースでは、set_configの RPCによる設定指定は廃止し、別の手段で設定を指定できるようにする予定です)。 また、Jubatusはサーバとして実装されており、現在提供しているインタフェースはMessagePack-RPC(TCP/IPv4)経由となって います。ネットワークが接続できない環境だと外部のマシンからRPCを実行することもできませんし、あまりにも重たい(量の多い)処理を実行させると通信 時にサーバもしくはクライアント側でタイムアウトが発生する可能性があります。

3. jubaclassifierを分散動作させて試してみる

jubaclassifierをOpenBlocksとx86系サーバというヘテロジニアスなアーキテクチャ間でJubatusの分散動作を試して みましょう。OpenBlocksと同様に、x86系LinuxでZooKeeperありでJubatusをインストールした環境を用意します。 x86_64でのREHL系Linux向けのRPMパッケージと、Debianパッケージは公式サイトでも提供しており、Quick Start — Jubatusの解説に従ってインストールが可能です。 shogun.rbでは、サーバとするホスト名、ポート番号は単体で動作させた時と同じ値のままにしておきます。

プロセス、ホスト構成

Tutorial in Distributed Mode — Jubatusを参考にして、以下のような構成でプロセスを起動します。
  • 192.168.0.101 Scientific Linux 6 or Debian Linux
    • Jubatusクライアント(Ruby: shogun.rb)
    • jubaclassifier_keeper
    • jubaclassifier
  • 192.168.0.100 OpenBlocks AX3
    • jubaclassifier
  • 192.168.0.102 Linux
    • ZooKeeper

プロセスの起動

OpenBlocksとScientific Linuxのコンソールで以下のプロセスをそれぞれ起動します。
jubaclassifier -z 192.168.0.102:2181 -n test -p 9198 -i 1
jubaclassifier_keeper -z 192.168.0.102:2181
jubaclassifier -z 192.168.0.102:2181 -n test -i 1
それぞれプロセスが起動した状態で、jubactlコマンドを実行するとZooKeeperと接続しているプロセスのホスト(IPアドレス)がZooKeeperに登録されていることを確認できます。
$ jubactl -z 192.168.0.102:2181 -s jubaclassifier -t classifier -n test -c status active jubakeeper members: 192.168.0.100_9199 active jubavisor members: active test members: 192.168.0.100_9190 192.168.0.101_9199

分散時の学習とMIXの確認

shogun.rbのメソッド呼び出し部分を以下のように分類を実行しないよ書き換えて、設定と学習のみをまず実行します。
# run example config(client) train(client) #predict(client)
分散してJubatusを稼働させていると、Jubatusは学習結果のモデル共有のためにMIX処理というものを行います(*1)。今回は Jubatusのconfigure時に--enable-debugを有効にしていると、jubaclassifierのログに以下のようなMIX処理 のログ出力を確認することができます。現在は、学習の回数もしくは一定期間ごとにMIX処理が自動的に発生します。以下のログは学習結果のデータを共有し ていることがMIX処理の転送データサイズから確認できますが、モデルを共有(同期)させる必要がないときは8バイト程度の転送になります。
I1225 13:33:30.516629 3240 linear_mixer.cpp:172] starting mix: I1225 13:33:30.517878 3240 linear_mixer.cpp:79] get diff from 192.168.0.100:9198 I1225 13:33:30.517902 3240 linear_mixer.cpp:79] get diff from 192.168.0.101:9199 I1225 13:33:30.520889 3240 linear_mixer.cpp:90] pull diff to 192.168.0.100:9198 I1225 13:33:30.520915 3240 linear_mixer.cpp:90] pull diff to 192.168.0.101:9199 I1225 13:33:30.523612 3240 linear_mixer.cpp:230] mixed with 2 servers in 0.006961 secs, 4144 bytes (serialized data) has been put. I1225 13:33:30.523646 3240 linear_mixer.cpp:184] .... 2th mix done.
*1: Overview — Jubatus http://jubat.us/ja/overview.html

分類の確認

shogun.rbを以下のように分類のみ実行するよう書き換え、jubaclassifier単体で実行したときと同じ実行結果である「徳川慶喜」「足利義昭」「北条守時」を得られることを確認します。
# run example #config(client) #train(client) predict(client)
繰り返しshogun.rbを実行しても同じ結果が返ることを確認したり、jubaclassifier_keeperのログを確認してどちらの jubaclassifierに対してRPCを実行しているか確認してみても良いでしょう。MIX処理で学習したモデルが共有されていることが実際に確認 できます。
I1225 13:51:53.796672 12127 keeper.hpp:134] random_proxy1 classify test I1225 13:51:53.796926 12127 keeper.hpp:139] request to 192.168.0.101:9199 I1225 13:51:53.799523 12127 keeper.hpp:134] random_proxy1 classify test I1225 13:51:53.799747 12127 keeper.hpp:139] request to 192.168.0.101:9199 I1225 13:51:53.802237 12127 keeper.hpp:134] random_proxy1 classify test I1225 13:51:53.802461 12127 keeper.hpp:139] request to 192.168.0.100:9198
このときは、jubaclassifier_keeperというプロセスをプロキシとして経由してclassify(分類のための)RPCを呼び出しています。trainやclassifyといったset_config以外のRPCは直接jubaclassifierを指定して実行しても構いませ ん。

jubaclassifier_keeperのclasifyは同じグループ名で起動しているjubaclassifierからランダムに1つ選んでRPCを呼びます。keeperを通したときにRPCをどう呼び出すかはclassifier.idlなどのIDLに定義されています。例えば、set_configは@broadcastというものが指定されており、これはclassifyと異なり参加しているすべてのプロセスに対して同じ内容のRPCを実行します。

まとめ

今回の内容で、以下のことが確認できました。
  • Jubatusの分類器jubaclassifierをOpenBlocksで動作させることができた
  • ZooKeeperのCクライアントライブラリをOpenBlocks上でJubatusと組み合わせ動作させることができた
  • 異なるアーキテクチャ間であるIntel 64(x86_64)のLinuxとOpenBlocks AX(ARM)でJubatusを動作させることができた
今回はJubatusのMIX処理についても説明しましたが、ここで覚えてもらいたい点があります。Jubatusは学習したモデルが分 散したサーバ間で共有(同期)されていきますが、常に同じモデルを一貫して保持することを保証していません。複数台の Jubatus(jubaclassifierなど)があったとき、いくつかでは学習済みのサーバがあっても、まだその結果が共有されていないサーバが存 在する可能性もあります。
機械学習や分散したときの特性も考えながら、OpenBlocksと他アーキテクチャCPUと組み合わせてJubatusを触っていってもらえるとありがたいです。