Faça previsões com ONNX em modelos de visão computacional do AutoML

APLICA-SE A: Python SDK azure-ai-ml v2 (atual)

Neste artigo, você aprenderá a usar o Open Neural Network Exchange (ONNX) para fazer previsões em modelos de visão computacional gerados a partir do aprendizado de máquina automatizado (AutoML) no Azure Machine Learning.

Para usar o ONNX para previsões, você precisa:

  1. Baixe arquivos de modelo ONNX de uma execução de treinamento AutoML.
  2. Compreender as entradas e saídas de um modelo ONNX.
  3. Pré-processe seus dados para que estejam no formato necessário para imagens de entrada.
  4. Execute inferência com o ONNX Runtime for Python.
  5. Visualize previsões para tarefas de deteção de objetos e segmentação de instâncias.

O ONNX é um padrão aberto para modelos de aprendizado de máquina e aprendizado profundo. Ele permite a importação e exportação de modelos (interoperabilidade) entre as estruturas de IA populares. Para obter mais detalhes, explore o projeto ONNX GitHub.

ONNX Runtime é um projeto de código aberto que suporta inferência entre plataformas. O ONNX Runtime fornece APIs em linguagens de programação (incluindo Python, C++, C#, C, Java e JavaScript). Você pode usar essas APIs para executar inferência em imagens de entrada. Depois de ter o modelo que foi exportado para o formato ONNX, você pode usar essas APIs em qualquer linguagem de programação que seu projeto precisa.

Neste guia, você aprenderá a usar APIs Python para ONNX Runtime para fazer previsões em imagens para tarefas de visão populares. Você pode usar esses modelos exportados ONNX entre idiomas.

Pré-requisitos

  • Obtenha um modelo de visão computacional treinado pelo AutoML para qualquer uma das tarefas de imagem suportadas: classificação, deteção de objetos ou segmentação de instância. Saiba mais sobre o suporte AutoML para tarefas de visão computacional.

  • Instale o pacote onnxruntime . Os métodos neste artigo foram testados com as versões 1.3.0 a 1.8.0.

Baixar arquivos de modelo ONNX

Você pode baixar arquivos de modelo ONNX de execuções do AutoML usando a interface do usuário do estúdio Azure Machine Learning ou o SDK Python do Azure Machine Learning. Recomendamos o download através do SDK com o nome do experimento e o ID de execução pai.

Azure Machine Learning Studio

No estúdio de Aprendizado de Máquina do Azure, vá para seu experimento usando o hiperlink para o experimento gerado no bloco de anotações de treinamento ou selecionando o nome do experimento na guia Experimentos em Ativos. Em seguida, selecione a melhor corrida filho.

Dentro da melhor execução filho, vá para Saídas+logs>train_artifacts. Use o botão Download para baixar manualmente os seguintes arquivos:

  • labels.json: Arquivo que contém todas as classes ou rótulos no conjunto de dados de treinamento.
  • model.onnx: Modelo em formato ONNX.

Captura de tela que mostra seleções para baixar arquivos de modelo O N N X.

Salve os arquivos de modelo baixados em um diretório. O exemplo neste artigo usa o diretório ./automl_models .

SDK do Python do Azure Machine Learning

Com o SDK, você pode selecionar a melhor execução filho (por métrica primária) com o nome do experimento e o ID de execução pai. Em seguida, você pode baixar os arquivos labels.json e model.onnx .

O código a seguir retorna a melhor execução filho com base na métrica primária relevante.

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)

Baixe o arquivo labels.json , que contém todas as classes e rótulos no conjunto de dados de treinamento.

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
)

Baixe o arquivo model.onnx .

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

No caso de inferência em lote para deteção de objetos e segmentação de instância usando modelos ONNX, consulte a seção sobre geração de modelos para pontuação em lote.

Geração de modelos para pontuação em lote

Por padrão, o AutoML for Images oferece suporte à pontuação em lote para classificação. Mas os modelos ONNX de deteção de objetos e segmentação de instâncias não suportam inferência em lote. No caso de inferência em lote para deteção de objetos e segmentação de instâncias, use o procedimento a seguir para gerar um modelo ONNX para o tamanho de lote necessário. Os modelos gerados para um tamanho de lote específico não funcionam para outros tamanhos de lote.

Baixe o arquivo de ambiente conda e crie um objeto de ambiente para ser usado com o trabalho de comando.

#  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,
)

Use os seguintes argumentos específicos do modelo para enviar o script. Para obter mais detalhes sobre argumentos, consulte hiperparâmetros específicos do modelo e, para nomes de modelos de deteção de objetos suportados, consulte a seção arquitetura de modelo suportada.

Para obter os valores de argumento necessários para criar o modelo de pontuação em lote, consulte os scripts de pontuação gerados na pasta de saídas das execuções de treinamento do AutoML. Use os valores de hiperparâmetro disponíveis na variável de configurações do modelo dentro do arquivo de pontuação para a melhor execução filho.

