Eseguire stime con ONNX in modelli di visione artificiale da AutoML

SI APPLICA A: SDK Python azure-ai-ml v2 (corrente)

Questo articolo illustra come usare Open Neural Network Exchange (ONNX) per eseguire stime sui modelli di visione artificiale generati da Machine Learning automatizzato (AutoML) in Azure Machine Learning.

Per usare ONNX per le stime, è necessario:

  1. Scaricare i file del modello ONNX da un'esecuzione di training autoML.
  2. Comprendere gli input e gli output di un modello ONNX.
  3. Pre-elaborare i dati in modo che siano nel formato richiesto per le immagini di input.
  4. Eseguire l'inferenza con il runtime ONNX per Python.
  5. Visualizzare le stime per attività di rilevamento oggetti e segmentazione istanza.

ONNX è uno standard aperto per i modelli di apprendimento automatico e Deep Learning. Consente l'importazione e l'esportazione di modelli (interoperabilità) tra i framework di intelligenza artificiale di uso comune. Per altre informazioni, vedere il progetto GitHub ONNX.

Il runtime ONNX è un progetto open source che supporta l'inferenza tra piattaforme. Il runtime ONNX fornisce API tra linguaggi di programmazione (inclusi Python, C++, C#, C, C, Java e JavaScript). È possibile usare queste API per eseguire l'inferenza nelle immagini di input. Dopo aver esportato il modello in formato ONNX, è possibile usare queste API in qualunque linguaggio di programmazione necessario per il progetto.

Questa guida spiega come usare le API di Python per il runtime ONNX per creare stime sulle immagini per attività di visione di uso comune. È possibile usare questi modelli ONNX esportati in vari linguaggi.

Prerequisiti

Scaricare i file del modello ONNX

È possibile scaricare i file del modello ONNX da esecuzioni di AutoML usando l'interfaccia utente di Azure Machine Learning Studio o l’SDK Python di Azure Machine Learning. È consigliabile eseguire il download tramite l'SDK con il nome dell'esperimento e l'ID esecuzione padre.

Studio di Azure Machine Learning

In Azure Machine Learning Studio, passare all'esperimento usando il collegamento ipertestuale all'esperimento generato nel notebook di training o selezionando il nome dell'esperimento nella scheda Esperimenti in Asset. Selezionare, quindi, la migliore esecuzione figlio.

All'interno della migliore esecuzione figlio, passare a Output + log>train_artifacts. Usare il pulsante Download per scaricare manualmente i file seguenti:

  • labels.json: file contenente tutte le classi o le etichette nel set di dati di training.
  • model.onnx: modello in formato ONNX.

Screenshot that shows selections for downloading O N N X model files.

Salvare i file del modello scaricati in una directory. Nell'esempio riportato in questo articolo viene usata la directory ./automl_models.

Python SDK di Azure Machine Learning

Con l'SDK è possibile selezionare la migliore esecuzione figlio (per metrica primaria) con il nome dell'esperimento e l'ID esecuzione padre. È possibile, quindi, scaricare i file labels.json e model.onnx.

Il codice seguente restituisce la migliore esecuzione figlio in base alla metrica primaria pertinente.

from azure.identity import DefaultAzureCredential
from azure.ai.ml import MLClient
mlflow_client = MlflowClient()

credential = DefaultAzureCredential()
ml_client = None
try:
    ml_client = MLClient.from_config(credential)
except Exception as ex:
    print(ex)
    # Enter details of your Azure Machine Learning workspace
    subscription_id = ''   
    resource_group = ''  
    workspace_name = ''
    ml_client = MLClient(credential, subscription_id, resource_group, workspace_name)
import mlflow
from mlflow.tracking.client import MlflowClient

# Obtain the tracking URL from MLClient
MLFLOW_TRACKING_URI = ml_client.workspaces.get(
    name=ml_client.workspace_name
).mlflow_tracking_uri

mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)

# Specify the job name
job_name = ''

# Get the parent run
mlflow_parent_run = mlflow_client.get_run(job_name)
best_child_run_id = mlflow_parent_run.data.tags['automl_best_child_run_id']
# get the best child run
best_run = mlflow_client.get_run(best_child_run_id)

Scaricare il file labels.json, contenente tutte le classi o le etichette nel set di dati di training.

local_dir = './automl_models'
if not os.path.exists(local_dir):
    os.mkdir(local_dir)

labels_file = mlflow_client.download_artifacts(
    best_run.info.run_id, 'train_artifacts/labels.json', local_dir
)

Scaricare il file model.onnx.

onnx_model_path = mlflow_client.download_artifacts(
    best_run.info.run_id, 'train_artifacts/model.onnx', local_dir
)

In caso di inferenza batch per rilevamento oggetti e segmentazione istanza usando modelli ONNX, vedere la sezione relativa alla generazione di modelli per l'assegnazione dei punteggi in batch.

Generazione di modelli per l'assegnazione dei punteggi in batch

Per impostazione predefinita, AutoML per le immagini supporta l'assegnazione dei punteggi in batch per la classificazione. I modelli ONNX di rilevamento oggetti e segmentazione istanza, però, non supportano l'inferenza batch. In caso di inferenza batch per rilevamento oggetti e segmentazione istanza, usare la procedura seguente per generare un modello ONNX per le dimensioni batch necessarie. I modelli generati per una dimensione batch specifica non funzionano per altre dimensioni batch.

Scaricare il file di ambiente conda e creare un oggetto ambiente da usare con il comando job.

#  Download conda file and define the environment

conda_file = mlflow_client.download_artifacts(
    best_run.info.run_id, "outputs/conda_env_v_1_0_0.yml", local_dir
)
from azure.ai.ml.entities import Environment
env = Environment(
    name="automl-images-env-onnx",
    description="environment for automl images ONNX batch model generation",
    image="mcr.microsoft.com/azureml/openmpi4.1.0-cuda11.1-cudnn8-ubuntu18.04",
    conda_file=conda_file,
)

Usare gli argomenti specifici del modello seguenti per inviare lo script. Per altri dettagli sugli argomenti, fare riferimento ai modelli di iperparametri specifici; per i nomi dei modelli di rilevamento oggetti supportati, fare riferimento alla sezione sull'architettura del modello supportata.

Per ottenere i valori degli argomenti necessari per la creazione del modello di assegnazione dei punteggi in batch, vedere gli script di assegnazione dei punteggi generati nella cartella di output delle esecuzioni di training AutoML. Usare i valori degli iperparametri disponibili nella variabile delle impostazioni del modello all'interno del file di assegnazione dei punteggi per la migliore esecuzione figlio.

Per la classificazione di immagini multiclasse, il modello ONNX generato per la migliore esecuzione figlio supporta l'assegnazione dei punteggi in batch per impostazione predefinita. Di conseguenza, non sono necessari argomenti specifici del modello per questo tipo di attività ed è possibile passare alla sezione Caricare etichette e file del modello ONNX.

Scaricare e mantenere il file ONNX_batch_model_generator_automl_for_images.py nella directory corrente per inviare lo script. Usare il comando job seguente per inviare lo script ONNX_batch_model_generator_automl_for_images.py disponibile nel repository GitHub azureml-examples per generare un modello ONNX con dimensioni batch specifiche. Nel codice seguente, l'ambiente del modello sottoposto a training viene usato per inviare questo script per la generazione e il salvataggio del modello ONNX nella directory di output.

Per la classificazione di immagini multiclasse, il modello ONNX generato per la migliore esecuzione figlio supporta l'assegnazione dei punteggi in batch per impostazione predefinita. Di conseguenza, non sono necessari argomenti specifici del modello per questo tipo di attività ed è possibile passare alla sezione Caricare etichette e file del modello ONNX.

