Amazon PersonalizeをPython SDKで入門

こんにちは。
本記事は Pythonその2 Advent Calendar 2019 3日目の記事です。
先日、[【福岡開催】AWS Solution Seminar](https://aws-seminar.smktg.jp/public/seminar/view/177)というものに参加しました。
とてもためになったのですが、その中で、このお題のworkshopに参加して、「手順多くて覚えらんねえ。。。」そんな感想になりました。
社内のパイセンから、「それPython sdkでいけるよ！」と言われたので、
スクリプト流してドーンと終わったら楽だなと思って、ここに書き記しておきます。

# Python sdk(boto3)の導入
それでは早速行きましょう。
流れとしては以下です。
1. IAMユーザーの作成
2. awscliをインストール（しなくてもいいけど簡単なので）
3. boto3のインストール

の３つくらいです。

## IAMユーザーの作成
ぶっちゃけルートユーザーでもアクセスキーとかシークレットキーでプログラムを作成することが可能です。
しかし、ルートユーザーにはいろんなアクセス権が認められているため、IAMユーザーでアクセス権を制御しておきましょう。
IAMは
`AWSマネジメントコンソール > IAM > ユーザー > ユーザーを追加`
からまあ、名前とかは適当に
<img width="894" alt="スクリーンショット 2019-11-23 22.31.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/44cb1062-d11f-40ca-20ec-6879cd27f759.png">


プログラムのみで使うのであれば、AWSマネジメントコンソールにアクセスできる必要はないです。
今回はコンソールで試し使いするため、チェックを入れておきましょう。

次にアクセス許可の設定ですが、アクセスグループを設定して追加してもらっても構わないですし、実験程度に使うなら「既存のポリシーを直接アタッチ＞AdmiinistratorAccess」で問題ないです。
<img width="882" alt="スクリーンショット 2019-11-23 18.26.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/53ad621d-4b7e-7217-a5f8-3df5798b5502.png">

これは各プロジェクトに従ってください。
あとは次々進んでIAMユーザーを作成してください。

作成成功画面でシークレットアクセスキーとパスワードは手元にメモっておきましょう。
これがboto3を操作するのに必要です。

## awscliのインストール
Macだったら`brew install awscli`で
Windowsだったら`scoop install aws`で(たぶんchocolateyでも)おそらく入ると思います。
ちなみに自分はvagrantを使っているため、`pip install awscli`で導入しました。

インストールできたら設定を追加しましょう。
ちなみにですが、自分はリッチなコマンド補完をインタラクティブに行えるaws-shellというものを使っています。

さて、アクセスキー等の設定をしていきましょう。

```
$ aws configure 
AWS Access Key ID [None]: ************************
AWS Secret Access Key [None]: ************************
Default region name [None]: us-east-1
Default output format [None]: json
```
まあ、適当に上みたいなこと聞かれるんで自分のものを設定しましょう。

## boto3のインストール
boto3はpython sdkのため、`pip install boto3`で導入することが可能です。
これだけです。
困ったことがあったら[boto3 Document](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html)を参照しましょう。

### boto3を使う
boto3の[quickstart](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html)をみましょう。
実は、その中の`Using boto3`までに書いてあることが、これまでにやってきたことです！
Using boto3に書いてあることをやっていくこととします。

```py
$ python

import boto3
session = boto3.session.Session(profile_name='python-advent-calendar-demo')
s3 = session.resource('s3')
for b in s3.buckets.all():
  print(b.name)
```
pythonコマンドで、インタラクティブに実行できる環境を準備し、boto3をインポートします。
で、ドキュメント通りにやっても、動かない場合が、profile名を指定した時です。
デフォルトではdefaltのprofileが指定されるため、一旦profile_nameを指定して、セッションを作っておきます。
今は、s3バケットを何も追加していないため、何も表示されません。
何か追加してみましょう。
せっかくなんでね。[ここ](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html?highlight=s3#S3.Client.create_bucket)をみながら、何か追加しましょう。

```py
s3.create_bucket(ACL='private', Bucket='python-advent-calendar-demo')
for b in s3.buckets.all():
  print(b.name)

python-advent-calendar-demo
```
この一行でパッとできちゃいます。ブラウザでも確認しておきましょう。

<img width="1077" alt="スクリーンショット 2019-11-23 23.21.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/ad5655c0-5d58-d0b0-28f4-7e8cda6b15ad.png">

ちゃんとできていますね！
適当にファイルもアップロードしておきましょう。

```py
s3.meta.client.upload_file('test.txt', 'python-advent-calendar-demo', 'text.txt')
```
<img width="1259" alt="スクリーンショット 2019-11-23 23.46.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/6ae042f2-d296-4187-d7e7-2c4bd8d652cc.png">

ちゃんとできていますね！！！


# Amazon Personalizeの利用

## Amazon Personalize
このサービスは、アプリケーションを使用している顧客に対して開発者が個別のレコメンデーションを簡単に作成できるようにする機械学習サービスです。
と、コピペ通りです。そんなサービスです。

## 何はともあれAWSマネジメントコンソール
Amazon Personalizeの実行権限を持ったIAMユーザーを作ったら[Amazon Personalize](https://aws.amazon.com/jp/personalize/)の画面へいきましょう。
さっそくAmazon Personalizeを使ってみます！

基本的には[Amazon Personalize コンソールを使用して、映画のレコメンデーションを特定のユーザーに返すキャンペーンを作成](https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/getting-started-console.html#getting-started-console-import-dataset)に従って、進めていきます。
学習させるまでの手順は多いですが、案内に従ってやれば大したことないです。

1. Dateset group detail
---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/6e50c2b9-77a3-dec5-9552-4e03f0e627ea.png)

2. Detaset details
---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/20d1456d-8cb3-208a-d235-256d053ffe7e.png)

3. Schema details
---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/56d89b15-2cd0-0b36-0d91-3f7fa9b66670.png)

```schema_definition.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"
}
```

4. Dataset import job details
---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/2ba19a9b-924f-c837-7a42-fe43115ee00c.png)

