Tutorial: Creación de una canalización de Azure Machine Learning para la clasificación de imágenes

SE APLICA A:Azure ML del SDK de Python v1

Nota

Para ver un tutorial que usa el SDK v2 para crear una canalización, consulte Tutorial: Uso de canalizaciones de ML para flujos de trabajo de ML de producción con el SDK de Python v2 en un Jupyter Notebook.

En este tutorial, aprenderá a crear una canalización de Azure Machine Learning para preparar los datos y entrenar un modelo de Machine Learning. Las canalizaciones de Machine Learning optimizan el flujo de trabajo con velocidad, portabilidad y reutilización, con el fin de que pueda centrarse en el aprendizaje automático, en lugar de en la infraestructura y la automatización.

En el ejemplo se entrena una pequeña red neuronal convolucional de Keras para clasificar las imágenes del conjunto de datos de MNIST Fashion.

En este tutorial, va a completar las siguientes tareas:

  • Configuración del área de trabajo
  • Creación de un experimento que contenga el trabajo
  • Aprovisionamiento de un destino de proceso para que realice el trabajo
  • Creación de un conjunto de datos en el que almacenar datos comprimidos
  • Creación de un paso de la canalización para preparar los datos para el entrenamiento
  • Definición de un entorno en tiempo de ejecución en el que realizar el entrenamiento
  • Creación de un paso de canalización para definir la red neuronal y realizar el entrenamiento
  • Creación de una canalización a partir de la suma de los pasos de canalización
  • Ejecución de la canalización en el experimento
  • Revisión de la salida de los pasos y de la red neuronal entrenada.
  • Registro del modelo para su uso posterior

Si no tiene una suscripción de Azure, cree una cuenta gratuita antes de empezar. Pruebe hoy mismo la versión gratuita o de pago de Azure Machine Learning.

Requisitos previos

  • Completa la Creación de recursos para empezar si aún no tienes un área de trabajo de Azure Machine Learning.
  • Un entorno de Python en el que se han instalado los paquetes azureml-core y azureml-pipeline. Este entorno es para definir y controlar los recursos de Azure Machine Learning y es independiente del entorno que se usa en tiempo de ejecución para el entrenamiento.

Importante

Actualmente, la versión más reciente de Python compatible con azureml-pipeline es Python 3.8. Si tiene dificultades para instalar el paquete azureml-pipeline, asegúrese de que python --version sea una versión compatible. Consulte la documentación del administrador de entornos virtuales de Python (venv, conda, etc.) para obtener instrucciones.

Inicio de una sesión interactiva de Python

En este tutorial se usa el SDK de Python para Azure Machine Learning para crear y controlar una canalización de Azure Machine Learning. En el tutorial se da por supuesto que va a ejecutar los fragmentos de código de forma interactiva en un entorno REPL de Python o en un cuaderno de Jupyter Notebook.

  • Este tutorial se basa en el cuaderno image-classification.ipynb que se encuentra en el directorio python-sdk/tutorial/using-pipelines del repositorio Azure Machine Learning Examples. El código fuente de los propios pasos está en el subdirectorio keras-mnist-fashion.

Tipos de importación

Importe todos los tipos de Azure Machine Learning que necesitará para este tutorial:

import os
import azureml.core
from azureml.core import (
    Workspace,
    Experiment,
    Dataset,
    Datastore,
    ComputeTarget,
    Environment,
    ScriptRunConfig
)
from azureml.data import OutputFileDatasetConfig
from azureml.core.compute import AmlCompute
from azureml.core.compute_target import ComputeTargetException
from azureml.pipeline.steps import PythonScriptStep
from azureml.pipeline.core import Pipeline

# check core SDK version number
print("Azure Machine Learning SDK Version: ", azureml.core.VERSION)

La versión del SDK de Azure Machine Learning debe ser 1.37 o posterior. Si no es así, actualícela con pip install --upgrade azureml-core.

Configuración del área de trabajo

Cree un objeto de área de trabajo a partir del área de trabajo de Azure Machine Learning existente.

