Créer et exécuter des pipelines Machine Learning à l’aide de composants avec le SDK Azure Machine Learning v2

S’APPLIQUE À : Kit de développement logiciel (SDK) Python azure-ai-mlv2 (préversion)

Dans cet article, vous allez apprendre à créer un pipeline Azure Machine Learning à l’aide du SDK Python v2 pour effectuer une tâche de classification d’images contenant trois étapes : préparation des données, entraînement d’un modèle de classification d’images et scoring du modèle. Les pipelines Machine Learning optimisent votre workflow à divers niveaux : vitesse, portabilité et réutilisation. Ainsi, vous pouvez vous concentrer sur le Machine Learning, plutôt que sur l’infrastructure et l’automatisation.

L’exemple entraîne un petit réseau neuronal convolutif Keras pour classifier les images dans le jeu de données Fashion MNIST. Le pipeline ressemble à ce qui suit.

Screenshot showing pipeline graph of the image classification Keras example.

Dans cet article, vous allez effectuer les tâches suivantes :

  • Préparer les données d’entrée pour le travail du pipeline
  • Créer trois composants pour préparer les données, entraîner et scorer
  • Composer un pipeline à partir des composants
  • Obtenir l’accès à l’espace de travail avec un calcul
  • Envoyer le travail du pipeline
  • Passer en revue la sortie des composants et du réseau neuronal entraîné
  • (Facultatif) Inscrire le composant pour pouvoir le réutiliser et le partager dans l’espace de travail

Si vous n’avez pas d’abonnement Azure, créez un compte gratuit avant de commencer. Essayez la version gratuite ou payante d’Azure Machine Learning dès aujourd’hui.

Prérequis

  • Espace de travail Azure Machine Learning (si vous n’avez pas d’espace de travail, suivez le tutoriel Créer des ressources).

  • Un environnement Python dans lequel vous avez installé le kit SDK Azure Machine Learning Python v2. Pour obtenir les instructions d’installation, consultez la section Bien démarrer. Cet environnement est destiné à la définition et au contrôle de vos ressources Azure Machine Learning et est séparé de l’environnement utilisé lors de l’exécution pour l’entraînement.

  • Cloner un dépôt d’exemples

    Pour exécuter les exemples d’entraînement, commencez par cloner le dépôt d’exemples et placez-le dans le répertoire sdk :

    git clone --depth 1 https://github.com/Azure/azureml-examples --branch sdk-preview
    cd azureml-examples/sdk
    

Démarrer une session Python interactive

Cet article utilise le SDK Python pour Azure Machine Learning pour créer et contrôler un pipeline Azure Machine Learning. Cet article part du principe que vous allez exécuter les extraits de code de manière interactive dans un environnement REPL Python ou un notebook Jupyter.

Cet article est basé sur le notebook image_classification_keras_minist_convnet.ipynb disponible dans le répertoire sdk/python/jobs/pipelines/2e_image_classification_keras_minist_convnet du dépôt Azure Machine Learning Examples.

Importer les bibliothèques nécessaires

Importez toutes les bibliothèques Azure Machine Learning requises dont vous aurez besoin dans cet article :

# import required libraries
from azure.identity import DefaultAzureCredential, InteractiveBrowserCredential

from azure.ai.ml import MLClient
from azure.ai.ml.dsl import pipeline
from azure.ai.ml import load_component

Préparer les données d’entrée pour le travail du pipeline

Vous devez préparer les données d’entrée pour ce pipeline de classification d’images.

Fashion-MNIST est un jeu de données d’images de mode divisé en 10 classes. Chaque image est une image en nuances de gris 28x28 et il y a 60 000 images d’entraînement et 10 000 images de test. Au niveau de la classification d’images, Fashion-MNIST est plus difficile que la base de données MNIST de chiffres écrits à la main classique. Elles sont distribuées sous la même forme binaire compressée que la base de données de chiffres manuscrite d’origine.

Pour définir les données d’entrée d’un travail qui référence les données web, exécutez :

from azure.ai.ml import Input

fashion_ds = Input(
    path="wasbs://demo@data4mldemo6150520719.blob.core.windows.net/mnist-fashion/"
)

