Azure Machine Learningを用いたモデルの学習とコンテナへのデプロイ

Azure Machine LearningはAzureマネージドの機械学習環境です。Notebookベースでの開発はもちろんのこと、自動で機械学習を行うAutoMLやドラッグ＆ドロップで機械学習が可能なデザイナー(Azure Machine Learning Studio(Classic)のようなもの)も提供しています。データセットやモデルだけではなく、Experimentや学習パイプラインをワークスペースに登録することができ、管理が非常にしやすい環境です。
コンピューティング環境としてもNotebook用VM、学習用クラスタ(CPU/GPU)、推論用クラスタ(ACI/AKS)、外部の計算リソース(Databricksクラスタなど)も作成・登録が可能です。

機械学習に関しては若干AzureML固有の要素があるものの、基本的にはScikit-Learnが使えれば問題ないと思ったのですが、デプロイ周りが分からなかったのでMSLearnをベースに纏めようと思います。

# 本編
まずはワークスペースと接続するおまじないの部分

```python
import azureml.core
from azureml.core import Workspace

# Load the workspace from the saved config file
ws = Workspace.from_config()
print('Ready to use Azure ML {} to work with {}'.format(azureml.core.VERSION, ws.name))
```
学習・モデルの登録

```python
from azureml.core import Experiment
from azureml.core import Model
import pandas as pd
import numpy as np
import joblib
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve

# Create an Azure ML experiment in your workspace
experiment = Experiment(workspace = ws, name = "diabetes-training")
run = experiment.start_logging()
print("Starting experiment:", experiment.name)

# load the diabetes dataset
print("Loading Data...")
diabetes = pd.read_csv('data/diabetes.csv')

# Separate features and labels
X, y = diabetes[['Pregnancies','PlasmaGlucose','DiastolicBloodPressure','TricepsThickness','SerumInsulin','BMI','DiabetesPedigree','Age']].values, diabetes['Diabetic'].values

# Split data into training set and test set
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=0)

# Train a decision tree model
print('Training a decision tree model')
model = DecisionTreeClassifier().fit(X_train, y_train)

# calculate accuracy
y_hat = model.predict(X_test)
acc = np.average(y_hat == y_test)
print('Accuracy:', acc)
run.log('Accuracy', np.float(acc))

# calculate AUC
y_scores = model.predict_proba(X_test)
auc = roc_auc_score(y_test,y_scores[:,1])
print('AUC: ' + str(auc))
run.log('AUC', np.float(auc))

# Save the trained model
model_file = 'diabetes_model.pkl'
joblib.dump(value=model, filename=model_file)
run.upload_file(name = 'outputs/' + model_file, path_or_stream = './' + model_file)

# Complete the run
run.complete()

# Register the model->outputs/diabetes_model.pklに保存
run.register_model(model_path='outputs/diabetes_model.pkl', model_name='diabetes_model',
                   tags={'Training context':'Inline Training'},
                   properties={'AUC': run.get_metrics()['AUC'], 'Accuracy': run.get_metrics()['Accuracy']})

print('Model trained and registered.')
```
モデルオブジェクトをnotebookの変数に格納します

```python
model = ws.models['diabetes_model']
print(model.name, 'version', model.version)
```
Webサービスとして展開する際のConfigファイル群を格納するディレクトリを作成

```python
import os

folder_name = 'diabetes_service'

# Create a folder for the web service files
experiment_folder = './' + folder_name
os.makedirs(folder_name, exist_ok=True)

print(folder_name, 'folder created.')
```
スコアリング用のスクリプトを生成。私もAzure Machine Learningを利用して初めて知りましたが、`%%writefile`というマジックコマンドはセルの中身をファイルで書き出してくれます。Azure MLではEstimatorやPipelineの中のStepでスクリプトを呼び出す場面が多いので非常に便利です。

```python
%%writefile $folder_name/score_diabetes.py
import json
import joblib
import numpy as np
from azureml.core.model import Model

# Called when the service is loaded⇒コンテナにデプロイされた初回にモデルを読み込む部分
def init():
    global model
    # Get the path to the deployed model file and load it
    model_path = Model.get_model_path('diabetes_model')
    model = joblib.load(model_path)

# Called when a request is received⇒リクエストを受けた場合に推論する部分
# ここでreturnした内容がinputされたデータ(request)に対して返される
def run(raw_data):
    # Get the input data as a numpy array
    data = np.array(json.loads(raw_data)['data'])
    # Get a prediction from the model⇒結果としては0,1の配列が帰ってくる
    predictions = model.predict(data)
    # Get the corresponding classname for each prediction (0 or 1)
    classnames = ['not-diabetic', 'diabetic']
    predicted_classes = []
    for prediction in predictions:
        predicted_classes.append(classnames[prediction])
    # Return the predictions as JSON
    return json.dumps(predicted_classes)
```
Notebookが動いているVM(学習環境)と同じ環境を作成するためのymlファイルを作成します。

