Amazon Personalize と Forecast を やってみた

r3_yamauchi
R3 Cloud Journey
Published in
15 min readAug 24, 2019

JAWS UG KANSAIAmazon Personalizeをやってみよう! ハンズオンに参加してきました。

実はハンズオンで実施した内容は GitHub その他で公開されており、予め行っていたので、今回はハンズオンの内容を踏まえて試してみたことと、先日GAになった Personalize の弟分サービス Amazon Forecast について検証したことを書いてみようと思います。
(長くなってしまいましたが最後までお読みいただけると嬉しいです。Amazon Forecast については後半に書いており、続編 があります。)

まずは Amazon Personalize について、ハンズオンでは ユーザー610人による9,700本の映画視聴履歴とその評価(レーティング)のCSVファイルを Personalize にインポートし学習させます。この学習結果のモデルを Personalize では「ソリューション」と呼びます。そして、この学習結果をもとにレコメンドを提供してくれるエンドポイントを作ります。このエンドポイント(Personalize では「キャンペーン」と呼ぶ)に対して GetRecommendations API を実行すると以下のようにレコメンドを得られます。

Amazon Personalize GetRecommendations
このサンプルは https://github.com/perima/personalize-workshop をもとにしました

他のユーザーで GetRecommendations すると異なる結果が返ってきます。

別のユーザー、ID: 15 でレコメンドを取得した結果

パーソナライズされた順位の取得( GetPersonalizedRanking )

キャンペーンに問い合わせするための API `personalize-runtime` には GetRecommendations とは別に、もう一つ GetPersonalizedRanking という API があります。これは渡されたアイテムの一覧に「パーソナライズした順位」を付けて返すというものです。GetPersonalizedRanking を使用するには PERSONALIZED_RANKING タイプのレシピを使用してソリューションを作成する必要があります。PERSONALIZED_RANKING タイプのレシピは現状 Personalized-Ranking というレシピ 一つなので、このレシピを使用して、もう一つ別のソリューションを作成します。

Personalized-Ranking レシピを指定してソリューションを作成する
Personalized-Ranking レシピを指定してソリューションを作成する

現状、AutoML は HRNN 系のみサポートしているようなので、Manual で使用するレシピを選択します。

Personalized-Ranking レシピで作ったソリューションを使うキャンペーンを作成すると、マネジメントコンソール画面のキャンペーンをテストする箇所の内容が変わります。ユーザーIDとアイテムの一覧(ここでは映画のID)をカンマ区切りで渡してやると順位を付けて返してきます。

GetPersonalizedRanking によるパーソナライズされた順位の取得
GetPersonalizedRanking によるパーソナライズされた順位の取得

異なるユーザーで GetPersonalizeRanking すると、異なる順位付けで返ってきます。

ユーザーによって異なる(パーソナライズされた)順位が返る
ユーザーによって異なる(パーソナライズされた)順位が返る

イベントの記録( PutEvents )

Amazon Personalize は CSVでインポートしたデータだけでなく、リアルタイムに発生するイベントデータをもとにレコメンデーションを行うことが出来ます。 Amazon Personalize へ イベントを送るのを試してみました。

まず、イベントを送れるようにするには Event-Interactions という種類のデータセットを作ります。このデータセットのスキーマによって、どのようなイベントを送れるようになるかが決まってきます。このあたりは https://github.com/perima/personalize-workshop に沿うと分かりやすかったです(多謝) 以下のスキーマによって映画の評価(レーティング)を送れるようになります。

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

さらに Event tracker を作成します。これはマネコンで「Create event tracker」するだけ。Tracking ID (追跡ID) が発行されます。

boto3 を使う場合は以下の要領でイベントを送ることができます。

import os
import json
import boto3
import datetime
personalize_events = boto3.client(service_name='personalize-events')def lambda_handler(event, context):response = personalize_events.put_events(
trackingId = os.environ['EVENT_TRACKING_ID'],
userId = os.environ['USER_ID'],
sessionId = 'session_id_{}'.format(os.environ['USER_ID']),
eventList = [{
'sentAt': datetime.datetime.now(),
'eventType': 'Interactions',
'properties': "{\"itemId\": \"" + os.environ['ITEM_ID'] + "\", \"eventValue\": \"true\", \"rating\": " + os.environ['RATE'] + "}"
}]
)

