Jubatus使ってみた 作ってみたJubatus
-
Upload
jubatusofficial -
Category
Technology
-
view
8.324 -
download
0
Transcript of Jubatus使ってみた 作ってみたJubatus
Jubatus使ってみた作ってみた Jubatus
ヱヂリウム株式会社渡邉卓也
概要使ってみた
ミドルウェアとして利用するにあたり各機能を検証今回は 0.5.0で追加された機能について、精度・性能を紹介
-近傍探索機能-クラスタリング機能
ここはなんとかならないか作ってみた
とある機能を Jubatusをフレームワークとして利用して実装フレームワークとしての利用法を紹介ここはどうにかならないか
対象バージョン: Jubatus 0.5.4本発表の内容は、 0.6.0にも基本的には適用できる、はず
2
近傍探索機能の特徴三つの手法を利用可能
MinHash-集合としての類似度( Jaccard index)を近似-ただし実数値も投入可能
LSH (Locality Sensitive Hashing)-特徴ベクトル同士のコサイン距離を近似
Euclid LSH-ハッシュ値に加えてベクトルのノルムを保存して距離計算する
recommenderとの違い投入された特徴ベクトル自体は保持しないので省メモリ
3
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
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
MinHashの精度:均等な分布の場合
6
人工データ(二値、 100次元)X軸:MinHashの出力した距離Y軸: Jaccard index
ハッシュ函数の数左上: 64 右上: 256 左下: 1024
Jubatusの出力の上位
64でもそこそこ精度が良い
MinHashの精度:類似度高が一定数ある場合
7
実データ 1(二値化)X軸:MinHashの出力した距離Y軸: Jaccard index
ハッシュ函数の数左上: 64 右上: 256 左下: 1024
類似度 0のレコードの推定値がばらつく
やはり上位の推定精度は良い
MinHashの精度:類似度低が多い場合
8
実データ 2(二値化)X軸:MinHashの出力した距離Y軸: Jaccard index
ハッシュ函数の数左上: 64 右上: 256 左下: 1024
かなり厳しい精度 なんとか使えそう
これなら問題ない
MinHashの精度:実数値で投入した場合
9
実データ 2(実数値のまま)X軸:MinHashの出力した距離Y軸:拡張 Jaccard index
ハッシュ函数の数左上: 64 右上: 256 左下: 1024
二値の場合よりも精度が上がっている
拡張 Jaccard indexとコサイン距離の関係
10
実データ 2X軸:拡張 Jaccard index Y軸:コサイン距離
積率相関係数: 0.91順位相関係数: 0.89
良い近似となっている
LSHの実装の特徴 random projectionによる LSH
特徴ベクトルがランダムな超平面に対してどちら側に属するか、によってビットベクトルを作る- Jubatusでは設定ファイルの hash_numにより射影回数を指定する
ビットベクトル同士のハミング距離により近傍探索特徴ベクトル同士のコサイン距離を反映した近傍探索結果となることが期待される
11
LSHの精度:負の値もとる実数値の場合
12
実データ 3X軸: LSHの出力した距離Y軸:コサイン距離
ハッシュ函数の数: 256
かなり厳しい精度
近傍探索機能の性能近傍探索速度
MinHash- 100万件、ハッシュ函数の数が 256の場合、 400ミリ秒あまり-特徴ベクトルの特性に関わらず一定-ハッシュ函数の数が 64の場合 2倍程度高速
LSH:MinHashの 1割程度高速データ投入速度
MinHash- 1万件、ハッシュ函数の数が 256の場合、約 20分-ただし特徴ベクトルの次元数に依存
- かなり次元数の多いデータを入れて計測している-ハッシュ函数の数が 64の場合 3倍程度高速
LSH:MinHashの 2倍程度高速メモリ消費量
100万件、ハッシュ函数の数が 256の場合、 200 MB程度
13
クラスタリング機能の特徴二つの手法が実装されている
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
クラスタリング機能の性能
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)
コアセットが線形に成長する問題再帰的な圧縮を行う部分
圧縮後の件数として指定する値が大きいため、再帰的な圧縮が実質的に機能しておらず、対数的な成長にならない
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);}
なぜか段数の二乗に比例
ミドルウェアとして利用する場合の問題点近傍探索機能
近傍探索速度の問題- 1回の探索は 1スレッドで直列実行される
- CPUのコアあたりの性能は近年頭打ち傾向- 100万件を超えるとオンライン用途には厳しくなってくる
-結果をキャッシュする、事前計算する等の対策は考えられるが…-素直にオンラインで使えるようになるのが望ましい
- 探索をマルチスレッド化し、複数コアを活かせるようにならないかデータ投入速度の問題
-レコード毎に RPCしなければならない- バルク投入できるようにならないか
-投入時のグローバルなロックにより実質直列実行される- 射影計算等はスレッドローカルな計算なのでロックをとらずに実行できるはず
-複数プロセス立ち上げて裏で mixさせるという対策はあるが…
全般機能毎にサーバプロセスを立ち上げて個別に管理しなければならない 17
作ってみた Jubatus
Jubatusをフレームワークとして用いるとある機能を実装
- RPCで呼び出される部分を一通り実装-単体試験まで実施
ただし、- jubaproxyは利用しない- mixの実装は行わない
18
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)}
テンプレートの具体化サーバの中身の実装
<service_name>_serv.tmpl.{hpp,cpp}が出力されている <service_name>_serv.{hpp,cpp}にコピーして中身を実装していく
起動時処理設定ファイルの読み込みモデルの初期化
各サーバで共通のメソッドの実装 get_status:必要であれば固有の情報を追加する
固有のメソッドの実装 IDLで指定したメソッドが空で用意されているので中身を書くその他必要なクラスも用意する
20
モジュール構成_serv
RPCを受け付けるとりあえずここにロジックを書いてしまってもよい
core/driver/<service_name>.{hpp,cpp} _servから呼ばれ、ロジックを呼び出す serverと coreを切り離すリファクタリングの途中?
core/<service_name>/ここにロジックを記述することが期待されている
_storageモデル(内部状態)を保持する既存の _storageにそのまま使えるものがなければ実装する既存のモジュールではここに多くのロジックが書かれている
_config設定ファイルの定義を行う
21
排他制御フレームワークでは RPCのメソッド単位で制御
IDLで指定:リードロック、ライトロック、ロックなし問題点
ロック区間が長い-スレッドローカルな計算をやっている間もずっとロックされる
応答時間に関する懸念-ライトロックが優先なので、データ投入中はリードロックなメソッドの応答が遅延する
現実的な実装としては…リードロックまたはロックなしを指定し、内部で細粒度の排他制御を行う- mutexはモデルとは別に保持し、シリアライズの対象外とする
ただし save/loadや mix時の排他制御についても考える必要がある
22
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
wafによるビルドwscriptを書く
ビルド方法を指定する為の Pythonスクリプトメソッドとして各種の指定を記述
- configure:コンパイラオプション等を指定- build:ソースやターゲットを指定
ビルド方法 $ ./waf configure $ ./waf build buildディレクトリにバイナリが出来上がる
24
bld.program( source = __sources, target = 'juba' + name, includes = '.', lib = __libraries )
単体試験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_);}
フレームワークとして利用する場合の問題点
モジュール間の関係が分かりにくい各クラスがどのような機能を担っており、互いにどのような関係にあるのかの情報がほしい
例えば…-ロジックが _storageと各機能のモジュールに分散しているが、どのような基準で切り分けているのか
- mixを行う為にはどのクラスを実装する必要があり、どのメソッドがどの順番で呼ばれるのか、どのようにデータをセットアップする必要があり、排他制御はどういった考え方で行えばよいのか
頻繁にインタフェースの変更を伴うリファクタリングが行われるいったん機能を実装してもすぐに動かなくなるおそれ
26