作者:xg123321123
出处:http://blog.csdn.net/xg123321123/article/details/53319080
声明:版权所有,转载请联系作者并注明出处
将尺寸为K×K的卷积核在某个位置对应的feature map区域表示为K×K的一维向量;
将feature map各个通道对应的向量之间,串联起来;
那么尺寸K×K的卷积核在某个位置对应的各个通道的feature map,组合起来就是长度为C×K×K的一维向量。
当卷积核对应到新的位置上,又得到新的一维向量。
那么卷积核对应整张图片的所有位置,就得到一个(H×W)×(C×K×K)的矩阵(假设卷积核每次移动的步数为1)。
同样地,将卷积核也表示为一维向量;
用于输出一个特征图的卷积核对应一个C×K×K的一维向量(因为用于输出同一个特征图的卷积核是共享权值的,所以在整张图的各个位置进行卷积的都是同一个卷积核);
假设要输出 Cout 个特征图,就需要 Cout 个卷积核,那么所有卷积核对应一个 Cout ×C×K×K的矩阵。
最后,特征图对应矩阵乘以卷积核对应矩阵的转置,得到输出矩阵 Cout ×(H×W),这就是输出的三维Blob( Cout ×H×W)。
用下图举个例子,请对号入座。
之所以要将卷积运算用im2col操作实现,是因为优化CNN中的卷积不是一件简单的事。
由于时间、成本上的种种原因,caffe作者作用了这样一种lazy but temporary的方案。
这种方案取得的效果还是比较好的。
详情见caffe作者吐槽Caffe卷积算法的链接:Convolution in Caffe: a memo · Yangqing/caffe Wiki · GitHub
ConvolutionLayer 是 BaseConvolutionLayer的子类,BaseConvolutionLayer 是 Layer 的子类。
ConvolutionLayer 除了继承了相应的成员变量和函数以外,自己的成员函数主要有:compute_output_shape,Forward_cpu 和 Backward_cpu。
compute_output_shape
template <typename Dtype>
void ConvolutionLayer::compute_output_shape() {
this->height_out_ = (this->height_ + 2 * this->pad_h_ - this->kernel_h_)
/ this->stride_h_ + 1; //输出feature map 的 height
this->width_out_ = (this->width_ + 2 * this->pad_w_ - this->kernel_w_)
/ this->stride_w_ + 1; //输出 feature map 的 width
}
Forward_cpu
template <typename Dtype>
void ConvolutionLayer::Forward_cpu(const vector *>& bottom,
const vector *>& top) {
const Dtype* weight = this->blobs_[0]->cpu_data();
//blobs_ 用来存储可学习的参数
//其中blobs_[0]是weight,blobs_[1]是bias
for (int i = 0; i < bottom.size(); ++i) {
//这里的i为输入bottom的个数,输入多少个bottom就产生相应个数的输出top
const Dtype* bottom_data = bottom[i]->cpu_data();
//这里cpu_data()一般是指不可改的数据
Dtype* top_data = top[i]->mutable_cpu_data();
//对应地,mutable_cpu_data()一般是指可改的数据
for (int n = 0; n < this->num_; ++n) {
//这里的n为输入feature map的个数
this->forward_cpu_gemm(bottom_data + bottom[i]->offset(n), weight,
top_data + top[i]->offset(n));//计算卷积操作之后的输出
if (this->bias_term_) {
const Dtype* bias = this->blobs_[1]->cpu_data();
this->forward_cpu_bias(top_data + top[i]->offset(n), bias);
}//加上bias
}
}
}
forward_cpu_gemm
template <typename Dtype>
void BaseConvolutionLayer::forward_cpu_gemm(const Dtype* input,
const Dtype* weights, Dtype* output, bool skip_im2col) {
const Dtype* col_buff = input;
if (!is_1x1_) {
if (!skip_im2col) {
// 如果没有1x1卷积,也没有skip_im2col
// 则使用conv_im2col_cpu对使用卷积核滑动过程中的每一个kernel大小的图像块
// 变成一个列向量,其中height=kernel_dim_
// width = 卷积后图像heght*卷积后图像width
conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());
}
col_buff = col_buffer_.cpu_data();
}
// 使用caffe的cpu_gemm来进行计算
for (int g = 0; g < group_; ++g) {
// 分组分别进行计算
// conv_out_channels_ / group_是每个卷积组的输出的channel
// kernel_dim_ = input channels per-group x kernel height x kernel width
// 计算的是output[output_offset_ * g] =
// weights[weight_offset_ * g] X col_buff[col_offset_ * g]
// weights的形状是 [conv_out_channel x kernel_dim_]
// col_buff的形状是[kernel_dim_ x (卷积后图像高度乘以卷积后图像宽度)]
// 所以output的形状自然就是conv_out_channel X (卷积后图像高度乘以卷积后图像宽度)
caffe_cpu_gemm(CblasNoTrans, CblasNoTrans, conv_out_channels_ / group_, conv_out_spatial_dim_, kernel_dim_,
(Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,
(Dtype)0., output + output_offset_ * g);
}
}
forward_cpu_bias
template type>
void BaseConvolutionLayertype>::forward_cpu_bias(Dtype* output,
const Dtype* bias) {
// output = bias * bias_multiplier_
// num_output 与 conv_out_channel是一样的
// num_output_ X out_spatial_dim_ = num_output_ X 1 1 X out_spatial_dim_
caffe_cpu_gemmtype>(CblasNoTrans, CblasNoTrans, num_output_,
out_spatial_dim_, 1, (Dtype)1., bias, bias_multiplier_.cpu_data(),
(Dtype)1., output);
}
Backward_cpu
template <typename Dtype>
void ConvolutionLayer::Backward_cpu(const vector *>& top,
const vector<bool>& propagate_down, const vector *>& bottom) {//propagate_down指是否反传
const Dtype* weight = this->blobs_[0]->cpu_data();
Dtype* weight_diff = this->blobs_[0]->mutable_cpu_diff();
if (this->param_propagate_down_[0]) {//param_propagate_down_[0]指weight是否更新
caffe_set(this->blobs_[0]->count(), Dtype(0), weight_diff);
}
if (this->bias_term_ && this->param_propagate_down_[1]) {//param_propagate_down_[1]指bias是否更新
caffe_set(this->blobs_[1]->count(), Dtype(0),
this->blobs_[1]->mutable_cpu_diff());
}
for (int i = 0; i < top.size(); ++i) {
const Dtype* top_diff = top[i]->cpu_diff();//上一层传下来的导数
const Dtype* bottom_data = bottom[i]->cpu_data();
Dtype* bottom_diff = bottom[i]->mutable_cpu_diff();//传给下一层的导数
if (this->bias_term_ && this->param_propagate_down_[1]) {
Dtype* bias_diff = this->blobs_[1]->mutable_cpu_diff();
for (int n = 0; n < this->num_; ++n) {
this->backward_cpu_bias(bias_diff, top_diff + top[i]->offset(n));
}
}
if (this->param_propagate_down_[0] || propagate_down[i]) {
for (int n = 0; n < this->num_; ++n) {
if (this->param_propagate_down_[0]) {
this->weight_cpu_gemm(bottom_data + bottom[i]->offset(n),
top_diff + top[i]->offset(n), weight_diff);
}//对weight 计算导数(用来更新weight)
if (propagate_down[i]) {
this->backward_cpu_gemm(top_diff + top[i]->offset(n), weight,
bottom_diff + bottom[i]->offset(n));
}//对bottom数据计算导数(传给下一层)
}
}
}
}
本篇博客主要参考自
《 (Caffe)卷积的实现 》
《在 Caffe 中如何计算卷积?》
《Caffe源码(五):conv_layer 分析 》