Amazon Personalizeでレコメンドしてみる話

#Amazon Personalizeとは
先日東京リージョンでGAになったばかりのサービスです。
amazon開くと、*あなたにオススメ*とかいいつつ、欲しかったあの商品やら気になるあの商品やらがずらりと表示されたりするけど、要はああいうシステムをamazonが使わせてくれると言う話。
普通、レコメンドといえば、TensorFlowやらもしくはSageMakerやら使ってゴリゴリやらなくちゃいけないけど、そういうのが完全マネージドで提供され、よくわかんなくてもポチポチしてればできちゃいます。

---
#使ってみた背景
弊社、とある通販サイトを運営してます。そしてやはりレコメンド商品表示枠というのはあって、ここには外部のASPサービスを使っています。ただ、コレ、顧客の購買情報とかみてないんですわ。
ここ、機械学習の出番じゃね？って常々思っていたところ、ちょうどPersonalizeが使えるようになったので、導入に向けて試しておくのです。

ちなみに筆者、機械学習は毛が生えたド素人に毛が生えた程度のド素人です。

---
#シナリオ
- ユーザーは30人いてうちの5人は購入履歴なし
- 好みに応じて購買する商品が違う
- 商品（お菓子）は10種類
- 若年層はチョコが好き
- 高齢層は和菓子が好き
- 購買履歴は10000件ある

---
## ユーザー一覧
|ID|好み|年齢|性別|
|---:|:---|---:|:---|
|1|チョコが好き|20|男|
|2|スナックが好き|40|男|
|3|甘いものが好き|15|女|
|4|和菓子が好き|60|女|
|5|チョコは食べない|30|男|
|6|チョコが好き|20|女|
|7|スナックが好き|10|男|
|8|甘いものが好き|18|女|
|9|和菓子が好き|50|女|
|10|チョコは食べない|50|男|
|11|チョコが好き|30|女|
|12|スナックが好き|20|女|
|13|甘いものが好き|25|女|
|14|和菓子が好き|30|女|
|15|チョコは食べない|70|女|
|16|チョコが好き|8|男|
|17|スナックが好き|30|男|
|18|甘いものが好き|35|女|
|19|和菓子が好き|35|女|
|20|チョコは食べない|20|男|
|21|チョコが好き|20|女|
|22|スナックが好き|40|女|
|23|甘いものが好き|15|男|
|24|和菓子が好き|60|男|
|25|チョコは食べない|80|女|
|26|購買履歴なし|10|女|
|27|購買履歴なし|40|男|
|28|購買履歴なし|15|男|
|29|購買履歴なし|70|男|
|30|購買履歴なし|65|女|

---
## 商品一覧
|商品名|ID|GENRE|
|:---|---:|:---|
|板チョコ|1|chocolate,sweet|
|チョコフレーク|2|chocolate,sweet|
|チョコクッキー|3|chocolate,sweet,cookie|
|ポテチ|4|snack,salty|
|クッキー|5|cookie|
|羊羹|6|japanese sweets,sweet|
|えびせん|7|snack,salty|
|チリポテト|8|snack,hot|
|バニラアイス|9|ice,sweet|
|せんべい|10|japanese sweets,salty|

---
## 購買データ

```Python:gendatas.py
import random
for i in range(10000):
     user_id = random.randrange(1, 26)
     item_id = 1
     if user_id  % 5 == 1:
       item_id = random.choice([1,2,3])
     elif user_id % 5 == 2:
       item_id = random.choice([4,8,7])
     elif user_id % 5 == 3:
       item_id = random.choice([1,6,9])
     elif user_id % 5 == 4:
       item_id = random.choice([6,7,10])
     elif user_id % 5 == 0:
       item_id = random.choice([4,5,9])
     else:
       item_id = random.randrange(1, 10)
     timestamp = random.randrange(886324817, 1086324817)
     print(str(user_id) + ',' + str(item_id) + ',' + str(timestamp))
```

---

```bash
$ echo 'USER_ID,ITEM_ID,TIMESTAMP' > data.csv
$ python gendatas.py >> data.csv
$ head -n 5 data.csv 
USER_ID,ITEM_ID,TIMESTAMP
3,9,1035139806
11,3,1033513918
10,9,1065571205
21,1,1010453337
$ head -n 5 users.csv 
USER_ID,AGE,GENDER
1,20,男
2,40,男
3,15,女
4,60,女
$ head -n 5 items.csv                                                                              
ITEM_ID,GENRE
1,chocolate|sweet
2,chocolate|sweet
3,chocolate|sweet|cookie
4,snack,salty
```

---
# S3に保存

```bash
$ aws s3 mb s3://amazon-personalize-shopping-example
$ mkdir data item user
$ mv data.csv data/
$ mv items.csv item/
$ mv users.csv user/
$ aws s3 sync data s3://amazon-personalize-shopping-example/data
upload: data/data.csv to s3://amazon-personalize-shopping-example/data/data.csv
$ aws s3 sync user s3://amazon-personalize-shopping-example/user
upload: user/users.csv to s3://amazon-personalize-shopping-example/user/users.csv
$ aws s3 sync item s3://amazon-personalize-shopping-example/item
upload: item/items.csv to s3://amazon-personalize-shopping-example/item/items.csv
```

