|
| 1 | +# Paddle 模型导出 ONNX 协议 |
| 2 | + |
| 3 | +## 一、ONNX 简介 |
| 4 | +开放神经网络交换(Open Neural Network Exchange,简称 ONNX),是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。借助它支持不同的人工智能框架(如 Pytorch、MXNet)采用相同格式存储模型数据并交互。 |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | +没有 ONNX 模型之前,采用各个框架训练的模型只能通过特定的转换工具进行转换。有了 ONNX 协议后,无论使用何种框架训练模型,训练完毕后都可以将模型转换为 ONNX 这种统一的格式进行存储。这就大大方便了算法及模型在不同框架之间的迁移。 |
| 9 | + |
| 10 | +目前官方支持加载 ONNX 模型并进行推理的深度学习框架,除飞桨外,还有 PyTorch、MXNet、Caffe2、ML.NET、TensorRT 和 Microsoft CNTK 等主流框架都对 ONNX 有着不同程度的支持;并且 TensorFlow 也非官方的支持 ONNX。除深度学习框架外,大多推理引擎或者国产硬件,比如如 RK、地平线、鲲云等也都支持加载 ONNX 模型进行推理,如果需要在这类硬件上部署,可通过 Paddle2ONNX 将 Paddle 模型导出成 ONNX 后进行部署。 |
| 11 | + |
| 12 | +## 二、Paddle2ONNX 安装 |
| 13 | +Paddle2ONNX 作为 Paddle 提供的模型转换工具,可以将 Paddle 模型转换为 ONNX 格式,Paddle2ONNX 的安装非常简单,只需要输入以下命令就可以完成 Paddle2ONNX 的安装。 |
| 14 | + |
| 15 | +``` |
| 16 | +python -m pip install paddle2onnx |
| 17 | +``` |
| 18 | + |
| 19 | +## 三、获取 Paddle2ONNX 模型库 |
| 20 | +Paddle2ONNX 目前提供了包括图像分类、图像分割、目标检测、文字识别四个类别的模型和部署示例代码,供开发者进行参考使用,可以点击[链接](https://github.com/PaddlePaddle/Paddle2ONNX/tree/develop/model_zoo)获取。其他更多的飞桨官方模型套件的各类常用模型,可以到对应的官方 Repo 下进行下载,然后再进行转换。 |
| 21 | + |
| 22 | +## 四、Paddle 转 ONNX 教程 |
| 23 | +在本教程中,我们将描述如何将 Paddle 模型转换为 ONNX 格式,然后使用 ONNXRuntime 运行它。具体操作分以下两种场景: |
| 24 | +* 如果使用 Paddle 训练模型,并将其导出为 ONNX 协议,请参考 4.1 节导出为 ONNX 模型。 |
| 25 | +* 如果是从 PaddleOCR、PaddleClas 或 PaddleHub 等 Repo 中下载的部署模型,或者是通过 Paddle 训练好并保存的部署模型,则参考 4.2 节转换为 ONNX 模型。 |
| 26 | + |
| 27 | +### 4.1 Paddle 训练模型导出为 ONNX 协议 |
| 28 | +本小节以 MNIST 手写数字识别网络作为例子,展示如何将训练模型导出为 ONNX 协议。 |
| 29 | + |
| 30 | +『手写数字识别任务』比较简单,普通的神经网络就能达到很高的精度,以下代码直接来自前面的[手写数字识别任务](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/beginner/quick_start_cn.html),无需修改。 |
| 31 | + |
| 32 | +``` |
| 33 | +import paddle |
| 34 | +from paddle.vision.transforms import Normalize |
| 35 | +
|
| 36 | +transform = Normalize(mean=[127.5], std=[127.5], data_format='CHW') |
| 37 | +# 下载数据集并初始化 DataSet |
| 38 | +train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform) |
| 39 | +test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform) |
| 40 | +
|
| 41 | +# 模型组网并初始化网络 |
| 42 | +lenet = paddle.vision.models.LeNet(num_classes=10) |
| 43 | +model = paddle.Model(lenet) |
| 44 | +
|
| 45 | +# 模型训练的配置准备,准备损失函数,优化器和评价指标 |
| 46 | +model.prepare(paddle.optimizer.Adam(parameters=model.parameters()), |
| 47 | + paddle.nn.CrossEntropyLoss(), |
| 48 | + paddle.metric.Accuracy()) |
| 49 | +
|
| 50 | +# 模型训练 |
| 51 | +model.fit(train_dataset, epochs=5, batch_size=64, verbose=1) |
| 52 | +# 模型评估 |
| 53 | +model.evaluate(test_dataset, batch_size=64, verbose=1) |
| 54 | +``` |
| 55 | +完成以上代码,就可以训练这个模型。请注意,此模型未完全训练以获得良好的准确性,此处仅用于演示目的。 |
| 56 | +使用 Paddle 完成模型训练之后,要导出模型,需要调用 paddle.onnx.export 接口,在导出模型时我们需要使用 paddle.static.InputSpec API 指定输入的 shape,如果输入中某一维为动态的,可以将该维指定为 None,在本例中我们设置第一维为动态,表示推理过程中该维可变。 |
| 57 | + |
| 58 | +使用 Paddle 完成模型训练之后,转换成 ONNX 协议只需要调用 paddle.onnx.export 接口,便会在指定的路径下生成 ONNX 模型。关于 paddle.onnx.export 接口更详细的使用方法,请参考 [API 文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/onnx/export_cn.html) 。 |
| 59 | +添加如下脚本,可以在 onnx.save 下生成 lenet.onnx 模型。 |
| 60 | +``` |
| 61 | +# export to ONNX |
| 62 | +save_path = 'onnx.save/lenet' # 需要保存的路径 |
| 63 | +x_spec = paddle.static.InputSpec([None, 1, 28, 28], 'float32', 'x') # 为模型指定输入的形状和数据类型,支持持 Tensor 或 InputSpec ,InputSpec 支持动态的 shape。 |
| 64 | +paddle.onnx.export(lenet, save_path, input_spec=[x_spec], opset_version=11) |
| 65 | +``` |
| 66 | + |
| 67 | +### 4.2 Paddle 部署模型转为 ONNX 协议 |
| 68 | +如果模型是从 PaddleOCR、PaddleClas 或 PaddleHub 等 Repo 中下载的部署模型,或者将 Paddle 模型保存为部署模型,均可以使用 Paddle2ONNX 的命令行进行转换。 |
| 69 | + |
| 70 | +本小节以 PaddleClas 提供的 MobileNetV3 分类模型作为例子,演示转换的过程。 |
| 71 | + |
| 72 | +1.首先可以使用如下命令下载保存下来的 Paddle 模型并解压,如果你已经有下载好的 Paddle 部署模型,则可以跳过此步骤。解压成功后在 mobilenetv3 文件夹下有 Paddle 的模型和参数文件。 |
| 73 | + |
| 74 | +``` |
| 75 | +wget https://bj.bcebos.com/paddle2onnx/model_zoo/mobilenetv3.tar.gz |
| 76 | +tar xvf mobilenetv3.tar.gz |
| 77 | +``` |
| 78 | + |
| 79 | +2.接下来可以使用 Paddle2ONNX 命令行工具将 MobileNetV3 转换为 ONNX 格式。转换成功后会生成 model.onnx 模型。命令相关的说明请参考:[Paddle2ONNX 命令行参数说明](https://github.com/PaddlePaddle/Paddle2ONNX) |
| 80 | + |
| 81 | +``` |
| 82 | +paddle2onnx --model_dir mobilenetv3 --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file model.onnx --enable_dev_version True --opset_version 13 --enable_onnx_checker True |
| 83 | +``` |
| 84 | +### 4.3 验证 ONNX 模型 |
| 85 | + |
| 86 | +接下来,我们验证一下导出的 ONNX 模型的正确性。 |
| 87 | + |
| 88 | +#### 4.3.1 检查 ONNX 模型的有效性 |
| 89 | + |
| 90 | +可以使用如下脚本验证导出的 ONNX 模型是否合理,包括检查模型的版本、图的结构、节点及其输入和输出。如下脚本的输出为 None 则表示模型转换正确。 |
| 91 | + |
| 92 | +``` |
| 93 | +# 导入 ONNX 库 |
| 94 | +import onnx |
| 95 | +# 载入 ONNX 模型 |
| 96 | +onnx_model = onnx.load("model.onnx") |
| 97 | +# 使用 ONNX 库检查 ONNX 模型是否合理 |
| 98 | +check = onnx.checker.check_model(onnx_model) |
| 99 | +# 打印检查结果 |
| 100 | +print('check: ', check) |
| 101 | +``` |
| 102 | + |
| 103 | +#### 4.3.2 验证模型是否匹配 |
| 104 | + |
| 105 | +接下来我们验证原始的 Paddle 模型和导出的 ONNX 模型是否有相同的计算结果。 |
| 106 | + |
| 107 | +``` |
| 108 | +# 导入所需的库 |
| 109 | +import numpy as np |
| 110 | +import onnxruntime |
| 111 | +import paddle |
| 112 | +
|
| 113 | +# 随机生成输入,用于验证 Paddle 和 ONNX 的推理结果是否一致 |
| 114 | +x = np.random.random((1, 3, 224, 224)).astype('float32') |
| 115 | +
|
| 116 | +# predict by ONNXRuntime |
| 117 | +ort_sess = onnxruntime.InferenceSession("model.onnx") |
| 118 | +ort_inputs = {ort_sess.get_inputs()[0].name: x} |
| 119 | +ort_outs = ort_sess.run(None, ort_inputs) |
| 120 | +
|
| 121 | +print("Exported model has been predicted by ONNXRuntime!") |
| 122 | +
|
| 123 | +# predict by Paddle |
| 124 | +model = paddle.jit.load("mobilenetv3/inference") |
| 125 | +model.eval() |
| 126 | +paddle_input = paddle.to_tensor(x) |
| 127 | +paddle_outs = model(paddle_input) |
| 128 | +
|
| 129 | +print("Original model has been predicted by Paddle!") |
| 130 | +
|
| 131 | +# compare ONNXRuntime and Paddle results |
| 132 | +np.testing.assert_allclose(ort_outs[0], paddle_outs.numpy(), rtol=1.0, atol=1e-05) |
| 133 | +
|
| 134 | +print("The difference of results between ONNXRuntime and Paddle looks good!") |
| 135 | +``` |
| 136 | + |
| 137 | +运行结果如下,可以看到 Paddle 模型和导出的 ONNX 模型在输入相同时计算结果一致: |
| 138 | + |
| 139 | + |
| 140 | + |
| 141 | +ONNXRuntime 的 API 和相关文档请查阅:[ONNXRuntime 文档](https://onnxruntime.ai/docs/)。 |
| 142 | + |
| 143 | +### 4.4 使用 ONNX 模型进行推理 |
| 144 | + |
| 145 | +导出的 ONNX 模型可以用支持 ONNX 模型的推理引擎进行推理,比如 ONNXRuntime 或 TensorRT 等。本节将用 ONNXRuntime 和 4.2 节导出的 ONNX 模型来进行展示如何使用 ONNXRuntime 对 ONNX 模型进行推理预测。 |
| 146 | + |
| 147 | +为了使用 ONNXRuntime 运行模型,需要使用所选配置参数为模型创建一个推理会话(这里我们使用默认配置)。可以使用如下命令,安装依赖、下载推理脚本和测试图片。 |
| 148 | + |
| 149 | +``` |
| 150 | +# 安装 ONNXRuntime 用于 ONNX 模型的推理 |
| 151 | +pythom -m pip install onnxruntime |
| 152 | +# 下载推理用的 infer.py 脚本 |
| 153 | +wget https://raw.githubusercontent.com/PaddlePaddle/Paddle2ONNX/develop/model_zoo/classification/infer.py |
| 154 | +# 下载测试用的图片 |
| 155 | +wget https://github.com/PaddlePaddle/Paddle2ONNX/raw/develop/model_zoo/classification/images/ILSVRC2012_val_00000010.jpeg |
| 156 | +``` |
| 157 | + |
| 158 | +以下脚本是 ONNX Runtme 使用 ONNX 模型推理的必要步骤,只需将前后处理改为实际应用场景下的逻辑便可使用,4.1 中的手写数字识别模型导出为 ONNX 协议之后,只需将图像前后处理逻辑加入进来便可用于部署。 |
| 159 | + |
| 160 | +``` |
| 161 | +# 导入 ONNX 包 |
| 162 | +import onnxruntime |
| 163 | +# 加载 ONNX 模型生成推理用 sess |
| 164 | +sess = onnxruntime.InferenceSession("model.onnx") |
| 165 | +# 数据预处理 |
| 166 | +data = preprocess(image_path) |
| 167 | +# 使用 ONNXRuntime 推理 |
| 168 | +result, = sess.run(None, {"inputs": data}) |
| 169 | +# 推理结果后处理 |
| 170 | +postprocess(result) |
| 171 | +``` |
| 172 | + |
| 173 | +使用如下命令运行脚本,使用 ONNXRuntime 推理。 |
| 174 | + |
| 175 | +``` |
| 176 | +# 使用 ONNX 模型和 ONNXRuntime 推理,对图片进行分类 |
| 177 | +python infer.py --model model.onnx --image_path images/ILSVRC2012_val_00000010.jpeg |
| 178 | +``` |
| 179 | + |
| 180 | +输入图片和最终的推理结果如下,可以看到准确的预测出输入的图片是一只贵宾犬。 |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | +``` |
| 185 | +# 分类出的前 5 个类别 |
| 186 | +TopK Indices: [265 153 850 332 283] |
| 187 | +# 前 5 个类别的置信度 |
| 188 | +TopK Scores: [0.4966848 0.25181034 0.15389322 0.01496286 0.01342606] |
| 189 | +``` |
| 190 | + |
| 191 | +备注:各类别 id 与明文标签请参考[ImageNet 标签](https://github.com/PaddlePaddle/PaddleClas/blob/release/2.3/deploy/utils/imagenet1k_label_list.txt) |
| 192 | + |
| 193 | +## 五、注意事项 |
| 194 | + |
| 195 | +1. 目前 Paddle2ONNX 已经支持 170 多个 Paddle OP,多数 Paddle 的模型都可顺利导出为 ONNX 格式,如果在转换中遇到不支持的 OP,请到 Paddle2ONNX Repo 下提 Issue,我们都会尽快支持。 |
| 196 | + |
| 197 | +2. PaddleSlim 量化模型导出 ONNX,参考量化导出说明文档:[量化模型导出说明](https://github.com/PaddlePaddle/Paddle2ONNX/blob/develop/docs/zh/quantize.md),如在导出 ONNX 过程遇到问题,欢迎通过[Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX)开源项目 Issue 方式反馈。 |
| 198 | + |
| 199 | +3. 使用 ONNXRuntime 验证转换模型, 请注意安装最新版本,最低要求 1.10.0 版本。查询 ONNXRuntime 版本可以使用如下命令: |
| 200 | + |
| 201 | +``` |
| 202 | +python -m pip show onnxruntime |
| 203 | +``` |
| 204 | + |
| 205 | +该命令的输出如下,Version 则为 ONNXRuntime 的版本: |
| 206 | + |
| 207 | + |
| 208 | + |
| 209 | +4. 模型转换过程会将 Paddle 的算子逐一转换为 ONNX 的算子。由于 ONNX 的算子随版本迭代不断升级,不同的推理引擎对 ONNX 算子版本支持也不完全一致,因此,使用 Paddle2ONNX 对 Paddle 模型进行转换时,需要指定 ONNX 的算子版本,此参数应根据待部署的推理引擎进行设置,在转换中可以使用 Paddle2ONNX 的 opset_version 选项进行指定,具体设置方法请参见[参数选项](https://github.com/PaddlePaddle/Paddle2ONNX#%E5%8F%82%E6%95%B0%E9%80%89%E9%A1%B9)。 |
0 commit comments