一、前言
caffe的几大基本模块中之一——Blob已经梳理完毕,现在该轮到Layer了。这一章先梳理layer基类和工厂类,为data_layer做一下铺垫。
Layer定义了layer的初始化,前向传播,后向传播。前向传播可以计算loss,后向传播计算梯度信息。
layer类是caffe中最为庞大的一类,所以作者使用工厂模式,这样当我们使用caffe中已经有的层或者自己定义的层时只需要注册就行了。
在运行期间,可以通过将LayerParameter protobuf参数传入函数就可以调用该层:
LayerRegistry<Dtype>::CreateLayer(param);
注册层的方式有两种:
其一、如果新建层是由其自身的构造函数创建的,注册如下:
REGISTER_LAYER_CLASS(layername);
其二、如果新建层由其他ctreator函数创建,形如:
template <typename Dtype>
Layer<Dtype*> GetlayernameLayer(const LayerParameter& param) {
// your implementation
}
那么可以通过如下方式注册:
REGISTER_LAYER_CREATOR(layername, GetlayernameLayer)
注意:每层只能注册一次。
二、源码分析
1、layer类
(1)成员变量
/** protobuf 中存储的层参数*/
LayerParameter layer_param_;
/** 状态: TRAIN or TEST */
Phase phase_;
/** 学习参数 */
vector<shared_ptr<Blob<Dtype> > > blobs_;
/** 是否进行后向传播的标志,数据层等都不进行后向传播 */
vector<bool> param_propagate_down_;
/** loss权重,一般只有最后一层权重不为0,其余层都不计算loss */
vector<Dtype> loss_;
(2)层的构造
构造函数完成层参数的传入,包括该层用于训练还是测试,层中存储权值的blob_。
注意构造函数不能是虚函数。
explicit Layer(const LayerParameter& param)
: layer_param_(param) {
phase_ = param.phase();
if (layer_param_.blobs_size() > 0) {
blobs_.resize(layer_param_.blobs_size());//通常blob_[0]是w,blob_[1]是b
for (int i = 0; i < layer_param_.blobs_size(); ++i) {
blobs_[i].reset(new Blob<Dtype>());
blobs_[i]->FromProto(layer_param_.blobs(i));
}
}
}
SetUp函数是自定义层的“构造函数”,主要做:
第一、检查输入和输出的数量;
第二、真正的setup layer;
第三、根据输入和内部结构重构输出的维度;
第四、设置loss权重;
void SetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
CheckBlobCounts(bottom, top);
LayerSetUp(bottom, top);
Reshape(bottom, top);
SetLossWeights(top);
}
下面是每个函数的详解:
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {}//bottom是输入数据,也就是上一层的输出;
//top是输出,但是这个输出没有经过reshape;
//该函数主要完成的是读入层参数完成层的搭建。
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) = 0;
//根据输入数据的维度修改输出和中间buffer的维度信息
检查输入输出的个数,有些层需要多个输入或者输出
virtual void CheckBlobCounts(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top)
设置每层的loss权重
inline void SetLossWeights(const vector<Blob<Dtype>*>& top) {
const int num_loss_weights = layer_param_.loss_weight_size();
if (num_loss_weights) {
CHECK_EQ(top.size(), num_loss_weights) << "loss_weight must be "
"unspecified or specified once per top blob.";
for (int top_id = 0; top_id < top.size(); ++top_id) {
const Dtype loss_weight = layer_param_.loss_weight(top_id);
if (loss_weight == Dtype(0)) { continue; }//如果权重为0,直接跳过
this->set_loss(top_id, loss_weight);
const int count = top[top_id]->count();
Dtype* loss_multiplier = top[top_id]->mutable_cpu_diff();
caffe_set(count, loss_weight, loss_multiplier);//将权重信息存在diff_blob中。
}
}
}
(3)前向传播
在前向传播之前需要保证reshape函数已经将top修正到合适的维度。
前向传播可以计算loss,具体的前向计算在具体的层中不一样,所以后面单独讲。
(4)后向传播
后向传播不是每次一层都需要的。
inline bool param_propagate_down(const int param_id) {
return (param_propagate_down_.size() > param_id) ?
param_propagate_down_[param_id] : false;
}
/**
* @brief Sets whether the layer should compute gradients w.r.t. a
* parameter at a particular index given by param_id.
*/
inline void set_param_propagate_down(const int param_id, const bool value) {
if (param_propagate_down_.size() <= param_id) {
param_propagate_down_.resize(param_id + 1, true);
}
param_propagate_down_[param_id] = value;
}
二、layer_factory
1、创建层注册表:
typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);
typedef std::map<string, Creator> CreatorRegistry;
static CreatorRegistry& Registry() {
static CreatorRegistry* g_registry_ = new CreatorRegistry();
return *g_registry_;
}
2、注册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;
}
3、根据参数调用层:
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);//regisry[type]是函数指针
该类不能被实例化,怎么做?将构造函数置于private域中。
在c++11中,可以直接跟“=delete”
private:
LayerRegistry() {}
最后,作者将注册写成了宏:
#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)
} // namespace caffe