Para classificação de imagem de várias classes, o modelo ONNX gerado para a melhor execução infantil suporta pontuação em lote por padrão. Portanto, nenhum argumento específico do modelo é necessário para esse tipo de tarefa e você pode pular para a seção Carregar os rótulos e arquivos de modelo ONNX.

Baixe e mantenha o ONNX_batch_model_generator_automl_for_images.py arquivo no diretório atual para enviar o script. Use o seguinte trabalho de comando para enviar o script ONNX_batch_model_generator_automl_for_images.py disponível no repositório GitHub azureml-examples, para gerar um modelo ONNX de um tamanho de lote específico. No código a seguir, o ambiente de modelo treinado é usado para enviar esse script para gerar e salvar o modelo ONNX no diretório de saídas.

Para classificação de imagem de várias classes, o modelo ONNX gerado para a melhor execução infantil suporta pontuação em lote por padrão. Portanto, nenhum argumento específico do modelo é necessário para esse tipo de tarefa e você pode pular para a seção Carregar os rótulos e arquivos de modelo ONNX.

Depois que o modelo em lote for gerado, baixe-o das saídas Outputs+logs>manualmente por meio da interface do usuário ou use o seguinte método:

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
)

Após a etapa de download do modelo, use o pacote Python do ONNX Runtime para executar a inferência usando o arquivo model.onnx . Para fins de demonstração, este artigo usa os conjuntos de dados de Como preparar conjuntos de dados de imagem para cada tarefa de visão.

Treinamos os modelos para todas as tarefas de visão com seus respetivos conjuntos de dados para demonstrar a inferência do modelo ONNX.

Carregue as etiquetas e os arquivos de modelo ONNX

O trecho de código a seguir carrega labels.json, onde os nomes de classe são ordenados. Ou seja, se o modelo ONNX prevê um ID de rótulo como 2, então ele corresponde ao nome do rótulo dado no terceiro índice no arquivo 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))

Obter detalhes de entrada e saída esperados para um modelo ONNX

Quando você tem o modelo, é importante saber alguns detalhes específicos do modelo e da tarefa. Esses detalhes incluem o número de entradas e o número de saídas, a forma ou o formato de entrada esperado para pré-processamento da imagem e a forma de saída para que você conheça as saídas específicas do modelo ou da tarefa.

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}") 

Formatos de entrada e saída esperados para o modelo ONNX

Cada modelo ONNX tem um conjunto predefinido de formatos de entrada e saída.

Este exemplo aplica o modelo treinado no conjunto de dados fridgeObjects com 134 imagens e 4 classes/rótulos para explicar a inferência do modelo ONNX. Para obter mais informações sobre como treinar uma tarefa de classificação de imagem, consulte o bloco de anotações de classificação de imagem de várias classes.

Formato de entrada

A entrada é uma imagem pré-processada.

Nome de entrada Forma de entrada Input type Description
entrada1 (batch_size, num_channels, height, width) ndarray(float) A entrada é uma imagem pré-processada, com a forma (1, 3, 224, 224) para um tamanho de lote de 1 e uma altura e largura de 224. Estes números correspondem aos valores utilizados no crop_size exemplo de treino.

Formato de saída

A saída é uma matriz de logits para todas as classes/rótulos.

Nome da saída Forma de saída Tipo de saída Description
saída1 (batch_size, num_classes) ndarray(float) Modelo retorna logits (sem softmax). Por exemplo, para classes de tamanho de lote 1 e 4, ele retorna (1, 4).

Pré-processamento

Execute as seguintes etapas de pré-processamento para a inferência do modelo ONNX:

  1. Converta a imagem em RGB.
  2. Redimensione a imagem para valid_resize_size e valid_resize_size os valores que correspondem aos valores usados na transformação do conjunto de dados de validação durante o treinamento. O valor padrão para valid_resize_size é 256.
  3. Centro cortar a imagem para height_onnx_crop_size e width_onnx_crop_size. Corresponde ao valid_crop_size valor padrão de 224.
  4. Altere HxWxC para CxHxW.
  5. Converter para o tipo float.
  6. Normalize com ImageNet mean = [0.485, 0.456, 0.406] e .std = [0.229, 0.224, 0.225]

Se você escolheu valores diferentes para os hiperparâmetros valid_resize_size e valid_crop_size durante o treinamento, então esses valores devem ser usados.

Obtenha a forma de entrada necessária para o modelo 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

Sem 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]

Com 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]

Inferência com ONNX Runtime

A inferência com o ONNX Runtime difere para cada tarefa de visão computacional.

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)

Pós-processamento

Aplique softmax() acima dos valores previstos para obter pontuações de confiança de classificação (probabilidades) para cada classe. Então a previsão será a classe com maior probabilidade.

Sem 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]))

Com 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]))

Visualizar previsões

Visualize uma imagem de entrada com etiquetas.

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()

Próximos passos