首先这里说明一下,下面的很多注释中都以lenet的地一个卷积层为参考(N x C x H x W),
输入为1x 1 x 28 x 28,
kernel : 20 x 1 x 5 x 5
output : 1 x 20 x 24 x 24
内部成员变量的值:
conv1 : LayerSetUp:
channel_axis_ = 1
first_spatial_axis = 2
num_axes = 4
num_spatial_axes_ = 2
spatial_dim_blob_shape = {2}
kernel_shape_就是一个一维的Blob, 长度为2,所以kernel_shape_data就是一个长度为2的数组。kernel_shape_data[0] 是height(5), kernel_shape_data[1]是width(5)
stride_也是一个blob, 同kernel_shape_, 也就是Height对应一个stride, width对应一个stride
Pad_和dilation_也是和stride_等一样。
num_output_ = 20 kernel的个数
conv_out_channels_ = num_output_ = 20就是输出channel就是kernel的个数
conv_in_channels_ = channels_ = 1;
weight_shape[0] = 20 //number
weight_shape[1] = 1; // channel
weight_shape[2] = 5 // height
weight_shape[3] = 5 //width
bias_term_ 是否有bias
vector<int> bias_shape(1, 20) //num为1,element_value为20
kernel_dim_ = 1* 5 * 5 = 25
Reshape():
first_spatial_axis = 2;
num_ = 1;
conv_out_spatial_dim_ = 24 x 24 = 576
col_offset_ = kernel_dim_ * conv_out_spatial_dim_ = 25 * 576 = 14400 这就是一张图片im2col之后形成的矩阵的元素个数.
output_offset_ = 20 * 576 = 11520
col_buffer_ shape : 25 24 24 (14400)
bottom_dim_ = 1 x 28 x 28 = 784
top_dim_ = 1 x 24 x 24 = 576
num_kernels_im2col_ = 1 x 576
num_kernels_col2im_ = bottom_dim_ = 784
out_spatial_dim_ = 24 x 24 = 576
bias_multiplier_ shape : 576 (一行,长度为576), 这里之所以设置这么长,就是为了计算矩阵的时候长度匹配,因为im2col有576列,但其实每个Kernel有一个bias.
所以bias_data是一个20 x 1 的矩阵, 然后bias_multiplier_ * bias_data 就形成了一个20 x 576(20行,576列)的矩阵,正好对应20个kernel(20行25列)x im2col(img)(25行,576列),
矩阵乘后也同样形成20x576的矩阵,直接对应相加就可以。也就是bias_multiplier_的维度是对应一个Kernel的bias,也就是这576数据的value是相同的,都是1。就是为了形成那个矩阵。
1.因为卷积层和反卷积层有一些相同的运算,为了复用,卷积层设计了一个基类: BaseConvolutionLayer, 其他卷积层实现需要继承这个基类。
2.卷积层主要的计算就是卷积,所以里面使用很多数学计算函数,大部分都定义在include/caffe/util/math_functions.hpp文件中, 还有im2col系列,定义在include/caffe/util/im2col.hpp文件中,这些函数是为了将要计算的数据转换为矩阵,便于利用矩阵计算来实现卷积的运算,这里记录一下:
1 caffe_cpu_gemm
void caffe_cpu_gemm(const CBLAS_TRANSPOSE TransA,
const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K,
const Dtype alpha, const Dtype* A, const Dtype* B, const Dtype beta,
Dtype* C);
功能: C=alpha*A*B+beta*C A,B,C 是输入矩阵(一维数组格式)
参数解释:TransA : A 矩阵是否转置 TransB: B是否转置
M : 矩阵A的行数(如果需要转置,转置后的行数)
N : 矩阵B的列数(如果需要转置,转置后的列数),
K:矩阵A的列数或者B的行数(他们两个必须一样才能相乘)
2 caffe_cpu_gemv
void caffe_cpu_gemv(const CBLAS_TRANSPOSE TransA, const int M, const int N,
const Dtype alpha, const Dtype* A, const Dtype* x, const Dtype beta,
Dtype* y);
功能: y=alpha*A*x+beta*y
其中X和Y是向量,A 是矩阵, M:A 的行数,N:向量长度,其实也是A的列数
TransA : 矩阵A是否转置
3 caffe_axpy 功能: Y=alpha*X+Y
4 caffe_set 功能: 用常数alpha 对Y 进行初始化
5 caffe_add_scalar 功能: 给 Y 的每个 element 加上常数 alpha
6 caffe_copy 功能: 函数 void *memcpy(void *dest, void *src, unsigned int count) 把src所指向的内存区域 copy到dest所指向的内存区域, count为块的大小
7 caffe_scal 功能: X = alpha*X
8 caffeine_cup_axpby 功能:Y= alpha*X+beta*Y
9 caffe_add、 caffe_sub、 caffe_mul、 caffe_div 功能:这四个函数分别实现element-wise的加减乘除(y[i] = a[i] + - * \ b[i])
10 caffe_powx、 caffe_sqr、 caffe_exp、 caffe_abs 功能 : 同样是element-wise操作,分别是y[i] = a[i] ^ b, y[i] = a[i]^2,y[i] = exp(a[i] ),y[i] = |a[i] |
11 int caffe_rng_rand 功能:返回一个随机数
12.caffe_nextafer 功能: 返回 b 较大方向上可以表示的最接近的数值。
13 caffe_cpu_strided_dot 功能: 返回 vector X 和 vector Y 的内积。 incx, incy : 步长,即每隔incx 或 incy 个element 进行操作。
14. caffe_cpu_dot:计算步长为1的内积;
15 caffe_cpu_hamming_distance 功能:返回 x 和 y 之间的海明距离。(两个等长字符串之间的海明距离是两个字符串对应位置的不同字符的个数。)
16 caffe_cpu_asum 功能:计算 vector x 的所有element的值之和。
17. caffe_sign:类似于正负号函数,仅返回-1或1
首先我们还是看一下卷积层的parameter结构,定义在caffe.proto里面:
message ConvolutionParameter {
optional uint32 num_output = 1; // The number of outputs for the layer
optional bool bias_term = 2 [default = true]; // whether to have bias terms
// Pad, kernel size, and stride are all given as a single value for equal
// dimensions in all spatial dimensions, or once per spatial dimension.
repeated uint32 pad = 3; // The padding size; defaults to 0
repeated uint32 kernel_size = 4; // The kernel size
repeated uint32 stride = 6; // The stride; defaults to 1
// Factor used to dilate the kernel, (implicitly) zero-filling the resulting
// holes. (Kernel dilation is sometimes referred to by its use in the
// algorithme à trous from Holschneider et al. 1987.)
repeated uint32 dilation = 18; // The dilation; defaults to 1
// For 2D convolution only, the *_h and *_w versions may also be used to
// specify both spatial dimensions.
optional uint32 pad_h = 9 [default = 0]; // The padding height (2D only)
optional uint32 pad_w = 10 [default = 0]; // The padding width (2D only)
optional uint32 kernel_h = 11; // The kernel height (2D only)
optional uint32 kernel_w = 12; // The kernel width (2D only)
optional uint32 stride_h = 13; // The stride height (2D only)
optional uint32 stride_w = 14; // The stride width (2D only)
optional uint32 group = 5 [default = 1]; // The group size for group conv
optional FillerParameter weight_filler = 7; // The filler for the weight
optional FillerParameter bias_filler = 8; // The filler for the bias
enum Engine {
DEFAULT = 0;
CAFFE = 1;
CUDNN = 2;
}
optional Engine engine = 15 [default = DEFAULT];
// The axis to interpret as "channels" when performing convolution.
// Preceding dimensions are treated as independent inputs;
// succeeding dimensions are treated as "spatial"(空间).
// With (N, C, H, W) inputs, and axis == 1 (the default), we perform
// N independent 2D convolutions, sliding C-channel (or (C/g)-channels, for
// groups g>1) filters across the spatial axes (H, W) of the input.
// With (N, C, D, H, W) inputs, and axis == 1, we perform
// N independent 3D convolutions, sliding (C/g)-channels
// filters across the spatial axes (D, H, W) of the input.
/*channel的axis, */
optional int32 axis = 16 [default = 1];
// Whether to force use of the general ND convolution, even if a specific
// implementation for blobs of the appropriate number of spatial dimensions
// is available. (Currently, there is only a 2D-specific convolution
// implementation; for input blobs with num_axes != 2, this option is
// ignored and the ND implementation will be used.)
/*这个参数表明是否强制使用一般的ND convolution的卷积实现,因为在caffe中实现了一个2D的卷积函数
im2col_cpu(),可能对于2维的卷积速度比一般的多维卷积计算函数im2col_nd_cpu()快。
如果这个flag设置为true时,所有计算会强制使用im2col_nd_cpu()函数计算,如果没有强制,
则计算2维的卷积则使用im2col_cpu()函数,计算多维的使用im2col_nd_cpu()函数*/
optional bool force_nd_im2col = 17 [default = false];
}
layer.hpp的部分函数解析:
/*关于loss, layer内部有一个vector<Dtype> loss_, 每个top blob对应一个Loss, 在如下这个函数中,
利用layer_param_中的loss_weight来初始化内部的loss_, 同时将对应的top blob中的diff里面的每个值都
设置为loss_weigth*/
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; }
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);
}
}
}
// Forward and backward wrappers. You should implement the cpu and
// gpu specific implementations instead, and should not change these
// functions.
/*在基类中定义了前向和反向计算,但具体的实现则是在Forward_cpu和Forward_gpu中定义的,这两个函数在各子类中实现。*/
template <typename Dtype>
inline Dtype Layer<Dtype>::Forward(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
Dtype loss = 0;
//Reshape在子类中实现,根据本layer的参数和输入blob的参数,计算得出输出blob的大小。
Reshape(bottom, top);
switch (Caffe::mode()) {
case Caffe::CPU:
//利用cpu执行前向运算
Forward_cpu(bottom, top);
for (int top_id = 0; top_id < top.size(); ++top_id) {
if (!this->loss(top_id)) { continue; }
const int count = top[top_id]->count();
const Dtype* data = top[top_id]->cpu_data();
const Dtype* loss_weights = top[top_id]->cpu_diff();
/*从这里可以看出,loss是由top blob的cpu_data和cpu_diff点积得来的。总的loss是所有top blob的loss之和*/
loss += caffe_cpu_dot(count, data, loss_weights);
}
break;
case Caffe::GPU:
Forward_gpu(bottom, top);
#ifndef CPU_ONLY
for (int top_id = 0; top_id < top.size(); ++top_id) {
if (!this->loss(top_id)) { continue; }
const int count = top[top_id]->count();
const Dtype* data = top[top_id]->gpu_data();
const Dtype* loss_weights = top[top_id]->gpu_diff();
Dtype blob_loss = 0;
caffe_gpu_dot(count, data, loss_weights, &blob_loss);
loss += blob_loss;
}
#endif
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
return loss;
}
template <typename Dtype>
inline void Layer<Dtype>::Backward(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom) {
switch (Caffe::mode()) {
case Caffe::CPU:
Backward_cpu(top, propagate_down, bottom);
break;
case Caffe::GPU:
Backward_gpu(top, propagate_down, bottom);
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
}
卷积层的基类头文件base_conv_layer.hpp
#ifndef CAFFE_BASE_CONVOLUTION_LAYER_HPP_
#define CAFFE_BASE_CONVOLUTION_LAYER_HPP_
#include <vector>
#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/im2col.hpp"
namespace caffe {
/**
* @brief Abstract base class that factors out the BLAS code common to
* ConvolutionLayer and DeconvolutionLayer.
*/
template <typename Dtype>
class BaseConvolutionLayer : public Layer<Dtype> {
public:
explicit BaseConvolutionLayer(const LayerParameter& param)
: Layer<Dtype>(param) {}
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
/*这里三个函数说明至少需要一个输入输出blob, 并且输入输出blob的数量必须一样*/
virtual inline int MinBottomBlobs() const { return 1; }
virtual inline int MinTopBlobs() const { return 1; }
virtual inline bool EqualNumBottomTopBlobs() const { return true; }
protected:
// Helper functions that abstract away the column buffer and gemm arguments.
// The last argument in forward_cpu_gemm is so that we can skip the im2col if
// we just called weight_cpu_gemm with the same input.
/*卷积层前向传播过程的矩阵乘法*/
void forward_cpu_gemm(const Dtype* input, const Dtype* weights,
Dtype* output, bool skip_im2col = false);
/*加入偏置*/
void forward_cpu_bias(Dtype* output, const Dtype* bias);
/*卷积层反向传播过程的矩阵乘法*/
void backward_cpu_gemm(const Dtype* input, const Dtype* weights,
Dtype* output);
/*主要用来计算权重的增量*/
void weight_cpu_gemm(const Dtype* input, const Dtype* output, Dtype*
weights);
/*加入偏置*/
void backward_cpu_bias(Dtype* bias, const Dtype* input);
#ifndef CPU_ONLY
void forward_gpu_gemm(const Dtype* col_input, const Dtype* weights,
Dtype* output, bool skip_im2col = false);
void forward_gpu_bias(Dtype* output, const Dtype* bias);
void backward_gpu_gemm(const Dtype* input, const Dtype* weights,
Dtype* col_output);
void weight_gpu_gemm(const Dtype* col_input, const Dtype* output, Dtype*
weights);
void backward_gpu_bias(Dtype* bias, const Dtype* input);
#endif
/// @brief The spatial dimensions of the input.
inline int input_shape(int i) {
return (*bottom_shape_)[channel_axis_ + i];
}
// reverse_dimensions should return true if we are implementing deconv, so
// that conv helpers know which dimensions are which.
virtual bool reverse_dimensions() = 0; // 是否支持反卷积
// Compute height_out_ and width_out_ from other parameters.
virtual void compute_output_shape() = 0; //计算卷积层的输出形状
/// @brief The spatial dimensions of a filter kernel.
Blob<int> kernel_shape_;
/// @brief The spatial dimensions of the stride.
Blob<int> stride_;
/// @brief The spatial dimensions of the padding.
Blob<int> pad_;
/// @brief The spatial dimensions of the dilation.
Blob<int> dilation_; //卷积和的膨胀数
/// @brief The spatial dimensions of the convolution input.
Blob<int> conv_input_shape_;
/// @brief The spatial dimensions of the col_buffer.
vector<int> col_buffer_shape_;
/// @brief The spatial dimensions of the output.
vector<int> output_shape_;
const vector<int>* bottom_shape_;
int num_spatial_axes_; //就是空间坐标的维度,不包括num和channel, 一般就是height和width,所以一般为2
int bottom_dim_;
int top_dim_;
// 在shape中channel的index, 比如如果是num*channel*height*width, 则channel_axis_就为channel的index,为1
int channel_axis_;
int num_;
int channels_;
int group_;
int out_spatial_dim_;
int weight_offset_;
int num_output_;
bool bias_term_;
bool is_1x1_;
bool force_nd_im2col_;
private:
// wrap im2col/col2im so we don't have to remember the (long) argument lists
/*im2col_cpu是caffe自己实现的计算二维卷积的函数,所以这里如果没有设置force_nd_im2col_,同时
是二维卷积,才会调用这个函数,否则调用通用的N维卷积函数im2col_nd_cpu。
这个函数的作用就是调用im2col_cpu函数时自动将内部的参数给填上了,
调用的时候就传入image_data和col_buff就可以了。关于im2col_cpu的解释,可以参考:
https://blog.youkuaiyun.com/Mrhiuser/article/details/52672824 注意,这里只是将Image转换为col格式
数据,没有计算卷积。输出结果就放在col_buff里面*/
inline void conv_im2col_cpu(const Dtype* data, Dtype* col_buff) {
if (!force_nd_im2col_ && num_spatial_axes_ == 2) {
im2col_cpu(data, conv_in_channels_,
conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],
kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],
pad_.cpu_data()[0], pad_.cpu_data()[1],
stride_.cpu_data()[0], stride_.cpu_data()[1],
dilation_.cpu_data()[0], dilation_.cpu_data()[1], col_buff);
} else {
im2col_nd_cpu(data, num_spatial_axes_, conv_input_shape_.cpu_data(),
col_buffer_shape_.data(), kernel_shape_.cpu_data(),
pad_.cpu_data(), stride_.cpu_data(), dilation_.cpu_data(), col_buff);
}
}
/*这个是上个函数的逆过程。*/
inline void conv_col2im_cpu(const Dtype* col_buff, Dtype* data) {
if (!force_nd_im2col_ && num_spatial_axes_ == 2) {
col2im_cpu(col_buff, conv_in_channels_,
conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],
kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],
pad_.cpu_data()[0], pad_.cpu_data()[1],
stride_.cpu_data()[0], stride_.cpu_data()[1],
dilation_.cpu_data()[0], dilation_.cpu_data()[1], data);
} else {
col2im_nd_cpu(col_buff, num_spatial_axes_, conv_input_shape_.cpu_data(),
col_buffer_shape_.data(), kernel_shape_.cpu_data(),
pad_.cpu_data(), stride_.cpu_data(), dilation_.cpu_data(), data);
}
}
#ifndef CPU_ONLY
inline void conv_im2col_gpu(const Dtype* data, Dtype* col_buff) {
if (!force_nd_im2col_ && num_spatial_axes_ == 2) {
im2col_gpu(data, conv_in_channels_,
conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],
kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],
pad_.cpu_data()[0], pad_.cpu_data()[1],
stride_.cpu_data()[0], stride_.cpu_data()[1],
dilation_.cpu_data()[0], dilation_.cpu_data()[1], col_buff);
} else {
im2col_nd_gpu(data, num_spatial_axes_, num_kernels_im2col_,
conv_input_shape_.gpu_data(), col_buffer_.gpu_shape(),
kernel_shape_.gpu_data(), pad_.gpu_data(),
stride_.gpu_data(), dilation_.gpu_data(), col_buff);
}
}
inline void conv_col2im_gpu(const Dtype* col_buff, Dtype* data) {
if (!force_nd_im2col_ && num_spatial_axes_ == 2) {
col2im_gpu(col_buff, conv_in_channels_,
conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],
kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],
pad_.cpu_data()[0], pad_.cpu_data()[1],
stride_.cpu_data()[0], stride_.cpu_data()[1],
dilation_.cpu_data()[0], dilation_.cpu_data()[1], data);
} else {
col2im_nd_gpu(col_buff, num_spatial_axes_, num_kernels_col2im_,
conv_input_shape_.gpu_data(), col_buffer_.gpu_shape(),
kernel_shape_.gpu_data(), pad_.gpu_data(), stride_.gpu_data(),
dilation_.gpu_data(), data);
}
}
#endif
int num_kernels_im2col_;
int num_kernels_col2im_;
int conv_out_channels_;
int conv_in_channels_;
int conv_out_spatial_dim_;
int kernel_dim_;
int col_offset_;
int output_offset_;
Blob<Dtype> col_buffer_;
Blob<Dtype> bias_multiplier_;
};
} // namespace caffe
#endif // CAFFE_BASE_CONVOLUTION_LAYER_HPP_
base_conv_layer.cpp:
#include <algorithm>
#include <vector>
#include "caffe/filler.hpp"
#include "caffe/layers/base_conv_layer.hpp"
#include "caffe/util/im2col.hpp"
#include "caffe/util/math_functions.hpp"
namespace caffe {
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
// Configure the kernel size, padding, stride, and inputs.
ConvolutionParameter conv_param = this->layer_param_.convolution_param();
/*这个是ConvolutionParameter中定义的一个参数,表明是否强制使用一般的ND convolution的卷积实现,
因为在caffe中实现了一个2D的卷积函数im2col_cpu(),可能对于2维的卷积速度比一般的多维卷积计算
函数im2col_nd_cpu()快。如果这个flag设置为true时,所有计算会强制使用im2col_nd_cpu()函数计算,
如果没有强制,则计算2维的卷积则使用im2col_cpu()函数,计算多维的使用im2col_nd_cpu()函数*/
force_nd_im2col_ = conv_param.force_nd_im2col();
/*CanonicalAxisIndex函数是为了对index做转换,也就是这里index可以为负值,
比如-1就是倒数第一个变量,CanonicalAxisIndex()函数可以将负的Index转变为对应的正index.
这里channel_axis_就是指定channel这个变量在shape vector中的index,
一般shape vector排列顺序为N * C * W * H, 所以channel index默认值为1*/
channel_axis_ = bottom[0]->CanonicalAxisIndex(conv_param.axis());
/*一般channel index是1, 则first_spatial_axis = 2, 也就是W的index*/
const int first_spatial_axis = channel_axis_ + 1;
/*N * C * W * H,应该为4*/
const int num_axes = bottom[0]->num_axes();
/*4 - 2 = 2*/
num_spatial_axes_ = num_axes - first_spatial_axis;
CHECK_GE(num_spatial_axes_, 0);
/*vector{2}*/
vector<int> spatial_dim_blob_shape(1, std::max(num_spatial_axes_, 1));
// Setup filter kernel dimensions (kernel_shape_).
/*kernel_shape_就是一维,长度为2*/
kernel_shape_.Reshape(spatial_dim_blob_shape);
/*所以kernel_shape_data就是一个长度为2的数组,kernel_shape_data[0]就是heigth,
kernel_shape_data[1]就是width*/
int* kernel_shape_data = kernel_shape_.mutable_cpu_data();
/*conv_param中定义了kernel的宽高(对于二维情况)*/
if (conv_param.has_kernel_h() || conv_param.has_kernel_w()) {
CHECK_EQ(num_spatial_axes_, 2)
<< "kernel_h & kernel_w can only be used for 2D convolution.";
CHECK_EQ(0, conv_param.kernel_size_size())
<< "Either kernel_size or kernel_h/w should be specified; not both.";
kernel_shape_data[0] = conv_param.kernel_h();
kernel_shape_data[1] = conv_param.kernel_w();
} else {
/*多维情况下用kernel_size来定义kernel维度,都定义在conv_param中*/
const int num_kernel_dims = conv_param.kernel_size_size();
CHECK(num_kernel_dims == 1 || num_kernel_dims == num_spatial_axes_)
<< "kernel_size must be specified once, or once per spatial dimension "
<< "(kernel_size specified " << num_kernel_dims << " times; "
<< num_spatial_axes_ << " spatial dims).";
for (int i = 0; i < num_spatial_axes_; ++i) {
kernel_shape_data[i] =
conv_param.kernel_size((num_kernel_dims == 1) ? 0 : i);
}
}
for (int i = 0; i < num_spatial_axes_; ++i) {
CHECK_GT(kernel_shape_data[i], 0) << "Filter dimensions must be nonzero.";
}
// Setup stride dimensions (stride_).
/*stride和kernel_shape_一样,其实就是heigth对应一个stride, width对应一个stride*/
stride_.Reshape(spatial_dim_blob_shape);
int* stride_data = stride_.mutable_cpu_data();
/*2维的情况*/
if (conv_param.has_stride_h() || conv_param.has_stride_w()) {
CHECK_EQ(num_spatial_axes_, 2)
<< "stride_h & stride_w can only be used for 2D convolution.";
CHECK_EQ(0, conv_param.stride_size())
<< "Either stride or stride_h/w should be specified; not both.";
stride_data[0] = conv_param.stride_h();
stride_data[1] = conv_param.stride_w();
} else {
/*多维的情况*/
const int num_stride_dims = conv_param.stride_size();
CHECK(num_stride_dims == 0 || num_stride_dims == 1 ||
num_stride_dims == num_spatial_axes_)
<< "stride must be specified once, or once per spatial dimension "
<< "(stride specified " << num_stride_dims << " times; "
<< num_spatial_axes_ << " spatial dims).";
const int kDefaultStride = 1;
for (int i = 0; i < num_spatial_axes_; ++i) {
stride_data[i] = (num_stride_dims == 0) ? kDefaultStride :
conv_param.stride((num_stride_dims == 1) ? 0 : i);
CHECK_GT(stride_data[i], 0) << "Stride dimensions must be nonzero.";
}
}
// Setup pad dimensions (pad_). pad_和stride一样
pad_.Reshape(spatial_dim_blob_shape);
int* pad_data = pad_.mutable_cpu_data();
/*2维的情况*/
if (conv_param.has_pad_h() || conv_param.has_pad_w()) {
CHECK_EQ(num_spatial_axes_, 2)
<< "pad_h & pad_w can only be used for 2D convolution.";
CHECK_EQ(0, conv_param.pad_size())
<< "Either pad or pad_h/w should be specified; not both.";
pad_data[0] = conv_param.pad_h();
pad_data[1] = conv_param.pad_w();
} else {
/*多维的情况*/
const int num_pad_dims = conv_param.pad_size();
CHECK(num_pad_dims == 0 || num_pad_dims == 1 ||
num_pad_dims == num_spatial_axes_)
<< "pad must be specified once, or once per spatial dimension "
<< "(pad specified " << num_pad_dims << " times; "
<< num_spatial_axes_ << " spatial dims).";
const int kDefaultPad = 0;
for (int i = 0; i < num_spatial_axes_; ++i) {
pad_data[i] = (num_pad_dims == 0) ? kDefaultPad :
conv_param.pad((num_pad_dims == 1) ? 0 : i);
}
}
// Setup dilation dimensions (dilation_). dilation也和stride一样
dilation_.Reshape(spatial_dim_blob_shape);
int* dilation_data = dilation_.mutable_cpu_data();
const int num_dilation_dims = conv_param.dilation_size();
CHECK(num_dilation_dims == 0 || num_dilation_dims == 1 ||
num_dilation_dims == num_spatial_axes_)
<< "dilation must be specified once, or once per spatial dimension "
<< "(dilation specified " << num_dilation_dims << " times; "
<< num_spatial_axes_ << " spatial dims).";
const int kDefaultDilation = 1;
for (int i = 0; i < num_spatial_axes_; ++i) {
dilation_data[i] = (num_dilation_dims == 0) ? kDefaultDilation :
conv_param.dilation((num_dilation_dims == 1) ? 0 : i);
}
// Special case: im2col is the identity for 1x1 convolution with stride 1
// and no padding, so flag for skipping the buffer and transformation.
/*检测是否有1x1的kernel*/
is_1x1_ = true;
for (int i = 0; i < num_spatial_axes_; ++i) {
is_1x1_ &=
kernel_shape_data[i] == 1 && stride_data[i] == 1 && pad_data[i] == 0;
if (!is_1x1_) { break; }
}
// Configure output channels and groups. 输入的channel
channels_ = bottom[0]->shape(channel_axis_);
//就是kernel的个数,也是输出的channel数
num_output_ = this->layer_param_.convolution_param().num_output();
CHECK_GT(num_output_, 0);
group_ = this->layer_param_.convolution_param().group();
CHECK_EQ(channels_ % group_, 0);
CHECK_EQ(num_output_ % group_, 0)
<< "Number of output should be multiples of group.";
if (reverse_dimensions()) {
conv_out_channels_ = channels_;
conv_in_channels_ = num_output_;
} else {
conv_out_channels_ = num_output_;
conv_in_channels_ = channels_;
}
// Handle the parameters: weights and biases.
// - blobs_[0] holds the filter weights
// - blobs_[1] holds the biases (optional)
/*卷积层权重参数其实就是kernel,所以weight_shape就是kernel的N x C x H x W, 所以下面赋值conv_out_channels_
就是kernel的个数,conv_in_channels_ / group_就是kernel的channel, 也就是输入的channel, 后面两个就是
Height和width.*/
vector<int> weight_shape(2);
weight_shape[0] = conv_out_channels_;
weight_shape[1] = conv_in_channels_ / group_;
for (int i = 0; i < num_spatial_axes_; ++i) {
weight_shape.push_back(kernel_shape_data[i]);
}
/*这里要注意的是,bias_term_这里是bool,如果参数里面有偏置, 则为true(1), 否则为false(0),所以这里就利用了true
为1, false为0的特性,vector创建函数为vector(num, value), 所以如果为true, 就是定义一个number=1,
value=num_output_的vector, 也就是只有一个元素,也就是说明bias其实就是一个一维数组,长度为num_output_,
也就是一个kernel对应一个bias, 这个bias_shape用来在下面初始化bias_blob, 如果为false, 则创建的vector的size为0,也就是没有bias.*/
bias_term_ = this->layer_param_.convolution_param().bias_term();
vector<int> bias_shape(bias_term_, num_output_);
if (this->blobs_.size() > 0) {
CHECK_EQ(1 + bias_term_, this->blobs_.size())
<< "Incorrect number of weight blobs.";
if (weight_shape != this->blobs_[0]->shape()) {
Blob<Dtype> weight_shaped_blob(weight_shape);
LOG(FATAL) << "Incorrect weight shape: expected shape "
<< weight_shaped_blob.shape_string() << "; instead, shape was "
<< this->blobs_[0]->shape_string();
}
if (bias_term_ && bias_shape != this->blobs_[1]->shape()) {
Blob<Dtype> bias_shaped_blob(bias_shape);
LOG(FATAL) << "Incorrect bias shape: expected shape "
<< bias_shaped_blob.shape_string() << "; instead, shape was "
<< this->blobs_[1]->shape_string();
}
LOG(INFO) << "Skipping parameter initialization";
} else {
//blobs_成员变量放的就是weight和bias, 所以如果有bias, 则有两个blob, 没有bias, 则只有一个bob.
if (bias_term_) {
this->blobs_.resize(2);
} else {
this->blobs_.resize(1);
}
// Initialize and fill the weights:
// output channels x input channels per-group x kernel height x kernel width
/*blob_[0]放的就是weigth blob, blob_[1]就是放的bias blob*/
this->blobs_[0].reset(new Blob<Dtype>(weight_shape));
shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(
this->layer_param_.convolution_param().weight_filler()));
weight_filler->Fill(this->blobs_[0].get());
// If necessary, initialize and fill the biases.
if (bias_term_) {
this->blobs_[1].reset(new Blob<Dtype>(bias_shape));
shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(
this->layer_param_.convolution_param().bias_filler()));
bias_filler->Fill(this->blobs_[1].get());
}
}
/*this->blobs_[0]->count(1)就是channel * height * width, 就是一个kernel的参数个数,便于后面计算每个kernel的offset*/
kernel_dim_ = this->blobs_[0]->count(1);
weight_offset_ = conv_out_channels_ * kernel_dim_ / group_;
// Propagate gradients to the parameters (as directed by backward pass).
this->param_propagate_down_.resize(this->blobs_.size(), true);//表明每个blob是否需要计算diff.
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const int first_spatial_axis = channel_axis_ + 1;
CHECK_EQ(bottom[0]->num_axes(), first_spatial_axis + num_spatial_axes_)
<< "bottom num_axes may not change.";
num_ = bottom[0]->count(0, channel_axis_);
CHECK_EQ(bottom[0]->shape(channel_axis_), channels_)
<< "Input size incompatible with convolution kernel.";
// TODO: generalize to handle inputs of different shapes.
for (int bottom_id = 1; bottom_id < bottom.size(); ++bottom_id) {
CHECK(bottom[0]->shape() == bottom[bottom_id]->shape())
<< "shape mismatch - bottom[0]: " << bottom[0]->shape_string()
<< " vs. bottom[" << bottom_id << "]: "
<< bottom[bottom_id]->shape_string();
}
// Shape the tops.
bottom_shape_ = &bottom[0]->shape();
compute_output_shape();
/*用vector的迭代器来初始化另一个vector,是[begin, end), 左闭右开,所以下面top_shape只含有
number一个元素,后面把channel, height, width依次添加进去*/
vector<int> top_shape(bottom[0]->shape().begin(),
bottom[0]->shape().begin() + channel_axis_);
top_shape.push_back(num_output_);
for (int i = 0; i < num_spatial_axes_; ++i) {
top_shape.push_back(output_shape_[i]);
}
for (int top_id = 0; top_id < top.size(); ++top_id) {
top[top_id]->Reshape(top_shape);
}
if (reverse_dimensions()) {
conv_out_spatial_dim_ = bottom[0]->count(first_spatial_axis);
} else {
conv_out_spatial_dim_ = top[0]->count(first_spatial_axis);
}
/*一张图片im2col之后形成的矩阵的元素个数*/
col_offset_ = kernel_dim_ * conv_out_spatial_dim_;
/*一张图片卷积输出的矩阵的元素个数*/
output_offset_ = conv_out_channels_ * conv_out_spatial_dim_ / group_;
// Setup input dimensions (conv_input_shape_).
/*就是输入的blob的channel x height x width */
vector<int> bottom_dim_blob_shape(1, num_spatial_axes_ + 1);
conv_input_shape_.Reshape(bottom_dim_blob_shape);
int* conv_input_shape_data = conv_input_shape_.mutable_cpu_data();
for (int i = 0; i < num_spatial_axes_ + 1; ++i) {
if (reverse_dimensions()) {
conv_input_shape_data[i] = top[0]->shape(channel_axis_ + i);
} else {
conv_input_shape_data[i] = bottom[0]->shape(channel_axis_ + i);
}
}
// The im2col result buffer will only hold one image at a time to avoid
// overly large memory usage. In the special case of 1x1 convolution
// it goes lazily unused to save memory.
col_buffer_shape_.clear();
col_buffer_shape_.push_back(kernel_dim_ * group_);
for (int i = 0; i < num_spatial_axes_; ++i) {
if (reverse_dimensions()) {
col_buffer_shape_.push_back(input_shape(i + 1));
} else {
col_buffer_shape_.push_back(output_shape_[i]);
}
}
col_buffer_.Reshape(col_buffer_shape_);
bottom_dim_ = bottom[0]->count(channel_axis_);
top_dim_ = top[0]->count(channel_axis_);
num_kernels_im2col_ = conv_in_channels_ * conv_out_spatial_dim_;
num_kernels_col2im_ = reverse_dimensions() ? top_dim_ : bottom_dim_;
// Set up the all ones "bias multiplier" for adding biases by BLAS
out_spatial_dim_ = top[0]->count(first_spatial_axis);
if (bias_term_) {
vector<int> bias_multiplier_shape(1, out_spatial_dim_);
bias_multiplier_.Reshape(bias_multiplier_shape);
caffe_set(bias_multiplier_.count(), Dtype(1),
bias_multiplier_.mutable_cpu_data());
}
}
/*计算权重矩阵(行20, 宽cx5x5) * im2col_data(行cx25, 宽24x24=576), 输出结果为行(20)x宽(24x24=576)*/
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::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) {
conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());
}
col_buff = col_buffer_.cpu_data();
}
for (int g = 0; g < group_; ++g) {
caffe_cpu_gemm<Dtype>(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);
}
}
/*将bias加上,top_data = bias(20行1列) * bias_multiplier(1行576列) + top_data(20行576列)
对于一维的数据,做矩阵计算时可以通过控制下面M, N, K的参数来让他作为一行还是一列来参与计算*/
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::forward_cpu_bias(Dtype* output,
const Dtype* bias) {
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_,
out_spatial_dim_, 1, (Dtype)1., bias, bias_multiplier_.cpu_data(),
(Dtype)1., output);
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::backward_cpu_gemm(const Dtype* output,
const Dtype* weights, Dtype* input) {
Dtype* col_buff = col_buffer_.mutable_cpu_data();
if (is_1x1_) {
col_buff = input;
}
for (int g = 0; g < group_; ++g) {
caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_,
conv_out_spatial_dim_, conv_out_channels_ / group_,
(Dtype)1., weights + weight_offset_ * g, output + output_offset_ * g,
(Dtype)0., col_buff + col_offset_ * g);
}
if (!is_1x1_) {
conv_col2im_cpu(col_buff, input);
}
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::weight_cpu_gemm(const Dtype* input,
const Dtype* output, Dtype* weights) {
const Dtype* col_buff = input;
if (!is_1x1_) {
conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());
col_buff = col_buffer_.cpu_data();
}
for (int g = 0; g < group_; ++g) {
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,
kernel_dim_, conv_out_spatial_dim_,
(Dtype)1., output + output_offset_ * g, col_buff + col_offset_ * g,
(Dtype)1., weights + weight_offset_ * g);
}
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::backward_cpu_bias(Dtype* bias,
const Dtype* input) {
caffe_cpu_gemv<Dtype>(CblasNoTrans, num_output_, out_spatial_dim_, 1.,
input, bias_multiplier_.cpu_data(), 1., bias);
}
#ifndef CPU_ONLY
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::forward_gpu_gemm(const Dtype* input,
const Dtype* weights, Dtype* output, bool skip_im2col) {
const Dtype* col_buff = input;
if (!is_1x1_) {
if (!skip_im2col) {
conv_im2col_gpu(input, col_buffer_.mutable_gpu_data());
}
col_buff = col_buffer_.gpu_data();
}
for (int g = 0; g < group_; ++g) {
caffe_gpu_gemm<Dtype>(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);
}
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::forward_gpu_bias(Dtype* output,
const Dtype* bias) {
caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_,
out_spatial_dim_, 1, (Dtype)1., bias, bias_multiplier_.gpu_data(),
(Dtype)1., output);
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::backward_gpu_gemm(const Dtype* output,
const Dtype* weights, Dtype* input) {
Dtype* col_buff = col_buffer_.mutable_gpu_data();
if (is_1x1_) {
col_buff = input;
}
for (int g = 0; g < group_; ++g) {
caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_,
conv_out_spatial_dim_, conv_out_channels_ / group_,
(Dtype)1., weights + weight_offset_ * g, output + output_offset_ * g,
(Dtype)0., col_buff + col_offset_ * g);
}
if (!is_1x1_) {
conv_col2im_gpu(col_buff, input);
}
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::weight_gpu_gemm(const Dtype* input,
const Dtype* output, Dtype* weights) {
const Dtype* col_buff = input;
if (!is_1x1_) {
conv_im2col_gpu(input, col_buffer_.mutable_gpu_data());
col_buff = col_buffer_.gpu_data();
}
for (int g = 0; g < group_; ++g) {
caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,
kernel_dim_, conv_out_spatial_dim_,
(Dtype)1., output + output_offset_ * g, col_buff + col_offset_ * g,
(Dtype)1., weights + weight_offset_ * g);
}
}
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::backward_gpu_bias(Dtype* bias,
const Dtype* input) {
caffe_gpu_gemv<Dtype>(CblasNoTrans, num_output_, out_spatial_dim_, 1.,
input, bias_multiplier_.gpu_data(), 1., bias);
}
#endif // !CPU_ONLY
INSTANTIATE_CLASS(BaseConvolutionLayer);
} // namespace caffe
conv_layer.hpp
#ifndef CAFFE_CONV_LAYER_HPP_
#define CAFFE_CONV_LAYER_HPP_
#include <vector>
#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/layers/base_conv_layer.hpp"
namespace caffe {
/**
* @brief Convolves the input image with a bank of learned filters,
* and (optionally) adds biases.
*
* Caffe convolves by reduction to matrix multiplication. This achieves
* high-throughput and generality of input and filter dimensions but comes at
* the cost of memory for matrices. This makes use of efficiency in BLAS.
*
* The input is "im2col" transformed to a channel K' x H x W data matrix
* for multiplication with the N x K' x H x W filter matrix to yield a
* N' x H x W output matrix that is then "col2im" restored. K' is the
* input channel * kernel height * kernel width dimension of the unrolled
* inputs so that the im2col matrix has a column for each input region to
* be filtered. col2im restores the output spatial structure by rolling up
* the output channel N' columns of the output matrix.
*/
template <typename Dtype>
class ConvolutionLayer : public BaseConvolutionLayer<Dtype> {
public:
/**
* @param param provides ConvolutionParameter convolution_param,
* with ConvolutionLayer options:
* - num_output. The number of filters.
* - kernel_size / kernel_h / kernel_w. The filter dimensions, given by
* kernel_size for square filters or kernel_h and kernel_w for rectangular
* filters.
* - stride / stride_h / stride_w (\b optional, default 1). The filter
* stride, given by stride_size for equal dimensions or stride_h and stride_w
* for different strides. By default the convolution is dense with stride 1.
* - pad / pad_h / pad_w (\b optional, default 0). The zero-padding for
* convolution, given by pad for equal dimensions or pad_h and pad_w for
* different padding. Input padding is computed implicitly instead of
* actually padding.
* - dilation (\b optional, default 1). The filter
* dilation, given by dilation_size for equal dimensions for different
* dilation. By default the convolution has dilation 1.
* - group (\b optional, default 1). The number of filter groups. Group
* convolution is a method for reducing parameterization by selectively
* connecting input and output channels. The input and output channel dimensions must be divisible
* by the number of groups. For group @f$ \geq 1 @f$, the
* convolutional filters' input and output channels are separated s.t. each
* group takes 1 / group of the input channels and makes 1 / group of the
* output channels. Concretely 4 input channels, 8 output channels, and
* 2 groups separate input channels 1-2 and output channels 1-4 into the
* first group and input channels 3-4 and output channels 5-8 into the second
* group.
* - bias_term (\b optional, default true). Whether to have a bias.
* - engine: convolution has CAFFE (matrix multiplication) and CUDNN (library
* kernels + stream parallelism) engines.
*/
explicit ConvolutionLayer(const LayerParameter& param)
: BaseConvolutionLayer<Dtype>(param) {}
virtual inline const char* type() const { return "Convolution"; }
protected:
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
virtual inline bool reverse_dimensions() { return false; }
virtual void compute_output_shape();
};
} // namespace caffe
#endif // CAFFE_CONV_LAYER_HPP_
conv_layer.cpp
#include <vector>
#include "caffe/layers/conv_layer.hpp"
namespace caffe {
/*这个就是计算输出的feature map的宽高, 宽高的计算公式为
W_out = ((W_in + 2 * pad - kernel_width) / stride) + 1
dilation_data为kernel膨胀,一般没有膨胀*/
template <typename Dtype>
void ConvolutionLayer<Dtype>::compute_output_shape() {
const int* kernel_shape_data = this->kernel_shape_.cpu_data();
const int* stride_data = this->stride_.cpu_data();
const int* pad_data = this->pad_.cpu_data();
const int* dilation_data = this->dilation_.cpu_data();
this->output_shape_.clear();
/*一般2维的话,num_spatial_axes_就是2,也就是宽高*/
for (int i = 0; i < this->num_spatial_axes_; ++i) {
// i + 1 to skip channel axis,这里是跳过channel
const int input_dim = this->input_shape(i + 1);
const int kernel_extent = dilation_data[i] * (kernel_shape_data[i] - 1) + 1;
const int output_dim = (input_dim + 2 * pad_data[i] - kernel_extent)
/ stride_data[i] + 1; //这个就是上面的计算公式
//这里就将算出来的shape放到成员变量output_shape_里面
this->output_shape_.push_back(output_dim);
}
}
template <typename Dtype>
void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const Dtype* weight = this->blobs_[0]->cpu_data();
for (int i = 0; i < bottom.size(); ++i) {
const Dtype* bottom_data = bottom[i]->cpu_data();
Dtype* top_data = top[i]->mutable_cpu_data();
for (int n = 0; n < this->num_; ++n) {
this->forward_cpu_gemm(bottom_data + n * this->bottom_dim_, weight,
top_data + n * this->top_dim_);
if (this->bias_term_) {
const Dtype* bias = this->blobs_[1]->cpu_data();
this->forward_cpu_bias(top_data + n * this->top_dim_, bias);
}
}
}
}
template <typename Dtype>
void ConvolutionLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
const Dtype* weight = this->blobs_[0]->cpu_data();
Dtype* weight_diff = this->blobs_[0]->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();
// Bias gradient, if necessary.
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 + n * this->top_dim_);
}
}
if (this->param_propagate_down_[0] || propagate_down[i]) {
for (int n = 0; n < this->num_; ++n) {
// gradient w.r.t. weight. Note that we will accumulate diffs.
if (this->param_propagate_down_[0]) {
this->weight_cpu_gemm(bottom_data + n * this->bottom_dim_,
top_diff + n * this->top_dim_, weight_diff);
}
// gradient w.r.t. bottom data, if necessary.
if (propagate_down[i]) {
this->backward_cpu_gemm(top_diff + n * this->top_dim_, weight,
bottom_diff + n * this->bottom_dim_);
}
}
}
}
}
#ifdef CPU_ONLY
STUB_GPU(ConvolutionLayer);
#endif
INSTANTIATE_CLASS(ConvolutionLayer);
} // namespace caffe
参考文献:
https://blog.youkuaiyun.com/jiongnima/article/details/69736844 详细讲解了im2col的作用。