Amazon Web Services ブログ

Amazon SageMaker を使用して本番稼働で ML モデルの A/B テストを行う



完全マネージドサービスの Amazon SageMaker では、開発者やデータサイエンティストが機械学習 (ML) モデルを迅速に構築、トレーニング、デプロイできます。Intuit、Voodoo、ADP、Cerner、Dow Jones、Thompson Reuters をはじめとする何万人ものお客様が、Amazon SageMaker を使って ML プロセスで発生する負担の大部分を解消しています。Amazon SageMaker を使用すれば、ホストされたエンドポイントに ML モデルをデプロイし、リアルタイムに推論結果を取得できます。エンドポイントのパフォーマンスメトリクスは Amazon CloudWatch で簡単に表示でき、自動スケーリングを有効化することで、トラフィックに基づいて自動的にエンドポイントのスケールを調整できるほか、可用性を失うことなく本番稼働でモデルを更新できます。

e コマースアプリケーションなど、多くの場合、オフラインでのモデル評価では不十分であり、モデル更新の意思決定をする前に、本番稼働でモデルの A/B テストを実施する必要があります。Amazon SageMaker を使用すると、エンドポイントで複数の本番稼働用バリアントを実行することで、ML モデル上で A/B テストを簡単に実施できます。本番稼働用バリアントを使用することで、さまざまなトレーニングデータセット、アルゴリズム、および ML フレームワークを使用してトレーニングされた ML モデルをテストしたり、異なるインスタンスタイプのモデルの振る舞いをテストしたり、あるいは上記すべてを組み合わせたテストを行うことができます。

今までの場合、Amazon SageMaker は、ユーザーがエンドポイント上の各バリアントに指定した配分に基づいて、バリアント間の推論トラフィックを分割していました。これは、各バリアントにどれくらいのトラフィックを送るかを制御する必要がある一方で、リクエストを特定のバリアントにルーティングする必要がない場合に役立ちます。たとえば、本番稼働でモデルを更新して、トラフィックの一部を新規モデルに転送することで、既存のモデルと比較する必要がある場合などです。しかしユースケースによっては、特定のモデルで推論リクエストを処理して、特定のバリアントを呼び出す必要があります。たとえば、異なるカスタマーセグメントをまたいで ML モデルがどのような振る舞いをするかをテストおよび比較し、あるセグメントのカスタマーからのリクエストすべてを、特定のバリアントを用いて処理する必要がある場合などです。

今では、どのバリアントで推論リクエストを処理するかを選べるようになりました。各推論リクエストで TargetVariant ヘッダーを指定することで、Amazon SageMaker は指定されたバリアントでリクエストを処理します。

ユースケース: Amazon Alexa

Amazon Alexa では、Amazon SageMaker を使用して、さまざまな ML ワークロードを管理しています。Amazon Alexa チームは、新しいセキュリティ脅威に先んじるため、ML モデルを頻繁に更新しています。そのため、チームは新しいモデルバージョンを本番稼働にリリースする前に、Amazon SageMaker のモデルテスト機能を使用して、どのバージョンがセキュリティ、プライバシー、ビジネス上のニーズを満たしているかをテストし、比較し、見極めています。お客様のセキュリティとプライバシーを守るためにチームが実施しているリサーチの詳細については、「Leveraging Hierarchical Representations for Preserving Privacy and Utility in Text (階層表現を活用してテキスト内のプライバシーとユーティリティを保護する)」をご参照ください。

Alexa エクスペリエンスおよびデバイス部門のソフトウェア開発マネージャーである Nathanael Teissier 氏は次のように述べています。「Amazon SageMaker のモデルテスト機能により、当社のプライバシー保護モデルの新バージョンをテストできました。すべてがお客様のプライバシー保護に向けた高い水準を満たしています。」「各リクエストで希望する本番稼働用バリアントを選択する新機能は、既存の設定を変更することなしに A/B テストを行うデプロイ戦略への新たな可能性を開きました。」

この記事では、バリアントにトラフィックを分散し、特定のバリアントを呼び出すことにより、Amazon SageMaker で ML モデルの A/B テストを簡単に実施する方法について説明します。私たちがテストするモデルは、さまざまなトレーニングデータセットを使用してトレーニングされ、Amazon SageMaker エンドポイント上に本番稼働用バリアントとしてデプロイされたものです。

Amazon SageMaker を使用した A/B テスト

本番稼働の ML ワークフローにおいて、データサイエンティストとエンジニアはしばしば、ハイパーパラメータチューニングの実行、追加的またはより最近のデータを使用したトレーニング、または機能選択の改善など、色々な方法でモデルの改良を試みています。本番稼働トラフィックを使用して新規モデルと旧モデルの A/B テストを実施することは、新規モデルの検証プロセスにおける効果的な最終ステップです。A/B テストでは、モデルのさまざまなバリアントをテストし、各バリアントの振る舞いを互いに比較します。新バージョンのパフォーマンスが既存のバージョン以上であれば、旧モデルと置き換えます。

