フリーランチ食べたい

No Free Lunch in ML and Life. Pythonや機械学習のことを書きます。

Amazon Personalize使い方まとめ / CloudFormationとPythonでレコメンドアプリケーションを学習・デプロイする

今週、Amazon PersonalizeがGAになりました。東京リージョンでも使うことができます。

この記事ではAmazon Personalizeの概要、使い方の解説を行います。PythonのAWS SDKを使ってレコメンドアプリケーションを作成していきます。

また事前準備のS3やIAM Roleの作成で、AWSの公式ドキュメントだと手作業が発生しているのですが、それだと大変なので一発で構築できるCloudFormationも紹介します。

aws.amazon.com

最初に触った感想

少し触ってみたのですが、以下の点で非常に良いと思いました。

  • 学習・予測(レコメンド取得)が全てサーバーレスで行える
  • 事前に準備されているアルゴリズムはDeep Learningベースで多く、それ以外も高度なもの

逆に以下のような不満な点もありました。

  • 用語が機械学習で一般的に使うものとかけ離れていて混乱する
  • ARNを引数に渡す処理が多く、CloudFormationで管理したいがまだ対応していない

1つめの不満点はこの記事で図解して解説してみました。2点目はCloudFormationに対応するのを待ちたいですね。

Amazon Personalizeとは

レコメンデーションアプリケーションを簡単に構築できるマネージドサービスです。 AWSの同様の機械学習サービス、Amazon SageMakerにも似ていますが、下記のような違いがあります。

  • レコメンデーションアルゴリズムに特化している(データ形式/出力が制限されている)
  • モデルの学習も稼働もサーバーレス(サーバーを意識する必要がない)

標準的なレコメンデーションに特化したサービスです。

Amazon Personalizeの構成

Amazon Personalizeはそれぞれの用語にクセがあって最初理解し辛いので、登場人物を図にまとめました。

f:id:ikedaosushi:20190614011518p:plain

この図に記載した通り、より一般的な言葉に直すと

  • Dataset Group: レコメンドアプリケーションのプロジェクト
  • Datasets: データ
  • Solutions: モデル
  • Recipes: アルゴリズム
  • Campaigns: 予測API

になると思います。

またそれぞれの登場人部の関係性は次のようになります。

f:id:ikedaosushi:20190614011213p:plain

これだけだと理解するのが難しいと思うので、とりあえず一通り手順を試してみて、この図を改めて見てもらうのがいいかもしれません。

Amazon Personalizeでレコメンドアプリケーションを作成する手順

Amazon Personalizeでレコメンドアプリケーションを作成するには下記の手順を作業する必要があります。

  • 事前準備1: RoleやS3バケットを作成する
  • 事前準備2: 使うデータをダウンロードする
  • データセットグループを作成する
  • データセットに学習データのインポートを行う
  • ソリューションを作成する
  • キャンペーンを作成する
  • レコメンドを行う

この順番でそれぞれの手順を書いていきたいと思います。

もしこの記事でわからないことがあったり、もっと詳細が気になった方はドキュメントを参照してください。

docs.aws.amazon.com

環境

  • Python: 3.7
  • boto3: 1.9.168
  • botocore: 1.12.168

事前準備1: RoleやS3バケットを作成する

Amazon Personalizeの利用に際して事前に必要なAWSのリソースは次の2つです。

  • S3バケット
  • IAMロール

手作業がなくて済むようにこれらを作成するCloudFormation設定ファイルを書きました。

https://github.com/ikedaosushi/python-sandbox/blob/master/az-personalize/cloudformation.yml

この設定ファイルをもとにCloudFormationのStackを作成します。

STACK_NAME=amazon-personalize-example-stack
REGION=ap-northeast-1
TEMPLATE_FILE=file://cloudformation.yml

aws cloudformation create-stack \
  --stack-name $STACK_NAME \
  --region $REGION \
  --template-body $TEMPLATE_FILE

これで事前準備1が完了しました。

CloudFormationで作成したリソースは、下記のように取得して以降で使用しています。

stack = boto3.resource('cloudformation').Stack(STACK_NAME)

STACK_NAME = "amazon-personalize-example-stack"
outputs = {o["OutputKey"]: o["OutputValue"] for o in stack.outputs}

S3_BUCKET_NAME = outputs["S3BucketName"]
IAM_ROLE_ARN = outputs["IAMRoleArn"]

事前準備2: 使うデータをダウンロードする

もう1つの準備として使うデータをダウンロードして手元に用意します。 今回はレコメンド用のテストデータとしてよく用いられるMovieLensを使います。 AWSのドキュメンテーションでも使われています。

grouplens.org

