ONNX
一种表示深度学习模型的开放格式,一套独立于环境和平台的标准格式。
ONNX文件存储了神经网络模型的权重和模型的结构信息、网络中各层的输入输出等一些信息。
使用Netron查看ONNX文件
Netron网址,除onnx文件之外,还能查看.tflite .pb .h5 等文件
Protobuf
Google提出的一种轻便高效的结构化数据存储格式,可以用于结构化数据序列化。ONNX使用Protobuf去存储神经网络的权重信息。
安装Protobuf Compiler
编译abseil
1. git clone https://github.com/abseil/abseil-cpp.git
2. cd ./abseil-cpp
3. mkdir build && cd ./build
4. cmake -DABSL_BUILD_TESTING=ON -DABSL_USE_GOOGLETEST_HEAD=ON -DCMAKE_CXX_STANDARD=14 ..
要求C++ 标准最低是 C++ 14
5. cmake --build . --target all
6. sudo make install
编译Protobuf Compiler
1. git clone https://github.com/protocolbuffers/protobuf.git
2. cd ./protobuf
3. git submodule update --init --recursive
4. cmake -S . -B ./build
5. cmake --build ./build --parallel 10
6. cd ./build
7. sudo make install
ubuntu 使用 sudo cmake install:
/usr/local/bin: 该目录用于存放编译安装的可执行文件
/usr/local/lib: 该目录用于存放编译安装的库文件
/usr/local/include: 该目录用于存放头文件
/usr/local/cmake/: 该目录存放xxConfig.cmake等文件,用于find_package使用
使用Protobuf
1. 编写.proto文件
2. 使用protoc ./addressbook.proto --cpp_out .
得到一个.pb.h 和 一个.pb.cc文件
3. 编写C++文件,头文件中 #include ".pb.h"
// .proto example
syntax = "proto3"; // 声明了protobuf的版本
package fixbug; // 声明了代码所在的包(对于C++来说是namespace)
// 登录消息类型
message LoginRequest {
string name = 1; // 1表示第一个字段
string pwd = 2;
}
// 定义登录响应消息类型
message LoginResponse {
int32 errcode = 1;
string errmsg = 2;
bool success = 3;
}
Onnx文件结构
// https://github.com/onnx/onnx/blob/main/onnx/onnx.proto
enum Vesion;
message AttributeProto {
enum AttributeType {
// 属性
}
...
}
message ValueInfoProto;
message NodeProto {
repeated string input = 1;
repeated string output = 2;
optional string name = 3;
optional string op_type = 4;
optional string domain = 7;
repeated AttributeProto attribute = 5;
optional string doc_string = 6;
}
message TrainingInfoProto;
message ModelProto;
message GraphProto;
message TensorProto;
message SparseTensorProto;
message TensorShapeProto;
message TypeProto;
message OperatorSetIdProto;
message FunctionProto;
拓扑结构
一个Onnx文件即是一个Modelproto, 每个Modelproto包含一些版本信息,生产者信息和一个GraphProto.
GraphProto定义了模型的计算逻辑,由参数化的节点列表组成,这些节点根据其输入和输出形成有向无环图。
其中包含了NodeProto(node)、TensorProto(initializer)、SparseTensorProto(sparse_initializer)、 ValueInfoProto(input)、 ValueInfoProto(output)、 ValueInfoProto(value_info)、TensorAnnotation(quantization_annotation), 其中node中存放了模型中所有的计算节点,input存放了模型的输入节点,output存放了模型中所有的输出节点,initializer存放了模型的所有权重参数。
每个NodeProto包括input 和 output 用于构建网络的拓扑关系。
AttributeProto数组,用来描述该节点的属性。
API
ONNX提供了python用于创建、修改和测试.onnx文件,可以很方便的对onnx文件进行操作。
// 将一个yolo.onnx模型分为不包含后处理和仅包含后处理两个部分
import onnx
input_path = "./yolo.onnx"
# 不包含后处理
# output_path = "./yolo_sub_module.onnx"
# input_names = ["input"]
# output_names = [
# "1105", "1106", "1107", "1132", "1133", "1134",
# "1159", "1160", "1161", "1186", "1187", "1188",
# "1051", "1052", "1053", "1078", "1079", "1080",
# ]
# onnx.utils.extract_model(input_path, output_path, input_names, output_names)
# 仅包含后处理
output_path = "./yolo_sub_post.onnx"
input_names = [
"1105", "1106", "1107", "1132", "1133", "1134",
"1159", "1160", "1161", "1186", "1187", "1188",
"1051", "1052", "1053", "1078", "1079", "1080",
]
output_names = [
"dets", "scores", "labels",
]
onnx.utils.extract_model(input_path, output_path, input_names, output_names)
也可以使用API来搭建一个模型
// 官方示例,更多参见https://onnx.ai/onnx/intro/python.html
import numpy
from onnx import numpy_helper, TensorProto
from onnx.helper import (
make_model, make_node, make_graph,
make_tensor_value_info)
from onnx.checker import check_model
# initializers
value = numpy.array([0.5, -0.6], dtype=numpy.float32)
A = numpy_helper.from_array(value, name='A')
value = numpy.array([0.4], dtype=numpy.float32)
C = numpy_helper.from_array(value, name='C')
# the part which does not change
X = make_tensor_value_info('X', TensorProto.FLOAT, [None, None])
Y = make_tensor_value_info('Y', TensorProto.FLOAT, [None])
node1 = make_node('MatMul', ['X', 'A'], ['AX'])
node2 = make_node('Add', ['AX', 'C'], ['Y'])
graph = make_graph([node1, node2], 'lr', [X], [Y], [A, C])
onnx_model = make_model(graph)
check_model(onnx_model)
print(onnx_model)
同样,除了使用可视化查看onnx模型的信息外,也可以使用api来打印模型信息
# the list of inputs
print('** inputs **')
print(onnx_model.graph.input)
# in a more nicely format
print('** inputs **')
for obj in onnx_model.graph.input:
print("name=%r dtype=%r shape=%r" % (
obj.name, obj.type.tensor_type.elem_type,
shape2tuple(obj.type.tensor_type.shape)))
# the list of outputs
print('** outputs **')
print(onnx_model.graph.output)
# in a more nicely format
print('** outputs **')
for obj in onnx_model.graph.output:
print("name=%r dtype=%r shape=%r" % (
obj.name, obj.type.tensor_type.elem_type,
shape2tuple(obj.type.tensor_type.shape)))
# the list of nodes
print('** nodes **')
print(onnx_model.graph.node)
# in a more nicely format
print('** nodes **')
for node in onnx_model.graph.node:
print("name=%r type=%r input=%r output=%r" % (
node.name, node.op_type, node.input, node.output))
验证
因为OnnxRuntime的python api接口比较简单,一般使用OnnxRuntime来进行验证结果,但ONNX本身也提供了验证,并且可以针对节点,中间层输出,自定义算子等进行验证。
import numpy
from onnx import numpy_helper, TensorProto
from onnx.helper import (
make_model, make_node, set_model_props, make_tensor,
make_graph, make_tensor_value_info)
from onnx.checker import check_model
from onnx.reference import ReferenceEvaluator
X = make_tensor_value_info('X', TensorProto.FLOAT, [None, None])
A = make_tensor_value_info('A', TensorProto.FLOAT, [None, None])
B = make_tensor_value_info('B', TensorProto.FLOAT, [None, None])
Y = make_tensor_value_info('Y', TensorProto.FLOAT, [None])
node1 = make_node('MatMul', ['X', 'A'], ['XA'])
node2 = make_node('Add', ['XA', 'B'], ['Y'])
graph = make_graph([node1, node2], 'lr', [X, A, B], [Y])
onnx_model = make_model(graph)
check_model(onnx_model)
sess = ReferenceEvaluator(onnx_model)
x = numpy.random.randn(4, 2).astype(numpy.float32)
a = numpy.random.randn(2, 1).astype(numpy.float32)
b = numpy.random.randn(1, 1).astype(numpy.float32)
feeds = {'X': x, 'A': a, 'B': b}
print(sess.run(None, feeds))
导出
不同的框架有不同导出ONNX文件的方法,以PyTorch为例:
def export_model_from_pytorch_to_onnx(pytorch_model, onnx_model_name):
batch_size = 1
# input to the model
x = torch.randn(batch_size, 1, 32, 32)
out = pytorch_model(x)
#print("out:", out)
# export the model
torch.onnx.export(pytorch_model, # model being run
x, # model input (or a tuple for multiple inputs)
onnx_model_name, # 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=9, # 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'}})
onnx-simplifier
用于简化onnx文件,得到更精简的表示,对pytorch、tf或者paddle转换得到的onnx模型做优化,去除很多胶水节点,以及做一些图优化,得到一个简洁明了的模型图