Amazon SageMaker は、同じエンドポイントで本番稼働用バリアントを使用して複数のモデルまたはモデルバージョンをテストすることを可能にします。各 ProductionVariant は、ML モデルやモデルのホスト用にデプロイされたリソースを識別します。複数の本番稼働用バリアント間でエンドポイントに呼び出しリクエストを配信するには、各バリアントに対してトラフィック分配をするか、各リクエストに対して直接バリアントを呼び出します。次のセクションでは、これら両方の ML モデルテスト方法について見ていきます。

トラフィックをバリアントに分配してモデルをテストする

トラフィックを複数のモデル間で分配することで複数のモデルをテストにするには、エンドポイント設定でそれぞれの本番稼働用バリアントに対する重みを指定することで、各モデルにルーティングするトラフィックの割合を指定します。Amazon SageMaker は、指定した重みに基づいて、本番稼働用バリアント間にトラフィックを分配します。これが、本番稼働用バリアントを使用する際のデフォルト動作です。次の図は、この仕組みをより詳細に示しています。各推論の応答は、リクエストを処理したバリアントの名前も含んでいます。

特定のバリアントを呼び出してモデルをテストする

各リクエストに対して特定のモデルを呼び出すことで複数のモデルをテストするには、リクエストに TargetVariant ヘッダーを設定します。重みを指定してトラフィックの分配比率を設定し、かつ TargetVariant を指定した場合、ターゲットされたルーティングがトラフィック分配より優先されます。次の図は、この仕組みをより詳細に示しています。この例の場合、推論リクエストに対して ProductionVariant3 を呼び出します。さらに、各リクエストに対してさまざまなバリアントを同時に呼び出すことができます。

ソリューションの概要

この記事は、この新機能の使用方法について、例を通じて説明します。Amazon SageMaker の Jupyter ノートブックを使用して、2つのモデルをホストするエンドポイントを作成します (ProductionVariantを使用)。両モデルとも、モバイル業者解約率予測のためのデータセットに基づいて、XGBoost アルゴリズムで構築された Amazon SageMaker を使用してトレーニングされています。モデルのトレーニング方法に関する詳細については、「Customer Churn Prediction with XGBoost (XGBoost を用いた顧客解約率予測)」 を参照してください。次のユースケースでは、同じデータセットの異なるサブセットに基づいて各モデルをトレーニングし、各モデルに対して異なるバージョンの XGBoost アルゴリズムを使用しました。

これらのアクティビティをご自分で試すには、サンプル「A/B Testing with Amazon SageMaker’ Jupyter Notebook (Amazon SageMaker の Jupyter Notebook を使用した A/B テスト)」をご使用ください。 Amazon SageMaker Studio または Amazon SageMaker ノートブックインスタンスのいずれかで実行できます。使用するデータセットは公開されており、Daniel T. Larose 著の『Discovering Knowledge in Data』で言及されています。著者により、データセットは、カリフォルニア大学アーバイン校の機械学習データセットリポジトリに帰属しています。

説明は、次のステップからなります。

  1. モデルを作成し、デプロイする
  2. デプロイしたモデルを呼び出す
  3. バリアントのパフォーマンスを評価する
  4. 推論トラフィックを本番稼働の選択済みバリアントにダイヤルアップ接続する

モデルを作成し、デプロイする

まず、Amazon Simple Storage Service (Amazon S3) でモデルをどこに配置するかを定義します。後続のステップでモデルをデプロイする際には、ここで定義した場所を使用します。次のコードを参照してください。

model_url = f"s3://{path_to_model_1}"
model_url2 = f"s3://{path_to_model_2}"

次に、コンテナイメージとモデルデータを使用してモデルオブジェクトを作成します。これらのモデルオブジェクトを使用して、エンドポイントの本番稼働用バリアントにデプロイします。ML モデルをさまざまなデータセット、アルゴリズム、ML フレームワーク、ハイパーパラメータに基づいてトレーニングすることで、モデルを開発できます。次のコードを参照してください。

from sagemaker.amazon.amazon_estimator import get_image_uri

model_name = f"DEMO-xgb-churn-pred-{datetime.now():%Y-%m-%d-%H-%M-%S}"
model_name2 = f"DEMO-xgb-churn-pred2-{datetime.now():%Y-%m-%d-%H-%M-%S}"
image_uri = get_image_uri(boto3.Session().region_name, 'xgboost', '0.90-1')
image_uri2 = get_image_uri(boto3.Session().region_name, 'xgboost', '0.90-2')

sm_session.create_model(name=model_name, role=role, container_defs={
    'Image': image_uri,
    'ModelDataUrl': model_url
})

