透過 AutoML 的電腦視覺模型上的 ONNX 進行預測

適用於Python SDK azure-ai-ml v2 (目前)

在本文中,您將了解如何使用 Open Neural Network Exchange (ONNX),對於在 Azure Machine Learning 中的自動化機器學習 (AutoML) 所產生的電腦視覺模型做出預測。

若要使用 ONNX 進行預測,您需要:

  1. 從 AutoML 定型回合下載 ONNX 模型檔案。
  2. 了解 ONNX 模型的輸入和輸出。
  3. 將資料進行前置處理,使其符合輸入影像的必要格式。
  4. 使用適用於 Python 的 ONNX Runtime 來執行推斷。
  5. 對物件偵測和執行個體分割工作進行視覺化的預測。

ONNX 是機器學習和深度學習的開放標準。 它能夠在熱門的 AI 架構中,進行模型匯入和匯出 (互通性)。 如需詳細資訊,請探索 ONNX GitHub 專案

ONNX Runtime 是支援跨平台推斷的開放原始碼專案。 ONNX Runtime 提供 API 跨平台的程式設計語言 (包括 Python、C++、C#、C、JAVA 和 JavaScript)。 您可以使用這些 API 在輸入影像上執行推斷。 當您具備已匯出為 ONNX 格式的模型之後,即可在您專案所需的任何程式設計語言上使用這些 API。

在本指南中,您將了解如何使用適用於 ONNX Runtime 的 Python API,對熱門視覺工作的影像進行預測。 您可以跨語言使用這些 ONNX 匯出的模型。

必要條件

下載 ONNX 模型檔案

您可以使用 Azure Machine Learning 工作室 UI 或 Azure Machine Learning Python SDK,從 AutoML 執行下載 ONNX 模型檔案。 建議您使用實驗名稱和父代執行識別碼,透過 SDK 進行下載。

Azure Machine Learning Studio

在 Azure Machine Learning 工作室中,使用定型筆記本產生的實驗超連結,或在[資產] 下的[實驗] 索引標籤上,選取實驗名稱,前往您的實驗。 然後選取最佳的子系執行。

在最佳的子系執行中,前往 Outputs+logs>train_artifacts。 使用 [下載] 按鈕,手動下載以下檔案:

  • labels.json:此檔案包含定型資料集中的所有類別或標籤。
  • model.onnx:ONNX 格式的模型。

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

將下載的模型檔案儲存在目錄中。 本文中的範例使用 ./automl_models 目錄。

Azure Machine Learning Python SDK

透過 SDK,您可以使用實驗名稱和父代執行識別碼,選取最佳子系執行 (根據主要計量)。 然後,您可以下載 labels.jsonmodel.onnx 檔案。

下列程式碼會根據相關的主要計量傳回最佳的子系執行。

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)

下載 labels.json 檔案,其包含定型資料集中的所有類別和標籤。

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
)

下載 model.onnx 檔案。

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

如果是使用 ONNX 模型進行物件偵測和執行個體分割的批次推斷,請參閱產生模型以進行批次評分一節。

產生模型以進行批次評分

AutoML for Images 預設支援分類的批次評分。 不過,物件偵測和執行個體分割 ONNX 模型不支援批次推斷。 如果是物件偵測和執行個體分割的批次推斷,請使用下列程序為所需的批次大小產生 ONNX 模型。 為特定批次大小產生的模型,不適用於其他批次大小。

下載 conda 環境檔案,並建立要與命令作業搭配使用的環境物件。

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

使用下列模型特定引數來提交指令碼。 如需引數的詳細資訊,請參閱模型特定超參數,若要了解支援的物件偵測模型名稱,請參閱支援的模型架構一節

若要取得建立批次評分模型所需的引數值,請參閱 AutoML 定型執行的輸出資料夾下產生的評分指令碼。 為了獲得最佳子執行,請使用評分檔案內的模型設定變數中可用的超參數值。

針對多類別影像分類,為最佳子執行產生的 ONNX 模型依預設可支援批次評分。 因此,此工作類型不需要任何模型特定的引數,您可以跳至載入標籤和 ONNX 模型檔案一節。