En définissant Input, vous créez une référence à l’emplacement de la source de données. Les données restant à leur emplacement existant, aucun coût de stockage supplémentaire n’est encouru.

Créer des composants pour créer un pipeline

La tâche de classification d’images peut être divisée en trois étapes : préparation des données, entraînement du modèle et scoring du modèle.

Un composant Azure Machine Learning est un élément de code autonome qui effectue une étape dans un pipeline de machine learning. Dans cet article, vous allez créer trois composants pour la tâche de classification d’images :

  • Préparer les données pour l’entraînement et les tests
  • Former un réseau neuronal pour la classification d’images avec des données de formation
  • Scorer le modèle avec des données de test

Pour chaque composant, vous devez préparer les éléments suivants :

  1. Préparer le script Python contenant la logique d’exécution

  2. Définir l’interface du composant

  3. Ajouter d’autres métadonnées du composant, notamment l’environnement d’exécution, la commande pour exécuter le composant, etc.

La section suivante montre comment créer des composants de deux manières différentes : les deux premiers composants avec une fonction Python et le troisième avec une définition YAML.

Créer le composant de préparation des données

Le premier composant de ce pipeline convertit les fichiers de données compressés de fashion_ds en deux fichiers CSV : l’un pour l’entraînement et l’autre pour le scoring. Vous allez utiliser une fonction Python pour définir ce composant.

Si vous suivez l’exemple du référentiel d’exemples Azure Machine Learning, les fichiers sources sont déjà disponibles dans le dossier prep/. Ce dossier contient deux fichiers pour construire le composant : prep_component.py qui définit le composant et conda.yaml qui définit l’environnement d’exécution du composant.

Définir le composant avec une fonction Python

En utilisant la fonction command_component() comme décorateur, vous pouvez facilement définir l’interface du composant, les métadonnées et le code à exécuter depuis une fonction Python. Chaque fonction Python décorée est transformée en spécification statique unique au format YAML que le service de pipeline peut traiter.

# Converts MNIST-formatted files at the passed-in input path to training data output path and test data output path
import os
from pathlib import Path
from mldesigner import command_component, Input, Output


@command_component(
    name="prep_data",
    version="1",
    display_name="Prep Data",
    description="Convert data to CSV file, and split to training and test data",
    environment=dict(
        conda_file=Path(__file__).parent / "conda.yaml",
        image="mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04",
    ),
)
def prepare_data_component(
    input_data: Input(type="uri_folder"),
    training_data: Output(type="uri_folder"),
    test_data: Output(type="uri_folder"),
):
    convert(
        os.path.join(input_data, "train-images-idx3-ubyte"),
        os.path.join(input_data, "train-labels-idx1-ubyte"),
        os.path.join(training_data, "mnist_train.csv"),
        60000,
    )
    convert(
        os.path.join(input_data, "t10k-images-idx3-ubyte"),
        os.path.join(input_data, "t10k-labels-idx1-ubyte"),
        os.path.join(test_data, "mnist_test.csv"),
        10000,
    )


def convert(imgf, labelf, outf, n):
    f = open(imgf, "rb")
    l = open(labelf, "rb")
    o = open(outf, "w")

    f.read(16)
    l.read(8)
    images = []

    for i in range(n):
        image = [ord(l.read(1))]
        for j in range(28 * 28):
            image.append(ord(f.read(1)))
        images.append(image)

    for image in images:
        o.write(",".join(str(pix) for pix in image) + "\n")
    f.close()
    o.close()
    l.close()

