Azure Machine Learning を使用して scikit-learn モデルを大規模にトレーニングする

適用対象: Python SDK azure-ai-ml v2 (現行)

この記事では、Azure Machine Learning Python SDK v2 を使って Scikit-learn トレーニング スクリプトを実行する方法について説明します。

この記事のサンプル スクリプトを使用し、アイリスの花の画像を分類して、scikit-learn のアイリス データセットに基づく機械学習モデルを構築します。

scikit-learn の機械学習モデルを一からトレーニングする場合でも、既存のモデルをクラウドに持ち込む場合でも、Azure Machine Learning のエラスティック クラウド コンピューティング リソースを使用して、オープンソースのトレーニング ジョブをスケールアウトできます。 Azure Machine Learning を使用して、運用レベルのモデルをビルド、デプロイ、バージョン管理、および監視することができます。

前提条件

この記事のコードは、Azure Machine Learning コンピューティング インスタンスまたは独自の Jupyter Notebook のいずれかで実行できます。

  • Azure Machine Learning コンピューティング インスタンス

    • 利用を開始するためのリソースの作成を実行して、コンピューティング インスタンスを作成します。 すべてのコンピューティング インスタンスには、SDK と notebook サンプル リポジトリにあらかじめ読み込まれている専用の notebook サーバーが含まれています。
    • Azure Machine Learning スタジオの [notebook] タブを選択します。 samples トレーニング用フォルダーで、v2 > sdk > jobs > single-step > scikit-learn > train-hyperparameter-tune-deploy-with-sklearn の順に選んでこのディレクトリに移動し、完了済みで展開済みのノートブックを見つけます。
    • サンプルのトレーニング用フォルダーの事前設定されたコードを使用すると、このチュートリアルを完了できます。
  • お使いの Jupyter Notebook サーバー。

ジョブをセットアップする

このセクションでは、必要な Python パッケージを読み込み、ワークスペースに接続し、コマンド ジョブを実行するコンピューティング リソースを作成し、ジョブを実行する環境を作成することで、トレーニング用のジョブを設定します。

ワークスペースに接続する

まず、Azure Machine Learning ワークスペースに接続する必要があります。 Azure Machine Learning ワークスペースは、サービス用の最上位のリソースです。 Azure Machine Learning を使用するときに作成する、すべての成果物を操作するための一元的な場所が提供されます。

ここでは、DefaultAzureCredential を使ってワークスペースへのアクセスを取得しています。 この資格情報を使うと、ほとんどの Azure SDK 認証シナリオに対応できます。

DefaultAzureCredential でうまくいかない場合は、azure-identity reference documentation または Set up authentication で他の使用可能な資格情報を確認してください。

# Handle to the workspace
from azure.ai.ml import MLClient

# Authentication package
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()

ブラウザーを使ってサインインと認証を行う方がよい場合は、次のコードのコメントを削除し、代わりにそれを使う必要があります。

# Handle to the workspace
# from azure.ai.ml import MLClient

# Authentication package
# from azure.identity import InteractiveBrowserCredential
# credential = InteractiveBrowserCredential()

次に、サブスクリプション ID、リソース グループ名、ワークスペース名を指定して、ワークスペースへのハンドルを取得します。 これらのパラメーターを見つけるには:

  1. Azure Machine Learning スタジオのツール バーの右上隅で、ワークスペース名を見つけます。
  2. ワークスペース名を選択して、リソース グループとサブスクリプション ID を表示します。
  3. リソース グループとサブスクリプション ID の値をコードにコピーします。
# Get a handle to the workspace
ml_client = MLClient(
    credential=credential,
    subscription_id="<SUBSCRIPTION_ID>",
    resource_group_name="<RESOURCE_GROUP>",
    workspace_name="<AML_WORKSPACE_NAME>",
)

このスクリプトの実行結果は、他のリソースとジョブを管理するために使用するワークスペース ハンドルになります。

Note

MLClient を作成しても、クライアントはワークスペースに接続されません。 クライアントの初期化は遅延型なので、初めて呼び出す必要が生じるときまで待機します。 この記事では、これはコンピューティングの作成時に発生します。

ジョブを実行するコンピューティング リソースを作成する

Azure Machine Learning には、ジョブを実行するためのコンピューティング リソースが必要です。 このリソースには、Linux または Windows OS の単一または複数ノードのマシン、または Spark などの特定のコンピューティング ファブリックを指定できます。

