Personalizeのドメイン最適化レコメンデーションを使ってみた

Amazon PersonalizeのVIDEO ON DEMANDやEコマースに最適化されたレコメンデーションを紹介
2022.02.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

データアナリティクス事業本部機械学習チームの中村です。

今回は、Amazon Personalizeで使用可能となったドメイン特化レコメンデーションをご紹介し、実際に使ってみます。

冒頭まとめ

  • PersonalizeでVIDEO ON DEMANDとEコマースのドメインに特化されたレコメンデーションができるようになった。
  • Domain dataset groupがドメイン特化、Custom dataset groupが従来のレコメンデーションという扱い。
  • ドメイン特化レコメンデーションのメリットは以下。
    • 従来よりも簡単な手数でリアルタイム推論まで構築できる。
    • チューニングなどが必要なしに構築が可能。
  • その反面、以下のような特性があるので要注意が必要。
    • 性能を見ながらパラメータの手動チューニング等ができない。
    • 現段階では、バッチレコメンドに対応していない。
    • 構築フローが異なることにより、料金体系が異なる。
    • ユーザーIDを見ながら購入済みの商品はレコメンドされない等、暗黙的なフィルター処理が存在するレシピがある。

リリースについて

Amazon Personalizeは、開発者がパーソナライズされた体験をユーザーに提供することを容易にする、フルマネージドな機械学習サービスであり、Amazonがパーソナライゼーションシステム構築で培った知識と経験が反映された技術を利用可能です。

以下のアップデートにより、あるドメインに最適化されたDomain dataset groupが使用可能となりました。 またこれまでのレコメンド処理はCustom dataset groupと、より広範な用途に使えるカスタムなDataset groupという位置づけとなりました。

本記事では、新たにリリースされたDomain dataset groupと今までのCustom dataset groupの違いを整理し、 実際に、Domain dataset groupのワークフローで、ドメインに最適化されたレコメンデーション環境を構築してみます。 構築は、PythonのSDK(boto3)を用いて実施しますが、マネジメントコンソールからでもほぼ同等のことが実施可能です。

各Dataset groupの概要

今回のリリースにより、2種類のDataset groupができました。

  • Domain dataset group
    • あるドメインに最適化されたレコメンドを使用するためのDataset groupです。
    • 現時点では、VIDEO_ON_DEMANDとECOMMERCEという2パターンのドメイン向けに準備されています。
    • こちらでは、口述のCustom dataset group側と同じレコメンド処理(Solutionと呼ばれる)も構築可能です。
  • Custom dataset group
    • より広範な用途に使えるカスタムなDataset groupです。
    • こちらは、後でDomain dataset groupのレコメンドを追加させることはできません。
    • これは、データに必須の項目(列)が、Domain dataset groupの方がより多いためと考えられます。

以降簡単のため、それぞれDomainCustomと略して表記します。

ワークフローの違い

ワークフローが大きく変わっています。Domainはより少ないステップで、実際のレコメンデーション取得まで環境を構築できます。 以下に図でワークフローを比較します。(各ステップはそれぞれSDKのAPI単位で分けて表現しています)

  • Customの場合
    • recommendationを取得するまでに、create_solution -> create_solution_version -> create_campaign と段階を踏む必要がありました。
    • ここでrecommendation取得はリアルタイム取得のことを指しています。
  • Domainの場合
    • create_recommenderだけで、recommendationが取得できます。
    • これによりcampaignがすでに作られている状態と同等となるため、recommender作成時点で料金が発生します。
    • import jobを作成する側はフローに差がありません。

スキーマの違い

スキーマは、学習するデータなどのフォーマットを定義したものです。S3に配置するデータはこのスキーマに沿ったカラムを有している必要があります。

Dataset type Required fields Reserved keywords
Interactions USER_ID (string)
ITEM_ID (string)
TIMESTAMP (long)
EVENT_TYPE (string)
EVENT_VALUE (float, null)
IMPRESSION (string)
RECOMMENDATION_ID (string, null)
Users USER_ID (string)
1 metadata field
Items ITEM_ID

1 metadata field

