深度学习模型转换onnx2ncnn

我们知道现在的深度学习训练框架(如tensorflow、caffe、pytorch、MXNet等等)都有自己的模型存储格式,那他们之间的转换就是一个常见的需求了,但是如果每个框架都要写转换到其他所有框架的代码,那就麻烦了,如果出现一种新框架,每种框架都要再写一种转换。所以最好的方式应该就是每个框架都有向一种统一的框架转换的代码,等需要转换的时候,只需要先转换成这种统一的模型格式,再由这种格式转到你的目标框架格式,这种统一的模型格式就是ONNX。而这种思想就类似与编译器中IR(Intermediate Representation,中间表达形式)的思想。
本篇就以ncnn源码中的onnx2ncnn为例,讲解一下onnx的基础以及onnx向其他框架转换的知识。如下图是ncnn源码下tools/onnx:


image.png

protobuf中的.proto语法

这里先通过onnx.proto来讲解一下.proto文件的语法以及onnx的基本数据结构。打开onnx.proto文件,去掉注释,除了头几行外,其他都是一个一个的message,这里截取一个message NodeProto。如下:

// 定义语法类型,通常proto3好于proto2,proto2好于proto1
syntax = "proto2";
// 定义作用域
package onnx;
// 类class NodeProto
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;
}
  1. required: 必须赋值的字符(onnx.proto中没有)
  2. optional: 可有可无的字段,可以使用[default = xxx]配置默认值
  3. repeated: 可重复变长字段,类似数组
    上面我们可以看到没有字段后面都有一个=数字,这是每个字段在每个message里独一无二的tag,tag 1-15是字节编码,16-2047使用2字节编码,所以1-15给频繁使用的字段,这里的tag都没有超过15。而上面的字段类型可以参考下图:


    image.png

    有了.proto文件,那如何使用呢?一般是编译的时候,通过.proto文件生成C++源文件来来使用,这个具体可参考protobuf官网。基本像下面这样一句代码就能生成:

protoc -I=input_dir --cpp_out=output_dir input_dir/onnx.proto

ONNX基础

从onnx.proto文件中,我们可以看到,onnx的数据结构,onnx的网络的每一层的数据结构是Node,由这些Node组成Graph,然后Graph和onnx模型的一些其他信息组成一个model,也就是最终的.onnx模型。

  1. NodeProto
message NodeProto {
//存放节点输入的名字 [类型:字符串列表]
  repeated string input = 1;   
// 存放节点输出的名字 [类型:字符串列表]
  repeated string output = 2;  
//节点名
  optional string name = 3;    
//节点的算子类型 [类型:字符串]
  optional string op_type = 4;  
//算子域[类型:字符串]
  optional string domain = 7;   
//存放节点的属性attributes [类型:任意]
  repeated AttributeProto attribute = 5;
//描述文档的字符串,这个默认为None [类型:字符串]
  optional string doc_string = 6;
}
  1. GraphProto
message GraphProto {
//生成的节点列表 [类型:NodeProto列表
  repeated NodeProto node = 1;
//graph的名字 [类型:字符串]
  optional string name = 2; 
//存放超参数 [类型:TensorProto列表]
  repeated TensorProto initializer = 5;
//描述文档的字符串,这个默认为None [类型:字符串]
  optional string doc_string = 10;
//存放graph的输入数据信息 [类型:ValueInfoProto列表]
  repeated ValueInfoProto input = 11;
//存放graph的输出数据信息 [类型:ValueInfoProto列表]
  repeated ValueInfoProto output = 12;
//存放中间层产生的输出数据的信息 [类型:ValueInfoProto列表]
  repeated ValueInfoProto value_info = 13;
  repeated TensorAnnotation quantization_annotation = 14;
  // repeated string input = 3;
  // repeated string output = 4;
  // optional int64 ir_version = 6;
  // optional int64 producer_version = 7;
  // optional string producer_tag = 8;
  // optional string domain = 9;
}
  1. ModelProto
message ModelProto {
  optional int64 ir_version = 1;
  repeated OperatorSetIdProto opset_import = 8;
  optional string producer_name = 2;
  optional string producer_version = 3;
  optional string domain = 4;
  optional int64 model_version = 5;
  optional string doc_string = 6;
//生成的graph
  optional GraphProto graph = 7;
  repeated StringStringEntryProto metadata_props = 14;
};

onnx2ncnn

看到onnx2ncnn.cpp,从main进入后,调用read_proto_from_binary载入并解析.onnx文件到onnx::ModelProto model。来看一下read_proto_from_binary:

static bool read_proto_from_binary(const char* filepath, google::protobuf::Message* message)
{
    //以都字节的形式打开文件
    std::ifstream fs(filepath, std::ifstream::in | std::ifstream::binary);
    if (!fs.is_open())
    {
        fprintf(stderr, "open failed %s\n", filepath);
        return false;
    }    
    //读入
    google::protobuf::io::IstreamInputStream input(&fs); 
    //反序列化字节流
    google::protobuf::io::CodedInputStream codedstr(&input);
    //限制最大字节数
    codedstr.SetTotalBytesLimit(INT_MAX, INT_MAX / 2);
    //解析出message
    bool success = message->ParseFromCodedStream(&codedstr);
    //关闭字节流
    fs.close();
    return success;
}

然后,在main中就是按解析出的node对应ncnn中的一层来写入ncnn模型文件,完成转换。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335