Le code ci-dessus définit un composant avec le nom d’affichage Prep Data en utilisant le décorateur @command_component :

  • name est l’identifiant unique du composant.

  • version est la version actuelle du composant. Un composant peut avoir plusieurs versions.

  • display_name est un nom d’affichage convivial du composant dans l’interface utilisateur, qui n’est pas unique.

  • description décrit généralement la tâche que ce composant peut effectuer.

  • environment spécifie l’environnement d’exécution pour ce composant. L’environnement de ce composant spécifie une image Docker et fait référence au fichier conda.yaml.

    Le fichier conda.yaml contient tous les packages utilisés pour le composant, notamment :

    name: imagekeras_prep_conda_env
    channels:
      - defaults
    dependencies:
      - python=3.7.11
      - pip=20.0
      - pip:
        - mldesigner==0.1.0b4
    
  • La fonction prepare_data_component définit une entrée pour input_data et deux sorties pour training_data et test_data. input_data est le chemin des données d’entrée. training_data et test_data sont des chemins de données de sortie pour les données d’entraînement et de test.

  • Ce composant convertit les données de input_data en CSV de données d’entraînement dans training_data et en CSV de données de test dans test_data.

Voici à quoi ressemble un composant dans l’interface utilisateur de studio.

  • Un composant est un bloc dans un graphe de pipeline.
  • input_data, training_data et test_data sont des ports du composant, qui se connecte à d’autres composants pour le streaming de données.

Screenshot of the Prep Data component in the UI and code.

Vous venez de préparer tous les fichiers sources pour le composant Prep Data.

Créer le composant train-model

Dans cette section, vous allez créer un composant pour entraîner le modèle de classification d’images dans la fonction Python, comme le composant Prep Data.

La différence, dans la mesure où la logique d’entraînement est plus compliquée, est que vous pouvez placer le code d’entraînement d’origine dans un fichier Python distinct.

Les fichiers sources de ce composant se trouvent dans le dossier train/ du référentiel d’exemples Azure Machine Learning. Ce dossier contient trois fichiers pour construire le composant :

  • train.py : contient la logique réelle pour entraîner le modèle.
  • train_component.py : définit l’interface du composant et importe la fonction dans train.py.
  • conda.yaml : définit l’environnement d’exécution du composant.

Obtenir un script contenant la logique d’exécution

Le fichier train.py contient une fonction Python normale qui effectue la logique du modèle d’entraînement pour entraîner un réseau neuronal Keras pour la classification d’images. Pour voir le code, consultez le fichier train.py sur GitHub.

Définir le composant avec une fonction Python

Après avoir défini la fonction de formation, vous pouvez utiliser @command_component dans le kit SDK Azure Machine Learning v2 pour wrapper votre fonction en tant que composant utilisable dans des pipelines Azure Machine Learning.

import os
from pathlib import Path
from mldesigner import command_component, Input, Output


@command_component(
    name="train_image_classification_keras",
    version="1",
    display_name="Train Image Classification Keras",
    description="train image classification with keras",
    environment=dict(
        conda_file=Path(__file__).parent / "conda.yaml",
        image="mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04",
    ),
)
def keras_train_component(
    input_data: Input(type="uri_folder"),
    output_model: Output(type="uri_folder"),
    epochs=10,
):
    # avoid dependency issue, execution logic is in train() func in train.py file
    from train import train

    train(input_data, output_model, epochs)

Le code ci-dessus définit un composant avec le nom d’affichage Train Image Classification Keras en utilisant @command_component :

  • La fonction keras_train_component définit une entrée input_data d’où proviennent les données d’entraînement, une entrée epochs spécifiant les époques durant l’entraînement et une sortie output_model où est généré le fichier de modèle. La valeur par défaut de epochs est 10. La logique d’exécution de ce composant provient de la fonction train() dans train.py ci-dessus.

Le composant train-model présente une configuration légèrement plus complexe que le composant prep-data. Le fichier conda.yaml se présente comme suit :

name: imagekeras_train_conda_env
channels:
  - defaults
dependencies:
  - python=3.7.11
  - pip=20.2
  - pip:
    - mldesigner==0.1.0b12
    - azureml-mlflow==1.50.0
    - tensorflow==2.7.0
    - numpy==1.21.4
    - scikit-learn==1.0.1
    - pandas==1.3.4
    - matplotlib==3.2.2
    - protobuf==3.20.0

Vous venez de préparer tous les fichiers sources pour le composant Train Image Classification Keras.

Créer le composant score-model

Dans cette section, outre les composants précédents, vous allez créer un composant pour scorer le modèle entraîné par le biais d’un script et d’une spécification YAML.