イベントが送られたことは CloudWatch メトリクス で確認できます。

CloudWatch メトリクスで Amazon Personalize へ送られたイベントの数を確認できる
CloudWatch メトリクスで Amazon Personalize へ送られたイベントの数を確認できる

また、 Amplify が Amazon Personalize をサポートしたので、以下の要領でイベントを送ることができます。( Analytics モジュールはもともと Amazon Pinpoint へイベントを送るモジュールでしたが、プラグイン(AmazonPersonalizeProvider)によって Personalize へもイベントを送れるように拡張されました)

Analytics.record({
eventType: "Interactions",
userId: this.state.userId,
properties: {
"itemId": movieId,
"eventValue": true,
"rating": this.state.rate
}
}, "AmazonPersonalize")

https://github.com/perima/personalize-workshop のサンプルを改造して、映画のタイトルをクリックする度に 1.5 という微妙な評価を送るようにしてみました。

Amplify の AmazonPersonalizeProvider を使用して Analytics.record でイベントを送る
Amplify の AmazonPersonalizeProvider を使用して Analytics.record でイベントを送る

ユーザーID: 10 で Finding Dory (2016)Star Trek Into Darkness (2013) にレーティング 1.5 を付けてからレコメンドを取得し直すと両作品がランキング外に落ちています。(冒頭の図と比較してください)

ファインディング・ドリー と スタートレック イントゥ・ダークネス を低評価したった

ただ、現状 Personalize にどのようなイベントデータが送られたかを確認する方法がないようです。Personalize に送ると共に Amazon Pinpoint にも送るとか、別途どこかで一元管理して次回ソリューションをアップデートする際の入力データに自分で加えてやる必要がありそうです。
(学習に反映させたくないゴミデータがあれば、その際に除外する)

続いて Amazon Forecast を試しました。Amazon Forecast に過去の時系列データを提供することで、未来の予測を行わせることができます。 https://aws.amazon.com/jp/blogs/aws/amazon-forecast-now-generally-available/ および https://github.com/aws-samples/amazon-forecast-samples に従い進めました。

Amazon Forecast の マネジメントコンソール ダッシュボード画面
Amazon Forecast の マネジメントコンソール ダッシュボード画面

Forecast と Personalize は とても似ています。
Personalize における「ソリューション」が Forecast では「Predictor」、「キャンペーン」が「Forecast」に対応するものと考えてよいと思います。(作成するのに要する時間も同程度かかります)

1. Getting_Data_Ready.ipynb では UCI Machine Learning Repository にある 2014/1/1 から 10/31 までの家庭の電力消費データ(一時間単位)を使用します。2014/1/1 から 10/30 分までを Forecast にインポートし、10/31 分を予測させます。

Predictor を作成する際、Forecast Horizon を指定します。この値により Forecast が予測可能な期間が決まるようです。Forecast frequency が 1hour で Forecast Horizon が 24 ということは 24時間分の予測しかできません。ここに大きな値を指定すればするほど学習に時間を要するような気がします。(天気予報と同じで、あまり先の予報は当てにならない(精度が落ちる)と思われます)

https://aws.amazon.com/jp/blogs/aws/amazon-forecast-now-generally-available/ 内の図 https://d2908q01vomqb2.cloudfront.net/da4b9237bacccdf19c0760cab7aec4a8359010b0/2019/08/19/backtest.png をお借りします。

また、「Country for holidays」を指定することができ、祝日であるか否かを加味させることができるようです。「Japan」も選択できるようなので Forecast の内部に「日本の祝日カレンダー」の情報を持っているようですね。独自の営業日カレンダーのようなものを使用したい場合は RELATED_TIME_SERIES のデータセットに入れる必要がありそうです。