下載 ONNX_batch_model_generator_automl_for_images.py 檔案並將其保存在目前的目錄中,以便提交指令碼。 使用下列命令作業提交 azureml-examples GitHub 存放庫中可用的指令碼 ONNX_batch_model_generator_automl_for_images.py,以產生特定批次大小的 ONNX 模型。 在下列程式碼中,定型的模型環境可用來提交此指令碼,以產生 ONNX 模型並將其儲存至輸出目錄。

針對多類別影像分類,為最佳子執行產生的 ONNX 模型依預設可支援批次評分。 因此,此工作類型不需要任何模型特定的引數,您可以跳至載入標籤和 ONNX 模型檔案一節。

產生批次模型之後,請從 [輸出 + 記錄]>[輸出] 手動透過 UI 加以下載,或使用下列方法:

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
)

在模型下載步驟之後,可以藉由使用 model.onnx 檔案,來使用 ONNX Runtime Python 套件以執行推斷。 基於示範目的,本文對於每個視覺工作使用來自如何準備影像資料集的資料集。

我們已使用其各自的資料集來定型所有視覺工作的模型,以示範 ONNX 模型推斷。

載入標籤和 ONNX 模型檔案

下列程式碼片段會載入 labels.json,其類別名稱已排序。 也就是說,如果 ONNX 模型預測標籤識別碼為 2,則會對應至 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))

取得 ONNX 模型的預期輸入和輸出詳細資料

當您擁有模型時,請務必了解某些特定模型和特定工作的詳細資料。 這些詳細資料包括輸入和輸出的數目、處理影像的預期輸入圖形或格式,以及輸出圖形,以便您知道特定模型或特定工作的輸出。

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

ONNX 模型的預期輸入和輸出格式

每個 ONNX 模型都有一組預先定義的輸入和輸出格式。

此範例會套用在 fridgeObjects 資料集上定型的模型,其中包含 134 個影像和 4 個類別/標籤,以說明 ONNX 模型推斷。 如需定型影像分類工作的詳細資訊,請參閱多類別影像分類筆記本

輸入格式

輸入是前置處理過的影像。

輸入名稱 輸入圖形 Input type 描述
input1 (batch_size, num_channels, height, width) ndarray(float) 輸入是前置處理過的影像,其圖形 (1, 3, 224, 224) 的批次大小為 1,高度和寬度為 224。 這些數字對應至定型範例中 crop_size 所使用的值。

輸出格式

輸出是所有類別/標籤的 logits 陣列。

輸出名稱 輸出圖形 輸出類型 描述
output1 (batch_size, num_classes) ndarray(float) 模型會傳回 logits (不含 softmax)。 例如,對於每個批次大小為 1 和 4 個類別,會傳回 (1, 4)

前置處理

針對 ONNX 模型推斷,執行下列前置處理步驟:

  1. 將影像轉換為 RGB。
  2. 將影像大小調整為 valid_resize_sizevalid_resize_size 值,使其對應至定型期間轉換驗證資料集所使用的值。 valid_resize_size 的預設值為 256。
  3. 將影像置中裁切成 height_onnx_crop_sizewidth_onnx_crop_size。 它會對應至 valid_crop_size,其預設值為 224。
  4. HxWxC 變更為 CxHxW
  5. 轉換成 float 類型。
  6. 使用 ImageNet 的 mean = [0.485, 0.456, 0.406]std = [0.229, 0.224, 0.225] 進行正規化。

如果您在定型期間,為超參數valid_resize_sizevalid_crop_size選擇不同的值,則會使用這些值。

取得 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

不含 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]

包含 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]

使用 ONNX Runtime 進行推斷

使用 ONNX Runtime 進行推斷,對於每個電腦視覺工作有所不同。

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)

後置處理

在預測值上套用 softmax(),以取得每個類別的分類信賴分數 (機率)。 然後,預測將會是具有最高機率的類別。

不含 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]))

包含 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]))

將預測視覺化

使用標籤將輸入影像視覺化

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

下一步