sm_session.create_model(name=model_name2, role=role, container_defs={
    'Image': image_uri2,
    'ModelDataUrl': model_url2
})

2 つの本番稼働用バリアントを、それぞれ固有のモデル要件とリソース要件 (インスタンスのタイプと数) を用いて作成します。送信されたリクエストをバリアント間で均等に分割するには、両方のバリアントの initial_weight 0.5 に設定します。次のコードを参照してください。

from sagemaker.session import production_variant

variant1 = production_variant(model_name=model_name,
                              instance_type="ml.m5.xlarge",
                              initial_instance_count=1,
                              variant_name='Variant1',
                              initial_weight=0.5)
                              
variant2 = production_variant(model_name=model_name2,
                              instance_type="ml.m5.xlarge",
                              initial_instance_count=1,
                              variant_name='Variant2',
                              initial_weight=0.5)

次のコードを使用して、Amazon SageMaker エンドポイントに本番稼働用バリアントをデプロイします。

endpoint_name = f"DEMO-xgb-churn-pred-{datetime.now():%Y-%m-%d-%H-%M-%S}"
print(f"EndpointName={endpoint_name}")

sm_session.endpoint_from_production_variants(
    name=endpoint_name,
    production_variants=[variant1, variant2]
)

デプロイしたモデルを呼び出す

これで、データをこのエンドポイントに送信し、リアルタイムに推論を取得できます。この記事の場合、Amazon SageMaker でサポートされているモデルのテストに関して、バリアントにトラフィックを分配する方法と、特定のバリアントを呼び出す方法といった両方のアプローチを使用します。

トラフィックをバリアントに分配する

Amazon SageMaker は、前のバリアント定義で設定した個々の重みに基づいて、エンドポイントの本番稼働用バリアント間でトラフィックを分配します。エンドコードの呼び出しには次のコードを使用します。

# get a subset of test data for a quick test
!tail -120 test_data/test-dataset-input-cols.csv > test_data/test_sample_tail_input_cols.csv
print(f"Sending test traffic to the endpoint {endpoint_name}. \nPlease wait...")

with open('test_data/test_sample_tail_input_cols.csv', 'r') as f:
    for row in f:
        print(".", end="", flush=True)
        payload = row.rstrip('\n')
        sm_runtime.invoke_endpoint(EndpointName=endpoint_name,
                                   ContentType="text/csv",
                                   Body=payload)
        time.sleep(0.5)
        
print("Done!")        

Amazon SageMaker は、Amazon CloudWatch の各バリアントのレイテンシーや呼び出し数といったメトリクスを出力します。エンドポイントメトリクスの全リストについては、「Monitor Amazon SageMaker with Amazon CloudWatch (Amazon CloudWatch を使用して Amazon SageMaker を監視する)」を参照してください。Amazon CloudWatch をクエリすると、バリアントごとの呼び出し数を取得でき、バリアント全体でデフォルトの呼び出しがどのように分割されているかが分かります。ほぼ次のグラフのような結果になるはずです。

特定のバリアントを呼び出す

次のユースケースでは、新しい Amazon SageMaker バリアント機能を使用して、特定のバリアントを呼び出します。これを実行するには、新しいパラメータを使用して、どの ProductionVariant を呼び出すかを定義します。次のコードは、すべてのリクエストに対して Variant1 を呼び出します。他のバリアントの呼び出しについても同じプロセスを使用できます。

print(f"Sending test traffic to the endpoint {endpoint_name}. \nPlease wait...")
with open('test_data/test_sample_tail_input_cols.csv', 'r') as f:
    for row in f:
        print(".", end="", flush=True)
        payload = row.rstrip('\n')
        sm_runtime.invoke_endpoint(EndpointName=endpoint_name,
                                   ContentType="text/csv",
                                   Body=payload,
                                   TargetVariant="Variant1") # <- Note new parameter
        time.sleep(0.5)

Variant1ですべての新規呼び出しが処理されていることを確認するには、CloudWatch をクエリして、バリアントごとの呼び出しの数を取得します。次のグラフは、最近の呼び出し (タイムスタンプが最新) において、Variant1 がすべてのリクエストを処理したことを示しています。Variant2 に対する呼び出しはありませんでした。

バリアントのパフォーマンスを評価する

次のグラフは、Variant1 の正確性、精度、リコール、F1 スコア、および ROC/AUC を評価しています。

次のグラフは、Variant2 による推論に対して同じメトリクスを評価しています。

Variant2 のパフォーマンスは、定義済みメトリックスのほとんどに対して優れているため、本番稼働の推論トラフィックのサービスを向上させるにはこちらを選択することになるでしょう。

 

推論トラフィックを本番稼働の選択済みバリアントにダイヤルアップ接続する