workspace = Workspace.from_config()

Importante

Este fragmento de código espera que la configuración del área de trabajo se guarde en el directorio actual o en su elemento primario. Para obtener más información sobre la creación de un área de trabajo, consulte Creación de recursos para el área de trabajo. Para más información sobre cómo guardar la configuración en un archivo, consulte Creación de un archivo de configuración de área de trabajo.

Creación de la infraestructura para la canalización

Cree un objeto Experiment que contenga los resultados de las ejecuciones de canalización:

exp = Experiment(workspace=workspace, name="keras-mnist-fashion")

Cree un objeto ComputeTarget que represente el recurso de máquina en el que se ejecutará la canalización. La red neuronal simple que se usa en este tutorial se entrena en tan solo unos minutos incluso en una máquina basada en CPU. Si desea usar una GPU para el entrenamiento, establezca use_gpu en True. El aprovisionamiento de un destino de proceso normalmente tarda unos cinco minutos.

use_gpu = False

# choose a name for your cluster
cluster_name = "gpu-cluster" if use_gpu else "cpu-cluster"

found = False
# Check if this compute target already exists in the workspace.
cts = workspace.compute_targets
if cluster_name in cts and cts[cluster_name].type == "AmlCompute":
    found = True
    print("Found existing compute target.")
    compute_target = cts[cluster_name]
if not found:
    print("Creating a new compute target...")
    compute_config = AmlCompute.provisioning_configuration(
        vm_size= "STANDARD_NC6" if use_gpu else "STANDARD_D2_V2"
        # vm_priority = 'lowpriority', # optional
        max_nodes=4,
    )

    # Create the cluster.
    compute_target = ComputeTarget.create(workspace, cluster_name, compute_config)

    # Can poll for a minimum number of nodes and for a specific timeout.
    # If no min_node_count is provided, it will use the scale settings for the cluster.
    compute_target.wait_for_completion(
        show_output=True, min_node_count=None, timeout_in_minutes=10
    )
# For a more detailed view of current AmlCompute status, use get_status().print(compute_target.get_status().serialize())

Nota

La disponibilidad de GPU depende de la cuota de la suscripción de Azure y de la capacidad de Azure. Consulte Administración y aumento de las cuotas de los recursos con Azure Machine Learning.

Creación de un conjunto de datos para los datos almacenados en Azure

Fashion-MNIST es un conjunto de datos de imágenes de moda dividido en 10 clases. Cada imagen es una imagen en escala de grises de 28 x 28 y hay 60 000 imágenes de entrenamiento y 10 000 de prueba. Como problema de clasificación de imágenes, Fashion-MNIST es más compleja que la base de datos de dígitos manuscritos de MNIST clásica. Se distribuye en el mismo formato binario comprimido que la base de datos de dígitos manuscritos original.

Para crear un objeto Dataset que haga referencia a los datos basados en web, ejecute:

data_urls = ["https://data4mldemo6150520719.blob.core.windows.net/demo/mnist-fashion"]
fashion_ds = Dataset.File.from_files(data_urls)

# list the files referenced by fashion_ds
print(fashion_ds.to_path())

Este código se completa rápidamente. Los datos subyacentes permanecen en el recurso de almacenamiento de Azure especificado en la matriz data_urls.

Creación del paso de canalización de preparación de datos

El primer paso de esta canalización convertirá los archivos de datos comprimidos de fashion_ds en un conjunto de datos de su propia área de trabajo que consta de archivos CSV listos para su uso en el entrenamiento. Una vez registrados en el área de trabajo, los colaboradores pueden acceder a estos datos para su propio análisis, entrenamiento, etc.

datastore = workspace.get_default_datastore()
prepared_fashion_ds = OutputFileDatasetConfig(
    destination=(datastore, "outputdataset/{run-id}")
).register_on_complete(name="prepared_fashion_ds")

