MAHOUT In Action レコメンダー事始め Chapter2 (Mahout In Action 自習その2)

少しペースが落ちてますが、引き続き MAHOUT in actionの自習です。

今日は、chapter 2 Introducing recommendersです。

Mahout in Action

Mahout in Action

この章でカバーすること

  • レコメンダーってなんだー
  • レコメンダー事始め
  • 推薦エンジンの評価
  • 実際データで試してみる

人はいろんなものに好き嫌いをつける。これらには、好きなものに似たものを好きだというパターンがある。

太郎が、ベーコンレタストマトサンドが好きだったら、
たぶん、クラブサンドも好きだろう。

推薦ってなに(推薦方法の種類)

何か探すとき、こんな感じでみつける。

  • 自分と似ている人が好きなものを見る → ユーザーベースレコメンダー
  • 前に気に入ったものに似ているものをさがし、他の人がどんな評価しているか見る → アイテムベースレコメンダー

推薦方法は大きくこん分けると2つある。

  • その1:協調フィルタリング(collaborative filtering)
    • ユーザとアイテムの関係のみを元に推薦する方法
    • ○アイテムそのものの情報はいらない
    • ○アイテムの種類が何かは関係ない
  • その2:内容ベース推薦(content-based recommendation)
    • アイテムの内容を見て判断する方法
    • ×ドメイン特有の知識が必要(知識をどう用意して、重みをどうつければいいかわからない)
    • ×知識を他のドメインに適用できない

MAHOUTでは、協調フィルタリングフレームワークを提供する。

レコメンダー事始め

とりあえず、ユーザーベースを説明。

入力:CSVファイル

ユーザID、アイテムID、プリファレンス(ユーザがアイテムをどのくらい好きか)

1,101,5.0
1,102,4.0
2,101,2.0
...
レコメンダーを作る

ユーザ1にアイテムを一つ推薦するとしたらどれかをプログラムを作ってみましょう。

package mia.recommender.ch02;

import org.apache.mahout.cf.taste.impl.model.file.*;
import org.apache.mahout.cf.taste.impl.neighborhood.*;
import org.apache.mahout.cf.taste.impl.recommender.*;
import org.apache.mahout.cf.taste.impl.similarity.*;
import org.apache.mahout.cf.taste.model.*;
import org.apache.mahout.cf.taste.neighborhood.*;
import org.apache.mahout.cf.taste.recommender.*;
import org.apache.mahout.cf.taste.similarity.*;
import java.io.*;
import java.util.*;

class RecommenderIntro {

  private RecommenderIntro() {
  }

  public static void main(String[] args) throws Exception {

    DataModel model = new FileDataModel(new File("intro.csv"));

    UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
    UserNeighborhood neighborhood =
      new NearestNUserNeighborhood(2, similarity, model);

    Recommender recommender = new GenericUserBasedRecommender(
        model, neighborhood, similarity);

    List<RecommendedItem> recommendations =
        recommender.recommend(1, 1);

    for (RecommendedItem recommendation : recommendations) {
      System.out.println(recommendation);
    }
  }
}
  • DataModel:データを蓄積
  • UserSimilarity:2ユーザ間の類似度計算方法
  • UserNeighborhood:ユーザグループ作成方法
  • Recommender:誰にどれを推薦するか決める方法
出力:ユーザに推薦するアイテム集合

アイテムID,ユーザがアイテムをどれくらい好きかの予測値

RecommendedItem[item:104, value:4.257081]

レコメンダーの評価

正しい推薦だったかどうかを評価するには、実際にどれくらい好きだったかで評価する。

訓練データとスコアリング

データを訓練データとテストデータに分けてシミュレーションする。

  • 訓練データ→レコメンダーに入れる
  • テストデータ→推薦結果と比較する

テストデータ内のスコアと、推薦結果の予測スコアの差で、評価する。

  • MAE(Mean Average Error) = 差の平均
  • RMSE(Root Mean Square Error) = 差の二乗の平均のルート
RecommenderEvaluator
package mia.recommender.ch02;

import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.eval.RecommenderBuilder;
import org.apache.mahout.cf.taste.eval.RecommenderEvaluator;
import org.apache.mahout.cf.taste.impl.eval.AverageAbsoluteDifferenceRecommenderEvaluator;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.apache.mahout.common.RandomUtils;

import java.io.File;

class EvaluatorIntro {

  private EvaluatorIntro() {
  }