```python
# このノートブックを動かしているVM環境を書き出す→デプロイ先のEnvスクリプトとして渡す
from azureml.core.conda_dependencies import CondaDependencies 

# Add the dependencies for our model (AzureML defaults is already included)
myenv = CondaDependencies()
myenv.add_conda_package("scikit-learn")

# Save the environment config as a .yml file
env_file = folder_name + "/diabetes_env.yml"
with open(env_file,"w") as f:
    f.write(myenv.serialize_to_string())
print("Saved dependency info in", env_file)

# Print the .yml file
with open(env_file,"r") as f:
    print(f.read())
```
ACIにデプロイします。ここで指定している`service_name`はエンドポイントのタブから参照する際のサービスIDになります。ACIのクラスタを作成する必要はないようです。じゃあいつ作るんだろうという疑問は残っています。

```python
from azureml.core.webservice import AciWebservice
from azureml.core.model import InferenceConfig

# Configure the scoring environment
inference_config = InferenceConfig(runtime= "python",
                                   source_directory = folder_name,
                                   entry_script="score_diabetes.py",
                                   conda_file="diabetes_env.yml")

deployment_config = AciWebservice.deploy_configuration(cpu_cores = 1, memory_gb = 1)

service_name = "diabetes-service"

service = Model.deploy(ws, service_name, [model], inference_config, deployment_config)

service.wait_for_deployment(True)
print(service.state)
```
デプロイが成功したかどうかの確認。正常であればHealthyと出力されます。

```python
print(service.state)
print(service.get_logs())
```

# デプロイしたサービスにリクエストを投げてみる
実際に利用してみます。複数人の特徴量をリストで渡してあげます。

AzureML SDKから利用する場合は以下のようなコードになります。この場合、serviceとしてコードの中であらかじめ対象のサービスに接続している必要があります。

```python
import json

# This time our input is an array of two feature arrays
x_new = [[2,180,74,24,21,23.9091702,1.488172308,22],
         [0,148,58,11,179,39.19207553,0.160829008,45]]

# Convert the array or arrays to a serializable list in a JSON document
input_json = json.dumps({"data": x_new})

# Call the web service, passing the input data
predictions = service.run(input_data = input_json)

# Get the predicted classes.
predicted_classes = json.loads(predictions)
   
for i in range(len(x_new)):
    print ("Patient {}".format(x_new[i]), predicted_classes[i] )
# output-sample> Patient [0, 148, 58, 11, 179, 39.19207553, 0.160829008, 45] not-diabetic
```
実際には、Azure ML SDK経由ではなく、REST APIとして業務アプリケーション側からリクエストが飛んでくることが主流だと思います。その場合は、[エンドポイント]タブからサービスのエンドポイントを取得します。

```python
# endpoint = service.scoring_uri
endpoint = <your service endpoint uri>
print(endpoint)
```
取得したエンドポイントに対して`requests`ライブラリの`post`を利用してペイロードを送信します。結果を確認すると、スコアリングスクリプトの`run()`関数で構成した形で結果が返ってきていることが分かります。

```python
import requests
import json

x_new = [[2,180,74,24,21,23.9091702,1.488172308,22],
         [0,148,58,11,179,39.19207553,0.160829008,45]]

# Convert the array to a serializable list in a JSON document
input_json = json.dumps({"data": x_new})

# Set the content type
headers = { 'Content-Type':'application/json' }

predictions = requests.post(endpoint, input_json, headers = headers)
predicted_classes = json.loads(predictions.json())

for i in range(len(x_new)):
    print ("Patient {}".format(x_new[i]), predicted_classes[i] )
# output-sample> Patient [0, 148, 58, 11, 179, 39.19207553, 0.160829008, 45] not-diabetic
```

# まとめ
モデルの学習からACIへのサービスとしてのデプロイまでを確認しました。詳細に関してはこちらのリンクからご確認ください。https://github.com/MicrosoftDocs/mslearn-aml-labs/blob/master/06-Deploying_a_model.ipynb
Azure ML上で学習したモデル以外でも基本的には.pklなどの形式で保存されたPythonのオブジェクトであればAzure MLに読み込ませてデプロイが可能です。学習済みのモデルが手元にある場合は試してみてもよいかもしれません。