Caffe源码学习系列二----卷积层
来源:互联网 发布:淘宝上怎么秒杀东西 编辑:程序博客网 时间:2024/05/16 14:34
本文转自:http://zhangliliang.com/2015/02/11/about-caffe-code-convolutional-layer/
感谢博主!
一开始笔者先看了卷积层的梯度传导公式,参考了这两篇:
- 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”实际上是应该还要旋转回来才是正确的卷积核。
- Caffe源码学习系列二----卷积层
- caffe源码学习(3)--卷积层
- caffe源码 卷积层
- CAFFE源码学习笔记之十一-卷积层conv_layer
- Caffe源码阅读(2) 卷积层
- Caffe源码阅读(2) 卷积层
- caffe源码 之 卷积层实现
- caffe源码深入学习5:超级详细的caffe卷积层代码解析
- 【深度学习】caffe之卷积层
- Caffe学习系列(二):数据层及参数
- caffe学习系列二视觉层及参数
- caffe 学习系列 视觉层
- caffe学习系列--层解读
- caffe反卷积层
- Caffe框架源码剖析(5)—卷积层ConvolutionLayer
- Caffe框架源码剖析(5)—卷积层ConvolutionLayer
- 【深度学习】Torch卷积层源码详解
- caffe 卷积层的理解
- 大型网站的 HTTPS 实践(四)——协议层以外的实践
- 使用 .Net Memory Profiler 诊断 .NET 应用内存泄漏(方法与实践)
- iOS 将图片等比例缩放
- Nepxion分布式RPC框架 - 管理中心
- js+HTML5实现视频截图的方法
- Caffe源码学习系列二----卷积层
- pcall()处理异常
- Struts2:Struts标签
- iOS开发--常见坑(横竖屏问题)
- 【空间释放】解决Linux下rm操作不释放空间的问题
- PS:mproving Object Detection With Deep Convolutional Networks via Bayesian Optimization..___CVPR2015
- [DB2]错误"Requesting too many semaphores" 解决办法
- UITableView优化技巧
- qemu核心机制分析-协程coroutine