Compartilhar via


Fazer previsões com o ONNX em modelos de pesquisa visual computacional do AutoML (v1)

APLICA-SE A: SDK do Python azureml v1

Importante

Alguns comandos da CLI do Azure neste artigo usam a extensão azure-cli-ml ou v1 do Azure Machine Learning. O suporte à extensão v1 terminará em 30 de setembro de 2025. Você poderá instalar e usar a extensão v1 até essa data.

Recomendamos que você faça a transição para a extensão ml ou v2, antes de 30 de setembro de 2025. Para obter mais informações sobre a extensão v2, confira Extensão da CLI do Azure ML e SDK do Python v2.

Neste artigo, você aprenderá a usar o ONNX (Open Neural Network Exchange) para fazer previsões sobre modelos de pesquisa visual computacional gerados por meio do AutoML (machine learning automatizado) no Azure Machine Learning.

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

  1. Baixar os arquivos de modelo do ONNX de uma execução de treinamento do AutoML.
  2. Entender as entradas e saídas de um modelo do ONNX.
  3. Pré-processar os seus dados para que eles estejam no formato necessário para as imagens de entrada.
  4. Executar a inferência com o ONNX Runtime para Python.
  5. Visualizar previsões para tarefas de segmentação de instâncias e detecção de objetos.

O ONNX é um padrão aberto para machine learning e modelos de aprendizado profundo. Ele permite a importação e exportação de modelos (interoperabilidade) entre estruturas de IA populares. Para obter mais detalhes, explore o Projeto ONNX do GitHub.

O ONNX Runtime é um projeto de código aberto que dá suporte à inferência de multiplataforma. 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 a inferência em imagens de entrada. Depois de exportar o modelo para o formato ONNX, você poderá usar essas APIs em qualquer linguagem de programação que seu projeto precisar.

Neste guia, você aprenderá a usar as APIs do Python para o ONNX Runtime para fazer previsões sobre imagens em tarefas populares da pesquisa visual. Você pode usar esses modelos exportados do ONNX nas linguagens.

Pré-requisitos

Baixar arquivos de modelo do ONNX

Você pode baixar arquivos de modelo do ONNX em execuções do AutoML usando a interface do usuário do Estúdio do Azure Machine Learning ou o SDK do Azure Machine Learning para Python. É recomendável baixar por meio do SDK com o nome do experimento e a ID de execução pai.

Azure Machine Learning Studio

No Estúdio do Azure Machine Learning, acesse seu experimento usando o hiperlink para o experimento gerado no notebook de treinamento ou selecionando o nome do experimento na guia Experimentos em Ativos. Em seguida, selecione a melhor execução filha.

Na melhor execução filha, acesse Saídas + logs>train_artifacts. Use o botão Baixar 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 no formato ONNX.

Captura de tela que mostra as seleções para baixar os arquivos de modelo ONNX.

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 filha (por métrica primária) com o nome do experimento e a 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 filha com base na métrica primária relevante.

from azureml.train.automl.run import AutoMLRun

# Select the best child run
run_id = '' # Specify the run ID
automl_image_run = AutoMLRun(experiment=experiment, run_id=run_id)
best_child_run = automl_image_run.get_best_child()

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

labels_file = 'automl_models/labels.json'
best_child_run.download_file(name='train_artifacts/labels.json', output_file_path=labels_file)

Baixe o arquivo model.onnx.

onnx_model_path = 'automl_models/model.onnx'
best_child_run.download_file(name='train_artifacts/model.onnx', output_file_path=onnx_model_path)

Geração de modelo para pontuação do lote

Por padrão, o AutoML para Imagens oferece suporte à pontuação do lote para classificação. No entanto, os modelos de detecção de objetos e segmentação de instâncias não dão suporte à inferência em lote. No caso de inferência do lote para detecção de objetos e segmentação de instâncias, use o procedimento a seguir para gerar um modelo do 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.

from azureml.core.script_run_config import ScriptRunConfig
from azureml.train.automl.run import AutoMLRun
from azureml.core.workspace import Workspace
from azureml.core import Experiment