上の１〜4までの手順を実行していけば、データのロードが完了します。
データについてはhttp://files.grouplens.org/datasets/movielens/ml-latest-small.zip のリンクからダウンロードできます。
この中の、rating.csvを使ってください。
データの前処理については[Amazon Personalize – Real-Time Personalization and Recommendation for Everyone](https://aws.amazon.com/jp/blogs/aws/amazon-personalize-real-time-personalization-and-recommendation-for-everyone/)を参照ください。

※もしかしたら、データロードの段階で、permission errorで怒られるかもしれません。
その場合は、[ここ](https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/data-prep-upload-s3.html)に従って、バケットポリシーを設定してください。
自分の場合は以下のようになりました。

```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:::tsumakoto-personalize-dev-1128",
                "arn:aws:s3:::tsumakoto-personalize-dev-1128/ratings.csv"
            ]
        }
    ]
}
```

ここまでくれば、あとは学習させるだけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/02829968-903f-e7ee-851d-0822df98b0f2.png)
上のようにActiveになったのが確認できたら、create solutionから以下のように設定して、学習を実行させましょう！！

5.Solution configuraion
---
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/f848acaf-2313-82c5-91b1-9ec39e396cae.png)

設定したら、Next > finishで学習が開始されます。
だいたい４０分くらいかかるので、待つしかないですね笑

この間にcliの方にいって、実行してみるといいかも！

6. Create Campaign
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/1cbc5cd8-bc86-894d-dd5c-15718c8b84b8.png)

学習が終了すると、モデルのデプロイができるようになります。
Create new CampaignからCampaignを作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/b9213ad0-7b77-cfda-5f48-998d8486a776.png)

さっき作ったソリューションを使って、適当にこんな感じで、作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/397374/3f7ec9dc-31b8-7e24-1795-41594a376ca3.png)

あとは、適当にUserIDを指定することでこのユーザーにあったRecomendを抽出してくれます。
機械学習なんでわかってなくともここまでのことができます。
素晴らしいですね。

## boto3での利用
これまで操作してきたことをそのままコードにするだけです。

