Göra förutsägelser med ONNX på modeller för visuellt innehåll från AutoML

GÄLLER FÖR: Python SDK azure-ai-ml v2 (aktuell)

I den här artikeln får du lära dig hur du använder Open Neural Network Exchange (ONNX) för att göra förutsägelser om modeller för visuellt innehåll som genererats från automatiserad maskininlärning (AutoML) i Azure Machine Learning.

Om du vill använda ONNX för förutsägelser måste du:

  1. Ladda ned ONNX-modellfiler från en AutoML-träningskörning.
  2. Förstå indata och utdata för en ONNX-modell.
  3. Förbearbeta dina data så att de är i det format som krävs för indatabilder.
  4. Utföra slutsatsdragning med ONNX Runtime för Python.
  5. Visualisera förutsägelser för objektidentifiering och instanssegmenteringsuppgifter.

ONNX är en öppen standard för maskininlärning och djupinlärningsmodeller. Det möjliggör modellimport och export (samverkan) i de populära AI-ramverken. Mer information finns i ONNX GitHub-projektet.

ONNX Runtime är ett projekt med öppen källkod som stöder plattformsoberoende slutsatsdragning. ONNX Runtime tillhandahåller API:er för olika programmeringsspråk (inklusive Python, C++, C#, C, Java och JavaScript). Du kan använda dessa API:er för att utföra slutsatsdragning på indatabilder. När du har den modell som har exporterats till ONNX-format kan du använda dessa API:er på valfritt programmeringsspråk som ditt projekt behöver.

I den här guiden får du lära dig hur du använder Python-API:er för ONNX Runtime för att göra förutsägelser på bilder för populära visionsuppgifter. Du kan använda dessa ONNX-exporterade modeller på flera språk.

Förutsättningar

Ladda ned ONNX-modellfiler

Du kan ladda ned ONNX-modellfiler från AutoML-körningar med hjälp av Azure Machine Learning-studio UI eller Azure Machine Learning Python SDK. Vi rekommenderar att du laddar ned via SDK:t med experimentnamnet och det överordnade körnings-ID:t.

Azure Machine Learning Studio

På Azure Machine Learning-studio går du till experimentet med hjälp av hyperlänken till experimentet som genereras i träningsanteckningsboken eller genom att välja experimentnamnet på fliken Experiment under Tillgångar. Välj sedan den bästa underordnade körningen.

Inom den bästa underordnade körningen går du till Utdata + loggar>train_artifacts. Använd knappen Ladda ned för att manuellt ladda ned följande filer:

  • labels.json: Fil som innehåller alla klasser eller etiketter i träningsdatauppsättningen.
  • model.onnx: Modell i ONNX-format.

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

Spara de nedladdade modellfilerna i en katalog. I exemplet i den här artikeln används katalogen ./automl_models .

Python-SDK för Azure Machine Learning

Med SDK:t kan du välja den bästa underordnade körningen (efter primärt mått) med experimentnamnet och det överordnade körnings-ID:t. Sedan kan du ladda ned filerna labels.json och model.onnx .

Följande kod returnerar den bästa underordnade körningen baserat på relevant primärt mått.

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)

Ladda ned filen labels.json , som innehåller alla klasser och etiketter i träningsdatauppsättningen.

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
)

Ladda ned filen model.onnx.

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

Vid batchinferens för objektidentifiering och instanssegmentering med hjälp av ONNX-modeller läser du avsnittet om modellgenerering för batchbedömning.

Modellgenerering för batchbedömning

Som standard stöder AutoML för bilder batchbedömning för klassificering. Men ONNX-modeller för objektidentifiering och instanssegmentering stöder inte batchinferens. Vid batchinferens för objektidentifiering och instanssegmentering använder du följande procedur för att generera en ONNX-modell för den batchstorlek som krävs. Modeller som genereras för en viss batchstorlek fungerar inte för andra batchstorlekar.

Ladda ned conda-miljöfilen och skapa ett miljöobjekt som ska användas med kommandojobbet.

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

Använd följande modellspecifika argument för att skicka skriptet. Mer information om argument finns i modellspecifika hyperparametrar och för objektidentifieringsmodellnamn som stöds finns avsnittet modellarkitektur som stöds.

Information om hur du hämtar de argumentvärden som behövs för att skapa batchbedömningsmodellen finns i bedömningsskripten som genereras under utdatamappen för AutoML-träningskörningarna. Använd hyperparametervärdena som är tillgängliga i variabeln för modellinställningar i bedömningsfilen för bästa underordnade körning.

För bildklassificering i flera klasser stöder den genererade ONNX-modellen för bästa underordnade körning batchbedömning som standard. Därför behövs inga modellspecifika argument för den här aktivitetstypen och du kan gå vidare till avsnittet Läs in etiketter och ONNX-modellfiler .