CREATION_TIMESTAMP (long)
  • Domainの場合(VIDEO_ON_DEMAND)
    • https://docs.aws.amazon.com/personalize/latest/dg/VIDEO-ON-DEMAND-datasets-and-schemas.html
    • Customと比較して、必須な項目(Reauired fields)がいくつか増えています。
    • 今回は、Interactionsのみを使いますので、EVENT_TYPEのみが追加で必須な項目となります。
    • EVENT_TYPEは値としても、Watchは必須、Clickはレシピによっては記録が推奨されています。
    • また予約語(Reserved Keywords)も多く増えているため、注意が必要です。
Dataset type Required fields Reserved keywords
Interactions USER_ID (string)
ITEM_ID (string)
TIMESTAMP (long)
EVENT_TYPE (string and depending on use case, Watch and Click event types)
EVENT_VALUE (float, null)
IMPRESSION (string)
RECOMMENDATION_ID (string, null)
Users USER_ID (string)
1 metadata field
Items ITEM_ID (string)
CREATION_TIMESTAMP (long)
GENRE_L1 (categorical string)
PRICE (float, null)
DURATION (float, null)
GENRE_L2 (categorical string)
GENRE_L3 (categorical string)
AVERAGE_RATING (float, null)
PRODUCT_DESCRIPTION (textual)
CONTENT_OWNER (categorical string)
CONTENT_CLASSIFICATION (categorical string)
  • Domainの場合(ECOMMERCE)
    • https://docs.aws.amazon.com/personalize/latest/dg/ECOMMERCE-datasets-and-schemas.html
    • VIDEO_ON_DEMANDと同様、Customと比較して、必須な項目(Reauired fields)がいくつか増えています。
    • EVENT_TYPEの値は、レシピによって必須なものが異なり、ViewもしくはPurchaseいずれかが必須、レシピによってはもう一方も推奨のものがあります。
    • 今回はVIDEO_ON_DEMAND側でトライアルしますので、ECOMMERCE側の詳細な説明は省略いたします。
Dataset type Required fields Reserved keywords
Interactions USER_ID (string)
ITEM_ID (string)
TIMESTAMP (long)
EVENT_TYPE (string and depending on use case, Purchase and View event types)
EVENT_VALUE (float, null)
IMPRESSION (string)
RECOMMENDATION_ID (string, null)
Users USER_ID (string)
1 metadata field
Items ITEM_ID (string)
PRICE (float)
CATEGORY_L1 (categorical string)
CATEGORY_L2 (categorical string)
CATEGORY_L3 (categorical string)
PRODUCT_DESCRIPTION (textual)
CREATION_TIMESTAMP (long)
AGE_GROUP (categorical string)
ADULT (categorical string)
GENDER (categorical string)

レシピの違い