これで Variant2Variant1 よりも優れていることが判明したので、Variant2 へのトラフィックを増やします。

選択したバリアントの呼び出しには、引き続き TargetVariant を使用できます。よりシンプルな方法として、UpdateEndpointWeightsAndCapacities を使用して各バリアントに割り当てられた重みを更新することもできます。これにより、エンドポイントを更新する必要なしに、本番稼働用バリアントへのトラフィックの分配を変更できます。

モデルの作成とエンドポイントの設定時に、バリアントの重みを指定してトラフィックを 50/50 に分割するシナリオを考えてみましょう。各バリアントの合計呼び出し数に関する下記の CloudWatch メトリクスは、各バリアントの呼び出しパターンを示します。

トラフィックの 75% を Variant2 にシフトするには、UpdateEndpointWeightsAndCapacities を使用して各バリアントに新たに重みを割り当てます。次のコードを参照してください。

sm.update_endpoint_weights_and_capacities(
    EndpointName=endpoint_name,
    DesiredWeightsAndCapacities=[
        {
            "DesiredWeight": 0.25,
            "VariantName": variant1["VariantName"]
        },
        {
            "DesiredWeight": 0.75,
            "VariantName": variant2["VariantName"]
        }
    ]
)

これにより、Amazon SageMaker はインスタンスリクエストの 75% を Variant2 に送信し、残りの 25% を Variant1 に送信します。

各バリアントの合計呼び出し数に関する下記の CloudWatch メトリクスでは、Variant1 よりも Variant2 の呼び出し数の方が高くなります。

メトリクスの監視を続け、バリアントのパフォーマンスに満足できたら、トラフィックの 100% をルーティングできます。このユースケースの場合、UpdateEndpointWeightsAndCapacities を使用してバリアントに対するトラフィックの割り当てを更新しました。Variant1 に対する重みは 0.0Variant2 に対する重みは 1.0 に設定されています。よって、Amazon SageMaker はすべてのインスタンスリクエストの 100% を Variant2 に送信します。次のコードを参照してください。

sm.update_endpoint_weights_and_capacities(
    EndpointName=endpoint_name,
    DesiredWeightsAndCapacities=[
        {
            "DesiredWeight": 0.0,
            "VariantName": variant1["VariantName"]
        },
        {
            "DesiredWeight": 1.0,
            "VariantName": variant2["VariantName"]
        }
    ]
)

各バリアントの合計呼び出し数に関する下記の CloudWatch メトリクスは、Variant2 によってすべての推論リクエストが処理されており、Variant1 によって処理されている推論リクエストがないことを示しています。

これで、エンドポイントを安全に更新し、Variant1 をエンドポイントから削除できます。エンドポイントに新しいバリアントを追加し、同じ手順を繰り返すことで、本番稼働で引き続き新しいモデルをテストすることもできます。

まとめ

Amazon SageMaker は、エンドポイント上で複数の本稼働用バリアントを実行することで、本番稼働で簡単に ML モデルの A/B テストを実行することを可能にします。SageMaker の機能を使用することで、さまざまなトレーニングデータセット、ハイパーパラメータ、アルゴリズム、または ML フレームワークを使用してトレーニングされたモデルをテストしたり、異なるインスタンスタイプ上でのモデルの振る舞いをテストしたり、あるいは上記すべてを組み合わせたテストを行うことができます。Amazon SageMaker は、エンドポイント上の各バリアントに対してユーザーが指定した配分に基づいて、バリアント間の推論トラフィックを分割していました。今では、特定のカスタマーセグメントに関してモデルをテストする場合、推論リクエストを処理するバリアントを TargetVariant ヘッダーを使用して指定できるようになりました。これにより Amazon SageMaker は指定されたリクエストにバリアントをルーティングします。A/B テストに関する詳細については、「AWS Developer Guide: Test models in production (AWS 開発者ガイド: 本番稼働でモデルをテストする)」 をご参照ください。


著者について

Kieran Kavanagh は、アマゾン ウェブ サービスのプリンシパルソリューションアーキテクトです。お客様と協力しながら AWS 技術ソリューションの設計と構築に携わっているほか、機械学習にも強い関心を持っています。余暇には、ハイキング、スノーボード、武道の練習を楽しんでいます。

 

 

 

Aakash Pydi は、Amazon SageMaker チームのソフトウェア開発エンジニアです。機械学習ワークフローを効率よく生産的にできるよう開発者を支援することに情熱を傾けています。余暇には、読書 (SF、経済、歴史、哲学)、ゲーム (リアルタイム戦略)、長時間のおしゃべりを楽しんでいます。

 

 

 

David Nigenda は、Amazon SageMaker チームのソフトウェア開発エンジニアです。現在は、本番稼働機械学習ワークフローに関する有用な見識を提供する仕事に集中しています。余暇には子供たちと遊んでいます。