[ワークショップ] Amazon Pinpointで的を絞ったアウトリーチを実行するワークショップに参加してきた #IMP214 #AWSreInvent
大阪オフィスの林です。 re:Invent 2023 も気付けば終盤ですね!
正直なぜこのワークショップを予約したのか理由を忘れてしまったのですが、いずれにしても普段では関わることのない業界やサービスだったので、なにかしら良い経験、もしかしたら将来の為にもなるだろう!と信じてそのまま参加してきました!
セッションタイトル
IMP214 | 患者または会員のエクスペリエンスをパーソナライズして、ミッションを拡大するのに役立ちます
IMP214 | Personalize patient or member experience to help amplify your mission
セッション概要
消費者セグメントが異なれば、異なるエンゲージメント戦略が必要になります。
これらのセグメントと対話するための効果的かつ的を絞った方法を導入すると、回避可能な予定外の急性期後のフォローアップ コストを大幅に削減できる可能性があります。
このワークショップでは、AWS のサービスを使用して患者と会員のエクスペリエンスをパーソナライズし、エンゲージメントを高め、EHR、請求、その他のシステムからのデータを使用して結果を改善する方法を学びます。
これらの概念は、パーソナライズされたメンバーエンゲージメントのためにあらゆる非営利組織に適用でき、ミッションを加速するのに役立ちます。
ワークショップ前の講義
『パーソナライズされたエクスペリエンスは、患者と医療従事者の定着率の向上と満足度の向上につながる。』というメッセージで講義が始まりました。
研究によると、入院患者に合わせたケアプランが再入院率の低下、満足度の向上、保険会社の切り替えの減少につながることが示されているそうです。
特に医療においては各個人に応じて対応内容を変更してアレンジしたりすることが重要な気がするので、イチ患者になった目線としても「ふむふむ」といった具合に納得の研究結果です。
そんなヘルスケア業界をユースケースに、パーソナライズされた推奨事項とユーザーのセグメント化のためのシステムを構築する方法をAWS のサービスを使用しながら学びます!という締めくくりでワークショップがスタートしました。
いざ、ワークショップ
ワークショップで関連するAWSのサービスとアーキテクチャです。
見た目はシンプルなのですが、思いのほか手順がモリモリで大変でした(汗
ざっくり下記の流れでワークショップを進めていきます。
最終的に「Amazon Pinpoint でセグメント内のユーザーに的を絞ったアウトリーチを実行」できればゴールです。
- SageMaker Studioのセットアップ
- Jupyterノートブックのセットアップ
- データの取り込み
- Amazon Personalizeのソリューション作成
- ユーザーセグメントを作成、からのテスト
SageMaker Studioのセットアップ
ここはシンプルにマネコンポチポチでSageMaker Studioのセットアップ進めます。
Jupyterノートブックのセットアップ
Jupyterノートブックを作成し、パッケージを更新したり後続の作業で必要なboto3のインストールを進めます。
import os os.environ["PIP_ROOT_USER_ACTION"] = "ignore" !python -m pip install -Uq pip !pip install -q --upgrade awscli boto3
引き続きワークショップデータをAmazon Personalize にインポートしていきます。
データの取り込み
臨床データセットの作成
このセクションでは架空の臨床データの作成をインポートを行っていきます。
Amazon Personalize のソリューション作成時に使用する事前定義関数を作成します。
データセグメントの作成で使った関数定義はこちら
def wait_for_dataset_group_job(dataset_group_arn): max_time = time.time() + 3 * 60 * 60 while time.time() < max_time: describe_dataset_group_response = personalize.describe_dataset_group( datasetGroupArn = dataset_group_arn ) status = describe_dataset_group_response["datasetGroup"]["status"] print("DatasetGroup: {}".format(status)) if status == "ACTIVE" or status == "CREATE FAILED": break time.sleep(60) def wait_for_dataset_import_job(dataset_import_job_arn): max_time = time.time() + 3 * 60 * 60 while time.time() < max_time: describe_dataset_import_job_response = personalize.describe_dataset_import_job( datasetImportJobArn = dataset_import_job_arn ) status = describe_dataset_import_job_response["datasetImportJob"]['status'] print("DatasetImportJob: {}".format(status)) if status == "ACTIVE" or status == "CREATE FAILED": break time.sleep(120) def wait_for_solution_version_job(solution_version_arn): max_time = time.time() + 3 * 60 * 60 while time.time() < max_time: describe_solution_version_response = personalize.describe_solution_version( solutionVersionArn = solution_version_arn ) status = describe_solution_version_response["solutionVersion"]["status"] print("SolutionVersion: {}".format(status)) start = describe_solution_version_response["solutionVersion"]["creationDateTime"] end = describe_solution_version_response["solutionVersion"]["lastUpdatedDateTime"] if status == "ACTIVE": print("Time took: {}".format(end - start)) break if status == "CREATE FAILED": print("Time took: {}".format(end - start)) print("Job Failed: {}".format(describe_solution_version_response["solutionVersion"]["failureReason"])) break time.sleep(180) def wait_for_batch_segment_job(batch_segment_job_arn): max_time = time.time() + 3 * 60 * 60 while time.time() < max_time: describe_job_response = personalize.describe_batch_segment_job( batchSegmentJobArn = batch_segment_job_arn ) status = describe_job_response["batchSegmentJob"]["status"] print("Batch Segment Job: {}".format(status)) start = describe_job_response["batchSegmentJob"]["creationDateTime"] end = describe_job_response["batchSegmentJob"]["lastUpdatedDateTime"] if status == "ACTIVE": print("Time took: {}".format(end - start)) break if status == "CREATE FAILED": print("Time took: {}".format(end - start)) print("Job Failed: {}".format(describe_job_response["batchSegmentJob"]["failureReason"])) break time.sleep(180) def import_dataset(job_name, dataset_arn, data_s3_url, role_arn): max_time = time.time() + 3 * 60 * 60 dataset_import_job_response = personalize.create_dataset_import_job( jobName = job_name, datasetArn = dataset_arn, dataSource = { "dataLocation": data_s3_url }, roleArn = role_arn ) dataset_import_job_arn = dataset_import_job_response['datasetImportJobArn'] print(dataset_import_job_arn) wait_for_dataset_import_job(dataset_import_job_arn)
その他複数のスキーマも作成しました
patient_schema = { "type": "record", "name": "Users", "namespace": "com.amazonaws.personalize.schema", "fields": [ { "name": "USER_ID", "type": "string" }, { "name": "gender", "type": "string", "categorical": True }, { "name": "age", "type": "int" }, { "name": "birthDate", "type": "string" }, { "name": "languageCode", "type": "string" }, { "name": "postalCode", "type": "string" } ], "version": "1.0" } create_schema_response = personalize.create_schema( name = "patient-schema-demo", schema = json.dumps(patient_schema) ) patient_schema_arn = create_schema_response['schemaArn'] print(patient_schema_arn)
interactions_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": "TIMESTAMP", "type": "long" } ], "version": "1.0" } create_schema_response = personalize.create_schema( name = "patient-interactions-schema-demo", schema = json.dumps(interactions_schema) ) interactions_schema_arn = create_schema_response['schemaArn'] print(interactions_schema_arn)
practitioners_schema = { "type": "record", "namespace": "com.amazonaws.personalize.schema", "version": "1.0", "name": "Items", "fields": [ { "name": "id", "type": [ "string", "null" ] }, { "name": "gender", "type": [ "string", "null" ] }, { "name": "givenName", "type": [ "string", "null" ] }, { "name": "postalCode", "type": [ "long", "null" ] }, { "name": "npi", "type": [ "long", "null" ] }, { "name": "practitionerRoleId", "type": [ "string", "null" ] }, { "name": "organizationName", "type": [ "string", "null" ] }, { "name": "specialtyCode", "type": [ "string", "null" ] }, { "name": "locationName", "type": [ "string", "null" ] }, { "name": "ITEM_ID", "type": [ "string" ] } ] } create_schema_response = personalize.create_schema( name = "practitioners-schema-demo", schema = json.dumps(practitioners_schema) ) practitioners_schema_arn = create_schema_response['schemaArn'] print(practitioners_schema_arn)
臨床データセットのインポート
続いて臨床データセットのインポートを行いました。
こちらもコマンドを打っていくだけなので、叩いたコマンドをトグルにまとめます。
データセットのインポート
create_dataset_group_response = personalize.create_dataset_group( name = "patient-personalize-demo" ) dataset_group_arn = create_dataset_group_response['datasetGroupArn'] print(dataset_group_arn) arn:aws:personalize:us-west-2:1234567890:dataset-group/patient-personalize-demo wait_for_dataset_group_job(dataset_group_arn) DatasetGroup: ACTIVE create_dataset_response = personalize.create_dataset( name = "interactions", datasetType = "INTERACTIONS", datasetGroupArn = dataset_group_arn, schemaArn = interactions_schema_arn ) interactions_dataset_arn = create_dataset_response['datasetArn'] print(interactions_dataset_arn) arn:aws:personalize:us-west-2:1234567890:dataset/patient-personalize-demo/INTERACTIONS create_dataset_response = personalize.create_dataset( name = "Patient", datasetType = "Users", datasetGroupArn = dataset_group_arn, schemaArn = patient_schema_arn ) patient_dataset_arn = create_dataset_response['datasetArn'] print(patient_dataset_arn) arn:aws:personalize:us-west-2:1234567890:dataset/patient-personalize-demo/USERS create_dataset_response = personalize.create_dataset( name = "Practitioner", datasetType = "Items", datasetGroupArn = dataset_group_arn, schemaArn = practitioners_schema_arn ) practitioners_dataset_arn = create_dataset_response['datasetArn'] print(practitioners_dataset_arn) arn:aws:personalize:us-west-2:1234567890:dataset/patient-personalize-demo/ITEMS with open('/opt/ml/metadata/resource-metadata.json') as notebook_info: data = json.load(notebook_info) resource_arn = data['ResourceArn'] region = resource_arn.split(':')[3] print(region) us-west-2 personalize_role_arn="arn:aws:iam::1234567890:role/workshop-PersonalizeRole-l6ksEH7Bzw43" interactions_filename="interactions.csv" practitioners_filename="practitioners_and_roles.csv" patients_filename="patients.csv" print("s3://"+bucket_name+"/"+interactions_filename) print(interactions_dataset_arn) interactions_s3_url = "s3://"+bucket_name+"/"+interactions_filename import_dataset("patient-interactions-import1", interactions_dataset_arn, interactions_s3_url, personalize_role_arn) s3://personalize-patient-data-workshop-e2b71230/interactions.csv arn:aws:personalize:us-west-2:1234567890:dataset/patient-personalize-demo/INTERACTIONS arn:aws:personalize:us-west-2:1234567890:dataset-import-job/patient-interactions-import1 DatasetImportJob: CREATE PENDING DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: ACTIVE print("loading from:"+"s3://"+bucket_name+"/"+practitioners_filename) print(practitioners_dataset_arn) practitioners_s3_url = "s3://"+bucket_name+"/"+practitioners_filename import_dataset("patient-personalize-practitioner-import", practitioners_dataset_arn, practitioners_s3_url, personalize_role_arn) loading from:s3://personalize-patient-data-workshop-e2b71230/practitioners_and_roles.csv arn:aws:personalize:us-west-2:1234567890:dataset/patient-personalize-demo/ITEMS arn:aws:personalize:us-west-2:1234567890:dataset-import-job/patient-personalize-practitioner-import DatasetImportJob: CREATE PENDING DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: ACTIVE print("loading from:"+"s3://"+bucket_name+"/"+practitioners_filename) print(patient_dataset_arn) patients_s3_url = "s3://"+bucket_name+"/"+patients_filename import_dataset("patient-personalize-patient-import", patient_dataset_arn, patients_s3_url, personalize_role_arn) loading from:s3://personalize-patient-data-workshop-e2b71230/practitioners_and_roles.csv arn:aws:personalize:us-west-2:1234567890:dataset/patient-personalize-demo/USERS arn:aws:personalize:us-west-2:1234567890:dataset-import-job/patient-personalize-patient-import DatasetImportJob: CREATE PENDING DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: CREATE IN_PROGRESS DatasetImportJob: ACTIVE
Amazon Personalizeのソリューションの作成
本手順のソリューション作成が40分程度かかる処理となっており、実施中にタイムアップとなり以降の手順はワークショップの中では進められませんでした。
CREATE IN_PROGRESS
のまま終わってしまい悲しみ。。。
ソリューションの作成
item_attri_user_recipe = 'arn:aws:personalize:::recipe/aws-item-attribute-affinity' create_solution_response = personalize.create_solution( name = "item-attr-affinity-amazon-patient-personalize-demo", datasetGroupArn = dataset_group_arn, recipeArn = item_attri_user_recipe, ) solution_arn = create_solution_response['solutionArn'] personalize.describe_solution(solutionArn = solution_arn) {'solution': {'name': 'item-attr-affinity-amazon-patient-personalize-demo', 'solutionArn': 'arn:aws:personalize:us-west-2:1234567890:solution/item-attr-affinity-amazon-patient-personalize-demo', 'performHPO': False, 'performAutoML': False, 'recipeArn': 'arn:aws:personalize:::recipe/aws-item-attribute-affinity', 'datasetGroupArn': 'arn:aws:personalize:us-west-2:1234567890:dataset-group/patient-personalize-demo', 'status': 'ACTIVE', 'creationDateTime': datetime.datetime(2023, 11, 30, 20, 38, 3, 863000, tzinfo=tzlocal()), 'lastUpdatedDateTime': datetime.datetime(2023, 11, 30, 20, 38, 3, 863000, tzinfo=tzlocal())}, 'ResponseMetadata': {'RequestId': '37c09dae-b737-4465-89dc-ce81339c93d8', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Thu, 30 Nov 2023 20:38:12 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '500', 'connection': 'keep-alive', 'x-amzn-requestid': '37c09dae-b737-4465-89dc-ce81339c93d8'}, 'RetryAttempts': 0}} create_solution_version_response = personalize.create_solution_version( solutionArn = solution_arn ) solution_version_arn = create_solution_version_response['solutionVersionArn'] print(solution_version_arn) arn:aws:personalize:us-west-2:1234567890:solution/item-attr-affinity-amazon-patient-personalize-demo/3a877218 wait_for_solution_version_job(solution_version_arn) SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS SolutionVersion: CREATE IN_PROGRESS
ここまでしかワークショップの中で手を動かして進められなかったのですが、この後は、
- ピンポイントプロジェクトを作成し、メールチャネルを構成
- S3からトリガーされるエンドポイントのインポート
- ユーザーセグメントをPinpointにインポート
- メールテンプレートを作成
- メールキャンペーンを作成
といった具合にワークショップが進む予定でした。残念。。。
おわりに
Jupyterノートブックのインターフェースを使ったり、Amazon Pinpointを使ったりと個人的に馴染みの少ないサービスで構成されたワークショップだったため、色々躓きはありましたが、めちゃくちゃ刺激になったワークショップでした。
ワークショップのゴールである「Amazon Pinpoint でセグメント内のユーザーに的を絞ったアウトリーチを実行」といった部分まで手を動かして進めることはできませんでしたが、何かの縁で関わったサービスですのでどこかでリベンジしたいと思います。