Scala製の機械学習サーバApache PredictionIOを使ってみよう

PredictionIOはSparkを中心としたJVMベースの機械学習アプリケーションを開発・運用するために必要なミドルウェアフレームワークなどを統合的に提供するもので、開発者はPredictionIOのフレームワークに従ってエンジンを作成することで、機械学習を使用したアプリケーションをWebサービスとしてデプロイすることができます。

まずはPredictionIOのWebサイトに掲載されているレコメンデーションのテンプレートを動かしてみます。

PredictionIOのインストール

ドキュメントを見るとソースからビルドするべしとなっており中々スパルタンな感じです。Dockerで動かすこともできるようですが、折角なのでソースからビルドしてみることにします(Javaは予めインストールしておく必要があります)。

まずはこちらから現時点で最新版の0.11.0-incubatingの配布物をダウンロードし、適当なディレクトリに展開します。ルートディレクトリがないようなのでディレクトリを掘ってから展開するのが良いです。

$ wget http://ftp.riken.jp/net/apache/incubator/predictionio/0.11.0-incubating/apache-predictionio-0.11.0-incubating.tar.gz
$ mkdir apache-predictionio-0.11.0-incubating
$ tar xvzfC apache-predictionio-0.11.0-incubating.tar.gz apache-predictionio-0.11.0-incubating

展開したディレクトリ内にあるmake-distribution.shでおもむろにビルドします。

$ cd apache-predictionio-0.11.0-incubating
$ ./make-distribution.sh

ビルドが成功するとPredictionIO-0.11.0-incubating.tar.gzというファイルができるので、これを適当な場所に展開します。

Sparkのインストール

PredictionIO-0.11.0-incubating/vendorsにSparkのディストリビューションを展開しておきます。が、PredictionIOがデフォルトでサポートしているのはSpark 1.6.3という古いのバージョンのようです。オプションでSpark 2系を使用するようビルドすることもできるのですが、デフォルトで最新のものが使われるようになって欲しいところです。

$ wget http://d3kbcqa49mib13.cloudfront.net/spark-1.6.3-bin-hadoop2.6.tgz
$ mkdir PredictionIO-0.11.0-incubating/vendors
$ tar zxvfC spark-1.6.3-bin-hadoop2.6.tgz PredictionIO-0.11.0-incubating/vendors

ストレージのインストール

PredictionIOの動作に必要なデータや、学習に使用するデータなどを格納するためのストレージを別途インストールする必要があります。PredictionIOは以下のストレージをサポートしています(ストレージに保存するデータにはメタデータ、イベントデータ、モデルデータの3種類があり、ストレージごとに保存できるデータが異なります)。

デフォルトではPostgreSQLを使用するようになっているのでこれを使ってみます。MacであればHomebrewでPostgreSQLをインストールできます。

$ brew install postgresql

ついでにbrew servicesで自動起動するようにしておきます(brew services stop postgresqlすると自動起動をやめることができます)。

$ brew services start postgresql

PredictionIOで使用するデータベースやユーザを作成します。

$ createdb pio
$ psql pio -c "create user pio with password 'pio'"

PostgreSQLのJDBCドライバをダウンロードしてPredictionIO-0.11.0-incubating/libに配置します。

$ cd PredictionIO-0.11.0-incubating/lib
$ wget https://jdbc.postgresql.org/download/postgresql-42.0.0.jar

PredictionIOを起動する

ここまでできたらPredictionIOを起動してみます。

$ cd PredictionIO-0.11.0-incubating/bin
$ ./pio eventserver &

すると、7070ポートでイベントサーバが起動します。この後の操作ではPredictionIO-0.11.0-incubating/binにあるpioコマンドを多用することになるのでこのディレクトリにPATHを通しておくとよいです。

学習データを登録する

PredictionIOのイベントサーバにアクセスするためのアクセスキーを発行します。

$ pio app new MyApp1

こんな感じでアクセスキーが発行されます。

...
[INFO] [App$] Initialized Event Store for this app ID: 1.
[INFO] [Pio$] Created a new app:
[INFO] [Pio$]       Name: MyApp1
[INFO] [Pio$]         ID: 1
[INFO] [Pio$] Access Key: i-zc4EleEM577EJhx3CzQhZZ0NnjBKKdSbp3MiR5JDb2zdTKKzH9nF6KLqjlMnvl

このアクセスキーを毎回指定するのは面倒なので以下のように環境変数に設定しておきます。

$ ACCESS_KEY=i-zc4EleEM577EJhx3CzQhZZ0NnjBKKdSbp3MiR5JDb2zdTKKzH9nF6KLqjlMnvl

学習データとして使用するデータをイベントサーバに登録してみます。