---
## バケットポリシー

```json
{
     "Version": "2012-10-17",
     "Id": "PersonalizeS3BucketAccessPolicy",
     "Statement": [
         {
             "Sid": "PersonalizeS3BucketAccessPolicy",
             "Effect": "Allow",
             "Principal": {
                 "Service": "personalize.amazonaws.com"
             },
             "Action": [
                 "s3:GetObject",
                 "s3:ListBucket"
             ],
             "Resource": [
                 "arn:aws:s3:::amazon-personalize-shopping-example",
                 "arn:aws:s3:::amazon-personalize-shopping-example/*"
             ]
         }
     ]
}
```

---
# 大筋
+ Personalize Consoleを開く
+ Get started
+ DataSetグループの作成
+ ソリューションの作成 (つまりトレーニング）
+ キャンペーンを作成する （エンドポイントができる）
+ レコメンデーションを取得する

---
# DataSetグループの作成
|subject|value|
|:------|:-----|
|Dataset group name|shopping-example-ds-grp|
|Dataset name|shopping-example-ds|
|Schema name|shopping-example-schema|

---
## DataSet Schema

```json
{
  "type": "record",
  "name": "Interactions",
  "namespace": "com.amazonaws.personalize.schema",
  "fields": [
      {
          "name": "USER_ID",
          "type": "string"
      },
      {
          "name": "ITEM_ID",
          "type": "string"
      },
      {
          "name": "TIMESTAMP",
          "type": "long"
      }
  ],
  "version": "1.0"
}
```

---
## Dataset import job details
Roleは新規に作る。
![スクリーンショット 2019-06-21 21.21.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/d752bedc-adaa-ffb1-13a3-643324679426.png)

---
## User Schema
Dataset name = shopping-example-user

```json:shopping-example-user-schema
{
  "type": "record",
  "name": "Users",
  "namespace": "com.amazonaws.personalize.schema",
  "fields": [
      {
          "name": "USER_ID",
          "type": "string"
      },
      {
          "name": "AGE",
          "type": "int"
      },
      {
          "name": "GENDER",
          "type": "string",
          "categorical": true
      }
  ],
  "version": "1.0"
}
```

---
## Userdata import job details
![スクリーンショット 2019-06-21 21.30.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/a4f4f124-c6ed-98b2-a79e-abd4059e206d.png)

---
## Item Schema
Dataset name = shopping-example-item

```json:shopping-example-item-schema
{
  "type": "record",
  "name": "Items",
  "namespace": "com.amazonaws.personalize.schema",
  "fields": [
      {
          "name": "ITEM_ID",
          "type": "string"
      },
      {
          "name": "GENRE",
          "type": "string",
          "categorical": true
      }
  ],
  "version": "1.0"
}
```

---
## Itemdata import job details
![スクリーンショット 2019-06-21 21.32.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/d7b39e42-4a4e-fdc3-6bea-422d76125ad4.png)

---
## 待つ
![スクリーンショット 2019-06-21 21.34.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/a23862c0-fcef-6a88-f3e3-d1b73816d4ea.png)

---
# ソリューションの作成
|subject|value|
|:------|:-----|
|Solution name|shopping-example-solution|
|Receipe selection|automatic|
automaticにするとaws-hrnnとaws-hrnn-metadataが選択されていました。
階層型リカレントニューラルネットワーク (HRNN) ってやつらしい。

https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/native-recipe-hrnn.html
https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/native-recipe-hrnn-metadata.html

---
# キャンペーンを開始する
|subject|value|
|:------|:-----|
|Campaign name|shopping-example-cp|
|Minimum provisioned TPS|1|

---
# CLIでテスト

```python:getitems.py
#ec2実行ロールにpersonalize:GetRecommendationsの権限が必要

import boto3
import sys

params = sys.argv
length = len(params)
itemlist = ['N','板チョコ','チョコフレーク','チョコクッキー','ポテチ','クッキー','羊羹','えびせん','チリポテト','バニラアイス','せんべい']

personalizert = boto3.client('personalize-runtime', 
region_name='ap-northeast-1')

response=personalizert.get_recommendations(

campaignArn="arn:aws:personalize:ap-northeast-1:xxxxxxxx:campaign/shopping-example-cp",
     userId=params[1])

print("Recommended items")

for item in response['itemList'][0:5]:
         print (itemlist[int(item['itemId'])])
```

---
## 実行テスト
### 購買履歴ありユーザ

```bash
$ python getitems.py 1 #チョコが好き
Recommended items
板チョコ
チョコフレーク
チョコクッキー
バニラアイス
羊羹
$ python getitems.py 2 #スナックが好き
Recommended items
ポテチ
チリポテト
えびせん
クッキー
バニラアイス
$ python getitems.py 4 #和菓子が好き
Recommended items
せんべい
羊羹
えびせん
ポテチ
チリポテ
$ python getitems.py 6 #チョコが好き
Recommended items
板チョコ
チョコフレーク
チョコクッキー
バニラアイス
羊羹
```

---
### 購買履歴なしユーザ

```bash
$ python getitems.py 26
Recommended items
羊羹
チョコフレーク
えびせん
クッキー
チリポテト
$ python getitems.py 27
Recommended items
羊羹
チョコフレーク
えびせん
クッキー
チリポテト
$ python getitems.py 28
Recommended items
羊羹
チョコフレーク
えびせん
クッキー
チリポテト

```

---
#結果
購買履歴ありユーザは好みに応じたレコメンドが表示されていた風ではあるものの、購買履歴なしユーザは期待した動作をしていなかった。
年齢や性別が近いものをうまく考慮してほしかったが、単純に購入されている総数順っぽい。
DataSetやSchemaの設計で解決しないか今後、検証する予定。

---
#延長戦
商品2つとそれに対する購買履歴を2x2追加してみました。item11がチョコアイス、12が柿の種という設定です

```
$ tac data/data.csv | head -n 4
27,12,986218443
27,12,1042178066
26,11,986218442
26,11,1042178065
$ tac item/items.csv | head -n 2
12,japanese sweets|salty
11,chocolate|sweet
```

---
## 実行

```bash
$ python getitems.py 26 #10歳女
Recommended items
板チョコ
バニラアイス
羊羹
チョコクッキー
ポテチ
$ python getitems.py 27 #40歳男
Recommended items
ポテチ
えびせん
バニラアイス
チリポテト
羊羹
```

---
## 延長戦の結果
- チョコアイス、柿の種は上位に上がってこないものの、好みの傾向は反映されているようにみえる
- 新商品など、購入数は少ないけどレコメンドに載せたいものを載せるのが難しい

総じて言うと・・
**なんだか微妙**

---
##参考にさせていただいたサイト
- https://docs.aws.amazon.com/personalize/latest/dg/what-is-personalize.html
- https://qiita.com/G-awa/items/7d18441737eb49906725

---
## ちなみに
[6月23日【ブロックチェーンエンジニアが教える】機械学習・ML Ops・kaggle勉強会](https://webbench.connpass.com/event/130089/) にてLTをさせていただこうと思い、記事にしました。

---
## 最後に全件をずらっと

```bash
$ for i in 1 2 3 4 5 26 27 28; do echo "${i}:"; python getitems.py $i; done
1:
Recommended items
板チョコ
チョコフレーク
チョコクッキー
バニラアイス
羊羹
ポテチ
クッキー
チョコアイス
柿の種
チリポテト
えびせん
せんべい
2:
Recommended items
ポテチ
チリポテト
えびせん
クッキー
バニラアイス
チョコフレーク
柿の種
チョコクッキー
チョコアイス
羊羹
せんべい
板チョコ
3:
Recommended items
バニラアイス
羊羹
板チョコ
クッキー
ポテチ
チョコクッキー
チョコフレーク
えびせん
せんべい
柿の種
チョコアイス
チリポテト
4:
Recommended items
せんべい
羊羹
えびせん
ポテチ
チリポテト
バニラアイス
板チョコ
クッキー
チョコアイス
柿の種
チョコクッキー
チョコフレーク
5:
Recommended items
バニラアイス
クッキー
ポテチ
羊羹
板チョコ
えびせん
チリポテト
せんべい
チョコアイス
柿の種
チョコクッキー
チョコフレーク
26:
Recommended items
板チョコ
バニラアイス
羊羹
チョコクッキー
ポテチ
チョコフレーク
えびせん
チリポテト
クッキー
せんべい
チョコアイス
柿の種
27:
Recommended items
ポテチ
えびせん
バニラアイス
チリポテト
羊羹
板チョコ
クッキー
チョコクッキー
チョコフレーク
せんべい
柿の種
チョコアイス
28:
Recommended items
ポテチ
バニラアイス
えびせん
羊羹
板チョコ
チリポテト
クッキー
チョコクッキー
チョコフレーク
せんべい
チョコアイス
柿の種
```

---
## スナック好きユーザ

```bash
$ python getitems.py 2 #40|男
Recommended items
ポテチ
チリポテト
えびせん
クッキー
バニラアイス
チョコフレーク
柿の種
チョコクッキー
チョコアイス
羊羹
せんべい
板チョコ
$ python getitems.py 7 #10|男
Recommended items
チリポテト
ポテチ
えびせん
せんべい
羊羹
クッキー
チョコクッキー
チョコフレーク
バニラアイス
柿の種
チョコアイス
板チョコ
$ python getitems.py 12 #20|女
Recommended items
チリポテト
ポテチ
えびせん
クッキー
せんべい
バニラアイス
羊羹
チョコクッキー
チョコフレーク
チョコアイス
柿の種
板チョコ
$ python getitems.py 17 #30|男
Recommended items
ポテチ
チリポテト
えびせん
せんべい
クッキー
羊羹
チョコクッキー
チョコフレーク
バニラアイス
チョコアイス
柿の種
板チョコ
$ python getitems.py 22 #40|女
Recommended items
チリポテト
ポテチ
えびせん
せんべい
羊羹
チョコクッキー
チョコフレーク
クッキー
チョコアイス
バニラアイス
柿の種
板チョコ
```