1. 定义及概述
Open Neural Network Exchange(ONNX,开放神经网络交换)格式,是一个用于表示深度学习模型的标准,可使模型在不同框架之间进行转移。
ONNX是一种针对机器学习所设计的开放式的文件格式,用于存储训练好的模型。定义了一个可扩展的计算图模型,以及内置运算符和标准数据类型的定义 。它使得不同的人工智能框架(如Pytorch, MXNet)可以采用相同格式存储模型数据并交互。 ONNX的规范及代码主要由微软,亚马逊 ,Facebook 和 IBM 等公司共同开发。
目前官方支持加载ONNX模型并进行推理的深度学习框架有: Caffe2, PyTorch, MXNet,ML.NET,TensorRT 和 Microsoft CNTK,并且 TensorFlow 也非官方的支持ONNX。
支持onnx的工具也比较多,比如netron可以直接打开onnx文件,实现模型结构的可视化
2. onnx与其他格式模型的转换
根据推理端需要使用的框架,再进行一次转换。
从很多使用的角度来说,onnx承担了中间过渡的作用,开发人员首先在pytorch等通用框架进行模型训练,然后将模型导出为onnx格式;之后还需要将onnx转换为tensorrt、ncnn等支持的.engine .bin .param格式
之前我在部署yolov5+deepsort过程中,就是使用pytorch->onnx->tensorrt来进行部署的,github上很多开源项目都是这样的流程。我实验室的师兄现在远程实习,需要部署在CPU上的模型也是通过onnx进行的转化。 (7条消息) [jetson浅试] yolov5+deepsort+Tensorrt C++部署(Xavier AGX)_qqsuiying的博客-CSDN博客
3. protobuf
onnx是通过protobuf这一序列化结构数据来存储神经网络的权重信息。
protobuf 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化(或者说序列化)。它很适合作为数据存储或者交换格式。Protobuf协议是一个以*.proto
后缀文件为基础的,这个文件描述了用户自定义的数据结构。ONNX是基于Protobuf来做数据存储和传输,那么自然onnx.proto
就是ONNX格式文件了,接下来我们就分析一下ONNX格式。
4. onnx格式分析
ONNX中最核心的部分就是onnx.proto,文件可以在github下载
(https://github.com/onnx/onnx/blob/master/onnx/onnx.proto)
它定义了ONNX这个数据协议的规则和一些其它信息,在这个文件里面以message
关键字开头的对象是我们需要关心的。这里列一下最核心的几个对象并解释一下它们之间的关系。
ModelProto
GraphProto
NodeProto
ValueInfoProto
TensorProto
AttributeProto
当我们加载一个onnx文件后,我们即获得了一个ModelProto,它包含一些版本信息、生产者信息和一个最重要的GraphProto。在GraphProto
里面又包含了四个repeated
数组,它们分别是node
(NodeProto
类型),input
(ValueInfoProto
类型),output
(ValueInfoProto
类型)和initializer
(TensorProto
类型),其中node
中存放了模型中所有的计算节点,input
存放了模型的输入节点,output
存放了模型中所有的输出节点,initializer
存放了模型的所有权重参数。
想要完整表述一个神经网络,既需要知道神经网络中各个节点的信息,也需要知道各节点间的拓扑关系。在onnx中,每个计算节点都需要有input和output两个数组,这两个数组都是string类型,通过input和output之间的指向关系,就可以构建出深度学习的拓扑图。(另外,GraphProto中的input不仅包含了输入节点,还包含了 模型所有的权重。例如,conv层中的w权重实体是保存在initializer汇总,那么相应的也会有一个同名的输入在input中,其背后的逻辑应该是把权重也看成模型的输入,并通过initializer
中的权重实体来对这个输入做初始化,即一个赋值的过程。)
最后,每个计算节点中还包含了一个AttributeProto
数组,用来描述该节点的属性,比如Conv
节点或者说卷积层的属性包含group
,pad
,strides
等等,每一个计算节点的属性,输入输出信息都详细记录在https://github.com/onnx/onnx/blob/master/docs/Operators.md
。
5. onnx.helper
现在我们知道ONNX是把一个网络的每一层或者说一个算子当成节点node
,使用这些Node
去构建一个Graph
,即一个网络。最后将Graph
和其它的生产者信息,版本信息等合并在一起生成一个Model
,也即是最终的ONNX模型文件。 在构建ONNX模型的时候,https://github.com/onnx/onnx/blob/master/onnx/helper.py
这个文件非常重要,我们可以利用它提供的make_node
,make_graph
,make_tensor
等等接口完成一个ONNX模型的构建,一个示例如下:
import onnx
from onnx import helper
from onnx import AttributeProto, TensorProto, GraphProto
# The protobuf definition can be found here:
# https://github.com/onnx/onnx/blob/master/onnx/onnx.proto
# Create one input (ValueInfoProto)
X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [3, 2])
pads = helper.make_tensor_value_info('pads', TensorProto.FLOAT, [1, 4])
value = helper.make_tensor_value_info('value', AttributeProto.FLOAT, [1])
# Create one output (ValueInfoProto)
Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [3, 4])
# Create a node (NodeProto) - This is based on Pad-11
node_def = helper.make_node(
'Pad', # node name
['X', 'pads', 'value'], # inputs
['Y'], # outputs
mode='constant', # attributes
)
# Create the graph (GraphProto)
graph_def = helper.make_graph(
[node_def],
'test-model',
[X, pads, value],
[Y],
)
# Create the model (ModelProto)
model_def = helper.make_model(graph_def, producer_name='onnx-example')
print('The model is:\n{}'.format(model_def))
onnx.checker.check_model(model_def)
print('The model is checked!')
这个官方示例为我们演示了如何使用onnx.helper
的make_tensor
,make_tensor_value_info
,make_attribute
,make_node
,make_graph
,make_node
等方法来完整构建了一个ONNX模型。需要注意的是在上面的例子中,输入数据是一个一维Tensor,初始维度为[2]
,这也是为什么经过维度为[1,4]
的Pad操作之后获得的输出Tensor维度为[3,4]
。另外由于Pad操作是没有带任何权重信息的,所以当你打印ONNX模型时,ModelProto
的GraphProto
是没有initializer
这个属性的。
6. onnx-simplifier
引用大佬的一个经典例子,通过pytorch转onnx中reshape算子的问题,来说明onnx-simplifier的处理过程。
详细可参考ONNX学习笔记 - 知乎 (zhihu.com)中的0x5部分
参考链接
(7条消息) ONNX简介_知更鸟k的博客-CSDN博客_onnx
ONNX学习 - 知乎 (zhihu.com)
ONNX学习笔记 - 知乎 (zhihu.com)及其扩展参考