$ curl -i -X POST http://localhost:7070/events.json?accessKey=$ACCESS_KEY \
-H "Content-Type: application/json" \
-d '{
  "event" : "rate",
  "entityType" : "user",
  "entityId" : "u0",
  "targetEntityType" : "item",
  "targetEntityId" : "i0",
  "properties" : {
    "rating" : 5
  }
  "eventTime" : "2014-11-02T09:39:45.618-08:00"
}'
$ curl -i -X POST http://localhost:7070/events.json?accessKey=$ACCESS_KEY \
-H "Content-Type: application/json" \
-d '{
  "event" : "buy",
  "entityType" : "user",
  "entityId" : "u1",
  "targetEntityType" : "item",
  "targetEntityId" : "i2",
  "eventTime" : "2014-11-10T12:34:56.123-08:00"
}'

登録したイベントを検索してみます。

$ curl "http://localhost:7070/events.json?accessKey=$ACCESS_KEY"

以下のように、2つのイベントが登録されていることが確認できます。

[
  {
    "eventId":"774fdb3a572f48479e76e34681c7371b",
    "event":"rate",
    "entityType":"user",
    "entityId":"u0",
    "targetEntityType":"item",
    "targetEntityId":"i0",
    "properties": {
      "rating":5
    },
    "eventTime":"2014-11-02T09:39:45.618-08:00",
    "creationTime":"2017-05-05T09:35:54.604Z"
  },  
  {
    "eventId":"c1182fe0451d4744a0b695770c8098e9",
    "event":"buy",
    "entityType":"user",
    "entityId":"u1",
    "targetEntityType":"item",
    "targetEntityId":"i2",
    "properties":{},
    "eventTime":"2014-11-10T12:34:56.123-08:00",
    "creationTime":"2017-05-05T09:36:05.372Z"
  }
]

レコメンデーションエンジンを動かしてみる

本題のレコメンデーションエンジンです。PredictionIOのオフィシャルで提供されているエンジンテンプレートを使用してみます。まずはgitコマンドでテンプレートのリポジトリを適当な場所にクローンします。

$ git clone https://github.com/apache/incubator-predictionio-template-recommender.git MyRecommendation
$ cd MyRecommendation

準備として予め用意されているデータをまとめてインポートします。まずはPredictionIOのPythonSDKをインストールします。

$ pip install predictionio

以下のようにするとサンプルデータをまとめてイベントサーバにインポートできます。

$ curl https://raw.githubusercontent.com/apache/spark/master/data/mllib/sample_movielens_data.txt --create-dirs -o data/sample_movielens_data.txt
$ python data/import_eventserver.py --access_key $ACCESS_KEY

次にMyRecommendation/engine.jsonを編集してappNameに先ほどアクセスキーを発行したしたアプリケーション名を設定します。

...
"datasource": {
  "params" : {
    "appName": "MyApp1"
  }
},
...

レコメンデーションエンジンをビルドします。

$ pio build --verbose

登録したイベントデータを使用して学習を行います。

$ pio train

ここでスタックオーバーフローが発生してしまうことがあります。その場合はMyRecommender/src/main/scala/ALSAlgorithm.scalaの以下の行のコメントアウトを外してチェックポイントを有効にします。

sc.setCheckpointDir("checkpoint")

ここまででようやく準備完了です。レコメンデーションエンジンをWebサービスとしてデプロイします。

$ pio deploy

8000ポートでレコメンデーションエンジンのWebサービスが起動します。ブラウザでアクセスすると以下のような画面が表示されるはずです。

f:id:takezoe:20170511132241p:plain

実際にレコメンデーションエンジンを呼び出してみます。

$ curl -H "Content-Type: application/json" \
-d '{ "user": "1", "num": 4 }' http://localhost:8000/queries.json

すると以下のような結果が返ってくるはずです。指定したユーザに対してスコアが高い順に4つのアイテムが返却されていることがわかります。

{
  "itemScores":[
    {"item":"22","score":4.072304374729956},
    {"item":"62","score":4.058482414005789},
    {"item":"75","score":4.046063009943821},
    {"item":"68","score":3.8153661512945325}
  ]
}

まとめ

だいぶ手順が長くなってしまいましたが、PredictionIOは機械学習を使用したアプリケーションを開発・運用するために以下のような機能を提供していることがおわかりいただけたのではないかと思います。

今回は実際にPredictionIOでどのようにエンジンを開発するのかについては触れませんでしたが、興味のある方はエンジンテンプレートのソースコードを参照していただければと思います。また、PredictionIOのWebサイトには他にもいくつかのエンジンテンプレートが紹介されており、同様の手順で動かすことができますのでこちらも試してみていただければと思います。