Recipe Recipe Types Required datasets 説明
Popularity-Count USER_PERSONALIZATION Interactions Interactionsデータから最も人気のアイテムをレコメンドします。
このレシピは、すべてのユーザーに対して同じアイテムをレコメンドするタイプです。
そのため、ベースラインのレシピとして使用できます。
HRNN USER_PERSONALIZATION Interactions HRNN(Hierachical RNN)モデルを使ったレシピです。
このレシピはlegacyであり、改善・統合されたUser-Personalizationレシピの使用が推奨です。
HRNN-Metadata USER_PERSONALIZATION Interactions
at least 1 metadata
HRNNレシピに、metadataから推測される特徴を考慮したものです。
metadataは、Interactions, Users, Itemsそれぞれに含まれるデータで、いずれか一つのmetadataが必要となります。
HRNNと同様にlegacyレシピです。
HRNN-Coldstart USER_PERSONALIZATION Interactions
Items
コールドスタート問題と一般的に呼ばれるものに対応した、新しいItemやInteractionに強いモデルです。
期間が短く、Interactionの数が一定数以下のItemをCold Itemとして扱って処理します。
HRNNと同様にlegacyレシピです。
User-Personalization USER_PERSONALIZATION Interactions 3つのlegacyレシピを統合したレシピとなります。
それ以外にモデルの自動更新や、IMPRESSIONデータ(ユーザーにレコメンド表示したデータ)の情報を使用することがでます。
Personalized-Ranking PERSONALIZED_RANKING Interactions ITEM_IDのリストをリクエストすると、ユーザーに応じたランキングに並び変えた結果を取得するレシピです。
存在しないITEM_IDが与えられた場合でもエラーとはなりませんが、最後尾に配置されます。
SIMS RELATED_ITEMS Interactions Interactionsデータのみを用いて、類似アイテムを取得するレシピです。
Itemの人気度合いとItem間の相関性のバランスをとって計算します。
Similar-Items RELATED_ITEMS Interactions
Items
InteractionsデータとItemsデータのmetadataおよび非構造化データ(数値やテキストなど)を使って、類似アイテムを取得するレシピです。
Itemsがない場合は、SIMSを使う必要があります。
Item-Affinity USER_SEGMENTATION Interactions Interactionsデータからユーザーをセグメンテーションします。
バッチジョブにのみ対応しているため、詳細は省略いたします。
Item-Attribute-Affinity USER_SEGMENTATION Interactions
Items
InteractionsデータとItemの属性からユーザーをセグメンテーションします。
バッチジョブにのみ対応しているため、詳細は省略いたします。
Recipe Required datasets 説明
Most popular Interactions(1000件以上のWatchを含む) 最も人気のVideoをレコメンドするレシピです。
Custom側のPopularity-Countレシピと似た扱いになると考えられます。
Because you watched X Interactions(1000件以上のWatchを含む) 指定したVideoを元に他のユーザーも見ているVideoをレコメンドしてもらうレシピです。
Interactionにより重点を置いてレコメンドするレシピと考えられます。
userIdに基づいて、既にWatch済みのアイテムは自動的にフィルタリングされます。
More like X Interactions(1000件以上のWatchを含む。Clickも推奨)
Items
指定したVideoと類似したVideoをレコメンドしてもらうレシピです。
InteractionsやItemsのmetadataや非構造化データにより重点をおいてレコメンドするレシピと考えられます。
userIdに基づいて、既にWatch済みのアイテムは自動的にフィルタリングされます。
Top picks for you Interactions(1000件以上のWatchを含む。Clickも推奨)
Items
ユーザー個人向けにパーソナライズされたレコメンドを取得するレシピです。
userIdに基づいて、既にWatch済みのアイテムは自動的にフィルタリングされます。
  • Domainの場合(ECOMMERCE)
Recipe Required datasets 説明
Most viewed Interactions(1000件以上のViewを含む) 最もViewされている商品をレコメンドするレシピです。
Custom側のPopularity-Countレシピと似た扱いになると考えられます。
Best sellers Interactions(1000件以上のPurchaseを含む) 最もPurchaseされている商品をレコメンドするレシピです。
こちらもCustom側のPopularity-Countレシピと似た扱いになると考えられます。
Frequently bought together Interactions(1000件以上のPurchaseを含む) 指定した商品と一緒に購入されることが多い商品をレコメンドしてもらうレシピとなります。
そのため、InteractionsのPurchaseデータを元にしたレシピとなっていると考えられます。
Customers who viewed X also viewed Interactions(1000件以上のViewを含む。Purchaseも推奨) 指定した商品に基づいて、その商品と同時によく見られている商品をレコメンドするレシピです。
userIdに基づいて、既にPurchase済みのアイテムは自動的にフィルタリングされます。
Recommended for you Interactions(1000件以上のViewを含む。Purchaseも推奨) ユーザー個人向けにパーソナライズされたレコメンドを取得するレシピです。
userIdに基づいて、既にPurchase済みのアイテムは自動的にフィルタリングされます。
  • 上記に紐づく、recipeのARNは以下となります。
    • arn:aws:personalize:::recipe/aws-popularity-count
    • arn:aws:personalize:::recipe/aws-hrnn
    • arn:aws:personalize:::recipe/aws-hrnn-metadata
    • arn:aws:personalize:::recipe/aws-hrnn-coldstart
    • arn:aws:personalize:::recipe/aws-user-personalization
    • arn:aws:personalize:::recipe/aws-personalized-ranking
    • arn:aws:personalize:::recipe/aws-sims
    • arn:aws:personalize:::recipe/aws-similar-items
    • arn:aws:personalize:::recipe/aws-item-affinity
    • arn:aws:personalize:::recipe/aws-item-attribute-affinity
    • arn:aws:personalize:::recipe/aws-vod-most-popular
    • arn:aws:personalize:::recipe/aws-vod-because-you-watched-x
    • arn:aws:personalize:::recipe/aws-vod-more-like-x
    • arn:aws:personalize:::recipe/aws-vod-top-picks
    • arn:aws:personalize:::recipe/aws-ecomm-popular-items-by-views
    • arn:aws:personalize:::recipe/aws-ecomm-popular-items-by-purchases
    • arn:aws:personalize:::recipe/aws-ecomm-frequently-bought-together
    • arn:aws:personalize:::recipe/aws-ecomm-customers-who-viewed-x-also-viewed
    • arn:aws:personalize:::recipe/aws-ecomm-recommended-for-you