Dopo aver generato il modello batch, scaricarlo manualmente da Output + log>output tramite l'interfaccia utente o usare il metodo seguente:

batch_size = 8  # use the batch size used to generate the model
returned_job_run = mlflow_client.get_run(returned_job.name)

# Download run's artifacts/outputs
onnx_model_path = mlflow_client.download_artifacts(
    returned_job_run.info.run_id, 'outputs/model_'+str(batch_size)+'.onnx', local_dir
)

Dopo il passaggio di download del modello, usare il pacchetto Python del runtime ONNX per eseguire l'inferenza usando il file model.onnx. A scopo dimostrativo, questo articolo usa i set di dati della sezione Come preparare set di dati di immagini per ogni attività di visione.

Sono stati sottoposti a training i modelli per tutte le attività di visione con i rispettivi set di dati per dimostrare l'inferenza del modello ONNX.

Caricare le etichette e i file del modello ONNX

Il frammento di codice seguente carica labels.json, dove i nomi delle classi sono ordinati, vale a dire che se il modello ONNX stima un ID etichetta come 2, questo corrisponde al nome dell'etichetta specificato nel terzo indice nel file labels.json.

import json
import onnxruntime

labels_file = "automl_models/labels.json"
with open(labels_file) as f:
    classes = json.load(f)
print(classes)
try:
    session = onnxruntime.InferenceSession(onnx_model_path)
    print("ONNX model loaded...")
except Exception as e: 
    print("Error loading ONNX file: ", str(e))

Ottenere i dettagli di input e output previsti per un modello ONNX

Quando si dispone del modello, è importante conoscere alcuni dettagli specifici del modello e dell'attività. Questi dettagli includono il numero di input e il numero di output, la forma o il formato di input previsti per la pre-elaborazione dell'immagine e la forma di output, in modo da conoscere gli output specifici del modello o dell'attività.

sess_input = session.get_inputs()
sess_output = session.get_outputs()
print(f"No. of inputs : {len(sess_input)}, No. of outputs : {len(sess_output)}")

for idx, input_ in enumerate(range(len(sess_input))):
    input_name = sess_input[input_].name
    input_shape = sess_input[input_].shape
    input_type = sess_input[input_].type
    print(f"{idx} Input name : { input_name }, Input shape : {input_shape}, \
    Input type  : {input_type}")  

for idx, output in enumerate(range(len(sess_output))):
    output_name = sess_output[output].name
    output_shape = sess_output[output].shape
    output_type = sess_output[output].type
    print(f" {idx} Output name : {output_name}, Output shape : {output_shape}, \
    Output type  : {output_type}") 

Formati di input e output previsti per il modello ONNX

Ogni modello ONNX ha un set predefinito di formati di input e output.

Questo esempio applica il modello sottoposto a training sul set di dati fridgeObjects con 134 immagini e 4 classi/etichette per spiegare l'inferenza del modello ONNX. Per altre informazioni sul training di un'attività di classificazione delle immagini, vedere il notebook di classificazione di immagini multiclasse.

Formato di input

L'input è un'immagine pre-elaborata.

Nome input Forma di input Tipo di input Descrizione
input1 (batch_size, num_channels, height, width) ndarray(float) L'input è un'immagine pre-elaborata, con la forma (1, 3, 224, 224) per dimensioni batch pari a 1 e un'altezza e una larghezza pari a 224. Questi numeri corrispondono ai valori usati per crop_size nell'esempio di training.

Formato di output

L'output è un array di logits per tutte le classi/etichette.

Nome output Forma di output Tipo di output Descrizione
output1 (batch_size, num_classes) ndarray(float) Il modello restituisce logits (senza softmax). Ad esempio, per le dimensioni batch 1 e 4 classi, restituisce (1, 4).

Pre-elaborazione

