由于这段时间着手实现tensorflow到ncnn的转换,开发过程中对ncnn框架有了一定的认识,特此分享。
关于tensorflow2ncnn的具体细节和步骤,可以参考我的github:
https://github.com/hanzy88/tensorflow2ncnn
目前已支持CNN+FC的转换,并且基于full cnn/mobilenetv2的yolov3已测试成功,由于一定的精度损失结果基本正确。测试在jetson nano上运行成功(因为full cnn的yolov3太大导致运行非常!卡顿)。
言归正传,之前看ncnn源码的时候从mat这些着手,虽然看了一些但实在没有具体的概念,于是这个系列文章主要自上而下从模型和权重的传入开始解读ncnn的前向传播。
一. 模型和权重文件介绍
在完成ncnn模型转换后,一般会得到两个文件:
ncnn.param
ncnn.bin
其中param存放的是模型结构,bin存放的是类似卷积这些op的权重文件。
param的结构如下:
7767517
3 3
Input input 0 1 data 0=4 1=4 2=1
InnerProduct ip 1 1 data fc 0=10 1=1 2=80
Softmax softmax 1 1 fc prob 0=0
第一行是magic num,指定7767517
第二行第一个是layer数量,第二个blob数量,其中blob是层与层之间传递的数据结构,定义在blob.h, blob.cpp, 如下:
class Blob
{
public:
// empty
Blob();
public:
#if NCNN_STRING
// blob name
std::string name;
#endif // NCNN_STRING
// layer index which produce this blob as output 记录输出层的索引
int producer;
// layer index which need this blob as input 记录输入层的索引
std::vector<int> consumers;
};
第三行至最后记录了每一层的传递信息,以第三行为例:
1列是当前层的op, 2列是层名,3,4列分别是输入和输出节点数,之后string就是输入节点名称和输出节点名称。再之后的数字是每一层传入的常量参数,具体继续往下看:
[layer type] [layer name] [input count] [output count] [input blobs] [output blobs] [layer specific params]
layer type : type name, such as Convolution Softmax etc
layer name : name of this layer, must be unique among all layer names
input count : count of the blobs this layer needs as input
output count : count of the blobs this layer produces as output
input blobs : name list of all the input blob names, seperated by space, must be unique among input blob names of all layers
output blobs : name list of all the output blob names, seperated by space, must be unique among output blob names of all layers
layer specific params : key=value pair list, seperated by space
层参数:
0=1 1=2.5 -23303=2,2.0,3.0
key index should be unique in each layer line, pair can be omitted if the default value used
the meaning of existing param key index can be looked up at https://github.com/Tencent/ncnn/wiki/operation-param-weight-table
integer or float key : index 0 ~ 19
integer value : int
float value : float
integer array or float array key : -23300 minus index 0 ~ 19
integer array value : [array size],int,int,...,int
float array value : [array size],float,float,...,float
来自官方的解释,如果你还是不太理解,就以我在tensorflow2ncnn.cpp中定义Range的参数读取代码为例:
else if(node.op() == "Range"){
const tensorflow::TensorProto& start = weights[node.input(0)];
const tensorflow::TensorProto& limit = weights[node.input(1)];
const tensorflow::TensorProto& delta = weights[node.input(2)];
const int * start_data = reinterpret_cast<const int *>(start.int_val().begin());
const int * limit_data = reinterpret_cast<const int *>(limit.int_val().begin());
const int * delta_data = reinterpret_cast<const int *>(delta.int_val().begin());
fprintf(pp, " 0=%d", *start_data);
fprintf(pp, " 1=%d", *limit_data);
fprintf(pp, " 2=%d", *delta_data);
}
这里的0,1,2是可以自己随意定义的,但是你需要知道它具体代表的含义,以便在前向传递的时候根据定义的 index(i.e.,0,1,2) 取值操作,所以同样是index 0在不同层可能具有不同含义。
bin文件结构如下:
+---------+---------+---------+---------+---------+---------+
| weight1 | weight2 | weight3 | weight4 | ....... | weightN |
+---------+---------+---------+---------+---------+---------+
^ ^ ^ ^
0x0 0x80 0x140 0x1C0
the model bina