وضع تنبؤات باستخدام ONNX عن نماذج رؤية الكمبيوتر من AutoML

ينطبق على: Python SDK azure-ai-ml v2 (الحالي)

في هذه المقالة، ستتعلم كيفية استخدام Open Neural Network Exchange (ONNX) لإجراء تنبؤات على نماذج رؤية الكمبيوتر التي تم إنشاؤها من التعلم الآلي التلقائي (AutoML) في Azure التعلم الآلي.

لاستخدام ONNX في التنبؤات، فأنت بحاجة إلى:

  1. تنزيل ملفات نموذج ONNX من تشغيل تدريب AutoML.
  2. فهم المدخلات والمخرجات في نموذج ONNX.
  3. قم بإجراء معالجة مسبقة لبياناتك بحيث تكون بالتنسيق المطلوب لإدخال الصور.
  4. الاستنتاج باستخدام «وقت تشغيل» ONNX في Python.
  5. تصور التنبؤات لمهام الكشف عن العناصر وتجزئة المثيل.

ONNX هو معيار مفتوح لنماذج التعلم الآلي والتعلم العميق. ويتيح استيراد النموذج وتصديره (إمكانية التشغيل التفاعلي) عبر أطر عمل الذكاء الاصطناعي الشائعة. للحصول على مزيد من التفاصيل، استكشف مشروع ONNX GitHub.

ONNX Runtime هو مشروع مفتوح المصدر يدعم الاستنتاج عبر النظام الأساسي. يوفر «وقت تشغيل» ONNX واجهات APIs عبر لغات البرمجة (بما في ذلك Python، و C++، و C#، وC، و Java، و JavaScript). يمكنك استخدام «واجهات برمجة التطبيقات» هذه لإجراء الاستنتاج على صور الإدخال. بعد توافر النموذج الذي تم تصديره إلى تنسيق ONNX، يمكنك استخدام «واجهات برمجة التطبيقات» في أي لغة برمجة يحتاجها مشروعك.

في هذا الدليل، ستتعلم كيفية استخدام واجهات برمجة تطبيقات Python لوقت تشغيل ONNX لإجراء تنبؤات على الصور لمهام الرؤية الشائعة. يمكنك استخدام النماذج المُصدّرة لـ ONNX عبر اللغات.

المتطلبات الأساسية

تنزيل ملفات نموذج ONNX

يمكنك تنزيل ملفات نموذج ONNX من عمليات تشغيل AutoML باستخدام «واجهة مستخدم» استوديو Azure Machine Learning أو Azure Machine Learning Python SDK. نوصي بتنزيلها عبر الـ 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.json و model.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 للصور تسجيل الدُفعات للتصنيف. لكن نماذج 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 في الدليل الحالي لإرسال البرنامج النصي. استخدم مهمة الأمر التالي لإرسال البرنامج النصي ONNX_batch_model_generator_automl_for_images.py المتاح في مستودع azureml-examples GitHub، لإنشاء نموذج ONNX بحجم دفعة معينة. في التعليمات البرمجية التالية، يتم استخدام بيئة النموذج المُدرب لإرسال هذا البرنامج النصي لإنشاء نموذج ONNX وحفظه في دليل المخرجات.

لتصنيف الصور متعدد الفئات، يدعم نموذج ONNX الذي تم إنشاؤه أفضل تشغيل فرعي لتسجيل الدفعات بشكل افتراضي. لذلك، لا توجد وسيطات خاصة بالنموذج مطلوبة لنوع المهمة ويمكنك التخطي إلى قسم تحميل التسميات وملفات نموذج ONNX.

بمجرد إنشاء نموذج الدفعة، قم بتنزيله إما من المخرجات + التسجيلات>المخرجات يدويًا من خلال واجهة المستخدم، أو استخدم الأسلوب التالي:

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
)

بعد تنزيل النموذج، يمكنك استخدام حزمة ONNX Runtime Python لإجراء الاستنتاج باستخدام ملف model.onnx. لأغراض التوضيح، تستخدم هذه المقالة مجموعات البيانات من كيفية إعداد مجموعات بيانات الصور في كل مهمة للرؤية.

قمنا بتدريب النماذج لجميع مهام الرؤية مع مجموعات البيانات الخاصة بها لإظهار استنتاج نموذج 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. لمزيد من المعلومات حول تدريب مهمة تصنيف الصور، راجع دفتر ملاحظات تصنيف للصور متعدد الفئات.

تنسيق الإدخال

إن الإدخال هو صورة معالجة مسبقًا.

اسم الإدخال شكل الإدخال نوع الإدخال ‏‏الوصف
input1 (batch_size, num_channels, height, width) ndarray(حر) إن الإدخال هو صورة معالجة مسبقًا، بها شكل (1, 3, 224, 224) بحجم دفعة قدره 1، وارتفاع وعرض يبلغ 224. تتوافق هذه الأرقام مع القيم المستخدمة في crop_size بمثالٍ للتدريب.

نموذج الإخراج

إن الإخراج عبارة عن صفيف logit لجميع الفئات/التسميات.

اسم الإخراج شكل الإخراج نوع الإخراج ‏‏الوصف
output1 (batch_size, num_classes) ndarray(حر) يرجع النموذج logits (بدون softmax). على سبيل المثال، بالنسبة لحجم الدفعة 1 و4 فئات، فإنه يُرجع (1, 4).

ما قبل المعالجة

قد بإجراء خطوات ما قبل المعالجة التالية لاستنتاج نموذج ONNX:

  1. قم بتحويل الصورة إلى RGB.
  2. قم بتغيير حجم الصورة إلى قيم valid_resize_size و valid_resize_size التي تتوافق مع القيم المستخدمة في تحويل مجموعة بيانات التحقق من الصحة أثناء التدريب. إن القيمة الافتراضية لـ valid_resize_size هي 256.
  3. قم باقتصاص الصورة من المركز إلى height_onnx_crop_size و width_onnx_crop_size. تتوافق مع valid_crop_size بقيمة افتراضية قدرها 224.
  4. قم بتغيير HxWxC إلى CxHxW
  5. قم بالتحويل إلى نوع حر.
  6. قم بالضبط باستخدام mean = [0.485, 0.456, 0.406] في ImageNet و std = [0.229, 0.224, 0.225].

إذا اخترت قيم مختلفة للمقاييس الفائقةvalid_resize_size و valid_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()

الخطوات التالية