本番環境のモデルを安全に更新 - Amazon SageMaker

本番環境のモデルを安全に更新

本番稼働用 ML ワークフローでは、データサイエンティストやエンジニアが、を使用して自動モデルチューニングを実行する SageMaker の実行、追加されたデータや最新のデータのトレーニング、機能選択の改善など、さまざまな方法で頻繁にモデルの改善を試みています。新しいモデルと本番稼働用トラフィックのある古いモデル間で A/B テストを実行することは、新しいモデルの検証プロセスの効果的な最終ステップとなります。A/B テストでは、モデルのさまざまなバリアントをテストし、各バリアントのパフォーマンスを比較します。モデルの新しいバージョンが、既存のバージョンよりも優れたパフォーマンスを発揮する場合は、モデルの古いバージョンを本番環境の新しいバージョンに置き換えます。

アマゾン SageMaker 本番稼働用バリアントを使用して、同じエンドポイントの背後にある複数のモデルまたはモデルバージョンをテストできます。各本番稼働用バリアントは、機械学習 (ML) モデルと、モデルをホストするためにデプロイされたリソースを識別します。本番稼働用バリアントを使用すると、異なるデータセットを使用してトレーニングされた ML モデル、異なるアルゴリズムと ML フレームワークを使用してトレーニングされた ML モデル、または異なるインスタンスタイプにデプロイされた ML モデル、またはこれらすべての組み合わせでテストできます。各バリアントにトラフィック分散を提供することで、エンドポイント呼び出し要求を複数の本番バリアントに分散できます。または、リクエストごとに特定のバリアントを直接呼び出すこともできます。このトピックでは、ML モデルをテストするための両方の方法について説明します。

トラフィック分散を指定してモデルをテストする

複数のモデル間でトラフィックを分散してテストするには、エンドポイント構成の各本番稼働用バリアントの重みを指定して、各モデルにルーティングされるトラフィックの割合を指定します。詳細については、 を参照してください。CreateEndpointConfig。次の図は、この詳しい仕組みを示しています。

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

リクエストごとに特定のモデルを呼び出して複数のモデルをテストするには、以下の値を入力して呼び出す特定のモデルバージョンを指定します。TargetVariant呼び出し時のパラメータInvokeEndpoint。 SageMaker 指定した本番稼働用バリアントを使用してリクエストが処理されるようにします。すでにトラフィック分散を指定し、 TargetVariant パラメータに値を指定している場合、ターゲットルーティングによってランダムなトラフィック分散が上書きされます。次の図は、この詳しい仕組みを示しています。

モデル A/B テスト例

次の例は、A/B モデルのテストの実行方法を示しています。この例を実装したサンプルノートブックについては、 「本番環境での A/B テスト ML モデル」を参照してください。

ステップ 1: モデルの作成とデプロイ

まず、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 を 1 に設定します。これにより、リクエストの 50% が Variant1 に送信され、残りの 50% が Variant2 に送信されます。両方のバリアントの重みの合計は 2 で、各バリアントの重みの割り当ては 1 です。つまり、各バリアントは合計トラフィックの 1/2 (50%) を受信することになります。

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=1) variant2 = production_variant(model_name=model_name2, instance_type="ml.m5.xlarge", initial_instance_count=1, variant_name='Variant2', initial_weight=1)

これで、これらの本番稼働用バリアントを 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] )

ステップ 2: デプロイされたモデルを呼び出す

今度は、このエンドポイントにリクエストを送信して、リアルタイムで推論を取得します。トラフィック分散と直接ターゲティングの両方を使用します。

まず、前のステップで構成したトラフィック分散を使用します。各推論応答には、要求を処理する本番稼働用バリアントの名前が含まれているため、2 つの本番稼働用バリアントへのトラフィックはほぼ等しいことがわかります。

# 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!")

SageMaker 次のようなメトリクスを出力しますLatencyそしてInvocationsAmazonの各バリエーションについて CloudWatch。以下のようなメトリクスの詳しいリストについては SageMaker 放出、見るAmazon Logs An SageMaker アマゾンと CloudWatch。クエリーしてみよう CloudWatch バリアント間で呼び出しを分割するデフォルトの方法を表示します。

次に、TargetVariant の呼び出しで Variant1invoke_endpoint としてを指定することで、モデルの特定のバージョンを呼び出します。

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"*) # Notice the new parameter time.sleep(0.5)

すべての新しい呼び出しがによって処理されたことを確認するにはVariant1、クエリできます CloudWatch バリアントごとの呼び出し数を取得します。最新の呼び出し (最新のタイムスタンプ) では、指定されたようにすべてのリクエストが Variant1 によって処理されたことがわかります。Variant2 に対する呼び出しはありませんでした。

ステップ 3: モデルのパフォーマンスの評価

どのモデルバージョンがより優れたパフォーマンスを発揮するかを確認するために、各バリアントの曲線の下にある正確性、精度、リコール、F1 スコア、レシーバー動作特性/領域を評価します。まず、Variant1 のメトリクスを見てみましょう。

次に、Variant2 のメトリクスを見てみましょう。

定義済みのメトリクスのほとんどについて、Variant2 のパフォーマンスが向上しているため、本番環境で使用するのはこれになります。

ステップ 4: トラフィックを最適なモデルに増やす

Variant2 のパフォーマンスが Variant1 より優れていると判断したため、より多くのトラフィックをそちらにシフトさせます。引き続き使用できますTargetVariant特定のモデルバリアントを呼び出すことができますが、より簡単なアプローチは、各バリアントに割り当てられた重みを更新する方法ですUpdateEndpointWeightsAndCapacities。これにより、エンドポイントを更新することなく、運用バリアントへのトラフィック分散が変更されます。セットアップセクションから、トラフィックを 50/50 に分割するようバリアントの重みを設定したことを思い出してください。- CloudWatch 以下の各バリアントの合計呼び出し回数のメトリクスは、各バリアントの呼び出しパターンを示しています。

今度は、トラフィックの 75% をVariant2を使用して各バリアントに新しい重みを割り当てることによってUpdateEndpointWeightsAndCapacities。 SageMaker 推論リクエストの 75% が次の宛先に送信されるようになりましたVariant2残りの 25% のリクエストはVariant1

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

- CloudWatch 各バリアントの合計呼び出し回数のメトリクスは、Variant2条件はVariant1:

引き続きメトリクスを監視し、バリアントのパフォーマンスに満足したら、トラフィックの 100% をそのバリアントにルーティングできます。UpdateEndpointWeightsAndCapacities を使用して、バリアントのトラフィック割り当てを更新します。分量Variant1が 0 に設定されていること。Variant2が、1 に設定されていること。 SageMaker すべての推論リクエストの 100% をVariant2

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

- CloudWatch 各バリアントの合計呼び出し回数のメトリクスは、すべての推論リクエストをVariant2また、推論リクエストは処理されていませんVariant1

これで、エンドポイントを安全に更新し、エンドポイントから Variant1 を削除できるようになりました。エンドポイントに新しいバリアントを追加し、ステップ 2 ~ 4 を実行することで、本番環境での新しいモデルのテストを続行することもできます。