wget -N http://files.grouplens.org/datasets/movielens/ml-100k.zip
unzip -o ml-100k.zip
rm ml-100k.zip

これで準備完了です。

データセットグループを作成する

それではAmazon Personalizeでアプリケーションを作成していきましょう。本当はこれ以降もS3バケットやIAMロールと同じようにCloudFormationで作成できればよかったのですが、まだAmazon Personalizeが対応していないのでPythonで作成していきます。

Amazon Personalizeを使うにあたり、まずレコメンドアプリケーションプロジェクトの単位となるデータセットグループを作成します。

以降のコードでは下記のように作成したboto3のAmazon Personalizeのクライアントを使うことを前提とします。

personalize = boto3.client('personalize')
personalize_runtime = boto3.client('personalize-runtime')

次のようにデータセットグループを作成します。

create_dataset_group_response = personalize.create_dataset_group(
    name = "amzon-personalize-example-dataset-group"
)

dataset_group_arn = create_dataset_group_response['datasetGroupArn']

実行するとデータセットグループが作成されます。

f:id:ikedaosushi:20190613234343p:plain

データセットに学習データのインポートを行う

次にデータセットに学習するデータのインポートを行います。 下記の3ステップで行っていきます。

  1. データセットのSchemaを作成する
  2. データをSchemaに合わせて加工し、S3にアップロードする
  3. データセットを作成し、S3からデータセットに読みこむジョブを作成する

データセットのSchemaを作成する

データセットにデータを読み込むにはSchemaを作成する必要があります。 公式のチュートリアルでは、映画に対するユーザーの評価であるRatingを使用していないのですが、今回はより実際に即するために使用した例にしたいので必須のフィールドである、 USER_IDITEM_IDTIMESTAMP に加えて EVENT_TYPEEVENT_VALUE を作成しておきます。この2つのフィールドも予約語として決まっています。

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

create_schema_response = personalize.create_schema(
    name = "amzon-personalize-example-schema",
    schema = json.dumps(schema)
)

schema_arn = create_schema_response['schemaArn']

実行するとデータセットのSchemaが作成されます。

f:id:ikedaosushi:20190613235434p:plain

データをSchemaに合わせて加工し、S3にアップロードする

今、作成したSchemaに合わせてデータを加工し、S3にアップロードします。

filename = "amazon-personalize-example"
data = pd.read_csv('./ml-100k/u.data', sep='\t', names=['USER_ID', 'ITEM_ID', 'RATING', 'TIMESTAMP'])

# Amazon Personalizeに合わせた形に整形
data["EVENT_VALUE"] = data["RATING"]
data["EVENT_TYPE"] = "rating"
data = data[['USER_ID', 'ITEM_ID', 'EVENT_TYPE', 'EVENT_VALUE', 'TIMESTAMP']]

# csvファイルとして書き出し
data.to_csv(filename, index=False)

# アップロード
boto3.Session().resource('s3').Bucket(S3_BUCKET_NAME).Object(filename).upload_file(filename)

加工したデータは下記のようになります。

f:id:ikedaosushi:20190614000207p:plain

EVENT_TYPE には種類を EVENT_VALUE には値を入れます。 Rating以外の例としては、クリックの回数や閲覧回数などが考えられます。

データセットを作成し、S3からデータセットに読みこむジョブを作成する

まずAmazon Personalize上にデータセットを作成します。

dataset_type = "INTERACTIONS"

create_dataset_response = personalize.create_dataset(
    name = "amazon-personalize-dataset",
    datasetType = dataset_type,
    datasetGroupArn = dataset_group_arn,
    schemaArn = schema_arn
)

dataset_arn = create_dataset_response['datasetArn']

S3からデータセットに読みこむジョブを作成します。

create_dataset_import_job_response = personalize.create_dataset_import_job(
    jobName = "amazon-personalize-dataset-import-job",
    datasetArn = dataset_arn,
    dataSource = {
        "dataLocation": f"s3://{S3_BUCKET_NAME}/{filename}"
    },
    roleArn = IAM_ROLE_ARN
)

dataset_import_job_arn = create_dataset_import_job_response['datasetImportJobArn']

インポートジョブが完了するとデータセットが作成されます。

f:id:ikedaosushi:20190614000805p:plain

ポイントとしては User-item interaction と書いてある行のデータセットのみが作成されていて Useritem と書いてある行のデータセットは空になっていることです。これは上のスクリプトで dataset_type = "INTERACTIONS" と指定したからで他に USER ITEM と指定できて、それぞれ下記のような情報を付与できるようです。これらは必須ではないので今回は作成しません。

