RE:PredictionIOから始めるレコメンド生活 ― 3. Engine Templateカスタマイズ(挫折)編 ―

[前回](http://qiita.com/terukizm/items/d490ce9f2f92da156cff)で準備は整ったので、Recommendation Templateを元にカスタマイズしていこうと思ったのですが、いまいち仕組みがよくわからず、難航しているうちに挫折しました… [^20]

[^20]: Engine Templateのお作法に従わなければいけない部分がだんだんしんどくなってきました…

以下に[Recommendation Template](https://github.com/apache/incubator-predictionio-template-recommender)をいじってわかったことをざっくりまとめておきますので、何かのお役にたてば幸いです。
（嘘書いてる可能性も高いので、参考程度にお願いいたします…）

## 0. DASE

以下を最初に軽く目を通しておくと理解しやすいと思います。

* [Learning DASE](http://predictionio.incubator.apache.org/customize/)
* [DASE Components Explained \(Recommendation\)](http://predictionio.incubator.apache.org/templates/recommendation/dase/)[^1]

[^1]: quickstartの一番下のリンクから飛べるのですが、最初気が付きませんでした。

Engine Templateの実装としては[Skelton Template](http://predictionio.incubator.apache.org/templates/vanilla/quickstart/)が一番シンプルなのですが、Model学習部分などが省略されてしまっているのでわかりにくいかもしれません。


## 1. 学習用データ登録

* REST API経由
* SDK経由($ python data/import_eventserver.py ...)

どちらもデータ構造は同じで、これらはPredictonIOによって決められています。以下を参照してください。

> [Events Modeling](http://predictionio.incubator.apache.org/datacollection/eventmodel/)

Recommendation Templateの以下のデータを例にすると、「評価(rate)イベントが発生し、ユーザ(user=u0)が、映画(item=i0)に対して、評価点5(properties.rating=5)をつけた」という意味合いになります。[^10]

[^10]: Recommendation Templateには"buy"イベントはrating=4として扱うという、わかるようなわからないようなロジックも入っています。

```
{
  "event" : "rate",
  "entityType" : "user",
  "entityId" : "u0",
  "targetEntityType" : "item",
  "targetEntityId" : "i0",
  "properties" : {
    "rating" : 5
  }
  "eventTime" : "2014-11-02T09:39:45.618-08:00"
}
```

PredictionIOに登録されたデータをEngine Templateから読み取るのは、以下のようなコードにより、DataSourceクラスで行います。[^1]
ここでは登録されたデータを、Sparkのデータ型である[RDD](http://dev.classmethod.jp/etc/apache-spark_rdd_investigation/)に変換しています。

[^1]: Recommendation Templateではeval()とtrain()でこの読み取りロジックを共通化して流用しています。

```src/main/scala/DataSource.scala
    val eventsRDD: RDD[Event] = PEventStore.find(
      appName = dsp.appName,
      entityType = Some("user"),
      eventNames = Some(List("rate", "buy")), // read "rate" and "buy" event
      // targetEntityType is optional field of an event.
      targetEntityType = Some(Some("item")))(sc)
```

## 2. 学習

基本的には以下の流れです。Recommendation Templateを例に説明します。

* [engine.json](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/engine.json#L4)で指定されている、[Engine.RecommendationEngine](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/Engine.scala#L24)が呼び出される
* 登録されている学習用データを読み取り、TrainingDataに格納する ([DataSource.readTraining()](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/DataSource.scala#L28))
* TrainingDataに対して前処理[^2]を行い、PreparedDataに格納する([Preparator.prepare()](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/Preparator.scala#L12))
* PreparedDataを入力として、機械学習モデルのトレーニングを行う([ALSAlgorithm.train()](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/ALSAlgorithm.scala#L62))
    * org.apache.spark.mllib.recommendation.MatrixFactorizationModelを拡張したALSModelを使い、org.apache.spark.mllib.recommendation.ALS.train()で学習し、結果をMatrixFactorizationModel.save()で永続化している

engine.jsonに記述してある各学習用パラメータが、[ALSAlgorithm.ALSAlgorithmParams](https://github.com/apache/incubator-predictionio-template-recommender/blob/8eb81bdf0395076c8cf261a84906de00f54c1be0/src/main/scala/ALSAlgorithm.scala#L16)に対応します。

[^2]: Recommendation Templateでは特に何もしていませんが、たとえばデータ型を変換したり、学習用データと評価用データに分ける、などを想定しているんじゃないかと思います。

## 3. レコメンデーション

* RESTAPI(POST http://localhost:8000/queries.json) で指定された問い合わせ内容が [Engine.Query](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/Engine.scala#L6) に格納される
* [ALSAlgorithm.predict()](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/ALSAlgorithm.scala#L78)が呼び出される
    * トレーニングしたALSModelと、指定されたQueryを使って、レコメンド結果を取得
* 結果を[Engine.PredictedResult](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/Engine.scala#L11)に格納して返却

## 4. Evalution

` $ pio eval org.template.recommendation.RecommendationEvaluation  org.template.recommendation.EngineParamsList` として実行した場合です。

* [RecommendationEvaluation](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/Evaluation.scala#L45)中でEngine(RecommendationEngine)と各種評価関数(MetricEvaluator)を指定
    * traitで[EngineParamsList](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/Evaluation.scala#L82)を使い挙動を上書き
* 登録されている学習用データを読み取り、TrainigDataに格納([DataSource.readEval()](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/DataSource.scala#L66))
    * このあたりは学習時のreadTraining()の処理と対応していると思われる[^4]
* training -> predictの流れを学習用パラメータを変更しながら、繰り返し実行
    * [ALSAlgorithm.batchPredict()](https://github.com/apache/incubator-predictionio-template-recommender/blob/develop/src/main/scala/ALSAlgorithm.scala#L96)が利用される
* 結果をMetricEvaluatorの評価関数を利用して判定、最も良かった学習用パラメータの組み合わせが返却される[^5]

[^4]: 学習用データと評価用データを分けたりする作業をreadEval()内でやっているのは、Preparatorでやるとtrainとevalの両方で適用されてしまうから（train時はデータを評価用にわけずに全部使いたいから？）という理由ではないかと。
[^5]: たぶんここはPredictionIO内でやってくれているんじゃないでしょうか。（自信ないです）



# さいごに

PredictionIO、現時点では荒削り感がすごいのですが、触っていて面白く、アプローチとしては非常にわかりやすいと思うので、これをきっかけに~~人柱~~触ってみてくれる人が増えればいいなあと思いました。

何かしらのお役にたてば幸いです。

---