料金体系の違い

ここでは現時点での算出の方法について説明しますので、最新の情報や具体的な金額については以下を参照ください。

https://aws.amazon.com/personalize/pricing/?nc1=h_ls

  • Custom
    • こちらが従来通りの料金体系です。
    • この中でも、Recipe TypeがUSER_SEGMENTATIONのものはさらに別の料金ルールとなり、そちらについての説明は省略します。
項目 説明
データ取り込み S3からPersonalizeにアップロードされるデータに1GB単位で料金が発生します。
トレーニング SolutionVersionを作成するトレーニング時間数に応じて1時間毎に料金が発生します。
リアルタイム推論 TPS(1時間あたりのレコメンド取得数)によって料金が変わります。
計算には設定するMinimum provisioned TPSと実際のTPSのどちらか大きい方が使用されます。
Minimum provisioned TPSを超えるようなリクエストがあった場合、自動的にスケールアップされます。
バッチ推論 レコメンデーション1000件あたりに料金が発生します。
  • Domain
    • Customと比較してモデルのtrainingには料金が発生しません。
    • recommender作成時にモデルtraining後、リアルタイム推論が同時に作成されますので、recommender作成直後から時間単位で料金が発生します。
項目 説明
データ取り込み S3からPersonalizeにアップロードされるデータに1GB単位で料金が発生します。
データセット内のユーザー数 Recommender作成後はrecommender1個あたり1時間毎に料金が発生し、
この料金はデータセットに存在するユーザー数によって異なります。
追加レコメンデーション Recommender作成後は時間単位で料金が発生し、
1時間の一定のレコメンド数まではその料金で変わりませんが、
1時間内で一定数を超えると、追加レコメンデーションの料金が発生します。

実際にVIDEO_ON_DEMANDで構築してみた

Modules

import boto3
from boto3.session import Session
import json
import pathlib
import pandas as pd
import numpy as np

データ作成

  • ml-test-small.zipのrating.csvを元にカラムを編集・追加します。
    • カラム名はルールがあるので、それに沿ってリネームします。
    • EVENT_TYPEが必須のフィールドとして必要なため、今回は乱数で生成します。
df_ratings = pd.read_csv("ml-latest-small/ratings.csv")

# columnsの書き換え
df_ratings = df_ratings.rename(columns={'userId': 'USER_ID', 'movieId': 'ITEM_ID', 'timestamp': 'TIMESTAMP'})

# ratingを削除
df_ratings = df_ratings.drop('rating', axis=1)

# EVENT_TYPEを生成
rns = np.random.RandomState(seed=777)
list_event_type = rns.choice(['Watch', 'Click'], size=len(df_ratings), replace=True, p=[0.8,0.2])
df_ratings['EVENT_TYPE'] = list_event_type

df_ratings.to_csv("ml-latest-small/ratings_mod.csv", index=False)

変数の事前定義

bucket_name = 'Your S3 bucket name'
import_uri = 'Your S3 file uri' # s3://bucket_name/file_path'

region_name = 'ap-northeast-1'
prefix = 'trial-20220222'

