欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

剖析Caffe源码之Layer_factory

程序员文章站 2022-06-15 14:16:42
...

<剖析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

剖析Caffe源码之Layer_factory

 可以看到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()方法:

剖析Caffe源码之Layer_factory

 函数里面定义了静态变量g_registry_,由static关键字作用可知,其静态变量的存储区域是在静态变量区域,不会随着函数调用完毕后而消失,故记录Layer注册信息是存储在静态变量区中的g_registry_中,该函数仅会在第一次调用时会new申请一个CreatorRegistry空间,后面多次调用不再申请新的空间.因为g_registry_为静态变量.

AddCreator()

AddCreator()函数为LayerRegistry类中提供的注册layer接口,其代码如下:

剖析Caffe源码之Layer_factory

将其相对应的Layer Creator函数指针以及Layer type注册到g_registry_中.首先调用Registry()获取到g_registry_指针(仅在第一次申请新的内存,其他相当与获取到g_registry_指针)

 LayerRegisterer

LayerRegisterer主要提供了对外接口注册功能,到现在,基本知道了Layer注册内部实现,其实内部就是个map,记录其注册Layer信息,那么对外接口主要在LayerRegisterer中,对外提供的注册接口是一个宏:

REGISTER_LAYER_CLASS(type)

该宏主要提供了对外接口,在caffe中搜索该宏:

剖析Caffe源码之Layer_factory

可以看到在各个layer中都有使用到该宏,比如data layer中如下:

剖析Caffe源码之Layer_factory

其注册的type为protxt中使用到的Type,一般是使用Layer名字前的作为Type, 比如DataLayer中以Data作为type, ExpLayer以Exp作为Type,方便编译维护.那么现在知道了Layer是怎么注册的,主要是通过REGISTER_LAYER_CLASS()函数,到此完成了其入门,但是如果想继续深入下去,可以继续探讨REGISTER_LAYER_CLASS源码

 REGISTER_LAYER_CLASS源码

REGISTER_LAYER_CLASS源码如下:

剖析Caffe源码之Layer_factory

这段代码其实看起来比较晦涩,可以看到里面有很多#到底是做什么用的?

这里要插播下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宏定义如下:

剖析Caffe源码之Layer_factory

那么最终展开如下:

  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类的构造函数,如下:

剖析Caffe源码之Layer_factory

 最后是调用的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源码

剖析Caffe源码之Layer_factory

 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;
}

运行结果如下:

剖析Caffe源码之Layer_factory

 遗留问题,不知道为什么,在用例代码中没有Net<float> nn(proto, caffe::TEST);这句代码即不会有Layer注册,只有加载网络才有Layer注册

总结

现在终于明白了两个问题:Layer是如何注册的,以及Layer如何创建的整个.

尽管layer_factory.hpp文件代码比较简单,但是并不容易阅读,需要很大耐心.

 

相关标签: caffe Layer_factory