次のスクリプト例では、Linux compute cluster をプロビジョニングします。 VM のサイズと価格の完全な一覧については、Azure Machine Learning pricing ページを参照してください。 この例で必要なのは、基本的なクラスターのみです。そのため、2 つの仮想 CPU コアと 7 GB の RAM がある Standard_DS3_v2 モデルを選択して、Azure Machine Learning コンピューティングを作成します。

from azure.ai.ml.entities import AmlCompute

# Name assigned to the compute cluster
cpu_compute_target = "cpu-cluster"

try:
    # let's see if the compute target already exists
    cpu_cluster = ml_client.compute.get(cpu_compute_target)
    print(
        f"You already have a cluster named {cpu_compute_target}, we'll reuse it as is."
    )

except Exception:
    print("Creating a new cpu compute target...")

    # Let's create the Azure ML compute object with the intended parameters
    cpu_cluster = AmlCompute(
        name=cpu_compute_target,
        # Azure ML Compute is the on-demand VM service
        type="amlcompute",
        # VM Family
        size="STANDARD_DS3_V2",
        # Minimum running nodes when there is no job running
        min_instances=0,
        # Nodes in cluster
        max_instances=4,
        # How many seconds will the node running after the job termination
        idle_time_before_scale_down=180,
        # Dedicated or LowPriority. The latter is cheaper but there is a chance of job termination
        tier="Dedicated",
    )

    # Now, we pass the object to MLClient's create_or_update method
    cpu_cluster = ml_client.compute.begin_create_or_update(cpu_cluster).result()

print(
    f"AMLCompute with name {cpu_cluster.name} is created, the compute size is {cpu_cluster.size}"
)

ジョブ環境を作成する

Azure Machine Learning ジョブを実行するには、環境が必要です。 Azure Machine Learning 環境では、コンピューティング リソースで機械学習トレーニング スクリプトを実行するために必要な依存関係 (ソフトウェア ランタイムやライブラリなど) がカプセル化されます。 この環境は、ローカル コンピューター上の Python 環境に似ています。

Azure Machine Learning では、キュレーションされた (または既製の) 環境を使うか、Docker イメージまたは Conda 構成を使ってカスタム環境を作成できます。 この記事では、Conda の YAML ファイルを使って、ジョブ用のカスタム環境を作ります。

カスタム環境を作成する

カスタム環境を作るには、YAML ファイルで Conda の依存関係を定義します。 まず、ファイルを格納するためのディレクトリを作ります。 この例では、そのディレクトリの名前を env にします。

import os

dependencies_dir = "./env"
os.makedirs(dependencies_dir, exist_ok=True)

次に、依存関係ディレクトリにファイルを作成します。 この例では、そのファイルの名前を conda.yml にします。

%%writefile {dependencies_dir}/conda.yaml
name: sklearn-env
channels:
  - conda-forge
dependencies:
  - python=3.8
  - pip=21.2.4
  - scikit-learn=0.24.2
  - scipy=1.7.1
  - pip:  
    - mlflow== 1.26.1
    - azureml-mlflow==1.42.0
    - mlflow-skinny==2.3.2

この仕様には、ジョブで使ういくつかの通常のパッケージ (numpy や pip など) が含まれています。

次に、YAML ファイルを使ってこのカスタム環境を作成し、ワークスペースに登録します。 環境は、実行時に Docker コンテナーにパッケージ化されます。

from azure.ai.ml.entities import Environment

custom_env_name = "sklearn-env"

job_env = Environment(
    name=custom_env_name,
    description="Custom environment for sklearn image classification",
    conda_file=os.path.join(dependencies_dir, "conda.yaml"),
    image="mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04:latest",
)
job_env = ml_client.environments.create_or_update(job_env)

print(
    f"Environment with name {job_env.name} is registered to workspace, the environment version is {job_env.version}"
)

環境の作成と使用の詳細については、「Azure Machine Learning でソフトウェア環境を作成して使用する」をご覧ください。

[オプション] Intel® Extension for Scikit-Learn を使用してカスタム環境を作成する

Intel ハードウェアで scikit-learn スクリプトを高速化したいですか? Intel® Extension for Scikit-Learn を conda yaml ファイルに追加し、上記の後続の手順に従ってみてください。 これらの最適化を有効にする方法については、この例の後半で説明します:

%%writefile {dependencies_dir}/conda.yaml
name: sklearn-env
channels:
  - conda-forge
dependencies:
  - python=3.8
  - pip=21.2.4
  - scikit-learn=0.24.2
  - scikit-learn-intelex
  - scipy=1.7.1
  - pip:  
    - mlflow== 1.26.1
    - azureml-mlflow==1.42.0
    - mlflow-skinny==2.3.2

