シンプルなレコメンドの考え方
特にMahoutの仕組や分散による高速化には触れない。
ごく単純にmahoutをJavaで試すだけ。
但し、前提として単純に言うと
人 | 好きなもの | 好きなもの | 好きなもの |
---|---|---|---|
Aさん | 林檎 | 蜜柑 | 苺 |
Bさん | 梨 | 葡萄 | バナナ |
Cさん | 林檎 | 蜜柑 | |
Dさん | 林檎 | 蜜柑 | 苺 |
さて、Cさんが次に欲しいものは何?
感覚的にそれは「おそらく苺」だろう、と。
ここでは4件しか無い上に好きなもの丸かぶりだから直ぐ分かるけど
Cさんに似た嗜好を持つ人はだれかを探して(AさんとDさん)、
そこからオススメの果物(苺)をレコメンドする。
今回はユークリッド距離を利用し、
より距離が近い=好みが類似している人を特定する。
(映画の評価など厳しめや甘めの人が混在するような場合は
本当はユークリッド距離は向かない)
実装
maven
pom.xml
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-core</artifactId>
<scope>provided</scope>
<version>0.9</version>
</dependency>
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-math</artifactId>
<scope>provided</scope>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-examples</artifactId>
<scope>provided</scope>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-collections</artifactId>
<scope>provided</scope>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.apache.mahout</groupId>
<artifactId>mahout-utils</artifactId>
<scope>provided</scope>
<version>0.5</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
</exclusion>
</exclusions>
</dependency>
Javaコード
sample.java
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericPreference;
import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
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.EuclideanDistanceSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.Preference;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;
public class AdHocTestSpace {
public static void main(String argv[]){
try {
/*
* 数値型に置き換えて値を渡す
* 第1引数
* 100 Aさん
* 200 Bさん
* 300 Cさん
* 400 Cさん
*
* 第2引数
* 1 林檎
* 2 蜜柑
* 3 苺
* 4 梨
* 5 葡萄
* 6 バナナ
*
* 第3引数
* ここは好みを表すが今回は5.0固定(値に特に意味は無い)
* やろうと思えば
* 1.0 あんまり・・・
* 2.0 ふつー
* 3.0 好き
* みたいに好みの重さを指定できる。
*/
List<Preference> list_1 = new ArrayList<>();
List<Preference> list_2 = new ArrayList<>();
List<Preference> list_3 = new ArrayList<>();
List<Preference> list_4 = new ArrayList<>();
// Aさんは、林檎と蜜柑と苺が好き
list_1.add(new GenericPreference(100, 1, 5.0f));
list_1.add(new GenericPreference(100, 2, 5.0f));
list_1.add(new GenericPreference(100, 3, 5.0f));
// Bさんは、梨と葡萄とバナナが好き
list_2.add(new GenericPreference(200, 4, 5.0f));
list_2.add(new GenericPreference(200, 5, 5.0f));
list_2.add(new GenericPreference(200, 6, 5.0f));
// Cさんは、林檎と蜜柑が好き
list_3.add(new GenericPreference(300, 1, 5.0f));
list_3.add(new GenericPreference(300, 2, 5.0f));
// Dさんは、林檎と蜜柑と苺が好き
list_4.add(new GenericPreference(400, 1, 5.0f));
list_4.add(new GenericPreference(400, 2, 5.0f));
list_4.add(new GenericPreference(400, 3, 5.0f));
/*
* これらの情報をモデルにセット
*/
FastByIDMap<PreferenceArray> input_info = new FastByIDMap<>();
input_info.put(100, new GenericUserPreferenceArray(list_1));
input_info.put(200, new GenericUserPreferenceArray(list_2));
input_info.put(300, new GenericUserPreferenceArray(list_3));
input_info.put(400, new GenericUserPreferenceArray(list_4));
DataModel model = new GenericDataModel(input_info);
// ユークリッド距離を利用
UserSimilarity similarity = new EuclideanDistanceSimilarity(model);
/**
* 類似(距離の近い)ユーザをn件迄を検索
* とりあえず、3くらいで
*/
UserNeighborhood neighborhood = new NearestNUserNeighborhood(3, similarity , model);
/**
* Cさんの好みをレコメンド
* 結果は 3:苺 になる
*/
Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity);
List<RecommendedItem> rcmmList = recommender.recommend(300, 5);
System.out.println("Cさんにオススメしたい果物のIDは以下のとおり");
rcmmList.stream().forEach(System.out::println);
} catch (TasteException ex) {
Logger.getLogger(AdHocTestSpace.class.getName()).log(Level.SEVERE, null, ex);
}
}
}