Eseguire i passaggi di pre-elaborazione seguenti per l'inferenza del modello ONNX:

  1. Convertire l'immagine in RGB.
  2. Ridimensionare l'immagine su valori valid_resize_size e valid_resize_size corrispondenti a quelli usati nella trasformazione del set di dati di convalida durante il training. Il valore predefinito per valid_resize_size è 256.
  3. Ritagliare al centro l'immagine su height_onnx_crop_size e width_onnx_crop_size. Corrisponde a valid_crop_size con il valore predefinito 224.
  4. Cambia HxWxC in CxHxW.
  5. Convertire in tipo float.
  6. Normalizzare con i mean = [0.485, 0.456, 0.406] e std = [0.229, 0.224, 0.225] di ImageNet.

Se si scelgono valori diversi per gli iperparametrivalid_resize_size e valid_crop_size durante il training, usare tali valori.

Ottenere la forma di input necessaria per il modello ONNX.

batch, channel, height_onnx_crop_size, width_onnx_crop_size = session.get_inputs()[0].shape
batch, channel, height_onnx_crop_size, width_onnx_crop_size

Senza PyTorch

import glob
import numpy as np
from PIL import Image

def preprocess(image, resize_size, crop_size_onnx):
    """Perform pre-processing on raw input image
    
    :param image: raw input image
    :type image: PIL image
    :param resize_size: value to resize the image
    :type image: Int
    :param crop_size_onnx: expected height of an input image in onnx model
    :type crop_size_onnx: Int
    :return: pre-processed image in numpy format
    :rtype: ndarray 1xCxHxW
    """

    image = image.convert('RGB')
    # resize
    image = image.resize((resize_size, resize_size))
    #  center  crop
    left = (resize_size - crop_size_onnx)/2
    top = (resize_size - crop_size_onnx)/2
    right = (resize_size + crop_size_onnx)/2
    bottom = (resize_size + crop_size_onnx)/2
    image = image.crop((left, top, right, bottom))

    np_image = np.array(image)
    # HWC -> CHW
    np_image = np_image.transpose(2, 0, 1) # CxHxW
    # normalize the image
    mean_vec = np.array([0.485, 0.456, 0.406])
    std_vec = np.array([0.229, 0.224, 0.225])
    norm_img_data = np.zeros(np_image.shape).astype('float32')
    for i in range(np_image.shape[0]):
        norm_img_data[i,:,:] = (np_image[i,:,:]/255 - mean_vec[i])/std_vec[i]
             
    np_image = np.expand_dims(norm_img_data, axis=0) # 1xCxHxW
    return np_image

# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images

test_images_path = "automl_models_multi_cls/test_images_dir/*" # replace with path to images
# Select batch size needed
batch_size = 8
# you can modify resize_size based on your trained model
resize_size = 256
# height and width will be the same for classification
crop_size_onnx = height_onnx_crop_size 

image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
    img = Image.open(image_files[i])
    img_processed_list.append(preprocess(img, resize_size, crop_size_onnx))
    
if len(img_processed_list) > 1:
    img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
    img_data = img_processed_list[0]
else:
    img_data = None

assert batch_size == img_data.shape[0]

Con PyTorch

import glob
import torch
import numpy as np
from PIL import Image
from torchvision import transforms

def _make_3d_tensor(x) -> torch.Tensor:
    """This function is for images that have less channels.

    :param x: input tensor
    :type x: torch.Tensor
    :return: return a tensor with the correct number of channels
    :rtype: torch.Tensor
    """
    return x if x.shape[0] == 3 else x.expand((3, x.shape[1], x.shape[2]))