Si vous suivez l’exemple du référentiel d’exemples Azure Machine Learning, les fichiers sources sont déjà disponibles dans le dossier score/. Ce dossier contient trois fichiers pour construire le composant :

  • score.py : contient le code source du composant.
  • score.yaml : définit l’interface et d’autres détails du composant.
  • conda.yaml : définit l’environnement d’exécution du composant.

Obtenir un script contenant la logique d’exécution

Le fichier score.py contient une fonction Python normale qui exécute la logique du modèle d’entraînement.

from tensorflow import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.utils import to_categorical
from keras.callbacks import Callback
from keras.models import load_model

import argparse
from pathlib import Path
import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import mlflow


def get_file(f):

    f = Path(f)
    if f.is_file():
        return f
    else:
        files = list(f.iterdir())
        if len(files) == 1:
            return files[0]
        else:
            raise Exception("********This path contains more than one file*******")


def parse_args():
    # setup argparse
    parser = argparse.ArgumentParser()

    # add arguments
    parser.add_argument(
        "--input_data", type=str, help="path containing data for scoring"
    )
    parser.add_argument(
        "--input_model", type=str, default="./", help="input path for model"
    )

    parser.add_argument(
        "--output_result", type=str, default="./", help="output path for model"
    )

    # parse args
    args = parser.parse_args()

    # return args
    return args


def score(input_data, input_model, output_result):

    test_file = get_file(input_data)
    data_test = pd.read_csv(test_file, header=None)

    img_rows, img_cols = 28, 28
    input_shape = (img_rows, img_cols, 1)

    # Read test data
    X_test = np.array(data_test.iloc[:, 1:])
    y_test = to_categorical(np.array(data_test.iloc[:, 0]))
    X_test = (
        X_test.reshape(X_test.shape[0], img_rows, img_cols, 1).astype("float32") / 255
    )

    # Load model
    files = [f for f in os.listdir(input_model) if f.endswith(".h5")]
    model = load_model(input_model + "/" + files[0])

    # Log metrics of the model
    eval = model.evaluate(X_test, y_test, verbose=0)

    mlflow.log_metric("Final test loss", eval[0])
    print("Test loss:", eval[0])

    mlflow.log_metric("Final test accuracy", eval[1])
    print("Test accuracy:", eval[1])

    # Score model using test data
    y_predict = model.predict(X_test)
    y_result = np.argmax(y_predict, axis=1)

    # Output result
    np.savetxt(output_result + "/predict_result.csv", y_result, delimiter=",")


def main(args):
    score(args.input_data, args.input_model, args.output_result)


# run script
if __name__ == "__main__":
    # parse args
    args = parse_args()

    # call main function
    main(args)

Le code dans score.py prend trois arguments de ligne de commande : input_data, input_model et output_result. Le programme score le modèle d’entrée en utilisant les données d’entrée, puis génère le résultat du scoring.

Définir le composant avec YAML

Dans cette section, vous allez apprendre à créer une spécification de composant au format de spécification de composant YAML valide. Ce fichier spécifie les informations suivantes :

  • Métadonnées : nom, nom d’affichage, version, type, etc.
  • Interface : entrées et sorties
  • Commande, code et environnement : la commande, le code et l’environnement utilisés pour exécuter le composant
$schema: https://azuremlschemas.azureedge.net/latest/commandComponent.schema.json
type: command

name: score_image_classification_keras
display_name: Score Image Classification Keras
inputs:
  input_data: 
    type: uri_folder
  input_model:
    type: uri_folder
outputs:
  output_result:
    type: uri_folder
code: ./
command: python score.py --input_data ${{inputs.input_data}} --input_model ${{inputs.input_model}} --output_result ${{outputs.output_result}}
environment:
  conda_file: ./conda.yaml
  image: mcr.microsoft.com/azureml/openmpi4.1.0-ubuntu20.04
  • name est l’identifiant unique du composant. Son nom d’affichage est Score Image Classification Keras.
  • Ce composant a deux entrées et une sortie.
  • Le chemin du code source est défini dans la section code. Quand le composant est exécuté dans le cloud, tous les fichiers de ce chemin sont chargés en tant qu’instantané de ce composant.
  • La section command spécifie la commande à exécuter lors de l’exécution de ce composant.
  • La section environment contient une image Docker et un fichier YAML Conda. Le fichier source se trouve dans le dépôt d’exemples.

