注意
单击 此处 下载完整示例代码
(可选) 将 PyTorch 模型导出到 ONNX 并使用 ONNX Runtime 运行¶
注意
从 PyTorch 2.1 开始,有两个版本的 ONNX 导出器。
torch.onnx.dynamo_export
是基于 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
的模型。该模型使用 Shi 等人在 “使用高效子像素卷积神经网络进行实时单图像和视频超分辨率” 中描述的高效子像素卷积层来提高图像分辨率。该模型期望以 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 图中,所有输入维度的输入大小将被固定,除非指定为动态轴。在本例中,我们导出具有批次大小为 1 的输入的模型,然后在 torch.onnx.export()
中的 dynamic_axes
参数中指定第一维为动态的。因此,导出的模型将接受大小为 [批次大小,1,224,224] 的输入,其中批次大小可以是可变的。
要了解有关 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 机器学习服务部署到云端进行模型推理。更多信息请查看 这里。
有关 ONNX Runtime 性能的更多信息,请查看 这里。
有关 ONNX Runtime 的更多信息,请查看 这里。
脚本的总运行时间:(0 分钟 0.000 秒)