开始
在第三节我们实现了读取网络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和返回对应子类指针的函数(给子类开辟空间)的结构体:
//定义返回Layer*的函数指针的别名,layer_creator_func,
//用于开辟各个子类的空间
typedef Layer* (*layer_creator_func)();
struct layer_registry_entry {
const char* name; //layer_type
layer_creator_func creator; //函数指针,返回Layer*,
};
第二步,我们需要实现给每一个子类开辟空间的函数:
#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名字的变化.所以我们可以定义一个宏来做这些重复的操作:
#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来构造一个数组,它包含了所有用到的对应关系:
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:
//确定一共有多少个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,我们就可以构建我们的函数了:
//基于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的流程:
- 构建返回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对应开辟空间的函数.
- 开辟子类空间,返回子类指针.
代码示例
测试程序放到了这里.
代码结构如下: