本案例展示了如何将 MobileNetV3 模型从 PaddleHub加载到本地,最终转换为 OpenVINO IR 模型。我们还展示了如何使用 OpenVINO 的 推理引擎对样本图像执行分类推理,并比较 PaddlePaddle 模型与 IR 模型。
环境描述:
4-model-optimizer-convert2IR
OpenVINO中模型优化器(Model Optimizer)支持tensorflow/Caffe模型转换为OpenVINO的中间层表示IR(intermediate representation),从而实现对模型的压缩与优化,方便推断引擎更快的加载与执行这些模型。
下面这张图说明了部署训练有素的深度学习模型的典型工作流程:
关于IR模型:IR模型是OpenVINO的中间层表示,由一个.xml文件(包含有关网络拓扑的信息)和一个.bin文件(包含权重和偏差二进制数据)组成。 read_model()
函数会读取IR模型。我们一般这两个文件(.xml和.bin)放于同一目录中,并且具有相同的文件名。
从上图中,我们可以很清晰地看到OpenVINO的整体工作流程:我们可以用过TensorFlow/PyTorch/PaddlePaddle训练完一个模型,但这个模型没有办法直接用于OpenVINO的模型推理。需要先把这些模型转化为IR中间件,通过模型优化器(Model Optimizer)。最后经过推理后,应用于User Applications。注意:生成的IR可以通过应用训练后量化方法(POT)进行额外的推理优化(参见案例5-pot-int8-simplifiedmode
,6_pot_objectdetection
,以及相关)。
本案例展示了如何将 MobileNetV3 模型从 PaddleHub加载到本地,最终转换为 OpenVINO IR 模型。我们还展示了如何使用 OpenVINO 的 推理引擎对样本图像执行分类推理,并比较 PaddlePaddle 模型与 IR 模型。
MobileNetV3是Google在2019年发布的新模型,作者通过结合NAS与NetAdapt进行搜索得到该网络结构,提供了Large和Small两个版本,分别适用于对资源不同要求的情况。对比于MobileNetV2,新的模型在速度和精度方面均有提升。该PaddleHubModule的模型结构为MobileNetV3 Large,基于ImageNet-2012数据集并采用PaddleClas提供的SSLD蒸馏方法训练得到,接受输入图片大小为224 x 224 x 3,支持finetune,也可以直接通过命令行或者Python接口进行预测。
第一步,我们加载Paddle预训练模型,并显示图像分类结果。相关代码:
import os
import time
import cv2
import matplotlib.pyplot as plt
import numpy as np
import paddlehub as hub
from IPython.display import Markdown, display
from PIL import Image
from openvino.runtime import Core
from paddle.static import InputSpec
from scipy.special import softmax
from pathlib import Path
'''
- `IMAGE_FILENAME` 设置为要使用的图像的文件名。
- `MODEL_NAME` 设置为要从 PaddleHub 下载的 PaddlePaddle 模型的名称。
不同模型可能使用不同的预处理方法,因此需要进行一些修改才能在原始模型和转换模型上获得相同的结果。
- `hub.config.server` 是 PaddleHub 服务器的 URL。我们不需要修改此设置。
'''
MODEL_DIR = 'model/paddle'
IMAGE_FILENAME = "data/coco_close.png"
MODEL_NAME = "mobilenet_v3_large_imagenet_ssld"
hub.config.server = "https://paddlepaddle.org.cn/paddlehub"
os.makedirs(MODEL_DIR, exist_ok=True)
onnx_model_path = Path(MODEL_DIR) / '{}.onnx'.format(MODEL_NAME)
'''
接下来,我们从 PaddleHub 加载模型,读取并显示图像,对该图像进行推理,并显示前三个预测结果。
`classifier.classification()` 将图像作为输入并返回图像的类名。 默认情况下会返回最佳网络结果。
使用 `top_k` 参数,返回最佳的 `k` 结果,其中 `k` 是一个数字。 预处理图像是在幕后完成的。
分类模型为 1000 个 ImageNet 类中的每一个返回一个包含浮点值的数组。 `classification()` 函数
将这些数字转换为类名和 softmax 概率。
'''
classifier = hub.Module(name=MODEL_NAME)
# Load image in BGR format, as specified in model documentation
image = cv2.imread(filename=IMAGE_FILENAME)
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
result = classifier.classification(images=[image], top_k=3)
print("We show top 3 classification results.")
for class_name, softmax_probability in result[0].items():
print(f"{class_name}, {softmax_probability:.5f}")
这里我们需要介绍一个函数:mobilenet_v3_large_imagenet_ssld.data_feed.process_image
。这个函数显示图像经过标准化、调整大小和裁剪,并且 BGR 图像在通过网络传播之前转换为 RGB。
from mobilenet_v3_large_imagenet_ssld.data_feed import process_image
'''
我们来看一下`process_image()`的结果
'''
pil_image = Image.open(IMAGE_FILENAME)
processed_image = process_image(pil_image)
print(f"Processed image shape: {processed_image.shape}")
# Processed image is in (C,H,W) format, convert to (H,W,C) to show the image
plt.imshow(np.transpose(processed_image, (1, 2, 0)))
下图是输入的原图:
下图为process_image()
之后的结果:
图像的颜色显示上有一些奇怪,可能是因为图像的标准化以及BGR转变成RGB的缘故。与此同时,从Terminal的返回:Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
,我们看到这个函数将原图像进行了标准化,归一化,裁剪(这里我不是很懂,背景被裁剪了,而目标物体没有任何损伤,这是巧合还是背后有其他逻辑。我换了其他几张图,发现应该没有什么magic,就是左边右边等距离裁剪)。
要将 PaddlePaddle 模型转换为 IR,我们首先将模型转换为 ONNX,然后将 ONNX 模型转换为 IR。
PaddlePaddle 的 MobileNet 模型包含有关输入形状、均值和比例值的信息,我们可以使用这些信息来转换模型。 下一个单元格显示如何获取这些值。
We convert the PaddlePaddle Model to ONNX with the .export_onnx_model()
method. This uses Paddle2ONNX. We convert the model with the input shape found in the previous cell.
我们使用 .export_onnx_model()
方法将 PaddlePaddle 模型转换为 ONNX(Paddle2ONNX)。我们使用在前一个单元格中找到的输入形状转换模型。
input_shape = list(classifier.cpu_predictor.get_input_tensor_shape().values())
print("input shape:", input_shape)
print("mean:", classifier.get_pretrained_images_mean())
print("std:", classifier.get_pretrained_images_std())
# PaddlePaddle 模型转换为 ONNX
target_height, target_width = next(iter(input_shape))[2:]
x_spec = InputSpec([1, 3, target_height, target_width], "float32", "x")
print(
"Exporting PaddlePaddle model to ONNX with target_height "
f"{target_height} and target_width {target_width}"
)
classifier.export_onnx_model(MODEL_DIR, input_spec=[x_spec], opset_version=11)
Terminal返回:
input shape: [[-1, 3, 224, 224]]
mean: [[0.485 0.456 0.406]]
std: [[0.229 0.224 0.225]]
Exporting PaddlePaddle model to ONNX with target_height 224 and target_width 224
2022-04-30 18:45:07 [INFO] ONNX model saved in model/paddle\mobilenet_v3_large_imagenet_ssld.onnx
调用 OpenVINO 模型优化器工具将 PaddlePaddle 模型转换为 OpenVINO IR,精度为 FP32。模型保存到当前目录。有下面几个参数需要先介绍一下:
半精度模型(FP16):我们可以在转化的过程中将TensorFlow模型转换成FP16精度的IR模型。在指令中对应--data_type
选项,比如:mo --input_model INPUT_MODEL --data_type FP16
。半精度模型大小应只有全精度模型的一般,但它可能会有一些精度下降,尽管对于大多数模型来说,精度下降可以忽略不计。
设置Layout:Layout定义了模型的形状尺寸,并且可以为设定输入模型的Layout和经过转换之后的IR输出模型的Layout,比如:mo --input_model tf_nasnet_large.onnx --layout "nhwc->nchw"
,或者我们只定义一个Layout:mo --input_model tf_nasnet_large.onnx --layout nhwc
。
设置Mean和Scale:通常使用归一化的输入数据训练神经网络模型。这意味着将输入数据值转换为特定范围内,例如 [0, 1] 或 [-1, 1]。有时,作为预处理的一部分,我们从输入数据值中减去平均值。输入数据预处理的实现方式有两种:
在第一种情况下,模型优化器生成具有所需预处理操作的 IR,并且不需要Mean和Scale参数。在第二种情况下,应向模型优化器提供有关Mean和Scale值的信息,以将其嵌入到生成的 IR 中。我们可以在命令中使用如下参数:
--mean_values
--scale_values
--scale
一个例子:mo --input_model unet.pdmodel --mean_values [123,117,104] --scale 255
。
修改输入通道:有时,您的应用程序的输入图像可以是 RGB (BGR) 格式,并且模型在 BGR (RGB) 格式的图像上进行训练,颜色通道顺序相反。在这种情况下,重要的是通过在推理之前恢复颜色通道来预处理输入图像。为了将此预处理步骤嵌入到 IR 中,模型优化器提供了 --reverse_input_channels 命令行参数来修改颜色通道。
在这个例子中,我们最终并没有设置这些参数,主要是因为mobilenet_v3_large_imagenet_ssld.data_feed.process_image
实际上已经将输入的图像进行了归一化,以及BGR转RGB,所以这里我们在转换IR模型的这一步的时候,就不需要进行预处理的这些设定了。
代码如下:
model_xml = f"{MODEL_NAME}.xml"
onnx_model_path = Path(MODEL_DIR) / '{}.onnx'.format(MODEL_NAME)
if not os.path.exists(model_xml):
mo_command = f"""mo
--framework=onnx
--input_model "{onnx_model_path}"
--input_shape "[1,3,{target_height},{target_width}]"
--data_type FP32
--output_dir "{MODEL_DIR}"
"""
mo_command = " ".join(mo_command.split())
display(Markdown(f"Model Optimizer command to convert the ONNX model to IR: `{mo_command}`"))
display(Markdown("_Converting model to IR. This may take a few minutes..._"))
! $mo_command
else:
print(f"{model_xml} already exists.")
加载 IR 模型,获取模型信息,加载图像,进行推理,将推理转换为结果,并显示输出。
代码如下:
# Load Inference Engine and IR model
ie = Core()
model = ie.read_model(model=f"{MODEL_DIR}/{MODEL_NAME}.xml", weights=f"{MODEL_DIR}/{MODEL_NAME}.bin")
compiled_model = ie.compile_model(model=model, device_name="CPU")
# Get model output
output_layer = compiled_model.output(0)
# Read, show, and preprocess input image
# See the "Show Inference on PaddlePaddle Model" section for source of process_image
image = Image.open(IMAGE_FILENAME)
plt.imshow(image)
input_image = process_image(image)[None,]
# Do inference
ie_result = compiled_model([input_image])[output_layer][0]
# Compute softmax probabilities for the inference result and find the top three values
softmax_result = softmax(ie_result)
top_indices = np.argsort(softmax_result)[-3:][::-1]
top_softmax = softmax_result[top_indices]
# Convert the inference results to class names, using the same labels as the PaddlePaddle classifier
for index, softmax_probability in zip(top_indices, top_softmax):
print(f"{classifier.label_list[index]}, {softmax_probability:.5f}")
返回结果:
Labrador retriever, 0.58936
flat-coated retriever, 0.03327
curly-coated retriever, 0.03317
首先,我们来测量对 50 张图像进行推理所需的时间并比较结果。为了公平比较,我们包括处理图像所需的时间。
代码如下:
num_images = 50
# PaddlePaddle's classification method expects a BGR numpy array
image = cv2.imread(filename=IMAGE_FILENAME)
# The process_image function expects a PIL image
pil_image = Image.open(fp=IMAGE_FILENAME)
# Show CPU information
ie = Core()
print(f"CPU: {ie.get_property(device_name='CPU', name='FULL_DEVICE_NAME')}")
# Show inference speed on PaddlePaddle model
start = time.perf_counter()
for _ in range(num_images):
result = classifier.classification(images=[image], top_k=3)
end = time.perf_counter()
time_ir = end - start
print()
print(
f"PaddlePaddle model on CPU: {time_ir/num_images:.4f} "
f"seconds per image, FPS: {num_images/time_ir:.2f}"
)
print("PaddlePaddle result:")
for class_name, softmax_probability in result[0].items():
print(f"{class_name}, {softmax_probability:.5f}")
#plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
# Show inference speed on OpenVINO IR model
compiled_model = ie.compile_model(model=model, device_name="CPU")
output_layer = compiled_model.output(0)
start = time.perf_counter()
input_image = process_image(pil_image)[None,]
for _ in range(num_images):
ie_result = compiled_model([input_image])[output_layer][0]
result_index = np.argmax(ie_result)
class_name = classifier.label_list[np.argmax(ie_result)]
softmax_result = softmax(ie_result)
top_indices = np.argsort(softmax_result)[-3:][::-1]
top_softmax = softmax_result[top_indices]
end = time.perf_counter()
time_ir = end - start
print()
print(
f"IR model in Inference Engine (CPU): {time_ir/num_images:.4f} "
f"seconds per image, FPS: {num_images/time_ir:.2f}"
)
print("OpenVINO result:")
for index, softmax_probability in zip(top_indices, top_softmax):
print(f"{classifier.label_list[index]}, {softmax_probability:.5f}")
#plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
结果如下:
PaddlePaddle model on CPU: 0.0833 seconds per image, FPS: 12.00
PaddlePaddle result:
Labrador retriever, 0.58936
flat-coated retriever, 0.03327
curly-coated retriever, 0.03317
IR model in Inference Engine (CPU): 0.0057 seconds per image, FPS: 175.44
OpenVINO result:
Labrador retriever, 0.58936
flat-coated retriever, 0.03327
curly-coated retriever, 0.03317
很明显,IR模型处理模型的速度远快于Paddle模型,精度上双方是一样的。
接下来,使用benchmark_app
测试IR模型性能。
为了测量这个 IR 模型的推理性能,我们使用 OpenVINO 的 Benchmark Tool。 可以在笔记本中运行:!benchmark_app
或 %sx benchmark_app
。
注意:为了获得最准确的性能估计,我们建议在关闭其他应用程序后在终端/命令提示符下运行 benchmark_app
。 运行 benchmark_app --help
以查看所有命令行选项。
好像benchmark_app
只能测OpenVINO自家的模型性能,所以这里我只能看一下IR模型出来的结果:
MODEL_DIR = 'model/paddle'
MODEL_NAME = "mobilenet_v3_large_imagenet_ssld"
hub.config.server = "https://paddlepaddle.org.cn/paddlehub"
os.makedirs(MODEL_DIR, exist_ok=True)
ir_path = Path(MODEL_DIR) / '{}.xml'.format(MODEL_NAME)
# Benchmark FP32 model
!benchmark_app -m $ir_path -d CPU -api async -t 15 -b 1
结果如下:
[Step 1/11] Parsing and validating input arguments
[ WARNING ] -nstreams default value is determined automatically for a device. Although the automatic selection usually provides a reasonable performance, but it still may be non-optimal for some cases, for more information look at README.
[Step 2/11] Loading OpenVINO
[ WARNING ] PerformanceMode was not explicitly specified in command line. Device CPU performance hint will be set to THROUGHPUT.
[ INFO ] OpenVINO:
API version............. 2022.1.0-7019-cdb9bec7210-releases/2022/1
[ INFO ] Device info
CPU
openvino_intel_cpu_plugin version 2022.1
Build................... 2022.1.0-7019-cdb9bec7210-releases/2022/1
[Step 3/11] Setting device configuration
[ WARNING ] -nstreams default value is determined automatically for CPU device. Although the automatic selection usually provides a reasonable performance, but it still may be non-optimal for some cases, for more information look at README.
[Step 4/11] Reading network files
[ INFO ] Read model took 102.49 ms
[Step 5/11] Resizing network to match image sizes and given batch
[ INFO ] Network batch size: 1
[Step 6/11] Configuring input of the model
[ INFO ] Model input '@HUB_mobilenet_v3_large_imagenet_ssld@image' precision u8, dimensions ([N,C,H,W]): 1 3 224 224
[ INFO ] Model output '@HUB_mobilenet_v3_large_imagenet_ssld@fc_0.tmp_1' precision f32, dimensions ([...]): 1 1000
[Step 7/11] Loading the model to the device
[ INFO ] Compile model took 350.42 ms
[Step 8/11] Querying optimal runtime parameters
[ INFO ] DEVICE: CPU
[ INFO ] AVAILABLE_DEVICES , ['']
[ INFO ] RANGE_FOR_ASYNC_INFER_REQUESTS , (1, 1, 1)
[ INFO ] RANGE_FOR_STREAMS , (1, 8)
[ INFO ] FULL_DEVICE_NAME , Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
[ INFO ] OPTIMIZATION_CAPABILITIES , ['FP32', 'FP16', 'INT8', 'BIN', 'EXPORT_IMPORT']
[ INFO ] CACHE_DIR ,
[ INFO ] NUM_STREAMS , 4
[ INFO ] INFERENCE_NUM_THREADS , 0
[ INFO ] PERF_COUNT , False
[ INFO ] PERFORMANCE_HINT_NUM_REQUESTS , 0
[Step 9/11] Creating infer requests and preparing input data
[ INFO ] Create 4 infer requests took 1.00 ms
[ WARNING ] No input files were given for input '@HUB_mobilenet_v3_large_imagenet_ssld@image'!. This input will be filled with random values!
[ INFO ] Fill input '@HUB_mobilenet_v3_large_imagenet_ssld@image' with random values
[Step 10/11] Measuring performance (Start inference asynchronously, 4 inference requests using 4 streams for CPU, inference only: True, limits: 15000 ms duration)
[ INFO ] Benchmarking in inference only mode (inputs filling are not included in measurement loop).
[ INFO ] First inference took 25.62 ms
[Step 11/11] Dumping statistics report
Count: 3640 iterations
Duration: 15018.17 ms
Latency:
Median: 15.89 ms
AVG: 16.40 ms
MIN: 9.74 ms
MAX: 59.99 ms
Throughput: 242.37 FPS