mahout をとりあえず動かしてレコメンドしてみる（動作確認程度）

## シンプルなレコメンドの考え方

特にMahoutの仕組や分散による高速化には触れない。
ごく単純にmahoutをJavaで試すだけ。

但し、前提として単純に言うと

| 人 | 好きなもの | 好きなもの | 好きなもの |
|:-----------|:-----------:|:------------:|:------------:|
| Aさん       |        林檎|     蜜柑     | 苺
| Bさん     |      梨|    葡萄    | バナナ
| Cさん      |        林檎 |     蜜柑     |
| Dさん      |        林檎 |     蜜柑     | 苺

さて、Cさんが次に欲しいものは何？

感覚的にそれは「おそらく苺」だろう、と。

ここでは４件しか無い上に好きなもの丸かぶりだから直ぐ分かるけど
Ｃさんに似た嗜好を持つ人はだれかを探して（ＡさんとＤさん）、
そこからオススメの果物（苺）をレコメンドする。

![image](https://qiita-image-store.s3.amazonaws.com/0/101086/21bab2e4-c919-77f7-4b91-50fdec19e033.png)

今回はユークリッド距離を利用し、
より距離が近い＝好みが類似している人を特定する。
（映画の評価など厳しめや甘めの人が混在するような場合は
  本当はユークリッド距離は向かない）

## 実装

### maven
```xml: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コード

```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 {
            /*
            * 数値型に置き換えて値を渡す
            * 第１引数
            *   100 Aさん
            *   200 Bさん
            *   300 Cさん
            *   400 Cさん
            *
            * 第２引数
            *   1 林檎
            *   2 蜜柑
            *   3 苺
            *   4 梨
            *   5 葡萄
            *   6 バナナ
            *
            * 第３引数
            *   ここは好みを表すが今回は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);
            
            /**
             * 類似（距離の近い）ユーザをｎ件迄を検索
             * とりあえず、３くらいで
             */
            UserNeighborhood neighborhood = new NearestNUserNeighborhood(3, similarity , model);
            
            /**
             * Cさんの好みをレコメンド
             * 結果は ３：苺 になる
             */
            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);
        }
    }    
}
```