Mahout in ActionのChapter3についての自分の理解をメモ。
嗜好データの表現
Preference オブジェクト
- Preference
オブジェクトは最も基本的な概念で、一つのユーザID、アイテムID、嗜好値で嗜好性を表す - 一つのオブジェクトが一人のユーザの一つのアイテムに対する嗜好性を表している
- 一つのGenericPre
ferenceは20バイトの有用なデータを保持しており、8バイトのユーザID(Javaのlong型)、8バイトのアイテムID(long)、4バイトの嗜好値(float)から成っている - オブジェクトが存在するためには上記以外に28バイトものオーバーヘッドがかかる
Preference Arrayと実装
- Preference
ArrayはPreference のコレクションを配列のように扱うためのインタフェース - 例としてGenericUse
rPreferenc eArrayは一つのユーザIDにアイテムIDの配列と嗜好値の配列を持つ - 必要なメモリはわずか12バイト(8バイトのアイテムID、4バイトの嗜好値の配列)
- Preference
オブジェクトと比べてメモリは節約できるがパフォーマンスの改善効果は少ない - これはPreference
Arrayでは要素が分散されて配置され、ガベージコレクタによって評価されるため
FastByIDMa pとFastIDSet
- Mahoutはmapやsetのようなデータ構造を使用するが、JavaのコレクションであるTreeSetやHashMapではなく、FastMap、FastByIDMa
p、やFastDISetを使用する - これらはMapやSetのようなものであるが、MahoutのRecommende
rが必要なものだけのために特化されている - パフォーマンスの劇的な改善というよりむしろメモリの使用量を削減する
Javaのコレクションとの違いは下記のような点
・FastByIDMapはHashMapと同じようにハッシュベースで、ハッシュの衝突の解決には分離連鎖法ではなく線形探査法を使う
・MahoutのRecommenderではキーとメンバーは常にlong型のプリミティブ型でオブジェクトではない。
・longのキーを使うことでメモリの使用量を抑えパフォーマンスを改善する。
・FastByIDMapは最大サイズという考え方があるためキャッシュの様に使用することができ、このサイズを越えた場合は、新しい要素を追加する際に使われていない要素が削除される FastIDSetは一つの要素ごとに平均14バイト使用する。HashSetは84バイト。
- FasByIDMap
はエントリごとに28バイト使用する
インメモリデータモデル
GenericDat aModel
- 最もシンプルなインメモリのDataModel実装はGenericDat
aModel - GenericDat
aModelは嗜好度の入力としてユーザIDとPreference ArrayのFastByIDMa pを受け取る
1 FastByIDMap<Preference Array> preference s = 2 new FastByIDMa p<Preference Array>(); 3 Preference Array prefsForUs er1 = 4 new GenericUse rPreferenc eArray(10); 5 6 prefsForUs er1.setUse rID(0, 1L); 7 prefsForUs er1.setIte mID(0, 101L); 8 prefsForUs er1.setVal ue(0, 3.0f); 9 prefsForUs er1.setIte mID(1, 102L); 10 prefsForUs er1.setVal ue(1, 4.5f); 11 ... (8 more) 12 13 preference s.put(1L, prefsForUs er1); 14 15 DataModel model = new GenericDat aModel(preference s);
- メモリの使用量は格納される嗜好データの数によるが、いくつかのテストの結果から、嗜好データ1つごとに28バイトのJava heap spaceが使われる。これにはすべてのデータとインデックスなどのデータ構造を含む。
ファイルベースのデータ
- FileDataMo
delはファイルからデータを読み込み、GenericDat aModelとしてメモリに格納する - CSVの他にTSVも使用可能。ファイル名の拡張子が.zipや.gzになっていればzipやgzipの圧縮ファイルも使用可能
コンポーネントのリフレッシュ
- データをリフレッシュするためには refresh(Collection
) というメソッドを使用する。 - このメソッドは最新のデータを元にコンポーネントの再読み込み、再計算、状態のリフレッシュを行う
- FileDataMo
delはその時点のファイルを読み込むだけで、パフォーマンスの問題で自動的にデータをリロードするようなことはしないため、refresh()メソッドを使用する
ファイルの更新
- FileDataMo
delはファイルの更新をサポートしている。メインのファイルを読み込んだ後でさらにファイルを読み込むことで先に読み込まれたデータを上書きする - 削除は空の嗜好値データを渡すことによって行われる
1 1,108,3.0 2 1,103,
- 上記のデータではユーザ1のアイテム108についての嗜好度データが作成または更新されて嗜好度3.0がセットされ、ユーザ1のアイテム103についての嗜好度データが削除される
- このためにはメインのデータファイルと同じディレクトリに格納し、最初のピリオドまでのファイル名を同じにしておく必要がある
DBベースのデータ
- MahoutではRDBから嗜好度データを読み込むことができる
- DBからデータを読み込んでRecommende
rを動かすのはかなり遅い - データの抽出、並び替え、シリアライズ、転送、結果セットのデシリアライズのオーバーヘッドは依然として最適化されたインメモリ構造からデータを読み込むよりかなり大きい
JDBCとMySQL
- 嗜好度データにはJDBCDataMo
delの実装を通してJDBCでアクセスする - JDBCDataMo
delのプライマリサブクラスはMySQL5.x用のMySQLJDBCD ataModel - Mahoutの開発バージョンにはPostgreSQL
用のJDBCDataMo del実装がある - デフォルトではすべての嗜好度データはtaste_pref
erencesというテーブルに、ユーザIDが格納されるuser_idカラム、アイテムIDが格納されるitem_idカラム、嗜好度データが格納されるpreference カラムとともに格納されているとみなされる - このテーブルにはJavaのlong型と互換性のあるtimestampカラムを含むことができる
JNDIによる設定
- JDBCDataMo
delはJNDIにjdbc/taste という名前で登録されているDataSource でアクセスできるものとみなされる
プログラムでの設定
- JNDIを直接使わなくても、MySQLJDBCD
ataModelのコンストラクタにDataSource を直接渡すことができる
1 MysqlDataSource dataSource = new MysqlDataS ource(); 2 dataSource .setServer Name("my_databas e_host"); 3 dataSource .setUser("my_user"); 4 dataSource .setPasswo rd("my_passwor d"); 5 dataSource .setDataba seName("my_databas e_name"); 6 JDBCDataMo del dataModel = new MySQLJDBCD ataModel( 7 dataSource , "my_prefs_t able", "my_user_co lumn", 8 "my_item_co lumn", "my_pref_va lue_column ");
- 上記がデータベース内のデータをレコメンドに使うためのすべて
- MySQLJDBCD
ataModelのドキュメントが明確にしているように、効率的にレコメンドを提供するには下記のようなデータベースやドライバに対するコンフィギュレーションが求められる
・ユーザIDとアイテムIDカラムはnullを許容せず、インデックスされていること
・プライマリキーはユーザIDとアイテムIDの複合値であること
・カラムのデータ型はJavaのlongとfloatに対応していること
・バッファやクエリキャッシュのチューニングのためにはMySQLJDBCDataModelのJavadocを参照
・MySQLのConnector/Jドライバを使う場合は、cache-Prep aredStatem entsパラメータをtrueに設定する
嗜好度データ無しの対処
- ユーザとアイテムの関連はあるがつながりの強さを表す値がない嗜好度データを扱うことがある
- Mahout-spe
akではこのようなデータはBoolean preference sと呼ばれ、”存在する” または ”存在しない” のいずれかの値を持つ - これは”yes”、”no”を表すのではなく、全ての有効なユーザとアイテムの関連において、”好き”、”好きではない”、”なし”の3つの状態を設定する
データを無視する場合
- 好む、好まないというのが相対的に似たような状態の場合、少なくとも関連が全くないものと比較するケースでは、嗜好度データを無視することは有益である
嗜好度なしのインメモリ表現
- 嗜好度を持たないことは嗜好データの表現を劇的に単純化し、パフォーマンスの改善と、メモリ使用量の大幅な削減を可能にする
- 嗜好度を持たないことで一つの嗜好度データごとに4バイト抑えられるはずであるが、実際にテストをした結果では4バイトから24バイト削減された
- GenericBoo
leanPrefDa taModelはGenericDat aModelとは別のDataModel実装であるが、嗜好度を内部に保持せず、FastIDSets のように関連のみを保持する - DataModelのgetItemIDs
ForUser()などのいくつかのメソッドは速くなる - getPrefere
ncesFromUs er()などのいくつかのメソッドは遅くなる。 - getPrefere
nceValue()メソッドは全てのケースにおいて1.0を返す。 - GenericBoo
leanDataMo delの便利なメソッドであるtoDataMap()を使ってPreference Arraysを要素としてもつFastByIDMa pを、FastIDSets を要素としてもつFastByIDMa pに変換して、GenericBoo leanDataMo delの入力として渡すことが可能
互換性のある実装の選択
- EuclideanD
istanceSim ilarityなどは嗜好度なしで動かしても役に立つ結果は得られないため、嗜好度無しのデータでは動作しない - 二つのデータが同じ値である場合、これらのピアソン相関は定義されない
- LogLikelih
oodSimilar ityは実際の嗜好度データに基づかない実装 - FileDataMo
delは入力データが嗜好度を含まない場合、自動的にGenericBoo leanPrefDa taModelを使用する - MySQLBoole
anPrefData Modelは嗜好度カラムを持たないデータベーステーブルを使用する場合に適している