Ladda ned och behåll ONNX_batch_model_generator_automl_for_images.py filen i den aktuella katalogen för att skicka skriptet. Använd följande kommandojobb för att skicka skriptet ONNX_batch_model_generator_automl_for_images.py som är tillgängligt på GitHub-lagringsplatsen azureml-examples för att generera en ONNX-modell med en specifik batchstorlek. I följande kod används den tränade modellmiljön för att skicka det här skriptet för att generera och spara ONNX-modellen i utdatakatalogen.

För bildklassificering i flera klasser stöder den genererade ONNX-modellen för bästa underordnade körning batchbedömning som standard. Därför behövs inga modellspecifika argument för den här aktivitetstypen och du kan gå vidare till avsnittet Läs in etiketter och ONNX-modellfiler .

När batchmodellen har genererats kan du antingen ladda ned den från utdata+loggar>manuellt via användargränssnittet eller använda följande metod:

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
)

Efter modellnedladdningssteget använder du PYTHON-paketet ONNX Runtime för att utföra slutsatsdragning med hjälp av filen model.onnx . I demonstrationssyfte använder den här artikeln datauppsättningarna från How to prepare image datasets for each vision task (Förbereda bilddatauppsättningar för varje visionsuppgift).

Vi tränade modellerna för alla visionsuppgifter med sina respektive datauppsättningar för att demonstrera ONNX-modellinferens.

Läs in etiketterna och ONNX-modellfilerna

Följande kodfragment läser in labels.json, där klassnamn sorteras. Om ONNX-modellen förutsäger ett etikett-ID som 2 motsvarar det etikettnamnet som anges i det tredje indexet i filen 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))

Hämta förväntad information om indata och utdata för en ONNX-modell

När du har modellen är det viktigt att känna till viss modellspecifik och uppgiftsspecifik information. Den här informationen omfattar antalet indata och antalet utdata, förväntad indataform eller format för förbearbetning av bilden och utdataform så att du känner till modellspecifika eller uppgiftsspecifika utdata.

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

Förväntade in- och utdataformat för ONNX-modellen

Varje ONNX-modell har en fördefinierad uppsättning indata- och utdataformat.

Det här exemplet använder modellen som tränats på datauppsättningen fridgeObjects med 134 bilder och 4 klasser/etiketter för att förklara ONNX-modellinferens. Mer information om hur du tränar en bildklassificeringsuppgift finns i notebook-filen för bildklassificering i flera klasser.

Indataformat

Indata är en förbearbetad avbildning.

Indatanamn Indataform Input type beskrivning
input1 (batch_size, num_channels, height, width) ndarray(float) Indata är en förbearbetad bild med formen (1, 3, 224, 224) för en batchstorlek på 1 och en höjd och bredd på 224. Dessa siffror motsvarar de värden som används för crop_size i träningsexemplet.

Utdataformat

Utdata är en matris med logits för alla klasser/etiketter.

Utdatanamn Utdataform Utdatatyp beskrivning
output1 (batch_size, num_classes) ndarray(float) Modellen returnerar logits (utan softmax). För batchstorlek 1 och 4-klasser returneras (1, 4)till exempel .

Förbehandling

Utför följande förbearbetningssteg för ONNX-modellinferensen:

  1. Konvertera bilden till RGB.
  2. Ändra storlek på avbildningen till valid_resize_size och valid_resize_size värden som motsvarar de värden som används i omvandlingen av valideringsdatauppsättningen under träningen. Standardvärdet för valid_resize_size är 256.
  3. Centrera beskära avbildningen till height_onnx_crop_size och width_onnx_crop_size. Det motsvarar valid_crop_size med standardvärdet 224.
  4. Ändra HxWxC till CxHxW.
  5. Konvertera till flyttaltyp.
  6. Normalisera med ImageNets mean = [0.485, 0.456, 0.406] och .std = [0.229, 0.224, 0.225]

Om du väljer olika värden för hyperparametrarvalid_resize_size och valid_crop_size under träningen ska dessa värden användas.

Hämta den indataform som behövs för ONNX-modellen.

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

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

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

Slutsatsdragning med ONNX Runtime

Slutsatsdragningen med ONNX Runtime skiljer sig åt för varje uppgift för visuellt innehåll.

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)

Postprocessing

Använd softmax() över förutsagda värden för att få klassificeringsförtroendepoäng (sannolikheter) för varje klass. Sedan blir förutsägelsen den klass med högst sannolikhet.

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

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

Visualisera förutsägelser

Visualisera en indatabild med etiketter.

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

Nästa steg