Mahout 0.8でレコメンデーションエンジンを作る


最近2つのプロジェクトで「オススメの◯◯」とかそれに類する機能を作ったので、ちょっとまとめてみる。

基本的なことはMahout in Actionに書いてあるので、そっちを読んで欲しい。一応、本エントリーでも基本的なことは簡単には説明するけど、本に載っていないこととかを主に書こうかと。

Mahout in Actionが発売された頃は、Mahoutの最新バージョンは多分0.6とかだったと思うけど、今は0.8がリリースされていて、0.9も開発中。自分が使ってるのは0.8なので、0.6とかの古いバージョンとの差異も分かる範囲で書く予定。

基本

協調フィルターとコンテンツベース

本に書いてあるけど、一応大雑把に基本を。知ってる人は飛ばしてください。

レコメンデーションエンジンの種類としては、以下の2種類

  • 協調フィルター(collaboration filter)
  • コンテンツベース(content-based filter)

前者は、ユーザーの行動(AさんがBという商品を購入した、Aさんが映画Cに★5つを付けた、等)を元にレコメンデーションを行う。後者は、コンテンツの中身を元にレコメンデーションする(Aさんが以前買った商品Bは恋愛小説なので、同じ恋愛小説の商品Cをおすすめする)。

実際にはこれらを組み合わせる事が多い。

以下、特に断りがなければ、協調フィルターに関して書いていく。

協調フィルターの2つの種類、アイテムベースとユーザーベース

協調フィルターの場合、登場する要素として「ユーザー」と「アイテム」の2つがある。ユーザーは一般的にはシステムの利用者で、その人達に対してオススメを提示する。アイテムは、一般的には商品で、ECサイトの商品だったり、音楽サイトでの楽曲やアルバムなどが挙げられる。

で、協調フィルター方式のレコメンデーションには大きく分けて、アイテムベースとユーザーベースの2種類がある。

アイテムベースは、アイテム同士の「類似度」を事前に計算しておく。Aさんに対してのレコメンデーションは、Aさんが以前高い「評価」をしたアイテムと似たものを提示する。

ユーザーベースはその逆で、ユーザー同士の「類似度」を事前に計算し、Aさんと似ているユーザーが高く評価しているアイテムを提示する。

評価

前項で「評価」と書いたが、これはECサイトでの商品の満足度のように、数値が付いているものもあれば、Facebookの「いいね」のように、0か1かの2値のものも含まれる。

類似度の計算

有名なのだと、ピアソン相関係数やユークリッド距離とか。その他色々。それぞれ特徴があるので、詳しくは本やインターネットで調べると良いかと。

実装上の話

しつこいようだけど、Mahout in Actionをある程度読んでいることを前提に書く。

DataModel

Mahout in Actionだと、フラットファイルからDataModelを構築しているんだけど、実際にはRDBMS等からデータを取得することが多いと思われる。

その場合、以下のように ReloadFromJDBCDataModel というものを使う。

    new ReloadFromJDBCDataModel(
      new MySQLJDBCDataModel(
        dataSource, "some_table", "user_id", // preference table, user id column, 
        "item_id", "score", "created_at") // item id column, preference column, timestamp column
    )

データの更新

バッチとかで事前にデータを作成して終わりっていう場合はあまり関係ないけど、レコメンデーションの結果をREST APIとかで提供する場合、最初にDataModelを構築して終わりというわけではなく、新しいデータが入ってきたらそれに対応しなければいけない。

これもMahout in Actionに書いてあるけど、Recommenderを始め、多くのクラスがRefreshableというインターフェースを実装している。例えば、定期的にRecommender.refreshを呼べば、その下にあるDataModelやXXSimilarityクラスなどのrefreshメソッドも呼ばれるようになっている。

ちょっとソースを見た感じ、refresh が呼ばれた場合、新たにデータを読み込みなおして、データの再作成が終わった段階で古いデータと置き換えるという実装になっているので、refreshの実行中にレコメンデーション出来ないという事態にはならなそう。

Rescorer (IDRescorer)

単純な協調フィルターだと、ユーザーベースかアイテムベースの選択、評価データの前処理、類似度計算のアルゴリズムくらいしかチューニングポイントがなく、なかなか思ったような結果が出ない場合もあるかも。そんな時に便利なのがRescorer あるいはIDRescorer。その名の通りスコアを調整する仕組み。例えば、ユーザーAに対してアイテムXのレコメンデーションのスコアが50点だとすると、その他の要素によってその50点を調整する仕組み。

ちょっと具体例を出してみる。ユーザーAに対して、山下達郎の「クリスマス・イブ」と小田和正の「ラブ・ストーリーは突然に」が共に50点のスコアだったとする。でも、今は12月だからクリスマス・ソングをオススメしたいという場合、IDRescorerの中で、クリスマス・イブの点数を上げてあげれば良い。

オンラインのレコメンデーションエンジンを作る場合の注意点だけど、RescorerはReccomender.recommendメソッドの中で呼ばれるので、その中で重たい処理をするとレコメンデーションを返すまでに時間がかかってしまう。

結果の評価とか

RecommenderEvaluator

RecommenderEvaluator ってのがあって、それでバックテストが出来るんだけど、それで評価できるのは単純なケースだけのような気がした。

協調フィルターの場合、最初に(ユーザーID, アイテムID, 評価値)のデータを作ってそれを元に計算を行う。映画レビューサイトとかのように、典型的なシナリオの場合はRecommenderEvaluatorで評価すれば良いと思う。つまり、ユーザーの行動=映画に対するレーティングだけで、そのユーザーが高い評価をするであろうアイテムを薦めれば良いっていう単純なケース。

一般的にはそんな単純なケースはあまり多くなく、通常は、複数のアクション(購入、評価、クリック、滞在時間 etc.)を組み合わせて考慮したいはず。その場合に

  • 各アクション毎に複数のRecommenderを作って、それぞれの結果を組み合わせる
  • 各アクションを1つの評価軸にマッピングしてRecommenderを作る

とか、色々方法はあると思うけど、いずれにしても、そもそも結果の組み合わせ方法なりマッピングの方法が最適かどうかが分からないので、RecommenderEvaluatorでうまく評価出来ないんじゃないかなと。この辺りは自信がないので、ツッコミがあれば是非。

人気のアイテムに偏らないようにする

これは類似度の計算方法などを調整すればある程度解決すると思うんだけど、例えば映画であれば、多くの人が「スター・ウォーズ」や「もののけ姫」を見たことがあると思うんだけど、その場合、「スター・ウォーズ」は見たことがあるけど「もののけ姫」は見たこと無い人の場合に、「もののけ姫」がおすすめされる可能性が高くなりがち。

この辺は、実際の結果を見ながら調整する必要があるかなと。

結果はトラッキング出来るようにしておく

クリック率とかは必ずトラッキングしておく。複数の指標やアルゴリズムを組み合わせている場合、どのアルゴリズムが有効か、というのが分かるようにしておく必要がある。

まとめ

Mahoutを使うと簡単にレコメンデーションエンジンが作れるけど、結果をみながら細かくチューニングしていかないと、使いものにならない。その為に、結果を確認出来る仕組みを作っておくと良い。

次回は、クラスタリングについても少々。

“Mahout 0.8でレコメンデーションエンジンを作る” への1件の返信

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です