次の方法で共有


高度なエントリ スクリプトの作成

適用対象:Python SDK azureml v1

この記事では、特殊なユース ケースのエントリ スクリプトを記述する方法について説明します。

前提条件

この記事では、Azure Machine Learning を使用してデプロイする、トレーニングされた機械学習モデルが既にあることを前提としています。 モデルのデプロイの詳細については、「機械学習モデルを Azure にデプロイする」を参照してください。

Swagger スキーマを自動生成する

Web サービスのスキーマを自動生成するには、定義された型オブジェクトのいずれかのコンストラクターに入力や出力のサンプルを指定します。 型とサンプルはスキーマを自動作成するために使用されます。 その後、Azure Machine Learning によって、デプロイ中に、Web サービスの OpenAPI 仕様 (以前は Swagger 仕様) が作成されます。

警告

サンプルの入力または出力に、機密データまたはプライベート データを使用しないでください。 AML でホストされる推論の Swagger ページで、サンプル データが公開されてしまいます。

現在サポートされている型は次のとおりです。

  • pandas
  • numpy
  • pyspark
  • 標準的な Python オブジェクト

スキーマ生成を使用するには、オープンソースの inference-schema パッケージのバージョン 1.1.0 以上を依存関係ファイルに含めます。 このパッケージについて詳しくは、GitHub の推論スキーマ を参照してください。 自動 Web サービスの使用に向けて準拠する Swagger を生成するには、スコアリング スクリプトの run() 関数が次のような API の状態である必要があります。

  • 最初のパラメーターが Inputs という名前の StandardPythonParameterType 型で、入れ子になっている
  • オプションの 2 番目のパラメーターが GlobalParameters という名前の StandardPythonParameterType 型である
  • Results という名前の、入れ子になっている StandardPythonParameterType 型の辞書が返される

変数 input_sampleoutput_sample で入力と出力のサンプル形式を定義します。これは Web サービスの要求と応答の形式を表します。 これらのサンプルを、run() 関数の入力と出力の関数デコレーターで使用します。 下の scikit-learn の例では、スキーマ生成が使用されています。

Power BI 互換エンドポイント

次の例は、先述の指示に従って API の形状を定義する方法を示しています。 このメソッドは、Power BI からデプロイされた Web サービスを使用するためにサポートされています。

import json
import pickle
import numpy as np
import pandas as pd
import azureml.train.automl
from sklearn.externals import joblib
from sklearn.linear_model import Ridge

from inference_schema.schema_decorators import input_schema, output_schema
from inference_schema.parameter_types.standard_py_parameter_type import StandardPythonParameterType
from inference_schema.parameter_types.numpy_parameter_type import NumpyParameterType
from inference_schema.parameter_types.pandas_parameter_type import PandasParameterType


def init():
    global model
    # Replace filename if needed.
    model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'sklearn_regression_model.pkl')
    # Deserialize the model file back into a sklearn model.
    model = joblib.load(model_path)


# providing 3 sample inputs for schema generation
numpy_sample_input = NumpyParameterType(np.array([[1,2,3,4,5,6,7,8,9,10],[10,9,8,7,6,5,4,3,2,1]],dtype='float64'))
pandas_sample_input = PandasParameterType(pd.DataFrame({'name': ['Sarah', 'John'], 'age': [25, 26]}))
standard_sample_input = StandardPythonParameterType(0.0)

# This is a nested input sample, any item wrapped by `ParameterType` will be described by schema
sample_input = StandardPythonParameterType({'input1': numpy_sample_input, 
                                        'input2': pandas_sample_input, 
                                        'input3': standard_sample_input})

sample_global_parameters = StandardPythonParameterType(1.0) # this is optional
sample_output = StandardPythonParameterType([1.0, 1.0])
outputs = StandardPythonParameterType({'Results':sample_output}) # 'Results' is case sensitive

@input_schema('Inputs', sample_input) 
# 'Inputs' is case sensitive

@input_schema('GlobalParameters', sample_global_parameters) 
# this is optional, 'GlobalParameters' is case sensitive

@output_schema(outputs)

def run(Inputs, GlobalParameters): 
    # the parameters here have to match those in decorator, both 'Inputs' and 
    # 'GlobalParameters' here are case sensitive
    try:
        data = Inputs['input1']
        # data will be convert to target format
        assert isinstance(data, np.ndarray)
        result = model.predict(data)
        return result.tolist()
    except Exception as e:
        error = str(e)
        return error

ヒント

スクリプトからの戻り値には、JSON にシリアル化できる任意の Python オブジェクトを指定できます。 たとえば、モデルが複数の列を含む Pandas のデータフレームを返す場合、次のコードのような出力デコレータを使用できます。