dataset_group_name          = f'{prefix}-dataset-group'
schema_interaction_name     = f'{prefix}-schema-interaction'
dataset_interaction_name    = f'{prefix}-dataset-interaction'
import_job_interaction_name = f'{prefix}-import-job-interaction'
recommender_name            = f'{prefix}-recommender-vod-most-popular'
iam_role_name               = f'{prefix}-personalize-exection-role'
iam_custom_policy_name      = f'{prefix}-personalize-execution-policy'

client_s3                  = boto3.client('s3', region_name=region_name)
client_personalize         = boto3.client('personalize', region_name=region_name)
client_personalize_runtime = boto3.client('personalize-runtime', region_name=region_name)
client_iam                 = boto3.client('iam', region_name=region_name)

S3 bucketの作成と設定

# create_bucket
location = {'LocationConstraint': region_name}
client_s3.create_bucket(Bucket=bucket_name, CreateBucketConfiguration=location)

# put_public_access_block
client_s3.put_public_access_block(
    Bucket=bucket_name,
    PublicAccessBlockConfiguration={
        'BlockPublicAcls': True,
        'IgnorePublicAcls': True,
        'BlockPublicPolicy': True,
        'RestrictPublicBuckets': True,
    },
)

# create bucket_policy
bucket_policy = {
    "Version": "2012-10-17",
    "Id": "PersonalizeS3BucketAccessPolicy",
    "Statement": [
        {
            "Sid": "PersonalizeS3BucketAccessPolicy",
            "Effect": "Allow",
            "Principal": {
                "Service": "personalize.amazonaws.com"
            },
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                f"arn:aws:s3:::{bucket_name}",
                f"arn:aws:s3:::{bucket_name}/*"
            ]
        }
    ]
}
bucket_policy = json.dumps(bucket_policy)

# put_bucket_policy
client_s3.put_bucket_policy(
    Bucket=bucket_name,
    Policy=bucket_policy,
)

S3 bucketへのアップロード

interactions_csv = pathlib.Path('ml-latest-small/ratings_mod.csv')
response = client_s3.upload_file(
    str(interactions_csv),
    bucket_name, 
    str(interactions_csv),
)

Domain dataset group作成

  • domain = VIDEO_ON_DEMANDとします。(未指定だとCustomになります)
response_create_dataset_group = client_personalize.create_dataset_group(
    name=dataset_group_name,
    domain='VIDEO_ON_DEMAND'
)

Schema作成

  • Interactionsのみをデータとして使います。
  • こちらも、domain = VIDEO_ON_DEMANDとする必要があります。
schema_interaction = {
    "type": "record",
    "name": "Interactions",
    "namespace": "com.amazonaws.personalize.schema",
    "fields": [
        {
            "name": "USER_ID",
            "type": "string"
        },
        {
            "name": "ITEM_ID",
            "type": "string"
        },
        {
            "name": "TIMESTAMP",
            "type": "long"
        },
        {
            "name": "EVENT_TYPE",
            "type": "string"
        },
    ],
    "version": "1.0"
}

schema_interaction = json.dumps(schema_interaction)

response_create_schema = client_personalize.create_schema(
    name=schema_interaction_name,
    schema=schema_interaction,
    domain='VIDEO_ON_DEMAND',
)

Dataset作成

response_create_dataset = client_personalize.create_dataset(
    name=dataset_interaction_name,
    schemaArn=response_create_schema['schemaArn'],
    datasetGroupArn=response_create_dataset_group['datasetGroupArn'],
    datasetType='Interactions'
)

IAMロール作成

  • まずはcustom policyの作成します。
iam_custom_policy_document = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket"
            ],
            "Effect": "Allow",
            "Resource": [
                f"arn:aws:s3:::{bucket_name}"
            ]
        },
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Effect": "Allow",
            "Resource": [
                f"arn:aws:s3:::{bucket_name}/*"
            ]
        }
    ]
}

response_create_policy = client_iam.create_policy(
    PolicyName=iam_custom_policy_name,
    PolicyDocument=json.dumps(iam_custom_policy_document),
)
  • 次にroleを作成します。
