Caffe源码阅读(2) 卷积层
来源:互联网 发布:运用python成为黑客 编辑:程序博客网 时间:2024/05/24 02:37
一开始笔者先看了卷积层的梯度传导公式,参考了这两篇:
- http://ufldl.stanford.edu/tutorial/supervised/ConvolutionalNeuralNetwork/
- http://cogprints.org/5869/1/cnn_tutorial.pdf
卷积层的参数的梯度可以这样来求:
看上去比全连接层复杂多了,但其实,他们本质上基本是一样的,依然可以套回全连接层的参数求导公式:
只需要额外增加一步
im2col
。这一步的意思是将首先将整张图片按照卷积的窗口大小切好(按照stride来切,可以有重叠),然后各自拉成一列。为啥要怎样做,因为对于这个小窗口内拉成一列的神经元来说来说,它们跟下一层神经元就是全连接了,所以这个小窗口里面的梯度计算就可以按照全连接来计算就可以了。
如果对照着Caffe的卷积层源码来看,就很清晰了。
forward的代码如下,假设没有分group,这段代码的意思是对于一个大小为num_的batch里面的任意一张图片,首先通过im2col
展开成多个列向量,之后直接就用wx+b的方式就能够算到输出了。
1234567891011121314151617181920212223242526272829303132
void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>* top) { for (int i = 0; i < bottom.size(); ++i) { const Dtype* bottom_data = bottom[i]->cpu_data(); Dtype* top_data = (*top)[i]->mutable_cpu_data(); Dtype* col_data = col_buffer_.mutable_cpu_data(); const Dtype* weight = this->blobs_[0]->cpu_data(); int weight_offset = M_ * K_; // number of filter parameters in a group int col_offset = K_ * N_; // number of values in an input region / column int top_offset = M_ * N_; // number of values in an output region / column for (int n = 0; n < num_; ++n) { // im2col transformation: unroll input regions for filtering // into column matrix for multplication. im2col_cpu(bottom_data + bottom[i]->offset(n), channels_, height_, width_, kernel_h_, kernel_w_, pad_h_, pad_w_, stride_h_, stride_w_, col_data); // Take inner products for groups. for (int g = 0; g < group_; ++g) { caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, K_, (Dtype)1., weight + weight_offset * g, col_data + col_offset * g, (Dtype)0., top_data + (*top)[i]->offset(n) + top_offset * g); } // Add bias. if (bias_term_) { caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_, N_, 1, (Dtype)1., this->blobs_[1]->cpu_data(), bias_multiplier_.cpu_data(), (Dtype)1., top_data + (*top)[i]->offset(n)); } } }}
所以卷积看成是多个局部的全连接。笔者记得在某个地方看到过,实现卷积的方式有两个,一个是像caffe里面的im2col,另外一个是傅里叶变换。而后者的速度比较快,那么看来facebook给出的加速代码应该是用了傅里叶变换咯:)
言归正传,如果能够理解到im2col
的作用,那么backward的代码也很容易理解了。
对于bias,直接就是delta(可能还要乘以bias_multiplier_
,这个是Caffe自己的功能,默认不开启,即bias_multiplier_=1
)
12345678910
// Bias gradient, if necessary.if (bias_term_ && this->param_propagate_down_[1]) { top_diff = top[i]->cpu_diff(); for (int n = 0; n < num_; ++n) {caffe_cpu_gemv<Dtype>(CblasNoTrans, num_output_, N_,1., top_diff + top[0]->offset(n),bias_multiplier_.cpu_data(), 1.,bias_diff); }}
对于weights,batch中的每张图片,首先还是用im2col
展开,之后用矩阵乘法表示累加:
1234567891011121314
// Since we saved memory in the forward pass by not storing all col// data, we will need to recompute them.im2col_cpu(bottom_data + (*bottom)[i]->offset(n), channels_, height_, width_, kernel_h_, kernel_w_, pad_h_, pad_w_, stride_h_, stride_w_, col_data);// gradient w.r.t. weight. Note that we will accumulate diffs.if (this->param_propagate_down_[0]) { for (int g = 0; g < group_; ++g) {caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, K_, N_,(Dtype)1., top_diff + top[i]->offset(n) + top_offset * g,col_data + col_offset * g, (Dtype)1.,weight_diff + weight_offset * g); }}
最后是计算需要传回前一层的delta,也是先看成多个独立的全连接,但是最后需要还原成带有空间结构的形状,需要调用逆过程col2im
,其实也是一个累加的过程,让每个空间位置的delta累加起来。
12345678910111213141516
// gradient w.r.t. bottom data, if necessary.if (propagate_down[i]) { if (weight == NULL) {weight = this->blobs_[0]->cpu_data(); } for (int g = 0; g < group_; ++g) {caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, K_, N_, M_,(Dtype)1., weight + weight_offset * g,top_diff + top[i]->offset(n) + top_offset * g,(Dtype)0., col_diff + col_offset * g); } // col2im back to the data col2im_cpu(col_diff, channels_, height_, width_, kernel_h_, kernel_w_, pad_h_, pad_w_, stride_h_, stride_w_, bottom_diff + (*bottom)[i]->offset(n));}
最后附带一提,在“关于卷积”那篇也提到了卷积运算是先要将kernel旋转180度之后再扫过去的。可以看出Caffe源码是没有这一步的,所以最后学出来的“kernel”实际上是应该还要旋转回来才是正确的卷积核。
0 0
- Caffe源码阅读(2) 卷积层
- Caffe源码阅读(2) 卷积层
- caffe源码 卷积层
- caffe卷积层代码阅读笔记
- caffe卷积层代码阅读笔记
- Caffe源码阅读(3)Softmax层和SoftmaxLoss层
- Caffe源码学习系列二----卷积层
- caffe源码 之 卷积层实现
- caffe源码学习(3)--卷积层
- caffe源码阅读之layer(2)——DataLayer层
- caffe源码阅读之layer(2)——Euclidean_loss_layer层
- caffe源码阅读之layer(2)——DataLayer层(2)
- Caffe源码阅读(1) 全连接层
- Caffe源码阅读(1) 全连接层
- caffe之(一)卷积层
- caffe反卷积层
- Caffe框架源码剖析(5)—卷积层ConvolutionLayer
- CAFFE源码学习笔记之十一-卷积层conv_layer
- Java中PreparedStatement和Statement的用法区别
- Java序列化与ProtocalBuffer序列化之深入分析(转)
- HDU 5671 Matrix——BestCoder Round #81(div.1 div.2)
- 单例模式中的线程安全问题
- 在源代码控制、两人合作中遇到的实际问题给出建议或者答疑
- Caffe源码阅读(2) 卷积层
- 自定义View-5-拖动选择按钮
- 浅谈dp 动态规划(1)
- 264. Ugly Number II
- android:向res/drawable里面添加图片+制作.9.png
- Android网络通信
- Caffe源码阅读(1) 全连接层
- HDU3746 Cyclic Nacklace
- 在Oracle中索引的使用