开始
在第三节我们实现了读取网络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
|
typedef Layer* (*layer_creator_func)();
struct layer_registry_entry { const char* name; layer_creator_func creator; };
|
第二步,我们需要实现给每一个子类开辟空间的函数:
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
| 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
| 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的流程:
- 构建返回Layer*的函数指针的别名:layer_creator_func
- 构建layer_type和layer_creator_func对应的结构体:layer_registry_entry
- 实现给每一个子类开辟空间的函数
- 基于layer_registry_entry来构造一个数组,它包含了所有layer_type和create函数用到的对应关系
- 基于layer_type找到该layer_type在layer_registry_entry中的id
- 基于上述id,找到layer_type对应开辟空间的函数.
- 开辟子类空间,返回子类指针.
代码示例
测试程序放到了这里.
代码结构如下: