26
Jubatus 使使使使使 使使使使使 Jubatus 使使使使使使使使使 使使使使

Jubatus使ってみた 作ってみたJubatus

Embed Size (px)

Citation preview

Page 1: Jubatus使ってみた 作ってみたJubatus

Jubatus使ってみた作ってみた Jubatus

ヱヂリウム株式会社渡邉卓也

Page 2: Jubatus使ってみた 作ってみたJubatus

概要使ってみた

ミドルウェアとして利用するにあたり各機能を検証今回は 0.5.0で追加された機能について、精度・性能を紹介

-近傍探索機能-クラスタリング機能

ここはなんとかならないか作ってみた

とある機能を Jubatusをフレームワークとして利用して実装フレームワークとしての利用法を紹介ここはどうにかならないか

対象バージョン: Jubatus 0.5.4本発表の内容は、 0.6.0にも基本的には適用できる、はず

2

Page 3: Jubatus使ってみた 作ってみたJubatus

近傍探索機能の特徴三つの手法を利用可能

MinHash-集合としての類似度( Jaccard index)を近似-ただし実数値も投入可能

LSH (Locality Sensitive Hashing)-特徴ベクトル同士のコサイン距離を近似

Euclid LSH-ハッシュ値に加えてベクトルのノルムを保存して距離計算する

recommenderとの違い投入された特徴ベクトル自体は保持しないので省メモリ

3

Page 4: Jubatus使ってみた 作ってみたJubatus

MinHashの実装の特徴 1

非負実数値に対して拡張されたMinHash法を利用 Ondrej Chum et al., "Near duplicate image detection: min-hash and tf-

idf weighting", BMVC 2008元々の Jaccard index

- intersection(Sa, Sb) / union(Sa, Sb)まず整数に対して拡張する

-特徴ベクトル a中の特徴 faiが値 nをとるとき、 n個の異なる要素に展

開した集合 S'aを作る

- intersection(S'a, S'b) = sumi(min(fai, fb

i))

- union(S'a, S'b) = sumi(max(fai, fb

i))この式は実数値に対してもそのまま使える

-本発表ではこの手法による類似度を「拡張 Jaccard index」とよぶ-コサイン距離のそこそこ良い近似になっている

拡張 Jaccard indexを近似するハッシュ函数を利用- h(fa

i) = -log x / fai, where x <- uniform(0, 1)

-もっともこのハッシュ函数は fai, fb

iが同じ値か 0をとる場合の近似だが…

4

Page 5: Jubatus使ってみた 作ってみたJubatus

MinHashの実装の特徴 2

b-bit minwise hashing Ping Li, Arnd Christian Konig, "b-bit minwise hashing", WWW 2010ハッシュ値の下位 bビットのみを保持する

- Jubatusでは 1ビットのみを保持している精度が下がる分、ハッシュ函数の数を増やす

-元よりもかなり少ない容量で同等の精度を達成できる- Jubatusでは設定ファイルの hash_numでハッシュ函数の数を指定する

類似度が低くなる程、類似度の推定値の分散が大きくなるという性質がある- 1ビットの場合、ランダムでも確率 0.5で一致するので…

5

Page 6: Jubatus使ってみた 作ってみたJubatus

MinHashの精度:均等な分布の場合

6

人工データ(二値、 100次元)X軸:MinHashの出力した距離Y軸: Jaccard index

ハッシュ函数の数左上: 64 右上: 256 左下: 1024

Jubatusの出力の上位

64でもそこそこ精度が良い

Page 7: Jubatus使ってみた 作ってみたJubatus

MinHashの精度:類似度高が一定数ある場合

7

実データ 1(二値化)X軸:MinHashの出力した距離Y軸: Jaccard index

ハッシュ函数の数左上: 64 右上: 256 左下: 1024

類似度 0のレコードの推定値がばらつく

やはり上位の推定精度は良い

Page 8: Jubatus使ってみた 作ってみたJubatus

MinHashの精度:類似度低が多い場合

8

実データ 2(二値化)X軸:MinHashの出力した距離Y軸: Jaccard index

ハッシュ函数の数左上: 64 右上: 256 左下: 1024

かなり厳しい精度 なんとか使えそう

これなら問題ない

Page 9: Jubatus使ってみた 作ってみたJubatus

MinHashの精度:実数値で投入した場合

9

実データ 2(実数値のまま)X軸:MinHashの出力した距離Y軸:拡張 Jaccard index

ハッシュ函数の数左上: 64 右上: 256 左下: 1024

二値の場合よりも精度が上がっている

Page 10: Jubatus使ってみた 作ってみたJubatus

拡張 Jaccard indexとコサイン距離の関係

10

実データ 2X軸:拡張 Jaccard index Y軸:コサイン距離

積率相関係数: 0.91順位相関係数: 0.89

良い近似となっている

Page 11: Jubatus使ってみた 作ってみたJubatus

LSHの実装の特徴 random projectionによる LSH

特徴ベクトルがランダムな超平面に対してどちら側に属するか、によってビットベクトルを作る- Jubatusでは設定ファイルの hash_numにより射影回数を指定する

ビットベクトル同士のハミング距離により近傍探索特徴ベクトル同士のコサイン距離を反映した近傍探索結果となることが期待される

11

Page 12: Jubatus使ってみた 作ってみたJubatus

LSHの精度:負の値もとる実数値の場合

12

実データ 3X軸: LSHの出力した距離Y軸:コサイン距離

ハッシュ函数の数: 256

かなり厳しい精度

Page 13: Jubatus使ってみた 作ってみたJubatus

近傍探索機能の性能近傍探索速度

MinHash- 100万件、ハッシュ函数の数が 256の場合、 400ミリ秒あまり-特徴ベクトルの特性に関わらず一定-ハッシュ函数の数が 64の場合 2倍程度高速

LSH:MinHashの 1割程度高速データ投入速度

MinHash- 1万件、ハッシュ函数の数が 256の場合、約 20分-ただし特徴ベクトルの次元数に依存

- かなり次元数の多いデータを入れて計測している-ハッシュ函数の数が 64の場合 3倍程度高速

LSH:MinHashの 2倍程度高速メモリ消費量

100万件、ハッシュ函数の数が 256の場合、 200 MB程度

13

Page 14: Jubatus使ってみた 作ってみたJubatus

クラスタリング機能の特徴二つの手法が実装されている

k-means GMM (Gaussian Mixture Model):ここでは扱わない

k-meansの実装の特徴一定数のレコードが投入される毎にバッチで全レコードに対してクラスタリングを行う- bucket_size毎にクラスタリング

初期配置は k-means++で決定コアセットによりレコード数を圧縮する

- bucket_size毎に compressed_bucket_sizeに圧縮- compressed_bucketが bucket_length個貯まったらもう一段圧縮-圧縮の段数が次第に増えていく仕組み

- cf. 位取り記法-理論的にはレコード数は O(log n)で増加するはずだが、実装の問題により O(n)で増加している

14

Page 15: Jubatus使ってみた 作ってみたJubatus

クラスタリング機能の性能

15

20 Newsgroupsを利用

パラメータ "k" : 3, "bucket_size" : 100, "compressed_bucket_size" : 10, "bicriteria_base_size" : 5, "bucket_length" : 2, "forgetting_factor" : 0, "forgetting_threshold" : 0.5

左上X軸:投入件数Y軸:クラスタリング時間 (s)(投入・圧縮にかかる時間も含む)

左下X軸:投入件数Y軸:メモリ消費量 (kB)

Page 16: Jubatus使ってみた 作ってみたJubatus

コアセットが線形に成長する問題再帰的な圧縮を行う部分

圧縮後の件数として指定する値が大きいため、再帰的な圧縮が実質的に機能しておらず、対数的な成長にならない

jubatus/core/clustering/compressive_storage.cpp void compressive_storage::carry_up(size_t r)

16

if (!is_next_bucket_full(r)) { /****/} else { wplist cr = mine_[r]; wplist crr = mine_[r + 1]; mine_[r].clear(); mine_[r + 1].clear(); concat(cr, crr); size_t dstsize = (r == 0) ? config_.compressed_bucket_size : 2 * r * r * config_.compressed_bucket_size; compressor_->compress(crr, config_.bicriteria_base_size, dstsize, mine_[r + 1]); carry_up(r + 1);}

なぜか段数の二乗に比例

Page 17: Jubatus使ってみた 作ってみたJubatus

ミドルウェアとして利用する場合の問題点近傍探索機能

近傍探索速度の問題- 1回の探索は 1スレッドで直列実行される

- CPUのコアあたりの性能は近年頭打ち傾向- 100万件を超えるとオンライン用途には厳しくなってくる

-結果をキャッシュする、事前計算する等の対策は考えられるが…-素直にオンラインで使えるようになるのが望ましい

- 探索をマルチスレッド化し、複数コアを活かせるようにならないかデータ投入速度の問題

-レコード毎に RPCしなければならない- バルク投入できるようにならないか

-投入時のグローバルなロックにより実質直列実行される- 射影計算等はスレッドローカルな計算なのでロックをとらずに実行できるはず

-複数プロセス立ち上げて裏で mixさせるという対策はあるが…

全般機能毎にサーバプロセスを立ち上げて個別に管理しなければならない 17

Page 18: Jubatus使ってみた 作ってみたJubatus

作ってみた Jubatus

Jubatusをフレームワークとして用いるとある機能を実装

- RPCで呼び出される部分を一通り実装-単体試験まで実施

ただし、- jubaproxyは利用しない- mixの実装は行わない

18

Page 19: Jubatus使ってみた 作ってみたJubatus

IDLによる外部インタフェース定義MessagePack IDLを基にした独自 IDLによって定義する

RPC用メソッドを定義-ロックの方式等-メソッドのシグネチャ

jenerator IDLファイルからサーバプログラムのテンプレートを自動生成 OCamlで記述されている

-どう書くとどういうコードが生成されるのかを確認する為には中を読むことになる

jeneratorのインストール手順- OPAMをインストールする- OPAMで必要なパッケージをインストールする

- $ opam install ounit- $ opam install extlib

- jeneratorをコンパイル・インストールする- $ omake- $ sudo PREFIX=/usr/local omake install 19

service foo { #@cht #@analysis #@pass int do_something(0: string id)}

Page 20: Jubatus使ってみた 作ってみたJubatus

テンプレートの具体化サーバの中身の実装

<service_name>_serv.tmpl.{hpp,cpp}が出力されている <service_name>_serv.{hpp,cpp}にコピーして中身を実装していく

起動時処理設定ファイルの読み込みモデルの初期化

各サーバで共通のメソッドの実装 get_status:必要であれば固有の情報を追加する

固有のメソッドの実装 IDLで指定したメソッドが空で用意されているので中身を書くその他必要なクラスも用意する

20

Page 21: Jubatus使ってみた 作ってみたJubatus

モジュール構成_serv

RPCを受け付けるとりあえずここにロジックを書いてしまってもよい

core/driver/<service_name>.{hpp,cpp} _servから呼ばれ、ロジックを呼び出す serverと coreを切り離すリファクタリングの途中?

core/<service_name>/ここにロジックを記述することが期待されている

_storageモデル(内部状態)を保持する既存の _storageにそのまま使えるものがなければ実装する既存のモジュールではここに多くのロジックが書かれている

_config設定ファイルの定義を行う

21

Page 22: Jubatus使ってみた 作ってみたJubatus

排他制御フレームワークでは RPCのメソッド単位で制御

IDLで指定:リードロック、ライトロック、ロックなし問題点

ロック区間が長い-スレッドローカルな計算をやっている間もずっとロックされる

応答時間に関する懸念-ライトロックが優先なので、データ投入中はリードロックなメソッドの応答が遅延する

現実的な実装としては…リードロックまたはロックなしを指定し、内部で細粒度の排他制御を行う- mutexはモデルとは別に保持し、シリアライズの対象外とする

ただし save/loadや mix時の排他制御についても考える必要がある

22

Page 23: Jubatus使ってみた 作ってみたJubatus

save/load機能の実装 save/load機能とは

モデル(サーバの内部状態)をファイルに保存・ファイルから読み込む機能

save/load機能は mix機能に相乗りしている mix関連クラスを実装・利用する必要がある手順

- _storageの pack(), unpack()を実装する- _mixableを実装する- _mixable->set_model()により _storageを _mixableに登録する- mixable_holder->register_mixable()により _mixableを mixable_holderに登録する

制御の流れ- RPCで save()が呼ばれる- _serv->get_mixable_holder()により mixable_holderを取得- mixable_holder->pack(), _mixable->pack(), _storage->pack()の順に呼ばれる

23

Page 24: Jubatus使ってみた 作ってみたJubatus

wafによるビルドwscriptを書く

ビルド方法を指定する為の Pythonスクリプトメソッドとして各種の指定を記述

- configure:コンパイラオプション等を指定- build:ソースやターゲットを指定

ビルド方法 $ ./waf configure $ ./waf build buildディレクトリにバイナリが出来上がる

24

bld.program( source = __sources, target = 'juba' + name, includes = '.', lib = __libraries )

Page 25: Jubatus使ってみた 作ってみたJubatus

単体試験googletestを利用する

試験コードを記述する

waf-unittest (unittest_gtest.py)で wafから実行する features引数で試験実施を指示する

$ ./waf build --check

25

bld.program( features = 'gtest', source = 'foo_storage_test.cpp',...

TEST(foo_storage, set_get_state) { foo_storage storage; std::string id = ID1; foo_state state = MAKE_STATE(1, 1.0); storage.set_state(id, state); foo_state state_ = storage.get_state(id); EXPECT_EQ(state, state_);}

Page 26: Jubatus使ってみた 作ってみたJubatus

フレームワークとして利用する場合の問題点

モジュール間の関係が分かりにくい各クラスがどのような機能を担っており、互いにどのような関係にあるのかの情報がほしい

例えば…-ロジックが _storageと各機能のモジュールに分散しているが、どのような基準で切り分けているのか

- mixを行う為にはどのクラスを実装する必要があり、どのメソッドがどの順番で呼ばれるのか、どのようにデータをセットアップする必要があり、排他制御はどういった考え方で行えばよいのか

頻繁にインタフェースの変更を伴うリファクタリングが行われるいったん機能を実装してもすぐに動かなくなるおそれ

26