El código anterior especifica un conjunto de datos que se basa en la salida de un paso de la canalización. Los archivos procesados subyacentes se colocarán en el almacenamiento de blobs predeterminado del almacén de datos del área de trabajo en la ruta de acceso especificada en destination. El conjunto de datos se registrará en el área de trabajo con el nombre prepared_fashion_ds.

Creación del origen del paso de canalización

El código que ha ejecutado hasta ahora tiene recursos de Azure creados y controlados. Ahora es el momento de escribir código que realice el primer paso en el dominio.

Si sigue el ejemplo del repositorio de ejemplos de Azure Machine Learning, el archivo de código fuente ya está disponible como keras-mnist-fashion/prepare.py.

Si va a trabajar desde cero, cree un subdirectorio denominado keras-mnist-fashion/. Cree un archivo, agréguele el siguiente código y asigne al archivo el nombre prepare.py.

# prepare.py
# Converts MNIST-formatted files at the passed-in input path to a passed-in output path
import os
import sys

# Conversion routine for MNIST binary format
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()

# The MNIST-formatted source
mounted_input_path = sys.argv[1]
# The output directory at which the outputs will be written
mounted_output_path = sys.argv[2]

# Create the output directory
os.makedirs(mounted_output_path, exist_ok=True)

# Convert the training data
convert(
    os.path.join(mounted_input_path, "mnist-fashion/train-images-idx3-ubyte"),
    os.path.join(mounted_input_path, "mnist-fashion/train-labels-idx1-ubyte"),
    os.path.join(mounted_output_path, "mnist_train.csv"),
    60000,
)

# Convert the test data
convert(
    os.path.join(mounted_input_path, "mnist-fashion/t10k-images-idx3-ubyte"),
    os.path.join(mounted_input_path, "mnist-fashion/t10k-labels-idx1-ubyte"),
    os.path.join(mounted_output_path, "mnist_test.csv"),
    10000,
)

El código de prepare.py toma dos argumentos de línea de comandos: el primero se asigna a mounted_input_path y el segundo a mounted_output_path. Si ese subdirectorio no existe, la llamada a os.makedirs lo crea. A continuación, el programa convierte los datos de entrenamiento y prueba, y genera los archivos separados por comas en mounted_output_path.

Especificación del paso de canalización

De nuevo en el entorno de Python que va a usar para especificar la canalización, ejecute este código para crear un PythonScriptStep para el código de preparación:

script_folder = "./keras-mnist-fashion"

prep_step = PythonScriptStep(
    name="prepare step",
    script_name="prepare.py",
    # On the compute target, mount fashion_ds dataset as input, prepared_fashion_ds as output
    arguments=[fashion_ds.as_named_input("fashion_ds").as_mount(), prepared_fashion_ds],
    source_directory=script_folder,
    compute_target=compute_target,
    allow_reuse=True,
)

La llamada a PythonScriptStep especifica que, cuando se ejecuta el paso de canalización:

  • Todos los archivos del directorio script_folder se cargan en el compute_target.
  • Entre esos archivos de origen cargados, se ejecutará el archivo prepare.py.
  • Los conjuntos de datos fashion_ds y prepared_fashion_ds se montarán en compute_target y aparecerán como directorios.
  • La ruta de acceso a los archivos fashion_ds será el primer argumento de prepare.py. En prepare.py, este argumento se asigna a mounted_input_path.
  • La ruta de acceso a prepared_fashion_ds será el segundo argumento de prepare.py. En prepare.py, este argumento se asigna a mounted_output_path
  • Como allow_reuse es True, no se vuelve a ejecutar hasta que cambian sus archivos o entradas de origen.
  • A este PythonScriptStep se le denominará prepare step.

La modularidad y la reutilización son ventajas clave de las canalizaciones. Azure Machine Learning puede determinar automáticamente el código fuente o los cambios en el conjunto de datos. La salida de un paso que no se ve afectado se reutilizará sin volver a ejecutar los pasos de nuevo si allow_reuse es True. Si un paso se basa en un origen de datos externo a Azure Machine Learning que puede cambiar (por ejemplo, una dirección URL que contiene datos de ventas), establezca allow_reuse en False y el paso de canalización se ejecutará cada vez que se ejecute la canalización.

