注意
点击此处下载完整的示例代码
(可选) 从 PyTorch 导出模型到 ONNX 并使用 ONNX Runtime 运行¶
创建于:2019 年 7 月 17 日 | 最后更新:2024 年 7 月 17 日 | 最后验证:2024 年 11 月 05 日
注意
截至 PyTorch 2.1,ONNX Exporter 有两个版本。
torch.onnx.dynamo_export
是最新的(仍处于 beta 阶段)导出器,基于 PyTorch 2.0 发布的 TorchDynamo 技术。torch.onnx.export
基于 TorchScript 后端,自 PyTorch 1.2.0 起可用。
在本教程中,我们将介绍如何使用 TorchScript torch.onnx.export
ONNX 导出器将 PyTorch 中定义的模型转换为 ONNX 格式。
导出的模型将使用 ONNX Runtime 执行。ONNX Runtime 是一个以性能为中心的 ONNX 模型引擎,可在多个平台和硬件(Windows、Linux 和 Mac 以及 CPU 和 GPU)上高效地进行推理。事实证明,ONNX Runtime 显著提高了多种模型的性能,如此处所述
在本教程中,您需要安装 ONNX 和 ONNX Runtime。您可以使用以下命令获取 ONNX 和 ONNX Runtime 的二进制构建版本
%%bash
pip install onnx onnxruntime
ONNX Runtime 建议为 PyTorch 使用最新的稳定运行时。
# Some standard imports
import numpy as np
from torch import nn
import torch.utils.model_zoo as model_zoo
import torch.onnx
超分辨率是一种提高图像、视频分辨率的方法,广泛应用于图像处理或视频编辑。在本教程中,我们将使用一个小型超分辨率模型。
首先,让我们在 PyTorch 中创建一个 SuperResolution
模型。该模型使用 “Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network” - Shi et al 中描述的高效亚像素卷积层,通过放大因子来提高图像分辨率。该模型期望图像的 YCbCr
的 Y 分量作为输入,并输出超分辨率中放大的 Y 分量。
该模型直接来自 PyTorch 的示例,未经修改
# Super Resolution model definition in PyTorch
import torch.nn as nn
import torch.nn.init as init
class SuperResolutionNet(nn.Module):
def __init__(self, upscale_factor, inplace=False):
super(SuperResolutionNet, self).__init__()
self.relu = nn.ReLU(inplace=inplace)
self.conv1 = nn.Conv2d(1, 64, (5, 5), (1, 1), (2, 2))
self.conv2 = nn.Conv2d(64, 64, (3, 3), (1, 1), (1, 1))
self.conv3 = nn.Conv2d(64, 32, (3, 3), (1, 1), (1, 1))
self.conv4 = nn.Conv2d(32, upscale_factor ** 2, (3, 3), (1, 1), (1, 1))
self.pixel_shuffle = nn.PixelShuffle(upscale_factor)
self._initialize_weights()
def forward(self, x):
x = self.relu(self.conv1(x))
x = self.relu(self.conv2(x))
x = self.relu(self.conv3(x))
x = self.pixel_shuffle(self.conv4(x))
return x
def _initialize_weights(self):
init.orthogonal_(self.conv1.weight, init.calculate_gain('relu'))
init.orthogonal_(self.conv2.weight, init.calculate_gain('relu'))
init.orthogonal_(self.conv3.weight, init.calculate_gain('relu'))
init.orthogonal_(self.conv4.weight)
# Create the super-resolution model by using the above model definition.
torch_model = SuperResolutionNet(upscale_factor=3)
通常,您现在会训练此模型;但是,在本教程中,我们将改为下载一些预训练权重。请注意,此模型并未经过充分训练以获得良好的准确率,此处仅用于演示目的。
重要的是在导出模型之前调用 torch_model.eval()
或 torch_model.train(False)
,以将模型转为推理模式。这是必需的,因为 dropout 或 batchnorm 等运算符在推理和训练模式下的行为不同。
# Load pretrained model weights
model_url = 'https://s3.amazonaws.com/pytorch/test_data/export/superres_epoch100-44c6958e.pth'
batch_size = 64 # just a random number
# Initialize model with the pretrained weights
map_location = lambda storage, loc: storage
if torch.cuda.is_available():
map_location = None
torch_model.load_state_dict(model_zoo.load_url(model_url, map_location=map_location))
# set the model to inference mode
torch_model.eval()
在 PyTorch 中导出模型通过跟踪或脚本编写工作。本教程将使用通过跟踪导出的模型作为示例。要导出模型,我们调用 torch.onnx.export()
函数。这将执行模型,记录用于计算输出的运算符的跟踪。由于 export
运行模型,我们需要提供输入张量 x
。其中的值可以是随机的,只要它是正确的类型和大小即可。请注意,除非指定为动态轴,否则输入大小将在导出的 ONNX 图中为所有输入维度固定。在本例中,我们导出模型时输入 batch_size 为 1,但随后在 torch.onnx.export()
中的 dynamic_axes
参数中将第一个维度指定为动态的。因此,导出的模型将接受大小为 [batch_size, 1, 224, 224] 的输入,其中 batch_size 可以是可变的。
要了解有关 PyTorch 导出接口的更多详细信息,请查看 torch.onnx 文档。
# Input to the model
x = torch.randn(batch_size, 1, 224, 224, requires_grad=True)
torch_out = torch_model(x)
# Export the model
torch.onnx.export(torch_model, # model being run
x, # model input (or a tuple for multiple inputs)
"super_resolution.onnx", # where to save the model (can be a file or file-like object)
export_params=True, # store the trained parameter weights inside the model file
opset_version=10, # the ONNX version to export the model to
do_constant_folding=True, # whether to execute constant folding for optimization
input_names = ['input'], # the model's input names
output_names = ['output'], # the model's output names
dynamic_axes={'input' : {0 : 'batch_size'}, # variable length axes
'output' : {0 : 'batch_size'}})
我们还计算了模型之后的输出 torch_out
,我们将使用它来验证我们导出的模型在 ONNX Runtime 中运行时是否计算相同的值。
但在使用 ONNX Runtime 验证模型输出之前,我们将使用 ONNX API 检查 ONNX 模型。首先,onnx.load("super_resolution.onnx")
将加载保存的模型,并输出 onnx.ModelProto
结构(用于捆绑 ML 模型的顶级文件/容器格式。有关更多信息,请参阅 onnx.proto 文档。)。然后,onnx.checker.check_model(onnx_model)
将验证模型的结构,并确认模型具有有效的模式。ONNX 图的有效性通过检查模型的版本、图的结构以及节点及其输入和输出来验证。
import onnx
onnx_model = onnx.load("super_resolution.onnx")
onnx.checker.check_model(onnx_model)
现在让我们使用 ONNX Runtime 的 Python API 计算输出。这部分通常可以在单独的进程或另一台机器上完成,但我们将在同一进程中继续,以便我们可以验证 ONNX Runtime 和 PyTorch 是否为网络计算相同的值。
为了使用 ONNX Runtime 运行模型,我们需要使用所选的配置参数(此处我们使用默认配置)为模型创建推理会话。创建会话后,我们使用 run() API 评估模型。此调用的输出是一个列表,其中包含 ONNX Runtime 计算的模型输出。
import onnxruntime
ort_session = onnxruntime.InferenceSession("super_resolution.onnx", providers=["CPUExecutionProvider"])
def to_numpy(tensor):
return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()
# compute ONNX Runtime output prediction
ort_inputs = {ort_session.get_inputs()[0].name: to_numpy(x)}
ort_outs = ort_session.run(None, ort_inputs)
# compare ONNX Runtime and PyTorch results
np.testing.assert_allclose(to_numpy(torch_out), ort_outs[0], rtol=1e-03, atol=1e-05)
print("Exported model has been tested with ONNXRuntime, and the result looks good!")
我们应该看到 PyTorch 和 ONNX Runtime 运行的输出在给定的精度范围内(rtol=1e-03
和 atol=1e-05
)在数值上匹配。作为旁注,如果它们不匹配,则 ONNX 导出器存在问题,因此请在这种情况下与我们联系。
模型之间的计时比较¶
由于 ONNX 模型针对推理速度进行了优化,因此在 ONNX 模型而不是原生 pytorch 模型上运行相同的数据应该会使性能提高高达 2 倍。批量大小越大,改进越明显。
import time
x = torch.randn(batch_size, 1, 224, 224, requires_grad=True)
start = time.time()
torch_out = torch_model(x)
end = time.time()
print(f"Inference of Pytorch model used {end - start} seconds")
ort_inputs = {ort_session.get_inputs()[0].name: to_numpy(x)}
start = time.time()
ort_outs = ort_session.run(None, ort_inputs)
end = time.time()
print(f"Inference of ONNX model used {end - start} seconds")
使用 ONNX Runtime 在图像上运行模型¶
到目前为止,我们已经从 PyTorch 导出了一个模型,并展示了如何加载它并在 ONNX Runtime 中使用虚拟张量作为输入来运行它。
在本教程中,我们将使用一张广泛使用的著名猫咪图像,如下所示