トレーニング ジョブを構成して送信する

このセクションでは、提供したトレーニング スクリプトを使用して、トレーニング ジョブを実行する方法について説明します。 最初に、トレーニング スクリプトを実行するためのコマンドを構成して、トレーニング ジョブを作成します。 次に、トレーニング ジョブを送信して Azure Machine Learning で実行します。

トレーニング スクリプトを準備する

この記事では、トレーニング スクリプト train_iris.py が用意されています。 実際には、コードを変更しなくても、あらゆるカスタム トレーニング スクリプトをそのまま Azure Machine Learning で実行できるはずです。

Note

提供されているトレーニング スクリプトは次の処理を実行します。

  • Azure Machine Learning の実行に対するいくつかのメトリックをログする方法を示します。
  • iris = datasets.load_iris() を使ってトレーニング データをダウンロードして抽出します。
  • モデルをトレーニングしてから、保存して登録します。

独自のデータを使ってアクセスするには、トレーニング中にデータを使用できるようにするためのジョブでのデータの読み取りと書き込み方法に関する記事を参照してください。

トレーニング スクリプトを使うには、最初にファイルを格納するディレクトリを作ります。

import os

src_dir = "./src"
os.makedirs(src_dir, exist_ok=True)

次に、ソース ディレクトリにスクリプト ファイルを作ります。

%%writefile {src_dir}/train_iris.py
# Modified from https://www.geeksforgeeks.org/multiclass-classification-using-scikit-learn/

import argparse
import os

# importing necessary libraries
import numpy as np

from sklearn import datasets
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split

import joblib

import mlflow
import mlflow.sklearn

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('--kernel', type=str, default='linear',
                        help='Kernel type to be used in the algorithm')
    parser.add_argument('--penalty', type=float, default=1.0,
                        help='Penalty parameter of the error term')

    # Start Logging
    mlflow.start_run()

    # enable autologging
    mlflow.sklearn.autolog()

    args = parser.parse_args()
    mlflow.log_param('Kernel type', str(args.kernel))
    mlflow.log_metric('Penalty', float(args.penalty))

    # loading the iris dataset
    iris = datasets.load_iris()

    # X -> features, y -> label
    X = iris.data
    y = iris.target

    # dividing X, y into train and test data
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

    # training a linear SVM classifier
    from sklearn.svm import SVC
    svm_model_linear = SVC(kernel=args.kernel, C=args.penalty)
    svm_model_linear = svm_model_linear.fit(X_train, y_train)
    svm_predictions = svm_model_linear.predict(X_test)

    # model accuracy for X_test
    accuracy = svm_model_linear.score(X_test, y_test)
    print('Accuracy of SVM classifier on test set: {:.2f}'.format(accuracy))
    mlflow.log_metric('Accuracy', float(accuracy))
    # creating a confusion matrix
    cm = confusion_matrix(y_test, svm_predictions)
    print(cm)

    registered_model_name="sklearn-iris-flower-classify-model"

    ##########################
    #<save and register model>
    ##########################
    # Registering the model to the workspace
    print("Registering the model via MLFlow")
    mlflow.sklearn.log_model(
        sk_model=svm_model_linear,
        registered_model_name=registered_model_name,
        artifact_path=registered_model_name
    )

    # # Saving the model to a file
    print("Saving the model via MLFlow")
    mlflow.sklearn.save_model(
        sk_model=svm_model_linear,
        path=os.path.join(registered_model_name, "trained_model"),
    )
    ###########################
    #</save and register model>
    ###########################
    mlflow.end_run()

if __name__ == '__main__':
    main()

[オプション] Intel® ハードウェアでパフォーマンスを向上させるために Scikit-Learn 最適化用の Intel 拡張機能を有効にする

(前のセクションで示したように) Intel® Extension for Scikit-Learn をインストールしている場合は、次に示すように、スクリプト ファイルの先頭に 2 行のコードを追加することで、パフォーマンスの最適化を有効にすることができます。

Scikit-Learn 用 Intel® 拡張機能の詳細については、パッケージの ドキュメント を参照してください。

%%writefile {src_dir}/train_iris.py
# Modified from https://www.geeksforgeeks.org/multiclass-classification-using-scikit-learn/

import argparse
import os

# Import and enable Intel Extension for Scikit-learn optimizations
# where possible

from sklearnex import patch_sklearn
patch_sklearn()

# importing necessary libraries
import numpy as np


from sklearn import datasets
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import train_test_split