output_sample = pd.DataFrame(data=[{"a1": 5, "a2": 6}])
@output_schema(PandasParameterType(output_sample))
...
result = model.predict(data)
return result

バイナリ (つまり、画像) データ

モデルがバイナリ データ (画像など) を受け入れる場合は、デプロイで使用される score.py ファイルを生の HTTP 要求を受け入れるように変更する必要があります。 生データを受け入れるには、エントリ スクリプトで AMLRequest クラスを使用して、@rawhttp デコレーターを run() 関数に追加します。

バイナリ データを受け付ける score.py の例を次に示します。

from azureml.contrib.services.aml_request import AMLRequest, rawhttp
from azureml.contrib.services.aml_response import AMLResponse
from PIL import Image
import json


def init():
    print("This is init()")
    

@rawhttp
def run(request):
    print("This is run()")
    
    if request.method == 'GET':
        # For this example, just return the URL for GETs.
        respBody = str.encode(request.full_path)
        return AMLResponse(respBody, 200)
    elif request.method == 'POST':
        file_bytes = request.files["image"]
        image = Image.open(file_bytes).convert('RGB')
        # For a real-world solution, you would load the data from reqBody
        # and send it to the model. Then return the response.

        # For demonstration purposes, this example just returns the size of the image as the response.
        return AMLResponse(json.dumps(image.size), 200)
    else:
        return AMLResponse("bad request", 500)

重要

AMLRequest クラスは azureml.contrib 名前空間にあります。 この名前空間内のエンティティは、このサービスが改善されるに従って頻繁に変更されます。 この名前空間内のものはすべて、Microsoft によって完全にはサポートされていないプレビューとして見なす必要があります。

これをローカルの開発環境でテストする必要がある場合は、次のコマンドを使用して、コンポーネントをインストールできます。

pip install azureml-contrib-services

注意

azureml-fe 側などのカスタム状態コードとして 500 は推奨されません。状態コードは 502 に書き換えられます。

  • 状態コードは azureml-fe 経由で渡され、次にクライアントに送信されます。
  • azureml-fe はモデル側から返された 500 のみを 502 に書き換え、クライアントは 502 を受信します。
  • ただし、azureml-fe 自体が 500 を返す場合、クライアント側は引き続き 500 を受信します。

AMLRequest クラスからは、score.py の未加工の投稿データにのみアクセスできます。クライアント側コンポーネントはありません。 クライアントからは、通常どおりにデータを投稿します。 たとえば、次の Python コードを使用すると、イメージ ファイルを読み取り、データを投稿することができます。

import requests

uri = service.scoring_uri
image_path = 'test.jpg'
files = {'image': open(image_path, 'rb').read()}
response = requests.post(uri, files=files)

print(response.json)

クロスオリジン リソース共有 (CORS)

クロスオリジン リソース共有を使用すると、Web ページ上のリソースを他のドメインから要求することができます。 CORS は、クライアント要求で送信され、サービス応答で返される HTTP ヘッダーを使用して機能します。 CORS と有効なヘッダーについて詳しくは、Wikipedia のクロスオリジン リソース共有に関する説明を参照してください。

CORS をサポートするようにモデル デプロイを構成するには、エントリ スクリプトで AMLResponse クラスを使用します。 このクラスを使用すると、応答オブジェクトにヘッダーを設定できます。

次の例では、エントリ スクリプトから応答に対して Access-Control-Allow-Origin ヘッダーを設定しています。

from azureml.contrib.services.aml_request import AMLRequest, rawhttp
from azureml.contrib.services.aml_response import AMLResponse


def init():
    print("This is init()")

