我们来分析一下Caffe中的源程序,来探究一下深度学习的奥秘。作为一个菜鸡程序员,我表示压力有点大!
/**
* @brief A layer factory that allows one to register layers.
* During runtime, registered layers can be called by passing a LayerParameter
* protobuffer to the CreateLayer function:
*
* LayerRegistry<Dtype>::CreateLayer(param);
*
* There are two ways to register a layer. Assuming that we have a layer like:
*
* template <typename Dtype>
* class MyAwesomeLayer : public Layer<Dtype> {
* // your implementations
* };
*
* and its type is its C++ class name, but without the "Layer" at the end
* ("MyAwesomeLayer" -> "MyAwesome").
*
* If the layer is going to be created simply by its constructor, in your c++
* file, add the following line:
*
* REGISTER_LAYER_CLASS(MyAwesome);
*
* Or, if the layer is going to be created by another creator function, in the
* format of:
*
* template <typename Dtype>
* Layer<Dtype*> GetMyAwesomeLayer(const LayerParameter& param) {
* // your implementation
* }
*
* (for example, when your layer has multiple backends, see GetConvolutionLayer
* for a use case), then you can register the creator function instead, like
*
* REGISTER_LAYER_CREATOR(MyAwesome, GetMyAwesomeLayer)
*
* Note that each layer type should only be registered once.
*/
#ifndef CAFFE_LAYER_FACTORY_H_
#define CAFFE_LAYER_FACTORY_H_
#include <map>
#include <string>
#include <vector>
#include "caffe/common.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
namespace caffe {
template <typename Dtype>
class Layer;
我们会发现,LayerRegistry
类中所有的成员都是静态的,这意味着不需要实例化即可使用该类的成员函数,其实在这个类的设计过程中作者也禁止了对其进行实例化的行为,在private
成员定义了默认的初始化函数即可禁止实例化。
template <typename Dtype>
class LayerRegistry {
public:
typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);
typedef std::map<string, Creator> CreatorRegistry;
首先定义一个函数指针类型Creator
和一个复合类型;该函数指针指向这样一个函数,它的输入值是一个LayerParameter
的引用,其中LayerParameter
是在caffe.proto
中定义的数据格式;
static CreatorRegistry& Registry() {
static CreatorRegistry* g_registry_ = new CreatorRegistry();
return *g_registry_;
}
这一个函数非常关键,里面包含了一个静态变量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;
}
这个AddCreator
函数其实就是在CreatorRegistry
这个map
中加入<string,Creator>
对,比如我们可以加入convlayer
对,<"Convolution", ConvolutionCreator>
这样的参数对;
// 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();
CreatorRegistry& registry = Registry();
CHECK_EQ(registry.count(type), 1) << "Unknown layer type: " << type
<< " (known types: " << LayerTypeListString() << ")";
return registry[type](param);
}
这个函数呢首先判断Caffe::root_solver()
的状态,这个状态是在common.h
头文件中定义了,具体指的什么我们下次再聊,,其实我现在也不知道~~o(╯□╰)o。那么我们首先导出每个LayerParameter
中的type
参数,如这个参数可能是conv
,relu
,innerproduct
等等,当然上述名字可能不精确。那接下里我们就是判断registry
这个map
中是否包含了这个type
的Creator
,如果有那就用这个Creator
来输出一个智能指针shared_ptr<Layer<Dtype> >
,这个shared_ptr
是在boost
中定义的,现在似乎C++11
也支持了,朋友们自己去研究一下吧,很有用的。
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;
}
这个函数就非常明了了,将registry
这个map
中所有的type
全部push_back到一个vector
中,然后输出这个vector
咯,,,
private:
// Layer registry should never be instantiated - everything is done with its
// static variables.
LayerRegistry() {}
在私有部分中声明了不带参数的构造函数意味着LayerRegistry LRtmp;
这种用法是非法的,因为在编译器看来这句话意味着要调用构造函数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;
}
};
这个函数很明确,就是返回一个string
对象,这个对象包含了所有的有Creator
的type
,可以学习的是对于这个问题的处理方法,基本上有以下几个点:
- 这个函数定义在私有部分,其实就是作为一个辅助函数,你可以看到,它只在
CreatLayer
这个函数中用到了,用来输出报错信息的; - 可以学习其对于
STL
的使用,关于迭代器,容器的知识可以参考C++ primer plus
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
类服务的,因为LayerRegistry
不能实例化,这样添加新的Creator
就比较方便了,在具体使用创建层的时候还是要使用LayerRegistry
类中的CreateLayer
函数的。
#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>) \
这种宏定义的写法非常值得学习,注意到宏定义中使用了##
,这个东西呢表示的是链接,是一个链接符;还有#
,这个玩意儿是表示转换成字符串的意思。给大家举个例子,如果在语句中这么写REGISTER_LAYER_CREATOR(testLayer, testCreator);
那么它其实表示的是static LayerRegisterer<float> g_creator_f_testLayer(testLayer, testCreator<float>); static LayerRegisterer<double> g_creator_d_testLayer(testLayer, testCreator<double>)
,所以大家可以看到写一个合适的#define
是非常强大的;同时我们注意到这里面使用了静态变量,其实是想让在cpp
文件中进行注册时是在编译的时候就注册了,并且只注册一次,哈哈~~完美
#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)
} // namespace caffe
#endif // CAFFE_LAYER_FACTORY_H_
其实这个两个#define
都可以用来注册层,只是一般是用这个了,如果你自己定义了一个creator
你可以用上面那个#define
来定义;其实你一般只需要定义一个这样的函数即可:Layer<Dtype> * typeLayer<Dtype>(param)
,这个一般是一个构造函数哈哈。。这个宏呢会在你#define
的时候自动调用这个名字的函数;
大家可以看到为什么要定义这么一堆东西呢,什么注册啊之类的,搞得这么复杂,这是为什么呢?试想一下,如果你要定义各种各样的层,如果没有这些东西你要怎么做?比如要定义一个ConvLayer
那你肯定是调用它的构造函数对吧,比如是这么调用的shared_ptr<Layer<float> > ptr = new ConvLayer( param );
对吧,差不多是这么调用的吧,如果你又要定义一个DataLayer
,你可能这么写shared_ptr<Layer<float> > ptr = new DataLayer( param );
你会发现你每次定义一个不同的层都要输入一个不同的函数名,或者是类构造函数名,这样是不是很傻鸡_,,那么这里的想法是通过注册的形式将所有的Creator
进行注册,这样子可以使用统一的接口来进行声明,感觉就非常明了了,,朋友,再细细体会体会是不是这样。。