[ワークショップ] Amazon Pinpointで的を絞ったアウトリーチを実行するワークショップに参加してきた #IMP214 #AWSreInvent

2023.12.01

大阪オフィスの林です。 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 でセグメント内のユーザーに的を絞ったアウトリーチを実行」といった部分まで手を動かして進めることはできませんでしたが、何かの縁で関わったサービスですのでどこかでリベンジしたいと思います。