< NCNN-Lession-5 > create_layer的实现

开始

在第三节我们实现了读取网络proto的基本流程,但是有一个功能没有实现,就是我们在实例化Layer的时候,其实是实例化了父类的Layer,并没有实例化子类的Layer,这样后面就没法调用子类Layer的方法.在上一节,我们实现了squeezenet中用到的所有子类Layer,所以在这一节,我们就实现如何对在读取网络proto的时候,智能的实例化不同的子类Layer.

我们继续插上新的小红旗:

作用

其实父类Layer的目的,就是为了能够利用C++中的多态和重载机制,然后智能化的实例化不同的子类Layer.

在上一节中,我们实现的子类Layer,有些自己的方法,又会继承父类Layer的方法.这样就可以优雅的用同一个接口去调用不同子类Layer的不同功能.

实现

由于在load_proto中,代表该Layer属于哪个子类的标识只有layer_type这个char*的标识,所以为了自动的把layer_type和开辟各个子空间的函数应起来.

第一步,我们需要一个layer_type和返回对应子类指针的函数(给子类开辟空间)的结构体

1
2
3
4
5
6
7
8
9
//定义返回Layer*的函数指针的别名,layer_creator_func,
//用于开辟各个子类的空间
typedef Layer* (*layer_creator_func)();

struct layer_registry_entry {
const char* name; //layer_type
layer_creator_func creator; //函数指针,返回Layer*,
};

第二步,我们需要实现给每一个子类开辟空间的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "layer/input.h"
namespace ncnn{
Layer* Input_final_layer_creator()
{return new Input();}
}
#include "layer/convolution.h"
namespace ncnn{
Layer* Convolution_final_layer_creator()
{return new Convolution();}
}
#include "layer/relu.h"
namespace ncnn{
Layer* Relu_final_layer_creator()
{return new Relu();}
}
...
...

不过上面挨个需要定义每一个Layer子类的开辟空间的函数,较为繁琐,我们发现每一个Layer子类的开辟空间的函数都较为类似,唯一不同的是Layer名字的变化.所以我们可以定义一个宏来做这些重复的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define DEFINE_LAYER_CREATOR(name) \
ncnn::Layer* name##_final_layer_creator() { return new name; }
}

#include "layer/input.h"
namespace ncnn {
DEFINE_LAYER_CREATOR(Input)
}
#include "layer/convolution.h"
namespace ncnn {
DEFINE_LAYER_CREATOR(Convolution)
}
#include "layer/relu.h"
namespace ncnn {
DEFINE_LAYER_CREATOR(ReLU)
}

第三步,接下来,我们实现一个类似于"字典"的功能,该"字典"的目的是使得从proto读入的layer_type和每一个Layer子类的开辟空间的函数对应

由于我们上面定义了layer_registry_entry这样一个结构体,这个结构体本质上的目的就是使得layer_type的char*变量和给Layer子类的开辟空间的函数对应.于是,我们基于layer_registry_entry来构造一个数组,它包含了所有用到的对应关系:

1
2
3
4
5
6
7
8
9
10
static const layer_registry_entry layer_registry[] = {  
{"ReLU", ReLU_final_layer_creator},
{"Input", Input_final_layer_creator},
{"Pooling", Pooling_final_layer_creator},
{"Convolution", Convolution_final_layer_creator},
{"Split", Split_final_layer_creator},
{"Concat", Concat_final_layer_creator},
{"Dropout", Dropout_final_layer_creator},
{"Softmax", Softmax_final_layer_creator},
};

第四步,有了这样的一个数组,我们只需要当layer_type传进来的时候,找到对应的结构体就好了,于是我们先确定应该输出上述数组的哪个元素,即layer_type对应的layer_registry_entry的数组id

1
2
3
4
5
6
7
8
9
10
//确定一共有多少个layer                                        
static const int layer_registry_entry_count =
sizeof(layer_registry) / sizeof(layer_registry_entry);

int layer_to_index(const char* type) {
for (int i = 0; i < layer_registry_entry_count; i++) {
if (strcmp(type, layer_registry[i].name) == 0) return i;
}
return -1;
}

第五步,找到了数组id,我们就可以构建我们的函数了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//基于id在数组里面找constructor                                                    
Layer* create_layer(int index) {
if (index < 0 || index >= layer_registry_entry_count) return 0;

layer_creator_func layer_creator = 0;
{ layer_creator = layer_registry[index].creator; }

if (!layer_creator) return 0;

Layer* layer = layer_creator();
layer->typeindex = index;
return layer;
}

我们最后总结一些create_layer的流程:

  1. 构建返回Layer*的函数指针的别名:layer_creator_func
  2. 构建layer_type和layer_creator_func对应的结构体:layer_registry_entry
  3. 实现给每一个子类开辟空间的函数
  4. 基于layer_registry_entry来构造一个数组,它包含了所有layer_type和create函数用到的对应关系
  5. 基于layer_type找到该layer_type在layer_registry_entry中的id
  6. 基于上述id,找到layer_type对应开辟空间的函数.
  7. 开辟子类空间,返回子类指针.

代码示例

测试程序放到了这里

代码结构如下:

< NCNN-Lession-5 > create_layer的实现

https://zhengtq.github.io/2020/12/10/ncnn-lession-5/

Author

Billy

Posted on

2020-12-10

Updated on

2021-03-13

Licensed under

Comments