  public static void main(String[] args) throws Exception {
    RandomUtils.useTestSeed();
    DataModel model = new FileDataModel(new File("intro.csv"));

    RecommenderEvaluator evaluator =
      new AverageAbsoluteDifferenceRecommenderEvaluator();
    // Build the same recommender for testing that we did last time:
    RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
      @Override
      public Recommender buildRecommender(DataModel model) throws TasteException {
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        UserNeighborhood neighborhood =
          new NearestNUserNeighborhood(2, similarity, model);
        return new GenericUserBasedRecommender(model, neighborhood, similarity);
      }
    };
    // Use 70% of the data to train; test using the other 30%.
    double score = evaluator.evaluate(recommenderBuilder, null, model, 0.7, 1.0);
    System.out.println(score);
  }
}

このコードは、毎回同じ結果を返す(RandomUtils.useTestSeed()を使ってるので)。実際のテストでは使わないように。
RMSEで評価したいとき、AverageAbsoluteDifferenceRecommenderEvaluatorをRMSERecommenderEvaluatorに変える。
evaluate()メソッドの引数

  • RecommenderBuilder:レコメンダー作成用オブジェクト
  • DataModelBuilder:データモデル作成用オブジェクト:トレーニングデータ作成方法を指定したいときは指定
  • DataModel:データ
  • double trainingPercentage:トレーニングに用いるデータ割合
  • double evaluationPercentage:評価に用いるデータ割合(たとえば、0.1にすると、9割のデータは無視される。少しの変更をすぐにテストしたいときは、小さい値を指定できる)※まあ通常は1.0のままだよな

適合率と再現率

評価値をそのまま当てる必要はない。実際には出力したもののうちでの正解が重要なことが多い。
評価には情報検索で使われるスコアを用いる。
システム出力は、たとえばユーザごとに評価値順にTOP10を出力するなどで決まる。

  • 適合率(precision):システムが出力した中で、正解の数の割合。上位によいものが含まれている割合を示している。
  • 再現率(recall):想定正解の中で、システムが出力できた数の割合。よいものが上位に含まれている割合を示している。
package mia.recommender.ch02;

import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.eval.IRStatistics;
import org.apache.mahout.cf.taste.eval.RecommenderBuilder;
import org.apache.mahout.cf.taste.eval.RecommenderIRStatsEvaluator;
import org.apache.mahout.cf.taste.impl.eval.GenericRecommenderIRStatsEvaluator;
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
import org.apache.mahout.common.RandomUtils;

import java.io.File;

class IREvaluatorIntro {

  private IREvaluatorIntro() {
  }

  public static void main(String[] args) throws Exception {
    RandomUtils.useTestSeed();    
    DataModel model = new FileDataModel(new File("intro.csv"));

    RecommenderIRStatsEvaluator evaluator =
      new GenericRecommenderIRStatsEvaluator();
    // Build the same recommender for testing that we did last time:
    RecommenderBuilder recommenderBuilder = new RecommenderBuilder() {
      @Override
      public Recommender buildRecommender(DataModel model) throws TasteException {
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
        UserNeighborhood neighborhood =
          new NearestNUserNeighborhood(2, similarity, model);
        return new GenericUserBasedRecommender(model, neighborhood, similarity);
      }
    };
    // Evaluate precision and recall "at 2":
    IRStatistics stats = evaluator.evaluate(recommenderBuilder,
                                            null, model, null, 2,
                                            GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD,
                                            1.0);
    System.out.println(stats.getPrecision());
    System.out.println(stats.getRecall());
  }
}


このコードは、毎回同じ結果を返す(RandomUtils.useTestSeed()を使ってるので)。実際のテストでは使わないように。
Top 2 を推薦したときの、適合率と再現率を示している。

でも、何をgoodとするのか実は示されていない。
評価値3以上?2以上?ユーザによっても違う?

RecommenderIRStatsEvaluator は、「よい」を決める閾値を、ユーザごとに決めている。

  • 閾値 = ユーザの平均評価値 + 分散
適合率と再現率の問題

「ベストなものはユーザはまだ知らない」ことに注意して評価しないといけない。
Booleanデータ(評価値のないデータ(見た/見ないのように2値しかない))の場合、もっと複雑
(評価値がないから、どれがベストなものかはわからないので)。
テストは、ランダムに好きなアイテムを選んで行うことで行うしかないかな。

ユーザの好きなアイテムは、ベストなアイテムの代理にはなる。でも、完全ではない。
Booleanデータの場合、適合率再現率テストしかない。
評価には限界があるkとを理解することが重要。

Grouplensデータでテスト

これは後で

以上。