caffe中的layer都是由layer_factory创建的,是典型的工厂模式。
我们看caffe的源码,就会发现,不管创建哪种layer,都可以用如下的函数来创建:
shared_ptr<Layer<Dtype> > layer = LayerRegistry<Dtype>::CreateLayer(layer_param);
这个函数就定义在layer_factory.hpp文件中,是 LayerRegistry<Dtype>
class的static函数.在这个函数中,它会根据传入的param中的type变量来创建对应的layer,非常方便。那么,它是怎么实现的呢?我们下面来详细讲解一下:
1.在layer_factory.hpp中定义了两个宏,如下:
#define REGISTER_LAYER_CREATOR(type, creator) \
static LayerRegisterer<float> g_creator_f_##type(#type, creator<float>); \
static LayerRegisterer<double> g_creator_d_##type(#type, creator<double>) \
#define REGISTER_LAYER_CLASS(type) \
template <typename Dtype> \
shared_ptr<Layer<Dtype> > Creator_##type##Layer(const LayerParameter& param) \
{ \
return shared_ptr<Layer<Dtype> >(new type##Layer<Dtype>(param)); \
} \
REGISTER_LAYER_CREATOR(type, Creator_##type##Layer)
在每个具体layer的cpp文件的最后,都调用了REGISTER_LAYER_CLASS宏,那么这个宏是干什么用的呢?我们看code会发现,REGISTER_LAYER_CLASS宏首先定义了一个函数,可以看出就是根据传入的type来生成一个creator_xxLayer的函数,在这个函数内部,new了一个该layer对象。然后就调用了REGISTER_LAYER_CREATOR宏。那REGISTER_LAYER_CREATOR这个宏又干了什么呢?我们可以看到,它定义了两个static 变量g_creator_f_xxtype。这两个变量的其实是一个模板类的两个实例,一个是float, 一个是double. 我们看一个就可以了,这个模板类为LayerRegisterer。 下面我们看一下这个类是干什么的,code如下:
template <typename Dtype>
class LayerRegisterer {
public:
LayerRegisterer(const string& type,
shared_ptr<Layer<Dtype> > (*creator)(const LayerParameter&)) {
// LOG(INFO) << "Registering layer type: " << type;
LayerRegistry<Dtype>::AddCreator(type, creator);
}
};
我们可以看到,这个类就定义了一个构造函数,所以上面宏定义里面创建该类的两个对象时,就自动调用了这个构造函数。我们可以发现,在该构造函数中,调用了另外一个类LayerRegistry<Dtype>
的一个static函数AddCreator(type, creator)
.到目前为止,我们知道的信息为在每个具体layer的实现cpp文件里面,都定义了一个creator_xxLayer函数,还定义了两个static变量。定义static变量的时候,自动调用了另外一个类的AddCreator
函数。现在,我们来看这个函数和这个函数所属的类。
2.这个类的名字为LayerRegistry, code如下:
emplate <typename Dtype>
class LayerRegistry {
public:
typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);
typedef std::map<string, Creator> CreatorRegistry;
static CreatorRegistry& Registry() {
static CreatorRegistry* g_registry_ = new CreatorRegistry();
LOG(INFO) << "-----------------------CreatorRegistry point is " << g_registry_;
LOG(INFO) << "g_registry size is " << g_registry_->size();
return *g_registry_;
}
// Adds a creator.
static void AddCreator(const string& type, Creator creator) {
CreatorRegistry& registry = Registry();
CHECK_EQ(registry.count(type), 0)
<< "Layer type " << type << " already registered.";
registry[type] = creator;
}
// Get a layer using a LayerParameter.
static shared_ptr<Layer<Dtype> > CreateLayer(const LayerParameter& param) {
if (Caffe::root_solver()) {
LOG(INFO) << "Creating layer " << param.name();
}
const string& type = param.type();
if (Caffe::root_solver()) {
LOG(INFO) << "layer type is " << type;
}
CreatorRegistry& registry = Registry();
for (auto &it : registry) {
LOG(INFO) << "--------------registry type is " << it.first;
}
CHECK_EQ(registry.count(type), 1) << "Unknown layer type: " << type
<< " (known types: " << LayerTypeListString() << ")";
return registry[type](param);
}
static vector<string> LayerTypeList() {
CreatorRegistry& registry = Registry();
vector<string> layer_types;
for (typename CreatorRegistry::iterator iter = registry.begin();
iter != registry.end(); ++iter) {
layer_types.push_back(iter->first);
}
return layer_types;
}
private:
// Layer registry should never be instantiated - everything is done with its
// static variables.
LayerRegistry() {}
static string LayerTypeListString() {
vector<string> layer_types = LayerTypeList();
string layer_types_str;
for (vector<string>::iterator iter = layer_types.begin();
iter != layer_types.end(); ++iter) {
if (iter != layer_types.begin()) {
layer_types_str += ", ";
}
layer_types_str += *iter;
}
return layer_types_str;
}
};
我们可以发现,该类的构造函数为private, 所以不能实例化对象,同时他的所有public函数都是static函数,所以可以在外部直接调用。现在我们发现,在上面调用的AddCreator函数中,他调用了Registry函数,在这个函数中,new出来了一个map.这里有一个关键点,就是static变量的初始化只执行一次,所以这里虽然每次都会调用static CreatorRegistry* g_registry_ = new CreatorRegistry();
,但只有第一次调用时真的new了一个map对象,后面的调用都没有再new对象出来,这是第一个关键点。分析上面的code,我们会发现,AddCreator函数其实就是将一个创建类的函数和对应的type注册到一个全局static map中,就是上面创建的g_registry_。这样当我们需要创建某个layer时,就可以根据它的type在这个map中找到对应的create layer函数,就可以创建对应的layer了。是不是很巧妙。
总结一下,主要有两个关键点:
1.就是static的初始化只执行一次,所以每个layer调用REGISTER_LAYER_CLASS宏时执行到static CreatorRegistry* g_registry_ = new CreatorRegistry()
时,只有第一次真正创建了对象,其他后续的执行都是直接使用这个static指针。
2. 就是各个layer的create函数是什么时候注册到这个map中的,这里就是利用了宏REGISTER_LAYER_CREATOR中那两个变量的定义。每个layer的这两个变量都是static变量,在程序运行之前就会首先被创建。正是在他们的创建构造函数里,将本layer的create函数注册到了这个map中,所以当你写的程序比如caffe开始运行时,所有声明了REGISTER_LAYER_CLASS的layer都已经自动注册到g_registry_这个map中了,直接使用就可以了,不用自己再注册(如果加log调试,我们会发现,这几个变量的创建在main函数运行之前)。这种方式虽然比较绕,但是对于caffe这种开源的软件,不同的开发者会开发不同的layer, 如果需要自己去注册的话,上层的app需要经常修改,不是很方便,这种方式下,只要开发者开发自己的layer的时候把这个宏声明一下,就自动注册到全局map中去了,非常方便。