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

10,337 views

Published on

Published in: Technology
0 Comments
6 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
10,337
On SlideShare
0
From Embeds
0
Number of Embeds
8,455
Actions
Shares
0
Downloads
16
Comments
0
Likes
6
Embeds 0
No embeds

No notes for slide

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

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

×