import joblib

import mlflow
import mlflow.sklearn

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('--kernel', type=str, default='linear',
                        help='Kernel type to be used in the algorithm')
    parser.add_argument('--penalty', type=float, default=1.0,
                        help='Penalty parameter of the error term')

    # Start Logging
    mlflow.start_run()

    # enable autologging
    mlflow.sklearn.autolog()

    args = parser.parse_args()
    mlflow.log_param('Kernel type', str(args.kernel))
    mlflow.log_metric('Penalty', float(args.penalty))

    # loading the iris dataset
    iris = datasets.load_iris()

    # X -> features, y -> label
    X = iris.data
    y = iris.target

    # dividing X, y into train and test data
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

    # training a linear SVM classifier
    from sklearn.svm import SVC
    svm_model_linear = SVC(kernel=args.kernel, C=args.penalty)
    svm_model_linear = svm_model_linear.fit(X_train, y_train)
    svm_predictions = svm_model_linear.predict(X_test)

    # model accuracy for X_test
    accuracy = svm_model_linear.score(X_test, y_test)
    print('Accuracy of SVM classifier on test set: {:.2f}'.format(accuracy))
    mlflow.log_metric('Accuracy', float(accuracy))
    # creating a confusion matrix
    cm = confusion_matrix(y_test, svm_predictions)
    print(cm)

    registered_model_name="sklearn-iris-flower-classify-model"

    ##########################
    #<save and register model>
    ##########################
    # Registering the model to the workspace
    print("Registering the model via MLFlow")
    mlflow.sklearn.log_model(
        sk_model=svm_model_linear,
        registered_model_name=registered_model_name,
        artifact_path=registered_model_name
    )

    # # Saving the model to a file
    print("Saving the model via MLFlow")
    mlflow.sklearn.save_model(
        sk_model=svm_model_linear,
        path=os.path.join(registered_model_name, "trained_model"),
    )
    ###########################
    #</save and register model>
    ###########################
    mlflow.end_run()

if __name__ == '__main__':
    main()

トレーニング ジョブを作成する

ジョブの実行に必要なすべての資産が揃ったので、次は Azure Machine Learning Python SDK v2 を使ってジョブを作成します。 そのためには、command を作成します。

Azure Machine Learning command は、クラウドでトレーニング コードを実行するために必要なすべての詳細を指定するリソースです。 これらの詳細には、入力と出力、使用するハードウェアの種類、インストールするソフトウェア、コードの実行方法が含まれます。 command には、1 つのコマンドを実行するための情報が含まれています。

コマンドを構成する

汎用 command を使用してトレーニング スクリプトを実行し、目的のタスクを実行します。 トレーニング ジョブの構成の詳細を指定する Command オブジェクトを作成します。

  • このコマンドの入力には、エポックの数、学習速度、モメンタム、出力ディレクトリが含まれます。
  • パラメーター値については次のとおりです。
    • このコマンドを実行するために作成したコンピューティング クラスター cpu_compute_target = "cpu-cluster" を指定します。
    • Azure Machine Learning ジョブを実行するために作成したカスタム環境 sklearn-env を指定します。
    • コマンド ライン アクション自体を構成します。この場合、コマンドは python train_iris.py です。 コマンドの入力と出力には ${{ ... }} 表記法を使用してアクセスできます。
    • 表示名や実験名などのメタデータを構成します。実験とは、特定のプロジェクトで行われるすべての反復のコンテナーです。 同じ実験名で送信されたすべてのジョブは、Azure Machine Learning スタジオ上で隣り合わせに表示されます。
from azure.ai.ml import command
from azure.ai.ml import Input

job = command(
    inputs=dict(kernel="linear", penalty=1.0),
    compute=cpu_compute_target,
    environment=f"{job_env.name}:{job_env.version}",
    code="./src/",
    command="python train_iris.py --kernel ${{inputs.kernel}} --penalty ${{inputs.penalty}}",
    experiment_name="sklearn-iris-flowers",
    display_name="sklearn-classify-iris-flower-images",
)

ジョブを送信する

次に、Azure Machine Learning で実行するジョブを送信します。 今回は ml_client.jobs に対して create_or_update を使います。

ml_client.jobs.create_or_update(job)

完了すると、ジョブは (トレーニングの結果として) モデルをワークスペースに登録し、Azure Machine Learning スタジオでジョブを表示するためのリンクを出力します。

警告

Azure Machine Learning では、ソース ディレクトリ全体をコピーすることで、トレーニング スクリプトが実行されます。 アップロードしたくない機密データがある場合は、.ignore ファイルを使用するか、ソース ディレクトリに含めないようにします。

