此次学习内容为relu.layer,从blob到layer,以后的每一个层都采取这样串联的方式,希望可以系统的帮助到别人。
需要的一点caffe基础:
从caffe.proto看起,这个源码文件在 src/caffe/proto,编译后产生c++文件在build/src/caffe/proto目录下有两个文件,分别为caffe.pb.h和caffe.pb.cc ,这几个文件有什么关系呢?别急,志轩原话:“那个caffe.pb.cc是对应caffe消息经过protobuf编译后的c++代码”,见我懵逼,志轩老师举了个例子:比如你在caffe.proto写了一个消息,然后编译,在caffe.pb.cc中会找到对应消息的c++源码(好像是重复了上面的意思。)
从caffe.proto开始到caffe.pb.cc
用文本编辑器打开caffe.proto文件,映入眼帘的是如下代码:
message BlobShape {
repeated int64 dim = 1 [packed = true];
}
看着很简单嘛,三行代码,我们再看看经过protobuf编译后产生的caffe.pb.h中有多少c++代码,打开c++.pb.h,对应638到728行之间的c++代码,protobuf简直就是神器呀,对我等代码弱鸡,可谓是福利。
好了,继续返回caffe.proto, 找到如下代码:
```
message BlobProto {
optional BlobShape shape = 7;
repeated float data = 5 [packed = true];
repeated float diff = 6 [packed = true];
repeated double double_data = 8 [packed = true];
repeated double double_diff = 9 [packed = true];
// 4D dimensions -- deprecated. Use "shape" instead.
optional int32 num = 1 [default = 0];
optional int32 channels = 2 [default = 0];
optional int32 height = 3 [default = 0];
optional int32 width = 4 [default = 0];
}
上述代码做的事情是规定了blob的维度,具体看:data是数据,diff是梯度,分别分float和double,下面是规定blob的维度,num*channels*height*width,规定了一个blob的大小,先不管repeated,opthonal是个啥意思,暂时当repeated对应在c++代码里是STL的vector,optional对应变量。
开始今天的第一个层:Relu层。
第一步:先看relu_layer.hpp,位于caffe根目录下的include/src/caffe/layers/relu_layer.hpp目录下,打开发现如下代码,
`template
class ReLULayer : public NeuronLayer {
这个c++代码说明了ReLU层继承于类NeuronLayer层,实际上NeuronLayer层继承于Layer(这是志轩老师给我说的,我暂时强迫自己接受),继续往下看,发现ReLU讲并不是很复杂的实现,讲了两个东西:一个forward传播,一个backward传播,并分了两种实现形式:CPU和GPU。具体代码此处不贴了。看完relu.pb.h(主要讲了relu实现了什么,具体怎么实现继续看),我们看一下relu.pb.cc,了解一下relu.pb.h里说的一些功能在relu.pb.cc里都是怎么实现的。打开relu.pb.cc,这里代码不多,我就直接全贴上来。
include
#include
#include "caffe/layers/relu_layer.hpp"
namespace caffe {
template <typename Dtype>
void ReLULayer::Forward_cpu(const vector *>& bottom, //定义bottom,top(都是blob)
const vector *>& top) {
const Dtype* bottom_data = bottom[0]->cpu_data();//底部数据是不可变的,因为可能底部的数据要用于别的层。
Dtype* top_data = top[0]->mutable_cpu_data();
const int count = bottom[0]->count(); //count=num*channels*h*w,即所有像素。
Dtype negative_slope = this->layer_param_.relu_param().negative_slope();
for (int i = 0; i < count; ++i) {
top_data[i] = std::max(bottom_data[i], Dtype(0))
+ negative_slope * std::min(bottom_data[i], Dtype(0));
}
}
template <typename Dtype>
void ReLULayer::Backward_cpu(const vector *>& top,
const vector<bool>& propagate_down,
const vector *>& bottom) {
if (propagate_down[0]) {
const Dtype* bottom_data = bottom[0]->cpu_data();
const Dtype* top_diff = top[0]->cpu_diff();
Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();
const int count = bottom[0]->count();
Dtype negative_slope = this->layer_param_.relu_param().negative_slope();
for (int i = 0; i < count; ++i) {
bottom_diff[i] = top_diff[i] * ((bottom_data[i] > 0)
+ negative_slope * (bottom_data[i] <= 0));
}
}
}
#ifdef CPU_ONLY
STUB_GPU(ReLULayer);
#endif
INSTANTIATE_CLASS(ReLULayer);
} // namespace caffe
注意看哦,首先include “caffe/layers/relu_layer.hpp”,类似函数声明,上面我注释了一下,感觉还算清楚,但具体语法别问我,我是c++菜比,forward里,遍历所有像素,实现的运算是:top=max(x,0)+negative_slope*min(x,0),这里negative_slope默认为0,所以relu的forward传播实现的实际上是max(x,0),再看backward传播,注意,同样的东西,只不过实现的函数是上述top表达式对x的导数,为啥,为啥?自己悟。好啦,到现在为止一个relu_layer的实现已经看完了,forward和backward。
最后,说一下写此博文的目的,第一:记录自己的caffe学习历程。第二:感谢志轩同学的慷慨帮助,有啥不懂的问他他也会给你解答。第三,自己学习caffe的过程才刚开始,之前都是在caffe的框架外围兜圈,从没想过看源码,借这个机会,记录下,希望帮助到可以帮助的人,会不定期更新,想写一套完整的教程,希望和志轩老师的约定能实现吧。水平有限,有需要改正的地方评论一下,或者有什么问题可以留言讨论。
“`