Share via


ONNX를 사용한 AutoML의 Computer Vision 모델에 대한 예측

적용 대상: Python SDK azure-ai-ml v2(현재)

이 문서에서는 ONNX(Open Neural Network Exchange)를 사용하여 Azure Machine Learning의 AutoML(자동화된 기계 학습)에서 생성된 컴퓨터 비전 모델을 예측하는 방법을 알아봅니다.

예측에 ONNX를 사용하려면 다음을 수행해야 합니다.

  1. AutoML 학습 실행에서 ONNX 모델 파일을 다운로드합니다.
  2. ONNX 모델의 입력 및 출력을 이해합니다.
  3. 입력 이미지에 필요한 형식이 되도록 데이터를 미리 처리합니다.
  4. Python용 ONNX 런타임을 통해 추론을 수행합니다.
  5. 개체 감지 및 인스턴스 구분 작업에 대한 예측을 시각화합니다.

ONNX는 기계 학습 및 딥 러닝 모델을 위한 개방형 표준입니다. 인기 있는 AI 프레임워크에서 모델 가져오기 및 내보내기(상호 운용성)를 지원합니다. 자세한 내용은 ONNX GitHub 프로젝트를 살펴보세요.

ONNX 런타임은 플랫폼 간 추론을 지원하는 오픈 소스 프로젝트입니다. ONNX 런타임은 프로그래밍 언어(Python, C++, C#, C, Java 및 JavaScript 포함)에서 API를 제공합니다. 이러한 API를 사용하여 입력 이미지에 대한 추론을 수행할 수 있습니다. 모델을 ONNX 형식으로 내보낸 후에는 프로젝트에 필요한 프로그래밍 언어에서 이러한 API를 사용할 수 있습니다.

이 가이드에서는 ONNX 런타임용 Python API를 사용하여 인기 있는 비전 작업에 대한 이미지를 예측하는 방법을 알아봅니다. 이러한 ONNX 내보낸 모델을 언어 간에 사용할 수 있습니다.

필수 조건

ONNX 모델 파일 다운로드

Azure Machine Learning 스튜디오 UI 또는 Azure Machine Learning Python SDK를 사용하여 AutoML 실행에서 ONNX 모델 파일을 다운로드할 수 있습니다. 실험 이름 및 부모 실행 ID를 가진 SDK를 통해 다운로드하는 것이 좋습니다.

Azure Machine Learning 스튜디오

Azure Machine Learning 스튜디오에서 학습 Notebook에 생성된 실험에 대한 하이퍼링크를 사용하거나 자산 아래의 실험 탭을 사용하여 실험으로 이동합니다. 그런 다음, 최상의 자식 실행을 선택합니다.

최상의 자식 실행 내에서 출력+로그>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를 사용하면 실험 이름 및 부모 실행 ID를 사용하여 가장 적합한 자식 실행(기본 메트릭 기준)을 선택할 수 있습니다. 그런 후, 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
)

모델 다운로드 단계 후에 ONNX 런타임 Python 패키지를 사용하여 model.onnx 파일로 추론을 수행합니다. 데모를 위해 이 문서에서는 이미지 데이터 세트 준비 방법을 통해 간단한 비전 작업을 위한 데이터 세트를 사용합니다.

각 데이터 세트를 사용하여 모든 비전 작업에 대한 모델을 학습하여 ONNX 모델 유추를 보여 줍니다.

레이블 및 ONNX 모델 파일 로드

다음 코드 조각은 클래스 이름이 정렬되어 있는 labels.json을 로드합니다. 즉, ONNX 모델이 레이블 ID를 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 모델에는 미리 정의된 입력 및 출력 형식 집합이 있습니다.

이 예제는 ONNX 모델 추론을 설명하기 위한 134개 이미지와 4개 클래스/레이블이 포함된 fridgeObjects 데이터 세트를 학습시킨 모델을 채택합니다. 이미지 분류 작업 학습에 대한 자세한 내용은 다중 클래스 이미지 분류 Notebook을 참조하세요.

입력 형식

입력은 전처리된 이미지입니다.

입력 이름 입력 셰이프 Input type 설명
input1 (batch_size, num_channels, height, width) ndarray(float) 입력은 전처리된 이미지로, 배치 크기가 1이고 높이 및 너비가 224인 셰이프 (1, 3, 224, 224)를 갖습니다. 이러한 숫자는 학습 예제의 crop_size에 사용되는 값에 해당합니다.

출력 형식

출력은 모든 클래스/레이블에 대한 로짓 배열입니다.

출력 이름 출력 셰이프 출력 형식 설명
output1 (batch_size, num_classes) ndarray(float) 모델은 로짓(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 크기로 이미지 가운데를 자릅니다. 기본값이 224인 valid_crop_size에 해당합니다.
  4. HxWxCCxHxW로 바꿉니다.
  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 런타임을 사용한 추론

ONNX 런타임을 사용하는 추론은 Computer Vision 작업마다 다릅니다.

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

다음 단계