ユーザー — このデータセットではユーザーに関するメタデータを提供します。これには、年齢、性別、ロイヤリティメンバーシップなど、パーソナライゼーションシステムで重要なシグナルとなる情報が含まれます。
アイテム — このデータセットでは、アイテムに関するメタデータを提供します。これには、価格、SKU タイプ、可用性などの情報が含まれます。

ソリューションを作成する

それではデータセットが準備できたので、ソリューションを作成します。 ソリューションという名前ですが、モデルを学習させるパート、と理解して問題ありません。

ソリューションを作成するには、今作成したデータセットの指定とレシピの指定をする必要があります。レシピとは学習するアルゴリズムのことです。事前準備したもの、あるいは自分でカスタマイズしたものも使うことができるようです。

コンソールから使えるアルゴリズムを確認することができます。

f:id:ikedaosushi:20190614003622p:plain

それぞれのアルゴリズムを詳しく知りたい場合は、公式ドキュメントを参照してください。 パッと見た感じ、ほとんどがDeepLearningを使ったアルゴリズムのようです。

事前定義済みレシピの使用 - Amazon Personalize

今回は、階層型リカレントニューラルネットワーク (HRNN) を使います。 ソリューションを作成します。

recipe_arn = "arn:aws:personalize:::recipe/aws-hrnn"
create_solution_response = personalize.create_solution(
    name = "amazon-personalize-dataset-solution",
    datasetGroupArn = dataset_group_arn,
    recipeArn = recipe_arn
)

solution_arn = create_solution_response['solutionArn']

「ソリューションのバージョンを作成する」ことがモデルを学習させることにあたるようです。 バージョンを作成します。

create_solution_version_response = personalize.create_solution_version(
    solutionArn = solution_arn
)

solution_version_arn = create_solution_version_response['solutionVersionArn']

学習にはしばらく時間がかかります。(10~20分)

完了するとコンソール上でソリューションがActiveなことを確認できます。

f:id:ikedaosushi:20190614004617p:plain

ソリューションの詳細を見るとバージョンもActiveになっています。

f:id:ikedaosushi:20190614004758p:plain

キャンペーンを作成する

学習が完了したらレコメンドを行うためのキャンペーンを作成します。

create_campaign_response = personalize.create_campaign(
    name = "amazon-personalize-dataset-campaign",
    solutionVersionArn = solution_version_arn,
    minProvisionedTPS = 1
)

campaign_arn = create_campaign_response['campaignArn']

これだけです。簡単ですね。 完了するとコンソールからもキャンペーンが作成され、Activeになっていることが確認できます。

f:id:ikedaosushi:20190614005126p:plain

レコメンドを行う

それではレコメンドを行ってみましょう。適当にユーザーIDを選んで呼び出してみます。

user_id = 384
get_recommendations_response = personalize_runtime.get_recommendations(
    campaignArn = campaign_arn,
    userId = str(user_id)
)

item_list = get_recommendations_response['itemList']
title_list = [item_maps[int(item["itemId"])] for item in item_list]

print(f"Recommendations: {json.dumps(title_list, indent=2)}")

出力結果は下記の通りです。

Recommendations: [
  "George of the Jungle (1997)",
  "Murder at 1600 (1997)",
  "Devil's Own, The (1997)",
  "Spawn (1997)",
  "Volcano (1997)",
  "Crash (1996)",
  "G.I. Jane (1997)",
  ....
]

ユーザーID: 384に対するレコメンドされた映画のリストを取得できました。

今回は USER_PERSONALIZATION タイプのアルゴリズムであるHRNNを使ったのでレコメンドされた映画に順位関係(よりレコメンドされる)はありません。 SEARCH_PERSONALIZATION を使えば映画内のレコメンド順位が得られるようです。

アルゴリズムのタイプによって呼び出しに必要なデータが下記のように変わってきます。

f:id:ikedaosushi:20190614003911p:plain

以上でレコメンドアプリケーションを作成することができました。

さいごに

GAになったばかりのAmazon Personalizeの解説とCloudFormationとPythonでレコメンドアプリケーションを実際に学習/デプロイする方法をまとめました。 実際にWebApplicationとして使いたい場合はLambdaと組み合わせるのが良いと思います。 そんなときにServerless Frameworkが便利なので次の記事でWebApplication APIの作成方法をまとめます。

※追記: 書きました

blog.ikedaosushi.com

使ったコードはGitHubにあげてあります。Jupyter Notebookへのリンクですが、同じディレクトリにCloudFormationの設定ファイルなどもあります。

github.com

またレコメンドアルゴリズム自体の入門記事は以前書いたので興味のある方はこちらもご覧ください🙏

blog.ikedaosushi.com