Vous disposez à présent de tous les fichiers sources pour le composant score-model.

Charger les composants pour créer le pipeline

Pour les composants prep-data et train-model définis par une fonction Python, vous pouvez importer les composants comme des fonctions Python normales.

Dans le code suivant, vous importez la fonction prepare_data_component() à partir du fichier prep_component.py dans le dossier prep et la fonction keras_train_component() à partir du fichier train_component dans le dossier train.

%load_ext autoreload
%autoreload 2

# load component function from component python file
from prep.prep_component import prepare_data_component
from train.train_component import keras_train_component

# print hint of components
help(prepare_data_component)
help(keras_train_component)

Pour le composant score défini par YAML, vous pouvez utiliser la fonction load_component() pour le chargement.

# load component function from yaml
keras_score_component = load_component(source="./score/score.yaml")

Créer votre pipeline

Vous venez de créer et de charger l’ensemble des composants et des données d’entrée nécessaires à la création du pipeline. Vous pouvez les utiliser pour composer un pipeline :

Remarque

Pour utiliser le calcul serverless, ajoutez from azure.ai.ml.entities import ResourceConfiguration en haut. Remplacez ensuite :

  • default_compute=cpu_compute_target, avec default_compute="serverless",
  • train_node.compute = gpu_compute_target avec train_node.resources = "ResourceConfiguration(instance_type="Standard_NC6s_v3",instance_count=2)
# define a pipeline containing 3 nodes: Prepare data node, train node, and score node
@pipeline(
    default_compute=cpu_compute_target,
)
def image_classification_keras_minist_convnet(pipeline_input_data):
    """E2E image classification pipeline with keras using python sdk."""
    prepare_data_node = prepare_data_component(input_data=pipeline_input_data)

    train_node = keras_train_component(
        input_data=prepare_data_node.outputs.training_data
    )
    train_node.compute = gpu_compute_target

    score_node = keras_score_component(
        input_data=prepare_data_node.outputs.test_data,
        input_model=train_node.outputs.output_model,
    )


# create a pipeline
pipeline_job = image_classification_keras_minist_convnet(pipeline_input_data=fashion_ds)

Le pipeline a un calcul par défaut, cpu_compute_target. Donc, si vous ne spécifiez pas de calcul pour un nœud spécifique, ce nœud s’exécutera sur le calcul par défaut.

Le pipeline a une entrée de niveau pipeline, pipeline_input_data. Vous pouvez affecter une valeur à l’entrée du pipeline quand vous envoyez un travail de pipeline.

Le pipeline contient trois nœuds, prepare_data_node, train_node et score_node.

  • input_data de prepare_data_node utilise la valeur de pipeline_input_data.

  • input_data de train_node provient de la sortie training_data de prepare_data_node.

  • input_data de score_node provient de la sortie test_data de prepare_data_node, et input_model provient de output_model de train_node.

  • Étant donné que train_node entraînera un modèle CNN, vous pouvez spécifier son calcul en tant que gpu_compute_target, ce qui peut améliorer les performances d’entraînement.

Envoyer votre travail de pipeline

Maintenant que vous avez construit le pipeline, vous pouvez envoyer le travail à l’espace de travail. Pour envoyer un travail, vous devez d’abord vous connecter à un espace de travail.

Accéder à votre espace de travail

Configurer les informations d’identification

Nous allons utiliser DefaultAzureCredential pour accéder à l’espace de travail. DefaultAzureCredential doit être capable de gérer la plupart des scénarios d’authentification du kit SDK Azure.

Référence vers d’autres informations d’identification disponibles si cela ne marche pas : exemple de configuration d’informations d’identification, document de référence azure-identity.

try:
    credential = DefaultAzureCredential()
    # Check if given credential can get token successfully.
    credential.get_token("https://management.azure.com/.default")
except Exception as ex:
    # Fall back to InteractiveBrowserCredential in case DefaultAzureCredential not work
    credential = InteractiveBrowserCredential()

