天天看点

【ncnn android】算法移植(六)——onnx2ncnn源码阅读理解/设计思路

上一篇写道:onnx2ncnn的时候,不支持sigmoid,upsample层,于是想着阅读onnx2ncnn的源码。目的:

  • 理解ncnn中onnx2ncnn的主要流程
  • 自定义upsample层(最高要求)

1. 相关资料

  1. Open Neural Network Exchange - ONNX ,onnx的文档
  2. https://github.com/Tencent/ncnn,注意ncnn的不同版本代码是不一样,这里以20180704为准。

2. 主要流程

2.1 ncnn.param保存网络结构参数的格式

2.2 onnx关键api

  1. graph

    GraphProto: graph定义了模型的计算逻辑以及带有参数的node节点,组成一个有向图结构;

    const onnx::GraphProto& graph = model.graph();

    关键属性
    【ncnn android】算法移植(六)——onnx2ncnn源码阅读理解/设计思路
  • initializer:好像是预训练的权重
  1. node

    NodeProto: 网络有向图的各个节点OP的结构,通常称为层,例如conv,relu层;

    const onnx::NodeProto& node = graph.node(i);

    关键属性
    【ncnn android】算法移植(六)——onnx2ncnn源码阅读理解/设计思路
  2. attribute

    AttributeProto:各OP的参数,通过该结构访问,例如:conv层的stride,dilation等;

    const onnx::AttributeProto& attr = node.attribute(i);

    【ncnn android】算法移植(六)——onnx2ncnn源码阅读理解/设计思路
  3. tensor

    TensorProto: 序列化的tensor value,一般weight,bias等常量均保存为该种结构;

// batchnorm
const onnx::TensorProto& scale = weights[node.input(1)];
const onnx::TensorProto& B = weights[node.input(2)];
const onnx::TensorProto& mean = weights[node.input(3)];
const onnx::TensorProto& var = weights[node.input(4)];
           

一些疑问

  • node.attribute怎么确定?比如conv,batchnorm有不同的参数

    猜想: 在pytorch2onnx中是不是又具体的定义或代码?

  • 比如batchnorm又多个预训练权重的保存顺序

    猜想: 还是在pytorch2onnx中定义的

4. 一些例子

主要分为两类,无结构参数,如batchnorm,直接保存到bin文件中(注意各个参数的顺序);第二类,有结构参数,无预训练权重。就需要将结构参数保存到ncnn.param网络结构参数中。

4.1 batchnorm

float epsilon = get_node_attr_f(node, "epsilon", 1e-5f);

const onnx::TensorProto& scale = weights[node.input(1)];
const onnx::TensorProto& B = weights[node.input(2)];
const onnx::TensorProto& mean = weights[node.input(3)];
const onnx::TensorProto& var = weights[node.input(4)];

int channels = get_tensor_proto_data_size(scale);

fprintf(pp, " 0=%d", channels);		// batchnorm的通道数

fwrite_tensor_proto_data(scale, bp);	// batchnorm的缩放变量
fwrite_tensor_proto_data(mean, bp);		// 均值
           

4.2 pooling

pooling层是没有预训练的参数,但是有很多类型(maxpool,averagepool),和网络参数(kernel_size, pads)等。

std::string auto_pad = get_node_attr_s(node, "auto_pad");//TODO
std::vector<int> kernel_shape = get_node_attr_ai(node, "kernel_shape");
std::vector<int> strides = get_node_attr_ai(node, "strides");
std::vector<int> pads = get_node_attr_ai(node, "pads");

int pool = op == "AveragePool" ? 1 : 0;
int pad_mode = 1;

if (auto_pad == "SAME_LOWER" || auto_pad == "SAME_UPPER")
{
// TODO
pad_mode = 2;
}

fprintf(pp, " 0=%d", pool);

if (kernel_shape.size() == 1) {
fprintf(pp, " 1=%d", kernel_shape[0]);
} else if (kernel_shape.size() == 2) {
fprintf(pp, " 1=%d", kernel_shape[1]);
fprintf(pp, " 11=%d", kernel_shape[0]);
}

if (strides.size() == 1) {
fprintf(pp, " 2=%d", strides[0]);
} else if (strides.size() == 2) {
fprintf(pp, " 2=%d", strides[1]);
fprintf(pp, " 12=%d", strides[0]);
}

if (pads.size() == 1) {
fprintf(pp, " 3=%d", pads[0]);
} else if (pads.size() == 2) {
fprintf(pp, " 3=%d", pads[1]);
fprintf(pp, " 13=%d", pads[0]);
} else if (pads.size() == 4) {
fprintf(pp, " 3=%d", pads[1]);
fprintf(pp, " 13=%d", pads[0]);
fprintf(pp, " 14=%d", pads[3]);
fprintf(pp, " 15=%d", pads[2]);
}

fprintf(pp, " 5=%d", pad_mode);
           

reference

  1. https://blog.csdn.net/SilentOB/article/details/102863944

继续阅读