assume_role_policy_document = {
    "Version": "2012-10-17",
    "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Service": "personalize.amazonaws.com"
          },
          "Action": "sts:AssumeRole"
        }
    ]
}


create_role_response = client_iam.create_role(
    RoleName = iam_role_name,
    AssumeRolePolicyDocument = json.dumps(assume_role_policy_document)
)

client_iam.attach_role_policy(
    RoleName = iam_role_name,
    PolicyArn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
)

client_iam.attach_role_policy(
    RoleName = iam_role_name,
    PolicyArn = response_create_policy['Policy']['Arn']
)

response_get_role = client_iam.get_role(RoleName=iam_role_name)

Import job作成

  • 作成した、IAM Roleを与える必要があります。
response_create_dataset_import_job = client_personalize.create_dataset_import_job(
    jobName=import_job_interaction_name,
    datasetArn=response_create_dataset['datasetArn'],
    dataSource={
        'dataLocation': import_uri
    },
    roleArn=response_get_role['Role']['Arn']
)

Recommender作成

  • 今回レシピは、aws-vod-most-popularを使います。
response_create_recommender = client_personalize.create_recommender(
    name=recommender_name,
    datasetGroupArn=response_create_dataset_group['datasetGroupArn'],
    recipeArn='arn:aws:personalize:::recipe/aws-vod-most-popular',
)

レコメンデーション取得

  • get_recommendationsは、recommenderArnもしくは、solutionVersionArnを指定する形となります。
    • Domainの場合は、recommenderArnを指定。
    • Customの場合は、solutionVersionArnを指定。
response_get_recommendations = client_personalize_runtime.get_recommendations(
    userId="1",
    numResults=5,
    recommenderArn=response_create_recommender['recommenderArn'],
)
  • 結果は以下のようなjson形式となります。
{
  "ResponseMetadata": {
    "RequestId": "9e8ec5f8-4a68-46d2-849f-14432877395a",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "content-type": "application/json",
      "date": "Mon, 21 Feb 2022 07:22:11 GMT",
      "x-amzn-requestid": "9e8ec5f8-4a68-46d2-849f-14432877395a",
      "content-length": "322",
      "connection": "keep-alive"
    },
    "RetryAttempts": 0
  },
  "itemList": [
    {
      "itemId": "356"
    },
    {
      "itemId": "296"
    },
    {
      "itemId": "318"
    },
    {
      "itemId": "593"
    },
    {
      "itemId": "2571"
    }
  ],
  "recommendationId": "RID-b0c39904-e612-4158-92e3-a1ca3661d508"
}

Recommender削除

  • Recommenderはcampaignを含んでいるため、稼働分だけ料金が発生します。
  • 使用後、不要となったら削除をしてください。
response_delete_recommender = client_personalize.delete_recommender(
    recommenderArn=response_create_recommender['recommenderArn']
)

その他の違い

metricsの取得について

Customは、SolutionVersionを作成した結果として、metricsの確認ができましたが、Domainではそれが実施できないようです。 もし必要な場合は、metrics計算用のスクリプトを書いて求めるか、Custom側でレシピを作成する必要があります。

バッチジョブへの対応

Customはリアルタイム推論以外にも、S3のファイルを読み込んで、結果をS3に出力するバッチジョブを実行することができましたが、 現状Domainは、バッチジョブには対応していないようですのでご注意ください。 バッチジョブが必要な場合は従来通りCustom側でレシピを作成する必要があります。

モデルの最適化オプション

Custom側には、モデルの最適化としてHPO(ハイパーパラメータ最適化)やAutoML(最良なモデルを選択)を使用可能でしたが、DomainはRecommender作成時にPersonalize側にお任せする形となりますので、設定項目としては選択できませんのでご注意ください。

まとめ

Domainでは、従来のCustomよりも簡単にリアルタイム推論まで構築できることがわかりました。用途にマッチする場合は構築までの時間が短くて済むのでとても良いと感じました。 必要な処理によっては、従来のCustomを使う必要もありますので、用途に応じて選択する必要がありそうです。