# specify experiment name
experiment_name = ''
# specify workspace parameters
subscription_id = ''
resource_group = ''
workspace_name = ''
# load the workspace and compute target
ws = ''
compute_target = ''
experiment = Experiment(ws, name=experiment_name)

# specify the run id of the automl run
run_id = ''
automl_image_run = AutoMLRun(experiment=experiment, run_id=run_id)
best_child_run = automl_image_run.get_best_child()

Use os seguintes argumentos específicos do modelo para enviar o script. Para obter mais detalhes sobre argumentos, veja Hiperparâmetros específicos do modelo e, para obter os nomes dos modelo de detecção de objetos com suporte, veja a seção Algoritmo de modelo com suporte.

Para obter os valores de argumento necessários para criar o modelo de pontuação do lote, veja 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 filha.

Para classificação de imagem de várias classes, o modelo do ONNX gerado para a melhor execução filha dá suporte à pontuação do lote por padrão. Portanto, argumentos específicos do modelo não são necessários para esse tipo de tarefa e você pode pular para a seção Carregar os rótulos e arquivos de modelo do ONNX.

Baixe e mantenha o arquivo ONNX_batch_model_generator_automl_for_images.py no diretório atual e envie o script. Use ScriptRunConfig para enviar o script ONNX_batch_model_generator_automl_for_images.py disponível no repositório GitHub do azureml-examples, para gerar um modelo do 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 do ONNX no diretório de saídas.

script_run_config = ScriptRunConfig(source_directory='.',
                                    script='ONNX_batch_model_generator_automl_for_images.py',
                                    arguments=arguments,
                                    compute_target=compute_target,
                                    environment=best_child_run.get_environment())

remote_run = experiment.submit(script_run_config)
remote_run.wait_for_completion(wait_post_processing=True)

Depois que o modelo do lote for gerado, baixe-o em Saídas+logs>saídas manualmente ou use o seguinte método:

batch_size= 8  # use the batch size used to generate the model
onnx_model_path = 'automl_models/model.onnx'  # local path to save the model
remote_run.download_file(name='outputs/model_'+str(batch_size)+'.onnx', output_file_path=onnx_model_path)

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 em Como preparar conjuntos de dados de imagens para cada tarefa de pesquisa visual.

Treinamos os modelos para todas as tarefas de pesquisa visual com os respectivos conjuntos de dados para demonstrar a inferência de modelo do ONNX.

Carregar os rótulos e arquivos de modelo do ONNX

O snippet de código a seguir carrega labels.json, no qual os nomes de classe são ordenados. Ou seja, se o modelo do ONNX prever uma ID de rótulo como 2, ela corresponderá ao nome do rótulo fornecido 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 do ONNX

Quando você tem o modelo, é importante conhecer alguns detalhes específicos do modelo e da tarefa. Esses detalhes incluem o número de entradas e de saídas, a forma ou formato de entrada esperado para pré-processar a imagem e a forma de saída para que você saiba 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 de saída esperados para o modelo do ONNX

Todo modelo do ONNX tem um conjunto predefinido de formatos de entrada e de 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 de modelo do ONNX. Para obter mais informações sobre como treinar uma tarefa de classificação de imagem, confira o notebook de classificação de imagem de várias classes.

Formato de entrada

A entrada é uma imagem pré processada.

Nome de entrada Forma de entrada Tipo de entrada Descrição
input1 (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. Esses números correspondem aos valores usados para crop_size no exemplo de treinamento.

Formato da saída

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

Nome de saída Forma de saída Tipo de saída Descrição
output1 (batch_size, num_classes) ndarray(float) O modelo retorna logits (sem softmax). Por exemplo, para o tamanho de lote de 1 e 4 classes, ele retorna (1, 4).

Pré-processamento

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

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

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

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

Realizar inferência com o ONNX Runtime é diferente em cada tarefa de pesquisa visual 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() sobre os valores previstos para obter as pontuações de confiança de classificação (probabilidades) de cada classe. Em seguida, a previsão será a classe com a 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

Visualizar uma imagem de entrada com rótulos

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óximas etapas