📚

Amazon Personalizeの導入検証

はじめに

とあるミーティングで協調フィルタリングを利用できないかという話がありました。
会社としてAWSを利用していることもあり、Amazon Personalizeの導入を検証してみようということとなりましたので、検証内容について記事にしようと思います。

Amazon Personalizeとは

Amazon Personalize は、データを使用してユーザーにアイテムのレコメンデーションを生成する、フルマネージド型の機械学習サービスです。
また、特定のアイテムやアイテムメタデータに対するユーザーの親和性に基づいてユーザーセグメントを生成することもできます。

Amazon Personalizeの構成について

構成についてはインフラチームの方にご協力いただき、以下の構成で検証しました。

  • 使用スキーマ
    Item Interactions

  • 使用レシピ
    User-Personalization-v2

準備するデータ

  • 入力データ
    後述のインポート時に受け渡すデータ

    • ユーザの行動ログ等から、特定のIDに対する情報を抽出してS3上へ保存
    • 検証に用いた入力データ(CSV)
      • データ件数:344,332件
        ※ 1,000件以上のデータが必要となり、1,000件未満の場合はインポート時にエラーになるとのこと
        USER_ID,ITEM_ID,TIMESTAMP,EVENT_TYPE
        00013033-****-****-****-************,29247,1734439815,false
        0001658c-****-****-****-************,29250,1730724322,true
        0001658c-****-****-****-************,29282,1731056266,true
        0001658c-****-****-****-************,29285,1736252405,true
        00019794-****-****-****-************,29237,1723642819,false
        :
        
  • 対象ユーザデータ
    後述のバッチ推論ジョブに受け渡すデータ

    • 直近の一定期間にログインしているユーザIDリストを抽出してS3上へ保存
    • 検証に用いたデータ(JSON)
      • データ件数:101,573件
        {"userId": "00013033-****-****-****-************"}
        {"userId": "0001658c-****-****-****-************"}
        {"userId": "000184e0-****-****-****-************"}
        {"userId": "00019794-****-****-****-************"}
        {"userId": "00022b9a-****-****-****-************"}
        :
        

実装方法

Amazon Personalizeの各段階単位でrakeタスクを作成しました。

  • インポート
    上述の入力データをS3から取得し、Amazon Personalizeのインポートジョブを開始。

    • 検証用コード
      job_name = ::Amazon::Personalize.import_job_name(input_time)
      data_location = ::Amazon::S3.personalize_import_data_location(input_time)
      
      puts "Amazon Personalizeへのデータセットインポート(#{job_name})を実行します"
      
      response = ::Amazon::Personalize.client.create_dataset_import_job(
        job_name: job_name,
        dataset_arn: ::Amazon::Personalize::DATASET_ARN,
        data_source: {
          data_location: data_location,
        },
        role_arn: ::Amazon::Personalize::ROLE_ARN,
      )
      
  • ソリューションバージョン
    インポートされた入力データを元にトレーニングを開始。
    ※ 20分〜48時間掛かるとのこと

    • 検証用コード
      solution_name = ::Amazon::Personalize.solution_name(input_time)
      
      puts "Amazon Personalizeのソリューションバージョン(#{solution_name})を作成します"
      
      response = ::Amazon::Personalize.client.create_solution_version(
        name: solution_name,
        solution_arn: ::Amazon::Personalize::SOLUTION_ARN,
      )
      
  • バッチ推論ジョブ
    前述の対象ユーザIDリストをS3から取得し、Amazon Personalizeのバッチ推論ジョブを開始。
    num_resultsで、推奨アイテムの出力件数を指定可能
    ※ 出力結果をS3へ保存

    • 検証用コード
      job_name = ::Amazon::Personalize.batch_job_name(input_time)
      solution_version_arn = ::Amazon::Personalize.solution_version_arn(input_time)
      s3_data_source_path = ::Amazon::S3.personalize_input_data_location(input_time)
      s3_data_destination_path = ::Amazon::S3.personalize_output_data_location(input_time)
      
      puts "Amazon Personalizeのバッチ推論ジョブ(#{job_name})を実行します"
      
      response = ::Amazon::Personalize.client.create_batch_inference_job(
        job_name: job_name,
        solution_version_arn: solution_version_arn,
        num_results: ::Amazon::Personalize::BATCH_INFERENCE_RESULT_NUM,
        job_input: {
          s3_data_source: {
            path: s3_data_source_path,
          },
        },
        job_output: {
          s3_data_destination: {
            path: s3_data_destination_path,
          },
        },
        role_arn: ::Amazon::Personalize::ROLE_ARN,
      )
      

検証方法