@rawhttp
def run(request):
    print("This is run()")
    print("Request: [{0}]".format(request))
    if request.method == 'GET':
        # For this example, just return the URL for GET.
        # For a real-world solution, you would load the data from URL params or headers
        # and send it to the model. Then return the response.
        respBody = str.encode(request.full_path)
        resp = AMLResponse(respBody, 200)
        resp.headers["Allow"] = "OPTIONS, GET, POST"
        resp.headers["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST"
        resp.headers['Access-Control-Allow-Origin'] = "http://www.example.com"
        resp.headers['Access-Control-Allow-Headers'] = "*"
        return resp
    elif request.method == 'POST':
        reqBody = request.get_data(False)
        # For a real-world solution, you would load the data from reqBody
        # and send it to the model. Then return the response.
        resp = AMLResponse(reqBody, 200)
        resp.headers["Allow"] = "OPTIONS, GET, POST"
        resp.headers["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST"
        resp.headers['Access-Control-Allow-Origin'] = "http://www.example.com"
        resp.headers['Access-Control-Allow-Headers'] = "*"
        return resp
    elif request.method == 'OPTIONS':
        resp = AMLResponse("", 200)
        resp.headers["Allow"] = "OPTIONS, GET, POST"
        resp.headers["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST"
        resp.headers['Access-Control-Allow-Origin'] = "http://www.example.com"
        resp.headers['Access-Control-Allow-Headers'] = "*"
        return resp
    else:
        return AMLResponse("bad request", 400)

重要

AMLResponse クラスは azureml.contrib 名前空間にあります。 この名前空間内のエンティティは、このサービスが改善されるに従って頻繁に変更されます。 この名前空間内のものはすべて、Microsoft によって完全にはサポートされていないプレビューとして見なす必要があります。

これをローカルの開発環境でテストする必要がある場合は、次のコマンドを使用して、コンポーネントをインストールできます。

pip install azureml-contrib-services

警告

Azure Machine Learning では、スコア付けサービスを実行しているコンテナーに POST 要求と GET 要求のみがルーティングされます。 これにより、ブラウザーではプレフライト CORS 要求に対して OPTIONS 要求が使用されるため、エラーが発生する可能性があります。

登録済みモデルの読み込み

エントリ スクリプトでモデルを検索するには、次の 2 つの方法があります。

  • AZUREML_MODEL_DIR: モデルの場所へのパスを含む環境変数
  • Model.get_model_path: 登録されているモデル名を使用してモデル ファイルへのパスを返す API

AZUREML_MODEL_DIR

AZUREML_MODEL_DIR は、サービスのデプロイ中に作成される環境変数です。 この環境変数を使用して、デプロイされたモデルの場所を見つけることができます。

次の表では、デプロイされるモデルの数に応じて、AZUREML_MODEL_DIR の値について説明します。

展開 環境変数の値
単一モデル モデルを含むフォルダーへのパス。
複数のモデル すべてのモデルを含むフォルダーへのパス。 モデルは、このフォルダー ($MODEL_NAME/$VERSION) で名前とバージョンによって検索されます

モデルの登録とデプロイ中、モデルは AZUREML_MODEL_DIR パスに配置され、元のファイル名が保持されます。

エントリ スクリプト内のモデル ファイルへのパスを取得するには、環境変数と探しているファイル名を組み合わせます。

単一モデルの例

# Example when the model is a file
model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'sklearn_regression_model.pkl')

# Example when the model is a folder containing a file
file_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), 'my_model_folder', 'sklearn_regression_model.pkl')

複数のモデルの例

このシナリオでは、2 つのモデルがワークスペースに登録されます。

  • my_first_model: 1 つのファイル (my_first_model.pkl) が含まれており、バージョンは 1 の 1 つのみです
  • my_second_model: 1 つのファイル (my_second_model.pkl) が含まれており、 12の 2 つのバージョンがあります

サービスがデプロイされた場合、両方のモデルがデプロイ操作で提供されます。

first_model = Model(ws, name="my_first_model", version=1)
second_model = Model(ws, name="my_second_model", version=2)
service = Model.deploy(ws, "myservice", [first_model, second_model], inference_config, deployment_config)

サービスをホストする Docker イメージでは、AZUREML_MODEL_DIR 環境変数に、モデルが配置されているディレクトリが格納されます。 このディレクトリで、各モデルは MODEL_NAME/VERSION のディレクトリ パスに配置されます。 ここで、MODEL_NAME は登録されているモデルの名前で、VERSION はモデルのバージョンです。 登録されたモデルを構成するファイルは、これらのディレクトリに格納されます。

この例で、パスは $AZUREML_MODEL_DIR/my_first_model/1/my_first_model.pkl$AZUREML_MODEL_DIR/my_second_model/2/my_second_model.pkl です。

# Example when the model is a file, and the deployment contains multiple models
first_model_name = 'my_first_model'
first_model_version = '1'
first_model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), first_model_name, first_model_version, 'my_first_model.pkl')
second_model_name = 'my_second_model'
second_model_version = '2'
second_model_path = os.path.join(os.getenv('AZUREML_MODEL_DIR'), second_model_name, second_model_version, 'my_second_model.pkl')

get_model_path

モデルを登録するときに、レジストリ内のモデルを管理するために使用されるモデル名を指定します。 この名前は、ローカル ファイル システム上のモデル ファイルのパスを取得する Model.get_model_path() メソッドで使用します。 フォルダーまたはファイルのコレクションを登録した場合、この API では、これらのファイルを含むディレクトリのパスが返されます。

モデルを登録する場合は名前を指定します。 この名前は、モデルがローカルにデプロイされるか、またはサービスのデプロイ時に配置されるときの場所に対応します。

フレームワーク固有の例

特定の機械学習のユース ケースに関するその他のエントリ スクリプトの例については、以下の記事を参照してください。