def preprocess(image, resize_size, crop_size_onnx):
    transform = transforms.Compose([
        transforms.Resize(resize_size),
        transforms.CenterCrop(crop_size_onnx),
        transforms.ToTensor(),
        transforms.Lambda(_make_3d_tensor),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
    
    img_data = transform(image)
    img_data = img_data.numpy()
    img_data = np.expand_dims(img_data, axis=0)
    return img_data

# following code loads only batch_size number of images for demonstrating ONNX inference
# make sure that the data directory has at least batch_size number of images

test_images_path = "automl_models_multi_cls/test_images_dir/*"  # replace with path to images
# Select batch size needed
batch_size = 8
# you can modify resize_size based on your trained model
resize_size = 256
# height and width will be the same for classification
crop_size_onnx = height_onnx_crop_size 

image_files = glob.glob(test_images_path)
img_processed_list = []
for i in range(batch_size):
    img = Image.open(image_files[i])
    img_processed_list.append(preprocess(img, resize_size, crop_size_onnx))
    
if len(img_processed_list) > 1:
    img_data = np.concatenate(img_processed_list)
elif len(img_processed_list) == 1:
    img_data = img_processed_list[0]
else:
    img_data = None

assert batch_size == img_data.shape[0]

Inferenza con il runtime ONNX

L'inferenza con il runtime ONNX è diversa per ogni attività di visione artificiale.

def get_predictions_from_ONNX(onnx_session, img_data):
    """Perform predictions with ONNX runtime
    
    :param onnx_session: onnx model session
    :type onnx_session: class InferenceSession
    :param img_data: pre-processed numpy image
    :type img_data: ndarray with shape 1xCxHxW
    :return: scores with shapes
            (1, No. of classes in training dataset) 
    :rtype: numpy array
    """

    sess_input = onnx_session.get_inputs()
    sess_output = onnx_session.get_outputs()
    print(f"No. of inputs : {len(sess_input)}, No. of outputs : {len(sess_output)}")    
    # predict with ONNX Runtime
    output_names = [ output.name for output in sess_output]
    scores = onnx_session.run(output_names=output_names,\
                                               input_feed={sess_input[0].name: img_data})
    
    return scores[0]

scores = get_predictions_from_ONNX(session, img_data)

Post-elaborazione

Applicare softmax() sui valori stimati per ottenere i punteggi di confidenza della classificazione (probabilità) per ogni classe. La stima, quindi, sarà la classe con la probabilità più alta.

Senza PyTorch

def softmax(x):
    e_x = np.exp(x - np.max(x, axis=1, keepdims=True))
    return e_x / np.sum(e_x, axis=1, keepdims=True)

conf_scores = softmax(scores)
class_preds = np.argmax(conf_scores, axis=1)
print("predicted classes:", ([(class_idx, classes[class_idx]) for class_idx in class_preds]))

Con PyTorch

conf_scores = torch.nn.functional.softmax(torch.from_numpy(scores), dim=1)
class_preds = torch.argmax(conf_scores, dim=1)
print("predicted classes:", ([(class_idx.item(), classes[class_idx]) for class_idx in class_preds]))

Visualizzare stime

Visualizzare un'immagine di input con etichette

import matplotlib.image as mpimg
import matplotlib.pyplot as plt
%matplotlib inline

sample_image_index = 0 # change this for an image of interest from image_files list
IMAGE_SIZE = (18, 12)
plt.figure(figsize=IMAGE_SIZE)
img_np = mpimg.imread(image_files[sample_image_index])

img = Image.fromarray(img_np.astype('uint8'), 'RGB')
x, y = img.size

fig,ax = plt.subplots(1, figsize=(15, 15))
# Display the image
ax.imshow(img_np)

label = class_preds[sample_image_index]
if torch.is_tensor(label):
    label = label.item()
    
conf_score = conf_scores[sample_image_index]
if torch.is_tensor(conf_score):
    conf_score = np.max(conf_score.tolist())
else:
    conf_score = np.max(conf_score)

display_text = '{} ({})'.format(label, round(conf_score, 3))
print(display_text)

color = 'red'
plt.text(30, 30, display_text, color=color, fontsize=30)

plt.show()

Passaggi successivi