まずは ARIMA というアルゴリズムを試してみました。このアルゴリズムはベーシックな移動平均ベースのようなので P10, P50, P90 の 3つの予測全てが同じような傾向を示しています。
(10/30分は Forecast に読み込ませた実績データで 10/31分が予測)

ARIMA による予測
ARIMA による予測

P10 と P90 の間に収まる可能性が 80% ということのようです。
(P10 よりも下振れする可能性が 10%、P90 よりも上振れする可能性が 10%、計20% は P10 と P90 の間から外れる可能性がある)

10/31 の実データ(これは Forecast にはインポートしなかったが、元の CSVの中には含まれている)を予測に重ね合わせたグラフが 3.Evaluating_Your_Predictor.ipynb の末尾に載っています。

client_12 の ARIMA による予測に実データを重ね合わせたグラフ
client_12 の ARIMA による予測に実データを重ね合わせたグラフ

前半は良い感じに P50 に沿っていますが、後半はかなりズレがありますし、P90 よりも上に外れている箇所もあります。

次に AutoML を使用して Predictor を作ってみました。今回のデータでは NPTS が使用されました。 同様に Forecast の結果を取得してみます。

NPTS による予測
NPTS による予測

P50, P90 では 13:00, 14:00 の鋭角な凹みを予測できています。

実データを重ねてみると、、

client_12 の NPTS による予測に実データを重ね合わせたグラフ
client_12 の NPTS による予測に実データを重ね合わせたグラフ

まずまず良い感じです。(P90 よりも大きく上に飛び出した箇所が出てきていますが)

client_12 では良い予測が出来たので、他のユーザーも確認してみます。

client_10 (左) と client_111 (右) の NPTS による予測に実データを重ね合わせたグラフ

client_10 はバッチリですが、 client_111 は ボロボロです。

Forecast に読み込ませたCSVの内容を確認してみると、 client_111 のデータは欠測や 0値が多いので、このような結果になったのも仕方のないことが分かりました。

Forecast と Personalize の Billing を比較してみると興味深いことが分かりました。

Personalize の キャンペーンを 一ヶ月間放置しておくと、このくらいの課金になります

Personalize は キャンペーン(リアルタイムのレコメンデーション)を作り `personalize-runtime` API 呼び出しを受け付けられる状態(エンドポイント)を維持していると、その時間 (provisioned TPS) に対して大きく課金されます。(キャンペーン作成時に指定する Minimum provisioned transactions per second の値に比例する)

一方、インポートデータ量やトレーニング(学習)時間には大きめの無料枠が設定されています。Forecast は トレーニング(学習)時間の無料枠が小さいですが、予測データの利用は無料枠で賄えそうです。現状 Forecast はイベントの登録(リアルタイム・アクティビティ)をサポートしておらず、予測データを一括エクスポートして使うことも出来るので、このような違いになっているのではないかと思いました。Forecast は予測精度(結果)を確認しながら何度も何度も(日々)学習を回して翌日分の予測を得る使い方をするのだと思います。

今回の例では CUSTOM ドメインを指定し、シンプルなデータ(スキーマ)で予測を行いましたが、実際のユースケースでは適切な事前定義済ドメインを指定し、スキーマに合わせたデータを投入する必要があるようです。

例えば「小売の需要予測」を行うなら RETAIL ドメインを選び、RELATED_TIME_SERIES データセットで `promotion_applied` (タイムスタンプ時にその商品のマーケティングプロモーションがあったかどうかを指定するフラグ)に値を入れてやらないと、特売による変動を認識できないことが容易に想像できます。

JAWS-UG では近々に在庫予測を題材にしたハンズオンを計画しているとのことですので、 INVENTORY_PLANNING ドメイン を使用するのか、あるいは単純在庫なら CUSTOM ドメインで精度を出せるのか、次回のハンズオンを楽しみに待ちたいと思います。

--

--

r3_yamauchi
R3 Cloud Journey

アールスリーインスティテュートで、これまでになかった画期的な kintoneカスタマイズサービス gusuku Customine(カスタマイン) を開発しています。kintone認定アプリデザイン/カスタマイズ スペシャリスト