ジョブ実行中の動作

ジョブの実行は、以下の段階を経て実施されます。

  • 準備:Docker イメージは、定義されている環境に従って作成されます。 イメージはワークスペースのコンテナー レジストリにアップロードされ、後で実行するためにキャッシュされます。 ログは実行履歴にもストリーミングされ、進行状況を監視するために表示することができます。 キュレーションされた環境が指定されている場合は、そのキュレーションされた環境を補足するキャッシュ済みのイメージが使用されます。

  • スケーリング: クラスターでの実行に現在使用可能な数より多くのノードが必要な場合、クラスターはスケールアップを試みます。

  • 実行中: スクリプト フォルダー src 内のすべてのスクリプトがコンピューティング先にアップロードされ、データ ストアがマウントまたはコピーされて、スクリプトが実行されます。 stdout からの出力と ./logs フォルダーが実行履歴にストリーミングされるので、実行の監視のために使用できます。

モデルのハイパーパラメーターをチューニングする

これで、SDK を使って簡単な Scikit-learn トレーニングを実行する方法を確認したので、モデルの精度をさらに向上させることができるかどうかを見てみましょう。 Azure Machine Learning の sweep 機能を使って、モデルのハイパーパラメーターをチューニングして最適化できます。

モデルのハイパーパラメーターを調整するには、トレーニング中に検索するパラメーター空間を定義します。 これを行うには、トレーニング ジョブに渡されるパラメーターの一部 (kernelpenalty) を azure.ml.sweep パッケージからの特別な入力に置き換えます。

from azure.ai.ml.sweep import Choice

# we will reuse the command_job created before. we call it as a function so that we can apply inputs
# we do not apply the 'iris_csv' input again -- we will just use what was already defined earlier
job_for_sweep = job(
    kernel=Choice(values=["linear", "rbf", "poly", "sigmoid"]),
    penalty=Choice(values=[0.5, 1, 1.5]),
)

次に、監視する主要メトリックや使用するサンプリング アルゴリズムなど、いくつかのスイープ固有のパラメーターを使用して、コマンド ジョブのスイープを構成します。

次のコードでは、ランダム サンプリングを使って、主要なメトリック Accuracy を最大化するために、ハイパーパラメーターのさまざまな構成セットを試します。

sweep_job = job_for_sweep.sweep(
    compute="cpu-cluster",
    sampling_algorithm="random",
    primary_metric="Accuracy",
    goal="Maximize",
    max_total_trials=12,
    max_concurrent_trials=4,
)

これで、以前と同様にこのジョブを送信できます。 今回は、トレーニング ジョブをスイープするスイープ ジョブを実行します。

returned_sweep_job = ml_client.create_or_update(sweep_job)

# stream the output and wait until the job is finished
ml_client.jobs.stream(returned_sweep_job.name)

# refresh the latest status of the job after streaming
returned_sweep_job = ml_client.jobs.get(name=returned_sweep_job.name)

ジョブの実行中に表示されるスタジオ ユーザー インターフェイス リンクを使用して、ジョブを監視できます。

最適なモデルを見つけて登録する

すべての実行が完了すると、最も高い精度でモデルを生成した実行を見つけることができます。

from azure.ai.ml.entities import Model

if returned_sweep_job.status == "Completed":

    # First let us get the run which gave us the best result
    best_run = returned_sweep_job.properties["best_child_run_id"]

    # lets get the model from this run
    model = Model(
        # the script stores the model as "sklearn-iris-flower-classify-model"
        path="azureml://jobs/{}/outputs/artifacts/paths/sklearn-iris-flower-classify-model/".format(
            best_run
        ),
        name="run-model-example",
        description="Model created from run.",
        type="custom_model",
    )

else:
    print(
        "Sweep job status: {}. Please wait until it completes".format(
            returned_sweep_job.status
        )
    )

その後、このモデルを登録できます。

registered_model = ml_client.models.create_or_update(model=model)

モデルをデプロイする

モデルは、登録後に、Azure Machine Learning に登録されている他のモデルと同じ方法でデプロイできます。 デプロイの詳細については、「Python SDK v2 を使用して、マネージド オンライン エンドポイントに機械学習モデルをデプロイしてスコアリングする」を参照してください。

次のステップ

この記事では、Scikit-learn モデルをトレーニングして登録し、デプロイ オプションについて説明しました。 Azure Machine Learning の詳細については、以下の他の記事をご覧ください。