Creación del paso de entrenamiento

Una vez que los datos se han convertido del formato comprimido a archivos CSV, se pueden usar para entrenar una red neuronal convolucional.

Creación del origen del paso de entrenamiento

En canalizaciones más grandes, es un procedimiento recomendado colocar el código fuente de cada paso en un directorio independiente (src/prepare/, src/train/, etc.), pero para este tutorial, simplemente use o cree el archivo train.py en el mismo directorio de origen keras-mnist-fashion/.

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

import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from azureml.core import Run

# dataset object from the run
run = Run.get_context()
dataset = run.input_datasets["prepared_fashion_ds"]

# split dataset into train and test set
(train_dataset, test_dataset) = dataset.random_split(percentage=0.8, seed=111)

# load dataset into pandas dataframe
data_train = train_dataset.to_pandas_dataframe()
data_test = test_dataset.to_pandas_dataframe()

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

X = np.array(data_train.iloc[:, 1:])
y = to_categorical(np.array(data_train.iloc[:, 0]))

# here we split validation data to optimiza classifier during training
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=13)

# test data
X_test = np.array(data_test.iloc[:, 1:])
y_test = to_categorical(np.array(data_test.iloc[:, 0]))


X_train = (
    X_train.reshape(X_train.shape[0], img_rows, img_cols, 1).astype("float32") / 255
)
X_test = X_test.reshape(X_test.shape[0], img_rows, img_cols, 1).astype("float32") / 255
X_val = X_val.reshape(X_val.shape[0], img_rows, img_cols, 1).astype("float32") / 255

batch_size = 256
num_classes = 10
epochs = 10