### 学習の部分

```main.py
import boto3
import os
from os.path import dirname, join


def main():
    session = boto3.session.Session(
            profile_name=os.environ.get('PROFILE_NAME'))
    print('created session')

    BucketCreater(session).exec(_load_bucket_policy())
    print('created bucket: {}'.format(os.environ.get('BUCKET_NAME')))

    ExportDataSource(session).upload(
            join(dirname(__file__), 'ratings.csv'),
            'ratings.csv')
    print('uploaded csv')

    datalocation = 's3://{}/ratings.csv'.format(os.environ.get('BUCKET_NAME'))
    job_arn = DatasetLoader(session).import_job(
        _load_schema_definition(),
        {'dataLocation': datalocation}
    )
    print('dataset import job')

    solution_version_arn = SolutionCreater(session, job_arn).exec()
    print('solution created')

    campaign_arn = CampaignCreater(session, solution_version_arn).exec()

    print('success!')
    print('campaign arn is {}'.format(campaign_arn))


```

上で書いている手順を、順に実行していけばモデルのデプロイまでできます。
ここのメインとなるであろうデータセットのロードの部分は

```py
# DatasetLoader.import_jobの一部

dataset_group = self.personalize.create_dataset_group(
        name=self.dataset_name)
dg_arn = dataset_group['datasetGroupArn']
description = self.personalize.describe_dataset_group(
            datasetGroupArn=dg_arn)['datasetGroup']

# 60sec待つ
print("creating dataset group")
for i in range(60):
    if description['status'] == 'ACTIVE':
        break
    else:
        time.sleep(1)
        description = self.personalize.describe_dataset_group(
                    datasetGroupArn=dg_arn)['datasetGroup']
if description['status'] != 'ACTIVE':
    print('dataset group not create')
    sys.exit(-1)

schema = self.personalize.create_schema(
        name=self.schema_name,
        schema=schema_definition)

dataset = self.personalize.create_dataset(
        name=self.dataset_name,
        schemaArn=schema['schemaArn'],
        datasetGroupArn=dataset_group['datasetGroupArn'],
        datasetType='Interactions')

dataset_import_job = self.personalize.create_dataset_import_job(
        jobName=self.job_name,
        datasetArn=dataset['datasetArn'],
        dataSource=data_source,
        roleArn=self.role_arn)

dij_arn = dataset_import_job['datasetImportJobArn']
description = self.personalize.describe_dataset_import_job(
            datasetImportJobArn=dij_arn
        )['datasetImportJob']
# 100mまで待つ
print("importing job")
for i in range(60):
    if description['status'] == 'ACTIVE':
        break
    else:
        time.sleep(100)
        description = self.personalize.describe_dataset_import_job(
                datasetImportJobArn=dij_arn
            )['datasetImportJob']
if description['status'] != 'ACTIVE':
    print('job do not success')
    sys.exit(-1)
```
ACTIVEになるまで待たなければならないのですが、実直にfor文で60secまでや100minutesまで待つようにする形で書いてます。
うーん、何かいいやり方ないですかね。。
とりあえずこれで。

### 推論の部分
推論の部分では、上でやったような結果がちゃんと表示されるようにします。

```py
session = boto3.session.Session(
        profile_name=os.environ.get('PROFILE_NAME'))
item_list = RecommendGetter(session, argv[1]).get(argv[2])
print("🔻Recommended item ID")
for item in item_list:
    print(item['itemId'])
    print('-------------')
```

推論の時は、クライアントとして`session.client('personalize-runtime')`を使います。
これに対して、`get_recommendations`を使えばレコメンドが取れることになります。

```
🔻Recommended item ID
1682
-------------
552 
-------------
104 
-------------
1265
-------------
527
-------------
1676
-------------
780
-------------
367
-------------
2395
-------------
```
上のような結果が出ればOKです！

# 終わりに
今回、IAMユーザーの作成とROLEは手でやりましたが、これもBoto3を使ってできると思います。
上で省略した部分は私のリポジトリにあるので、よければみてください。
https://github.com/TsuMakoto/aws_personalize_demo
おわり。