梳理caffe代码base_conv_layer(十八)
Posted langb2014
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了梳理caffe代码base_conv_layer(十八)相关的知识,希望对你有一定的参考价值。
这个是实现卷积的基类。
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); 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 iff 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. // kernel的形状 = [kernel_h, kernel_w] Blob<int> kernel_shape_; /// @brief The spatial dimensions of the stride. // 步长形状 = [stride_h, stride_w] Blob<int> stride_; /// @brief The spatial dimensions of the padding. // pad的形状 = [pad_h, pad_w] Blob<int> pad_; /// @brief The spatial dimensions of the convolution input. // 卷积的输入形状 = [输入图像通道数, 输入图像h, 输入图像w] Blob<int> conv_input_shape_; /// @brief The spatial dimensions of the col_buffer. // col_buffer的形状 = [kernel_dim_, conv_out_spatial_dim_ ] 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_; // 输入度维度 = 输入图像通道数*输入图像的h*输入图像w int bottom_dim_; // 输出维度 = 输出通道数*输出h*输出w int top_dim_; // 输入图像的第几个轴是通道 int channel_axis_; // batchsize int num_; // 输入图像的通道数 int channels_; // 卷积组的大小 int group_; // 输出空间维度 = 卷积之后的图像长*卷积之后图像的宽 int out_spatial_dim_; // 使用卷积组用到的 int weight_offset_; // 卷积后的图像的通道数 int num_output_; // 是否启用偏置 bool bias_term_; // 是不是1x1卷积 bool is_1x1_; // 强制使用n维通用卷积 bool force_nd_im2col_; // conv_in_channels_ * conv_out_spatial_dim_ int num_kernels_im2col_; // num_kernels_col2im_ = reverse_dimensions() ? top_dim_ : bottom_dim_ int num_kernels_col2im_; // 卷积的输出通道数 ,在参数配置文件中设置 int conv_out_channels_; // 卷积的输入通道数 (即输入图像的通道数) int conv_in_channels_; // 卷积的输出的空间维度 = 卷积后图像h*卷积后图像w int conv_out_spatial_dim_; // 卷积核的维度 = 输入图像的维度*卷积核的h*卷积核的w int kernel_dim_; // 在使用gropu参数的时候使用的offset int col_offset_; int output_offset_; // im2col的时候使用的存储空间 Blob<Dtype> col_buffer_; // 将偏置扩展成矩阵的东东 Blob<Dtype> bias_multiplier_; private: // wrap im2col/col2im so we don't have to remember the (long) argument lists 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_实现部分:
#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(); force_nd_im2col_ = conv_param.force_nd_im2col();//im2col,一般情况下num_spatial_axes_==2,即将2维图像拉成向量,但force_nd_im2col_针对的是更general的情况n-d“图像” channel_axis_ = bottom[0]->CanonicalAxisIndex(conv_param.axis());//获取channel的axis const int first_spatial_axis = channel_axis_ + 1;//chanel_axis是从零开始 const int num_axes = bottom[0]->num_axes();//数据的axis数量 num_spatial_axes_ = num_axes - first_spatial_axis;//空间轴的个数 CHECK_GE(num_spatial_axes_, 0); vector<int> bottom_dim_blob_shape(1, num_spatial_axes_ + 1); vector<int> spatial_dim_blob_shape(1, std::max(num_spatial_axes_, 1));//当num_spatial_axes_==2时,spatial_dim_blob_shape这个vector只包含一个元素且值为2 // Setup filter kernel dimensions (kernel_shape_). // 设置kernel的dimensions kernel_shape_.Reshape(spatial_dim_blob_shape);//以spatial_dim_blob_shape为参数来构造一个Blob,即kernel_shape_,则这个Blob的维度信息只包含一个维度,值为2,也就是说这个Blob的count_==2。尽管这个Blob的维度信息只包含一个维度,因为在后续的计算(Im2col)中,我只关心这个Blob中的数据的值,而不关心这个Blob的shape信息,例如在Im2col()中,只要取出相应数值即可kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],pad_.cpu_data()[0], pad_.cpu_data()[1]。 int* kernel_shape_data = kernel_shape_.mutable_cpu_data(); 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是一个二维数组 kernel_shape_data[1] = conv_param.kernel_w(); } else { 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_.Reshape(spatial_dim_blob_shape); int* stride_data = stride_.mutable_cpu_data(); 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_). //设置相应的stride dimensions, pad_.Reshape(spatial_dim_blob_shape); int* pad_data = pad_.mutable_cpu_data(); 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); } } // Special case: im2col is the identity for 1x1 convolution with stride 1 // and no padding, so flag for skipping the buffer and transformation. 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. channels_ = bottom[0]->shape(channel_axis_); 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."; //channel 和 输出 feature map 个数必须为group的整数倍,每个group中只用本group的featrue map 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) 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_ = 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 { 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 this->blobs_[0].reset(new Blob<Dtype>(weight_shape));//blobs_[0]的维度信息是四个维度,count_为四个维度的值相乘 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));//blobs_[1]的维度信息是1个维度,count_为num_output_ shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>( this->layer_param_.convolution_param().bias_filler())); bias_filler->Fill(this->blobs_[1].get()); } } kernel_dim_ = this->blobs_[0]->count(1);//是一个三维维度的乘积 weight_offset_ = conv_out_channels_ * kernel_dim_ / group_;//写成(conv_out_channels_ / group_) * kernel_dim_更直观。这个offset是相对group分组来讲的。 // Propagate gradients to the parameters (as directed by backward pass). this->param_propagate_down_.resize(this->blobs_.size(), true); } 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.所有的输入bottom都必须有相同的shape for (int bottom_id = 1; bottom_id < bottom.size(); ++bottom_id) { CHECK(bottom[0]->shape() == bottom[bottom_id]->shape()) << "All inputs must have the same shape."; } // Shape the tops.卷积层应该默认有多少个bottom 就有多少个top输出 bottom_shape_ = &bottom[0]->shape(); compute_output_shape(); 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); } //group分,对conv_in_channels分组; 卷积窗口在输入“图像”上按步长滑动,(可以想象)形成了多个子图;然后将所有子图拉成一列,列的长度就是col_offset_。 col_offset_ = kernel_dim_ * conv_out_spatial_dim_;//col_offset_与im2col_cpu()函数中channels_col的计算是相似的,但是值并不相等,原因在于:channels_col是将卷积层输入的通道数conv_in_channels_用于相乘,但kernel_dim_只用到了一部分channel,即conv_in_channels_/group_ 。 output_offset_ = conv_out_channels_ * conv_out_spatial_dim_ / group_;//卷积层的输出特征图也要分组,当然group_默认为1。写成(conv_out_channels_ / group_) * conv_out_spatial_dim_更直观 // Setup input dimensions (conv_input_shape_). vector<int> bottom_dim_blob_shape(1, num_spatial_axes_ + 1); conv_input_shape_.Reshape(bottom_dim_blob_shape);//与pad_、 stride_类似 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_是一个vector col_buffer_shape_.push_back(kernel_dim_ * group_);//所有conv_in_channels_个输入的channels都包含其中。 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_);//一般情况下,col_buffer_的维度信息为三个维度。col_buffer_shape_的存储的元素为:kernel_dim_ * group_, 输出特征图的H, 输出特征图的W。可以认为col_buffer_内所存储的数据的维度为:(kernel_dim_ * group_) × H × W,且与kernel_dim_ x conv_out_spatial_dim_有密切关系. 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);//out_spatial_dim_ == conv_out_spatial_dim_ if (bias_term_) { vector<int> bias_multiplier_shape(1, out_spatial_dim_); bias_multiplier_.Reshape(bias_multiplier_shape);//bias_multiplier_这个Blob的count_为out_spatial_dim_,是输出特征图的H×W caffe_set(bias_multiplier_.count(), Dtype(1), bias_multiplier_.mutable_cpu_data()); } } //////////////////////注意: col_offset_ output_offset_ weight_offet 都是因为group_分组而存在的\\\\\\\\\\\\\\\\\\\\\\\\\\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) { // 如果没有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_channels_ /group_) x kernel_dim_ // weights的形状是 [conv_out_channel x kernel_dim_] // col_buff相当于数据,它的形状是[kernel_dim_ x (卷积后图像高度乘以卷积后图像宽度)]= // kernel_dim_ x conv_out_spatial_dim_ // 所以output的形状自然就是conv_out_channel X (卷积后图像高度乘以卷积后图像宽度)= // (conv_out_channels_ /group_) x conv_out_spatial_dim_ 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); } }//只是对一张图像进行前向传播!与全连接层类比,conv_out_channels_ / group_相当与全连接层的输出神经元个数;conv_out_spatial_dim_相当于全连接层中的样本个数;kernel_dim_相当与全连接层中每个样本特征向量的维数。 template <typename Dtype> void BaseConvolutionLayer<Dtype>::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_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_, out_spatial_dim_, 1, (Dtype)1., bias, bias_multiplier_.cpu_data(), (Dtype)1., output); }//卷积后加bias 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); } }//计算关于bottom data的导数以便传给下一层 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); } }//计算关于weight的导数用于更新。 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); }//计算关于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
以上是关于梳理caffe代码base_conv_layer(十八)的主要内容,如果未能解决你的问题,请参考以下文章