実装したrakeタスクを各段階単位毎に、Step Functionsを使用して実行検証を行いました。

  • Step Functions入力内容

    • インポート
      所要時間:5分程度
      {
        "BatchName": "personalize_test",
        "ContainerOverrides": [
          {
            "Name": "rails",
            "Command": [
              "bundle exec rake 'amazon_personalize:import[20241217]'"
            ]
          }
        ]
      }
      
    • ソリューションバージョン
      所要時間:35分程度
      {
        "BatchName": "personalize_test",
        "ContainerOverrides": [
          {
            "Name": "rails",
            "Command": [
              "bundle exec rake 'amazon_personalize:solution_version[20241217]'"
            ]
          }
        ]
      }
      
    • バッチ推論ジョブ
      所要時間:25分程度
      {
        "BatchName": "personalize_test",
        "ContainerOverrides": [
          {
            "Name": "rails",
            "Command": [
              "bundle exec rake 'amazon_personalize:batch[20241217]'"
            ]
          }
        ]
      }
      
  • バッチ推論ジョブ出力結果(JSON)
    output > recommendedItemsが推奨アイテム、output > scoresが推奨スコアとなっているようです。

    {"input":{"userId":"0012eac7-****-****-****-************"},"output":{"recommendedItems":["29250","29244","29248","29138","29285","29253","21111","29246","29085","29087","29247","29198","20081","29140","21031","29288","21021","20091","21071","21040"],"scores":[0.136713,0.1022388,0.0640918,0.0460043,0.0425726,0.0405212,0.0331288,0.0330632,0.0278411,0.0205003,0.0196259,0.0159225,0.0144948,0.0140418,0.0137584,0.0134812,0.0121707,0.0111479,0.0110803,0.0108602],"reason":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]},"error":null}
    {"input":{"userId":"0020d17f-****-****-****-************"},"output":{"recommendedItems":["29246","29253","21031","29285","29247","29138","20081","21021","29087","29140","29198","21040","20101","20091","21071","21011","20112","29288","21052","22061"],"scores":[0.1068694,0.0636964,0.0509201,0.0402247,0.035403,0.0351684,0.0343339,0.0320063,0.0290015,0.0245361,0.0196966,0.0189302,0.0179869,0.0177468,0.0171735,0.0169165,0.0166385,0.0145626,0.0142752,0.0139428],"reason":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]},"error":null}
    {"input":{"userId":"003e9762-****-****-****-************"},"output":{"recommendedItems":["21021","29198","20091","20092","29253","29250","29138","20101","21040","29247","29132","29150","21052","29140","21071","21011","21031","20111","22021","29215"],"scores":[0.0354327,0.0348429,0.0325985,0.0312761,0.0273405,0.0266878,0.0263825,0.0258054,0.0256752,0.0251981,0.0242595,0.0241931,0.0226157,0.0210119,0.0195839,0.0194613,0.0175042,0.0173113,0.0171496,0.0163496],"reason":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]},"error":null}
    {"input":{"userId":"0049530b-****-****-****-************"},"output":{"recommendedItems":["29250","29244","29237","29248","29253","29285","29138","29246","21111","29087","29085","29198","29247","20081","29288","29140","21031","29195","21021","20091"],"scores":[0.129704,0.0673726,0.0628911,0.0529978,0.0481487,0.0437304,0.0437122,0.0375579,0.0357287,0.02612,0.0223392,0.0181387,0.0176589,0.0154694,0.0144906,0.0138976,0.0128613,0.0122286,0.0119781,0.0109675],"reason":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]},"error":null}
    {"input":{"userId":"00515afa-****-****-****-************"},"output":{"recommendedItems":["29282","29250","29237","29138","29248","29285","29244","29246","29253","21111","29085","29087","29198","29247","20081","29140","29288","21031","20091","21021"],"scores":[0.1248266,0.1040329,0.0687805,0.0403912,0.0355089,0.0339318,0.032356,0.0293623,0.0292881,0.0286942,0.0261286,0.0228931,0.0210295,0.0205433,0.0168924,0.0156077,0.0146812,0.0129214,0.0126407,0.0125027],"reason":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null]},"error":null}
    :
    

定期実行

実際にアプリ導入を考える際に、EventBridgeスケジューラを使用してStep Functionsを定期実行する等が考えられそうです。

つまづいた点

  • 検証に用いる入力データ量
    少なくとも数千件程度のデータが欲しかったが、開発環境では行動ログの量が少なく…予定していた取得範囲を拡大しないと足りなかった。
  • ローカル環境からの実行確認
    Step Functionsから実行するには、開発環境へデプロイする必要があり…事前に処理が正しいか確認(ローカル環境から実行)する際、S3やPersonalizeへのアクセスキーを発行していただく必要があった。
    ※ Step Functionsからの実行時はIAMロールにアクセス権限を付与する形となっており、アクセスキーは不要となっていた

まとめ

今回の検証で、入力データ抽出〜バッチ推論ジョブ出力結果を取得することが出来ました。
所感としては、「ユーザが何らかのIDに対してアクションする」という条件下であれば、割と幅広く使用できるのかなと思いました。
実際に導入する際には、バッチ推論ジョブ出力結果をアプリ内(RDS等)へ反映する必要があります。
また、料金面もあるため…以下の点等について、各プロジェクトや仕様毎に考慮が必要になるかと思います。

・トレーニング対象となる入力データ抽出範囲(期間)
・インポート〜バッチ推論ジョブ出力取得頻度
・etc...

最後まで読んでいただき、ありがとうございます。
何かしらの参考になれば幸いです。

GitHubで編集を提案
Happy Elements

Discussion