首先,让我们加载图像,使用标准 PIL python 库对其进行预处理。请注意,此预处理是处理用于训练/测试神经网络的数据的标准做法。
我们首先调整图像大小以适应模型输入的大小 (224x224)。然后,我们将图像拆分为 Y、Cb 和 Cr 分量。这些分量表示灰度图像 (Y),以及蓝色差 (Cb) 和红色差 (Cr) 色度分量。Y 分量对人眼更敏感,我们对此分量感兴趣,我们将对其进行变换。提取 Y 分量后,我们将其转换为张量,这将是我们模型的输入。
from PIL import Image
import torchvision.transforms as transforms
img = Image.open("./_static/img/cat.jpg")
resize = transforms.Resize([224, 224])
img = resize(img)
img_ycbcr = img.convert('YCbCr')
img_y, img_cb, img_cr = img_ycbcr.split()
to_tensor = transforms.ToTensor()
img_y = to_tensor(img_y)
img_y.unsqueeze_(0)
现在,作为下一步,让我们获取表示灰度调整大小的猫咪图像的张量,并按照之前的说明在 ONNX Runtime 中运行超分辨率模型。
ort_inputs = {ort_session.get_inputs()[0].name: to_numpy(img_y)}
ort_outs = ort_session.run(None, ort_inputs)
img_out_y = ort_outs[0]
此时,模型的输出是一个张量。现在,我们将处理模型的输出,从输出张量中构造回最终输出图像,并保存图像。后处理步骤已从超分辨率模型的 PyTorch 实现 此处 采用。
img_out_y = Image.fromarray(np.uint8((img_out_y[0] * 255.0).clip(0, 255)[0]), mode='L')
# get the output image follow post-processing step from PyTorch implementation
final_img = Image.merge(
"YCbCr", [
img_out_y,
img_cb.resize(img_out_y.size, Image.BICUBIC),
img_cr.resize(img_out_y.size, Image.BICUBIC),
]).convert("RGB")
# Save the image, we will compare this with the output image from mobile device
final_img.save("./_static/img/cat_superres_with_ort.jpg")
# Save resized original image (without super-resolution)
img = transforms.Resize([img_out_y.size[0], img_out_y.size[1]])(img)
img.save("cat_resized.jpg")
这是两张图像之间的比较

低分辨率图像

超分辨率后的图像
ONNX Runtime 是一个跨平台引擎,您可以在多个平台以及 CPU 和 GPU 上运行它。
ONNX Runtime 还可以部署到云端,以使用 Azure Machine Learning Services 进行模型推理。更多信息请访问 此处。
有关 ONNX Runtime 性能的更多信息,请访问 此处。
有关 ONNX Runtime 的更多信息,请访问 此处。
脚本总运行时间: ( 0 分钟 0.000 秒)