# construct neuron network
model = Sequential()
model.add(
    Conv2D(
        32,
        kernel_size=(3, 3),
        activation="relu",
        kernel_initializer="he_normal",
        input_shape=input_shape,
    )
)
model.add(MaxPooling2D((2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(64, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(128, (3, 3), activation="relu"))
model.add(Dropout(0.4))
model.add(Flatten())
model.add(Dense(128, activation="relu"))
model.add(Dropout(0.3))
model.add(Dense(num_classes, activation="softmax"))

model.compile(
    loss=keras.losses.categorical_crossentropy,
    optimizer=keras.optimizers.Adam(),
    metrics=["accuracy"],
)

# start an Azure ML run
run = Run.get_context()


class LogRunMetrics(Callback):
    # callback at the end of every epoch
    def on_epoch_end(self, epoch, log):
        # log a value repeated which creates a list
        run.log("Loss", log["loss"])
        run.log("Accuracy", log["accuracy"])


history = model.fit(
    X_train,
    y_train,
    batch_size=batch_size,
    epochs=epochs,
    verbose=1,
    validation_data=(X_val, y_val),
    callbacks=[LogRunMetrics()],
)

score = model.evaluate(X_test, y_test, verbose=0)

# log a single value
run.log("Final test loss", score[0])
print("Test loss:", score[0])

run.log("Final test accuracy", score[1])
print("Test accuracy:", score[1])

plt.figure(figsize=(6, 3))
plt.title("Fashion MNIST with Keras ({} epochs)".format(epochs), fontsize=14)
plt.plot(history.history["accuracy"], "b-", label="Accuracy", lw=4, alpha=0.5)
plt.plot(history.history["loss"], "r--", label="Loss", lw=4, alpha=0.5)
plt.legend(fontsize=12)
plt.grid(True)

# log an image
run.log_image("Loss v.s. Accuracy", plot=plt)

# create a ./outputs/model folder in the compute target
# files saved in the "./outputs" folder are automatically uploaded into run history
os.makedirs("./outputs/model", exist_ok=True)

# serialize NN architecture to JSON
model_json = model.to_json()
# save model JSON
with open("./outputs/model/model.json", "w") as f:
    f.write(model_json)
# save model weights
model.save_weights("./outputs/model/model.h5")
print("model saved in ./outputs/model folder")

La mayoría de los desarrolladores de Machine Learning debería estar familiarizado con este código:

  • Los datos se particionan en conjuntos de entrenamiento y validación para el entrenamiento, y en un subconjunto de pruebas independiente para la puntuación final.
  • La forma de entrada es 28x28x1 (solo 1 porque la entrada es de escala de grises), habrá 256 entradas en un lote y hay 10 clases.
  • El número de períodos de entrenamiento será 10
  • El modelo tiene tres capas convolucionales, con agrupación y deserción máximas, seguidas de una capa densa y una cabeza softmax.
  • El modelo se ha ajustado para 10 períodos y, después, se evalúa.
  • La arquitectura del modelo se escribe en outputs/model/model.json y las ponderaciones en outputs/model/model.h5

Sin embargo, parte del código es específico de Azure Machine Learning. run = Run.get_context() recupera un objeto Run, que contiene el contexto del servicio actual. El origen train.py usa este objeto run para recuperar el conjunto de datos de entrada a través de su nombre (una alternativa al código de prepare.py que recuperó el conjunto de datos a través de la matriz argv de argumentos de script).

El objeto run también se usa para registrar el progreso del entrenamiento al final de cada período y, al final del entrenamiento, para registrar el grafo de pérdida y precisión a lo largo del tiempo.

Creación del paso de canalización de entrenamiento

El paso de entrenamiento tiene una configuración ligeramente más compleja que el paso de preparación. El paso de preparación solo usaba bibliotecas estándar de Python. Normalmente, deberá modificar el entorno en tiempo de ejecución en el que se ejecuta el código fuente.

Cree un archivo conda_dependencies.yml con el siguiente contenido:

dependencies:
- python=3.7
- pip:
  - azureml-core
  - azureml-dataset-runtime
  - keras==2.4.3
  - tensorflow==2.4.3
  - numpy
  - scikit-learn
  - pandas
  - matplotlib

La clase Environment representa el entorno en tiempo de ejecución en el que se ejecuta una tarea de aprendizaje automático. Asocie la especificación anterior al código de entrenamiento con:

keras_env = Environment.from_conda_specification(
    name="keras-env", file_path="./conda_dependencies.yml"
)

train_cfg = ScriptRunConfig(
    source_directory=script_folder,
    script="train.py",
    compute_target=compute_target,
    environment=keras_env,
)

Al crear el propio paso de entrenamiento se usa un código similar al código utilizado para crear el paso de preparación:

train_step = PythonScriptStep(
    name="train step",
    arguments=[
        prepared_fashion_ds.read_delimited_files().as_input(name="prepared_fashion_ds")
    ],
    source_directory=train_cfg.source_directory,
    script_name=train_cfg.script,
    runconfig=train_cfg.run_config,
)

Creación y ejecución de la canalización

Ahora que ha especificado las entradas y salidas de datos y ha creado los pasos de la canalización, puede reunir todo en una canalización y ejecutarla:

pipeline = Pipeline(workspace, steps=[prep_step, train_step])
run = exp.submit(pipeline)

El objeto Pipeline que cree se ejecuta en workspace y está compuesto de los pasos de preparación y entrenamiento que ha especificado.

Nota

Esta canalización tiene un gráfico de dependencias simple: el paso de entrenamiento se basa en el paso de preparación y el paso de preparación se basa en el conjunto de datos fashion_ds. Las canalizaciones de producción a menudo tendrán dependencias mucho más complejas. Los pasos pueden basarse en varios pasos ascendentes, un cambio de código fuente en uno de los primeros pasos puede tener consecuencias de gran alcance, etc. Azure Machine Learning se encarga de estos problemas en su lugar. Solo tiene que pasar la matriz de steps y Azure Machine Learning se encarga de calcular el gráfico de ejecución.

La llamada para submit el Experiment se completa rápidamente y produce una salida parecida a:

Submitted PipelineRun 5968530a-abcd-1234-9cc1-46168951b5eb
Link to Azure Machine Learning Portal: https://ml.azure.com/runs/abc-xyz...

Para supervisar la ejecución de la canalización, abra el vínculo o puede bloquearlo hasta que se complete mediante la ejecución de:

run.wait_for_completion(show_output=True)

Importante

La primera ejecución de canalización tarda aproximadamente 15 minutos. Se deben descargar todas las dependencias, se crea una imagen de Docker y se aprovisiona y se crea el entorno de Python. La repetición de la ejecución de la canalización tarda mucho menos, ya que esos recursos se reutilizan en lugar de crearse. Sin embargo, el tiempo de ejecución total de la canalización depende de la carga de trabajo de los scripts y de los procesos que se ejecutan en cada paso de la canalización.

Una vez completada la canalización, puede recuperar las métricas que registró en el paso de entrenamiento:

run.find_step_run("train step")[0].get_metrics()

Si está satisfecho con las métricas, puede registrar el modelo en el área de trabajo:

run.find_step_run("train step")[0].register_model(
    model_name="keras-model",
    model_path="outputs/model/",
    datasets=[("train test data", fashion_ds)],
)

Limpieza de recursos

No complete esta sección si planea ejecutar otros tutoriales de Azure Machine Learning.

Detención de la instancia de proceso

Si usó una instancia de proceso, detenga la máquina virtual cuando no la esté usando a fin de reducir el costo.

  1. En el área de trabajo, seleccione Compute.

  2. En la lista, seleccione el nombre de la instancia de proceso.

  3. Seleccione Detener.

  4. Cuando esté listo para volver a usar el servidor, seleccione Iniciar.

Eliminar todo el contenido

Si no va a usar los recursos creados, elimínelos para no incurrir en cargos:

  1. En Azure Portal, seleccione Grupos de recursos en el menú de la izquierda.
  2. En la lista de grupos de recursos, seleccione el que ha creado.
  3. Seleccione Eliminar grupo de recursos.
  4. Escriba el nombre del grupo de recursos. A continuación, seleccione Eliminar.

También puede mantener el grupo de recursos pero eliminar una sola área de trabajo. Muestre las propiedades del área de trabajo y seleccione Eliminar.

Pasos siguientes

En este tutorial, ha usado los siguientes tipos:

  • Workspace representa su área de trabajo de Azure Machine Learning. Contenía:
    • El Experiment que contiene los resultados de las ejecuciones de entrenamiento de la canalización
    • El Dataset que cargó lentamente los datos contenidos en el almacén de datos Fashion-MNIST
    • El ComputeTarget que representa las máquinas en las que se ejecutan los pasos de canalización
    • El Environment que es el entorno en tiempo de ejecución en el que se ejecutan los pasos de canalización
    • La Pipeline que reúne los pasos de PythonScriptStep en un solo conjunto
    • El Model que registró después de estar satisfecho con el proceso de entrenamiento

El objeto Workspace contiene referencias a otros recursos (cuadernos, puntos de conexión, etc.) que no se usaron en este tutorial. Para más información, consulte ¿Qué es un área de trabajo de Azure Machine Learning?

OutputFileDatasetConfig promueve la salida de una ejecución a un conjunto de datos basado en archivos. Para más información sobre los conjuntos de datos y cómo trabajar con datos, consulte Acceso a los datos.

Para más información sobre los entornos y los destinos de proceso, consulte ¿Qué son los destinos de proceso en Azure Machine Learning? y ¿Qué son los entornos de Azure Machine Learning?

ScriptRunConfig asocia un ComputeTarget y un Environment a los archivos de código fuente de Python. PythonScriptStep toma ese ScriptRunConfig y define sus entradas y salidas, que en esta canalización era el conjunto de datos de archivos que creó OutputFileDatasetConfig.

Para más ejemplos de cómo crear canalizaciones mediante el SDK de Machine Learning, consulte el repositorio de ejemplos.