Obtenir un descripteur vers un espace de travail avec un calcul

Créez un objet MLClient pour gérer les services Azure Machine Learning. Si vous utilisez le calcul serverless, il est inutile de créer ces calculs.

# Get a handle to workspace
ml_client = MLClient.from_config(credential=credential)

# Retrieve an already attached Azure Machine Learning Compute.
cpu_compute_target = "cpu-cluster"
print(ml_client.compute.get(cpu_compute_target))
gpu_compute_target = "gpu-cluster"
print(ml_client.compute.get(gpu_compute_target))

Important

Cet extrait de code s’attend à ce que le fichier JSON de configuration de l’espace de travail soit enregistré dans le répertoire actif ou son parent. Pour plus d’informations sur la création d’un espace de travail, consultez Créer des ressources d’espace de travail. Pour plus d’informations sur l’enregistrement de la configuration dans un fichier, consultez Créer un fichier de configuration d’espace de travail.

Envoyer un travail de pipeline à l’espace de travail

Maintenant que vous disposez d’un descripteur pour votre espace de travail, vous pouvez envoyer votre travail de pipeline.

pipeline_job = ml_client.jobs.create_or_update(
    pipeline_job, experiment_name="pipeline_samples"
)
pipeline_job

Le code ci-dessus envoie le travail de pipeline de classification d’images à une expérience appelée pipeline_samples. Il crée automatiquement l’expérience si elle n’existe pas. pipeline_input_data utilise fashion_ds.

L’appel à pipeline_job produit une sortie semblable à celle-ci :

L’appel à submitExperiment s’exécute rapidement et produit une sortie similaire à ce qui suit :

Expérience Nom Type Statut Details Page
pipeline_samples sharp_pipe_4gvqx6h1fb pipeline Préparation en cours Lien vers Azure Machine Learning studio.

Vous pouvez surveiller l’exécution du pipeline en ouvrant le lien ou vous pouvez bloquer jusqu’à ce qu’il se termine en exécutant :

# wait until the job completes
ml_client.jobs.stream(pipeline_job.name)

Important

La première exécution du pipeline prend environ 15 minutes. Toutes les dépendances doivent être téléchargées, une image Docker est créée, et l’environnement Python est provisionné et créé. La réexécution du pipeline prend beaucoup moins de temps, car ces ressources sont réutilisées au lieu d’être créées. Toutefois, le temps total d’exécution du pipeline dépend de la charge de travail de vos scripts et des processus qui s’exécutent à chaque étape du pipeline.

Consulter les sorties et déboguer votre pipeline dans l’interface utilisateur

Vous pouvez ouvrir le Link to Azure Machine Learning studio, qui est la page de détail du travail de votre pipeline. Le graphe de pipeline apparaît comme ceci.

Screenshot of the pipeline job detail page.

Pour consulter les journaux et les sorties de chaque composant, vous pouvez cliquer avec le bouton droit sur le composant ou sélectionner le composant pour ouvrir son volet de détails. Si vous souhaitez en savoir plus sur le débogage de votre pipeline dans l’interface utilisateur, veuillez consulter la rubrique Comment déboguer une défaillance du pipeline.

(Facultatif) Inscrire les composants dans l’espace de travail

Dans la section précédente, vous avez créé un pipeline avec trois composants pour réaliser une tâche de classification d’images de bout en bout. Vous pouvez également inscrire les composants auprès de votre espace de travail pour pouvoir les partager et les réutiliser dans l’espace de travail. L’exemple suivant illustre l’inscription du composant prep-data.

try:
    # try get back the component
    prep = ml_client.components.get(name="prep_data", version="1")
except:
    # if not exists, register component using following code
    prep = ml_client.components.create_or_update(prepare_data_component)

# list all components registered in workspace
for c in ml_client.components.list():
    print(c)

En utilisant ml_client.components.get(), vous pouvez inscrire un composant par nom et version. En utilisant ml_client.components.create_or_update(), vous pouvez inscrire un composant précédemment chargé avec une fonction Python ou YAML.

Étapes suivantes