剖析Caffe源码之Layer_factory
在<剖析Caffe源码之Layer>,对Layer代码进行了基本分析,可以知道Layer是所有的其他Layer的基本类,由此来扩展出各个神经网络中所需要的Layer,体现了caffe的可扩展性.那么问题来了,既然caffe中定义了各种Layer,那边在实际运行中,caffe是如何知道执行到做需要的Layer type的Layer?,下面将这层洋葱给剥掉.
在这里将不得不提到caffe中的LayerRegistry类,该类的主要功能就是将caffe中已有的各种类型Layer注册到caffe中,caffe以便知道执行的是那个Layer
Layer_factory
Layer_factory主要是提供向caffe中提供Layer注册功能,以便caffe知道运行时能够找到相关的Layer.,其主要代码位于caffe\include\caffe\layer_factory.hpp和caffe\scr\caffe\layer_factory.cpp文件中.主要功能是在layer_factory.hpp中
LayerRegistry
在\layer_factory.hpp文件中可以看到LayerRegistry类,该类主要是提供Layer功能,在public中CreatorRegistry类型
CreatorRegistry
可以看到CreatorRegistry为一个map类型,其里面的key-value对应的值分别为string, Creator, 其中string为Layer的type,而Creator可以看到是一个函数指针,其入参为LayerParameter,其意义为相对应用的Layer creator函数用于产生相对应的Layer,该设计模式为C++的工厂模式,是caffe中用到的比较关键的模式.
由上述定义可以大概猜到CreatorRegistry是用于记录Layer的注册的数据结构,key-value对应的Layer type和其对应的Layer Creator函数.继续深入挖掘,既然CreatorRegistry是注册的数据结构,那边其对应的具体数据在哪里?一定会有一个类似的全局变量采用该结构,用于记录实际注册的Layer状况.
Registry()
继续深入layer_factory.hpp代码,看到LayerRegistry类中,定义的Regis 遗留问题,不知道为什么,在用例代码中没有Net<float> nn(proto, caffe::TEST);这句代码即不会有Layer注册,只有加载网络才有Layer注册try()方法:
函数里面定义了静态变量g_registry_,由static关键字作用可知,其静态变量的存储区域是在静态变量区域,不会随着函数调用完毕后而消失,故记录Layer注册信息是存储在静态变量区中的g_registry_中,该函数仅会在第一次调用时会new申请一个CreatorRegistry空间,后面多次调用不再申请新的空间.因为g_registry_为静态变量.
AddCreator()
AddCreator()函数为LayerRegistry类中提供的注册layer接口,其代码如下:
将其相对应的Layer Creator函数指针以及Layer type注册到g_registry_中.首先调用Registry()获取到g_registry_指针(仅在第一次申请新的内存,其他相当与获取到g_registry_指针)
LayerRegisterer
LayerRegisterer主要提供了对外接口注册功能,到现在,基本知道了Layer注册内部实现,其实内部就是个map,记录其注册Layer信息,那么对外接口主要在LayerRegisterer中,对外提供的注册接口是一个宏:
REGISTER_LAYER_CLASS(type)
该宏主要提供了对外接口,在caffe中搜索该宏:
可以看到在各个layer中都有使用到该宏,比如data layer中如下:
其注册的type为protxt中使用到的Type,一般是使用Layer名字前的作为Type, 比如DataLayer中以Data作为type, ExpLayer以Exp作为Type,方便编译维护.那么现在知道了Layer是怎么注册的,主要是通过REGISTER_LAYER_CLASS()函数,到此完成了其入门,但是如果想继续深入下去,可以继续探讨REGISTER_LAYER_CLASS源码
REGISTER_LAYER_CLASS源码
REGISTER_LAYER_CLASS源码如下:
这段代码其实看起来比较晦涩,可以看到里面有很多#到底是做什么用的?
这里要插播下C++里面的一个基础知识,
#
的主要作用是将宏参数不经扩展地转换成字符串常量,比如一下代码:#define NAME(name) #name
定义如上宏,在实际定义宏参数name,其实并不知道其name的真正类型,如果要将name变量的值作为字符传,那边可以在其前面加上#,如何name为Sam
NAME(Sam);
最后结果为字符串Sam
##作用其实就是作为链接符号,将其值作为其一部分,来链接起来,例子如下:
#define NAME(name) name_is_##name
如何name为Sam, 那么其实结果相当与name_is_Sam.
那么可以尝试对其REGISTER_LAYER_CLASS(Data)进行展开为如下代码:
template <typename Dtype>
shared_ptr<Layer<Dtype> > Creator_DataLayer(const LayerParameter& param)
{
return shared_ptr<Layer<Dtype> >(new typeData<Dtype>(param));
}
REGISTER_LAYER_CREATOR(type, Creator_DataLayer)
REGISTER_LAYER_CREATOR宏定义如下:
那么最终展开如下:
template <typename Dtype>
shared_ptr<Layer<Dtype> > Creator_DataLayer(const LayerParameter& param)
{
return shared_ptr<Layer<Dtype> >(new DataLayer<Dtype>(param));
}
static LayerRegisterer<float> g_creator_f_Data(Data, Creator_DataLayer);
static LayerRegisterer<double> g_creator_d_Data(Data, Creator_DataLayer);
在DataLayer中其最终展开的结果如上,相当于创建了两个静态LayerRegisterer全局变量类的g_creator_f_Data和g_creator_d_Data,类型分别为float和double.相当与将Creator_DataLayer函数指针注册上去.
继续查看LayerRegisterer类的构造函数,如下:
最后是调用的LayerRegistry类中的AddCreator,进行注册.
那么问题来了既然REGISTER_LAYER_CLASS()能够实现Layer注册功能,何时才能能够真正调用注册.还要继续补充static关键字作用,static关键子修饰变量是存储在静态存储区中,不依赖于类的具体实例,它是在main函数运行之前就已经开始申请内存空间,那么既然注册最终是通过g_creator_f_Data和g_creator_d_Data,两个静态变量实现的,所以在main函数运行之前就会新建LayerRegisterer空间,进而调用到LayerRegisterer的构造函数.
那么就可以得出使用REGISTER_LAYER_CLASS宏注册的类,其在main函数之前就实现了注册Layer功能,这就是LayerRegisterer设计精妙之处.
另外一个问题Layer是如何产生的?
首先看一下CreateLayer源码
CreateLayer()函数如上图,其最终调用的是注册的Creator()函数指针,而Creator注册的是各个Layer注册函数,以DataLayer为例子,最终展开代码:
template <typename Dtype>
shared_ptr<Layer<Dtype> > Creator_DataLayer(const LayerParameter& param)
{
return shared_ptr<Layer<Dtype> >(new DataLayer<Dtype>(param));
}
相当与new 创建了一个DataLayer,创建过程中将会调用DataLayer的构造函数,在Layer中已经讲解了其构造函数,可以查看剖析Caffe源码之Layer
用例
用例遍历访问所有注册的Layer,代码如下:
#include <vector>
#include <iostream>
#include <caffe/net.hpp>
using namespace std;
using namespace caffe;
int main(void)
{
std::string proto("lenet_train_test.prototxt");
//cout<<"Test 11111111111"<<endl;
//Net<float> nn(proto, caffe::TEST);
LayerRegistry<double>::CreatorRegistry & registry = LayerRegistry<double>::Registry();
cout<<registry.size()<<endl;
for(LayerRegistry<double>::CreatorRegistry::iterator iter=registry.begin();iter != registry.end();++iter)
{
cout<<iter->first<<endl;
}
Net<float> nn(proto, caffe::TEST);
return 0;
}
运行结果如下:
遗留问题,不知道为什么,在用例代码中没有Net<float> nn(proto, caffe::TEST);这句代码即不会有Layer注册,只有加载网络才有Layer注册
总结
现在终于明白了两个问题:Layer是如何注册的,以及Layer如何创建的整个.
尽管layer_factory.hpp文件代码比较简单,但是并不容易阅读,需要很大耐心.
上一篇: 设计模式--工厂方法模式(Factory Method)
下一篇: JSP中的全文检索