Caffe框架源码剖析(4)—卷积层基类BaseConvolutionLayer

来源:互联网 发布:itunes上软件版本号 编辑:程序博客网 时间:2024/04/30 04:00
数据层DataLayer正向传导的目标层是卷积层ConvolutionLayer。卷积层的是用一系列的权重滤波核与输入图像进行卷积,具体实现是通过将图像展开成向量,作用矩阵乘法实现卷积。

    同样,首先看一下卷积层的类图。



    BaseConvolutionLayer类是所有卷积层的基类,它负责初始化滤波核、将图像转成向量,以及封装了BLAS库的矩阵运算函数。Yangqing Jia在Convolution in Caffe: a memo 一文中说明了卷积的实现方式和优缺点:优点是速度快,缺点也很明显,内存开销大。也还有Alex Krizhevsky等人更好的实现方式,但是由于时间和精力原因并没有采用,毕竟是不想写博士论文意外创作的 偷笑

    ConvolutionLayer类是卷积层的实例类,如果系统支持cuDNN,则实例类为CuDNNConvolutionLayer。提醒一下,在CUDA计算能力3.0及以上的GPU上才支持cuDNN,这里CUDA GPUs 可以看到GPU的计算能力。如果当前GPU不支持cuDNN,Caffe又是默认开启cuDNN的,那么运行时构造CuDNNConvolutionLayer对象实例时将报错。这时需要在CommonSettings.props中将<UseCuDNN>标签改为false,或者在工程属性“C/C++”->“预处理器”中将USE_CUDNN定义项删除,再重新编译Caffe。


[cpp] view plain copy
  1. template <typename Dtype>  
  2. class BaseConvolutionLayer : public Layer<Dtype> {  
  3.  public:  
  4.   explicit BaseConvolutionLayer(const LayerParameter& param)  
  5.       : Layer<Dtype>(param) {}  
  6.   virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,  
  7.       const vector<Blob<Dtype>*>& top);  
  8.   virtual void Reshape(const vector<Blob<Dtype>*>& bottom,  
  9.       const vector<Blob<Dtype>*>& top);  
  10.   
  11.   virtual inline int MinBottomBlobs() const { return 1; }  
  12.   virtual inline int MinTopBlobs() const { return 1; }  
  13.   virtual inline bool EqualNumBottomTopBlobs() const { return true; }  
  14.   
  15.  protected:  
  16.   // 一系列BLAS辅助函数  
  17.   void forward_cpu_gemm(const Dtype* input, const Dtype* weights,  
  18.       Dtype* output, bool skip_im2col = false);  
  19.   void forward_cpu_bias(Dtype* output, const Dtype* bias);  
  20.   void backward_cpu_gemm(const Dtype* input, const Dtype* weights,  
  21.       Dtype* output);  
  22.   void weight_cpu_gemm(const Dtype* input, const Dtype* output, Dtype*  
  23.       weights);  
  24.   void backward_cpu_bias(Dtype* bias, const Dtype* input);  
  25.   
  26. #ifndef CPU_ONLY  
  27.   void forward_gpu_gemm(const Dtype* col_input, const Dtype* weights,  
  28.       Dtype* output, bool skip_im2col = false);  
  29.   void forward_gpu_bias(Dtype* output, const Dtype* bias);  
  30.   void backward_gpu_gemm(const Dtype* input, const Dtype* weights,  
  31.       Dtype* col_output);  
  32.   void weight_gpu_gemm(const Dtype* col_input, const Dtype* output, Dtype*  
  33.       weights);  
  34.   void backward_gpu_bias(Dtype* bias, const Dtype* input);  
  35. #endif  
  36.   
  37.   /// @brief The spatial dimensions of the input.  
  38.   inline int input_shape(int i) {  
  39.     return (*bottom_shape_)[channel_axis_ + i];  
  40.   }  
  41.   // 纯虚函数,用来识别是卷积(return false)还是反卷积(return true)  
  42.   virtual bool reverse_dimensions() = 0;  
  43.   // 纯虚函数,计算卷积后输出尺寸虚函数  
  44.   virtual void compute_output_shape() = 0;  
  45.   
  46.   /// 卷积核尺寸  
  47.   Blob<int> kernel_shape_;  
  48.   /// 卷积核平移步幅  
  49.   Blob<int> stride_;  
  50.   /// 图像补齐像素数  
  51.   Blob<int> pad_;  
  52.   /// 图像膨胀像素数  
  53.   Blob<int> dilation_;  
  54.   /// @brief The spatial dimensions of the convolution input.  
  55.   Blob<int> conv_input_shape_;  
  56.   /// @brief The spatial dimensions of the col_buffer.  
  57.   vector<int> col_buffer_shape_;  
  58.   /// @brief The spatial dimensions of the output.  
  59.   vector<int> output_shape_;  
  60.   const vector<int>* bottom_shape_;  
  61.   int num_spatial_axes_;  
  62.   int bottom_dim_;  
  63.   int top_dim_;  
  64.   
  65.   int channel_axis_;  
  66.   int num_;  
  67.   int channels_;   // 通道数  
  68.   int group_;      // 组数  
  69.   int out_spatial_dim_;  
  70.   int weight_offset_;    // 权重offset,此处为25*20 = 500  
  71.   int num_output_;      // 输出数  
  72.   bool bias_term_;      // 是否包含偏置项  
  73.   bool is_1x1_;           // 是否为1x1模式  
  74.   bool force_nd_im2col_;  
  75.   
  76. private:  
  77.   // 将图像转成列向量  
  78.   inline void conv_im2col_cpu(const Dtype* data, Dtype* col_buff) {  
  79.     if (!force_nd_im2col_ && num_spatial_axes_ == 2) {  
  80.       im2col_cpu(data, conv_in_channels_,  
  81.           conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],  
  82.           kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],  
  83.           pad_.cpu_data()[0], pad_.cpu_data()[1],  
  84.           stride_.cpu_data()[0], stride_.cpu_data()[1],  
  85.           dilation_.cpu_data()[0], dilation_.cpu_data()[1], col_buff);  
  86.     } else {  
  87.       im2col_nd_cpu(data, num_spatial_axes_, conv_input_shape_.cpu_data(),  
  88.           col_buffer_shape_.data(), kernel_shape_.cpu_data(),  
  89.           pad_.cpu_data(), stride_.cpu_data(), dilation_.cpu_data(), col_buff);  
  90.     }  
  91.   }  
  92.   inline void conv_col2im_cpu(const Dtype* col_buff, Dtype* data) {  
  93.     if (!force_nd_im2col_ && num_spatial_axes_ == 2) {  
  94.       col2im_cpu(col_buff, conv_in_channels_,  
  95.           conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],  
  96.           kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],  
  97.           pad_.cpu_data()[0], pad_.cpu_data()[1],  
  98.           stride_.cpu_data()[0], stride_.cpu_data()[1],  
  99.           dilation_.cpu_data()[0], dilation_.cpu_data()[1], data);  
  100.     } else {  
  101.       col2im_nd_cpu(col_buff, num_spatial_axes_, conv_input_shape_.cpu_data(),  
  102.           col_buffer_shape_.data(), kernel_shape_.cpu_data(),  
  103.           pad_.cpu_data(), stride_.cpu_data(), dilation_.cpu_data(), data);  
  104.     }  
  105.   }  
  106. #ifndef CPU_ONLY  
  107.   inline void conv_im2col_gpu(const Dtype* data, Dtype* col_buff) {  
  108.     if (!force_nd_im2col_ && num_spatial_axes_ == 2) {  
  109.       im2col_gpu(data, conv_in_channels_,  
  110.           conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],  
  111.           kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],  
  112.           pad_.cpu_data()[0], pad_.cpu_data()[1],  
  113.           stride_.cpu_data()[0], stride_.cpu_data()[1],  
  114.           dilation_.cpu_data()[0], dilation_.cpu_data()[1], col_buff);  
  115.     } else {  
  116.       im2col_nd_gpu(data, num_spatial_axes_, num_kernels_im2col_,  
  117.           conv_input_shape_.gpu_data(), col_buffer_.gpu_shape(),  
  118.           kernel_shape_.gpu_data(), pad_.gpu_data(),  
  119.           stride_.gpu_data(), dilation_.gpu_data(), col_buff);  
  120.     }  
  121.   }  
  122.   inline void conv_col2im_gpu(const Dtype* col_buff, Dtype* data) {  
  123.     if (!force_nd_im2col_ && num_spatial_axes_ == 2) {  
  124.       col2im_gpu(col_buff, conv_in_channels_,  
  125.           conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],  
  126.           kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],  
  127.           pad_.cpu_data()[0], pad_.cpu_data()[1],  
  128.           stride_.cpu_data()[0], stride_.cpu_data()[1],  
  129.           dilation_.cpu_data()[0], dilation_.cpu_data()[1], data);  
  130.     } else {  
  131.       col2im_nd_gpu(col_buff, num_spatial_axes_, num_kernels_col2im_,  
  132.           conv_input_shape_.gpu_data(), col_buffer_.gpu_shape(),  
  133.           kernel_shape_.gpu_data(), pad_.gpu_data(), stride_.gpu_data(),  
  134.           dilation_.gpu_data(), data);  
  135.     }  
  136.   }  
  137. #endif  
  138.   int num_kernels_im2col_;  
  139.   int num_kernels_col2im_;  
  140.   int conv_out_channels_;    // 卷积操作输出通道数  
  141.   int conv_in_channels_;     // 卷积操作输入通道数  
  142.   int conv_out_spatial_dim_;  
  143.   int kernel_dim_;           // 卷积核的尺寸(例如5x5卷积核,kernel_dim_为25)  
  144.   int col_offset_;  
  145.   int output_offset_;  
  146.   Blob<Dtype> col_buffer_;  
  147.   Blob<Dtype> bias_multiplier_;  
  148. };  

base_conv_layer.cpp文件较长

[cpp] view plain copy
  1. template <typename Dtype>  
  2. void BaseConvolutionLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,  
  3.       const vector<Blob<Dtype>*>& top) {  
  4.   // 设置滤波核尺寸等参数  
  5.   ConvolutionParameter conv_param = this->layer_param_.convolution_param();  
  6.   force_nd_im2col_ = conv_param.force_nd_im2col();  
  7.   channel_axis_ = bottom[0]->CanonicalAxisIndex(conv_param.axis());  
  8.   const int first_spatial_axis = channel_axis_ + 1;  
  9.   const int num_axes = bottom[0]->num_axes();  
  10.   num_spatial_axes_ = num_axes - first_spatial_axis;  
  11.   CHECK_GE(num_spatial_axes_, 0);  
  12.   vector<int> bottom_dim_blob_shape(1, num_spatial_axes_ + 1);  
  13.   vector<int> spatial_dim_blob_shape(1, std::max(num_spatial_axes_, 1));  
  14.   // 设置卷积核维度  
  15.   kernel_shape_.Reshape(spatial_dim_blob_shape);  
  16.   int* kernel_shape_data = kernel_shape_.mutable_cpu_data();  
  17.   if (conv_param.has_kernel_h() || conv_param.has_kernel_w()) {  
  18.     CHECK_EQ(num_spatial_axes_, 2)  
  19.         << "kernel_h & kernel_w can only be used for 2D convolution.";  
  20.     CHECK_EQ(0, conv_param.kernel_size_size())  
  21.         << "Either kernel_size or kernel_h/w should be specified; not both.";  
  22.     kernel_shape_data[0] = conv_param.kernel_h();  
  23.     kernel_shape_data[1] = conv_param.kernel_w();  
  24.   } else {  
  25.     const int num_kernel_dims = conv_param.kernel_size_size();  
  26.     CHECK(num_kernel_dims == 1 || num_kernel_dims == num_spatial_axes_)  
  27.         << "kernel_size must be specified once, or once per spatial dimension "  
  28.         << "(kernel_size specified " << num_kernel_dims << " times; "  
  29.         << num_spatial_axes_ << " spatial dims).";  
  30.       for (int i = 0; i < num_spatial_axes_; ++i) {  
  31.         kernel_shape_data[i] =  
  32.             conv_param.kernel_size((num_kernel_dims == 1) ? 0 : i);  
  33.       }  
  34.   }  
  35.   for (int i = 0; i < num_spatial_axes_; ++i) {  
  36.     CHECK_GT(kernel_shape_data[i], 0) << "Filter dimensions must be nonzero.";  
  37.   }  
  38.   // 设置卷积核平移步幅  
  39.   stride_.Reshape(spatial_dim_blob_shape);  
  40.   int* stride_data = stride_.mutable_cpu_data();  
  41.   if (conv_param.has_stride_h() || conv_param.has_stride_w()) {  
  42.     CHECK_EQ(num_spatial_axes_, 2)  
  43.         << "stride_h & stride_w can only be used for 2D convolution.";  
  44.     CHECK_EQ(0, conv_param.stride_size())  
  45.         << "Either stride or stride_h/w should be specified; not both.";  
  46.     stride_data[0] = conv_param.stride_h();  
  47.     stride_data[1] = conv_param.stride_w();  
  48.   } else {  
  49.     const int num_stride_dims = conv_param.stride_size();  
  50.     CHECK(num_stride_dims == 0 || num_stride_dims == 1 ||  
  51.           num_stride_dims == num_spatial_axes_)  
  52.         << "stride must be specified once, or once per spatial dimension "  
  53.         << "(stride specified " << num_stride_dims << " times; "  
  54.         << num_spatial_axes_ << " spatial dims).";  
  55.     const int kDefaultStride = 1;  
  56.     for (int i = 0; i < num_spatial_axes_; ++i) {  
  57.       stride_data[i] = (num_stride_dims == 0) ? kDefaultStride :  
  58.           conv_param.stride((num_stride_dims == 1) ? 0 : i);  
  59.       CHECK_GT(stride_data[i], 0) << "Stride dimensions must be nonzero.";  
  60.     }  
  61.   }  
  62.   // 设置图像补齐像素数  
  63.   pad_.Reshape(spatial_dim_blob_shape);  
  64.   int* pad_data = pad_.mutable_cpu_data();  
  65.   if (conv_param.has_pad_h() || conv_param.has_pad_w()) {  
  66.     CHECK_EQ(num_spatial_axes_, 2)  
  67.         << "pad_h & pad_w can only be used for 2D convolution.";  
  68.     CHECK_EQ(0, conv_param.pad_size())  
  69.         << "Either pad or pad_h/w should be specified; not both.";  
  70.     pad_data[0] = conv_param.pad_h();  
  71.     pad_data[1] = conv_param.pad_w();  
  72.   } else {  
  73.     const int num_pad_dims = conv_param.pad_size();  
  74.     CHECK(num_pad_dims == 0 || num_pad_dims == 1 ||  
  75.           num_pad_dims == num_spatial_axes_)  
  76.         << "pad must be specified once, or once per spatial dimension "  
  77.         << "(pad specified " << num_pad_dims << " times; "  
  78.         << num_spatial_axes_ << " spatial dims).";  
  79.     const int kDefaultPad = 0;  
  80.     for (int i = 0; i < num_spatial_axes_; ++i) {  
  81.       pad_data[i] = (num_pad_dims == 0) ? kDefaultPad :  
  82.           conv_param.pad((num_pad_dims == 1) ? 0 : i);  
  83.     }  
  84.   }  
  85.   // 设置图像膨胀像素数  
  86.   dilation_.Reshape(spatial_dim_blob_shape);  
  87.   int* dilation_data = dilation_.mutable_cpu_data();  
  88.   const int num_dilation_dims = conv_param.dilation_size();  
  89.   CHECK(num_dilation_dims == 0 || num_dilation_dims == 1 ||  
  90.         num_dilation_dims == num_spatial_axes_)  
  91.       << "dilation must be specified once, or once per spatial dimension "  
  92.       << "(dilation specified " << num_dilation_dims << " times; "  
  93.       << num_spatial_axes_ << " spatial dims).";  
  94.   const int kDefaultDilation = 1;  
  95.   for (int i = 0; i < num_spatial_axes_; ++i) {  
  96.     dilation_data[i] = (num_dilation_dims == 0) ? kDefaultDilation :  
  97.                        conv_param.dilation((num_dilation_dims == 1) ? 0 : i);  
  98.   }  
  99.   // 只有对于尺寸1x1、平移步幅为1、图像不补齐的卷积核才将标志位1x1置为true  
  100.   is_1x1_ = true;  
  101.   for (int i = 0; i < num_spatial_axes_; ++i) {  
  102.     is_1x1_ &=  
  103.         kernel_shape_data[i] == 1 && stride_data[i] == 1 && pad_data[i] == 0;  
  104.     if (!is_1x1_) { break; }  
  105.   }  
  106.   // 配置通道数  
  107.   channels_ = bottom[0]->shape(channel_axis_);  
  108.   num_output_ = this->layer_param_.convolution_param().num_output();  
  109.   CHECK_GT(num_output_, 0);  
  110.   group_ = this->layer_param_.convolution_param().group();  
  111.   CHECK_EQ(channels_ % group_, 0);  
  112.   CHECK_EQ(num_output_ % group_, 0)  
  113.       << "Number of output should be multiples of group.";  
  114.   if (reverse_dimensions()) {  
  115.     conv_out_channels_ = channels_;  
  116.     conv_in_channels_ = num_output_;  
  117.   } else {  
  118.     conv_out_channels_ = num_output_;  // 设置卷积操作输出通道数  
  119.     conv_in_channels_ = channels_;     // 设置卷积操作输入通道数  
  120.   }  
  121.   // 处理权重参数和偏置参数  
  122.   // - blobs_[0] holds the filter weights  
  123.   // - blobs_[1] holds the biases (optional)  
  124.   vector<int> weight_shape(2);  
  125.   weight_shape[0] = conv_out_channels_;  
  126.   weight_shape[1] = conv_in_channels_ / group_;  
  127.   for (int i = 0; i < num_spatial_axes_; ++i) {  
  128.     weight_shape.push_back(kernel_shape_data[i]);  
  129.   }  
  130.   // 从配置中读取是否包含偏置项  
  131.   bias_term_ = this->layer_param_.convolution_param().bias_term();  
  132.   vector<int> bias_shape(bias_term_, num_output_);  
  133.   if (this->blobs_.size() > 0) {  
  134.     CHECK_EQ(1 + bias_term_, this->blobs_.size())  
  135.         << "Incorrect number of weight blobs.";  
  136.     if (weight_shape != this->blobs_[0]->shape()) {  
  137.       Blob<Dtype> weight_shaped_blob(weight_shape);  
  138.       LOG(FATAL) << "Incorrect weight shape: expected shape "  
  139.           << weight_shaped_blob.shape_string() << "; instead, shape was "  
  140.           << this->blobs_[0]->shape_string();  
  141.     }  
  142.     if (bias_term_ && bias_shape != this->blobs_[1]->shape()) {  
  143.       Blob<Dtype> bias_shaped_blob(bias_shape);  
  144.       LOG(FATAL) << "Incorrect bias shape: expected shape "  
  145.           << bias_shaped_blob.shape_string() << "; instead, shape was "  
  146.           << this->blobs_[1]->shape_string();  
  147.     }  
  148.     LOG(INFO) << "Skipping parameter initialization";  
  149.   } else {  
  150.     if (bias_term_) {  
  151.       this->blobs_.resize(2);  // 如果包含偏置项,则blobs_为两项  
  152.     } else {  
  153.       this->blobs_.resize(1);  // 如果不包含偏置项,则blobs_只有一项(权重项)  
  154.     }  
  155.     // 初始化和填充权重  
  156.     // output channels x input channels per-group x kernel height x kernel width  
  157.     this->blobs_[0].reset(new Blob<Dtype>(weight_shape));  
  158.     shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(  
  159.         this->layer_param_.convolution_param().weight_filler()));  
  160.     // 使用随机数填充Xavier滤波核  
  161.     weight_filler->Fill(this->blobs_[0].get());  
  162.     // 如果启用偏置,则初始化偏置滤波核  
  163.     if (bias_term_) {  
  164.       this->blobs_[1].reset(new Blob<Dtype>(bias_shape));  
  165.       shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(  
  166.           this->layer_param_.convolution_param().bias_filler()));  
  167.       // 使用0填充偏置滤波核  
  168.       bias_filler->Fill(this->blobs_[1].get());  
  169.     }  
  170.   }  
  171.   kernel_dim_ = this->blobs_[0]->count(1);  
  172.   // 计算权重offset  
  173.   weight_offset_ = conv_out_channels_ * kernel_dim_ / group_;  
  174.   // Propagate gradients to the parameters (as directed by backward pass).  
  175.   this->param_propagate_down_.resize(this->blobs_.size(), true);  
  176. }  
  177.   
  178. // 正向传导矩阵运算函数   
  179. template <typename Dtype>  
  180. void BaseConvolutionLayer<Dtype>::forward_cpu_gemm(const Dtype* input,  
  181.     const Dtype* weights, Dtype* output, bool skip_im2col) {  
  182.   // 先将图像转为列向量  
  183.   const Dtype* col_buff = input;  
  184.   if (!is_1x1_) {  
  185.     if (!skip_im2col) {  
  186.       conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());  
  187.     }  
  188.     col_buff = col_buffer_.cpu_data();  
  189.   }  
  190.   // 矩阵乘法实现卷积运算  
  191.   for (int g = 0; g < group_; ++g) {  
  192.     caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, conv_out_channels_ /  
  193.         group_, conv_out_spatial_dim_, kernel_dim_,  
  194.         (Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,  
  195.         (Dtype)0., output + output_offset_ * g);  
  196.   }  
  197. }  
  198.   
  199. // CPU正向传导偏置(在上一步卷积计算结果上加上偏置)  
  200. template <typename Dtype>  
  201. void BaseConvolutionLayer<Dtype>::forward_cpu_bias(Dtype* output,  
  202.     const Dtype* bias) {  
  203.   caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_,  
  204.       out_spatial_dim_, 1, (Dtype)1., bias, bias_multiplier_.cpu_data(),  
  205.       (Dtype)1., output);  
  206. }  
  207.   
  208. // CPU反向传导  
  209. template <typename Dtype>  
  210. void BaseConvolutionLayer<Dtype>::backward_cpu_gemm(const Dtype* output,  
  211.     const Dtype* weights, Dtype* input) {  
  212.   Dtype* col_buff = col_buffer_.mutable_cpu_data();  
  213.   if (is_1x1_) {  
  214.     col_buff = input;  
  215.   }  
  216.   for (int g = 0; g < group_; ++g) {  
  217.     caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_,  
  218.         conv_out_spatial_dim_, conv_out_channels_ / group_,  
  219.         (Dtype)1., weights + weight_offset_ * g, output + output_offset_ * g,  
  220.         (Dtype)0., col_buff + col_offset_ * g);  
  221.   }  
  222.   if (!is_1x1_) {  
  223.     conv_col2im_cpu(col_buff, input);  
  224.   }  
  225. }  
  226.   
  227. // CPU计算权重的偏导  
  228. template <typename Dtype>  
  229. void BaseConvolutionLayer<Dtype>::weight_cpu_gemm(const Dtype* input,  
  230.     const Dtype* output, Dtype* weights) {  
  231.   const Dtype* col_buff = input;  
  232.   if (!is_1x1_) {  
  233.     conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());  
  234.     col_buff = col_buffer_.cpu_data();  
  235.   }  
  236.   for (int g = 0; g < group_; ++g) {  
  237.     caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,  
  238.         kernel_dim_, conv_out_spatial_dim_,  
  239.         (Dtype)1., output + output_offset_ * g, col_buff + col_offset_ * g,  
  240.         (Dtype)1., weights + weight_offset_ * g);  
  241.   }  
  242. }  
  243.   
  244. // CPU反向传导偏置项  
  245. template <typename Dtype>  
  246. void BaseConvolutionLayer<Dtype>::backward_cpu_bias(Dtype* bias,  
  247.     const Dtype* input) {  
  248.   caffe_cpu_gemv<Dtype>(CblasNoTrans, num_output_, out_spatial_dim_, 1.,  
  249.       input, bias_multiplier_.cpu_data(), 1., bias);  
  250. }  
  251.   
  252. #ifndef CPU_ONLY  
  253. // GPU正向传导  
  254. template <typename Dtype>  
  255. void BaseConvolutionLayer<Dtype>::forward_gpu_gemm(const Dtype* input,  
  256.     const Dtype* weights, Dtype* output, bool skip_im2col) {  
  257.   const Dtype* col_buff = input;  
  258.   if (!is_1x1_) {  
  259.     if (!skip_im2col) {  
  260.       conv_im2col_gpu(input, col_buffer_.mutable_gpu_data());  
  261.     }  
  262.     col_buff = col_buffer_.gpu_data();  
  263.   }  
  264.   for (int g = 0; g < group_; ++g) {  
  265.     caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, conv_out_channels_ /  
  266.         group_, conv_out_spatial_dim_, kernel_dim_,  
  267.         (Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,  
  268.         (Dtype)0., output + output_offset_ * g);  
  269.   }  
  270. }  
  271.   
  272. // GPU正向传导偏置项  
  273. template <typename Dtype>  
  274. void BaseConvolutionLayer<Dtype>::forward_gpu_bias(Dtype* output,  
  275.     const Dtype* bias) {  
  276.   caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_,  
  277.       out_spatial_dim_, 1, (Dtype)1., bias, bias_multiplier_.gpu_data(),  
  278.       (Dtype)1., output);  
  279. }  
  280.   
  281. // GPU计算数据项偏导  
  282. template <typename Dtype>  
  283. void BaseConvolutionLayer<Dtype>::backward_gpu_gemm(const Dtype* output,  
  284.     const Dtype* weights, Dtype* input) {  
  285.   Dtype* col_buff = col_buffer_.mutable_gpu_data();  
  286.   if (is_1x1_) {  
  287.     col_buff = input;  
  288.   }  
  289.   for (int g = 0; g < group_; ++g) {  
  290.     caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_,  
  291.         conv_out_spatial_dim_, conv_out_channels_ / group_,  
  292.         (Dtype)1., weights + weight_offset_ * g, output + output_offset_ * g,  
  293.         (Dtype)0., col_buff + col_offset_ * g);  
  294.   }  
  295.   if (!is_1x1_) {  
  296.     conv_col2im_gpu(col_buff, input);  
  297.   }  
  298. }  
  299.   
  300. // GPU计算权重的偏导  
  301. template <typename Dtype>  
  302. void BaseConvolutionLayer<Dtype>::weight_gpu_gemm(const Dtype* input,  
  303.     const Dtype* output, Dtype* weights) {  
  304.   const Dtype* col_buff = input;  
  305.   if (!is_1x1_) {  
  306.     conv_im2col_gpu(input, col_buffer_.mutable_gpu_data());  
  307.     col_buff = col_buffer_.gpu_data();  
  308.   }  
  309.   for (int g = 0; g < group_; ++g) {  
  310.     caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,  
  311.         kernel_dim_, conv_out_spatial_dim_,  
  312.         (Dtype)1., output + output_offset_ * g, col_buff + col_offset_ * g,  
  313.         (Dtype)1., weights + weight_offset_ * g);  
  314.   }  
  315. }  
  316.   
  317. // GPU反向传导偏置项  
  318. template <typename Dtype>  
  319. void BaseConvolutionLayer<Dtype>::backward_gpu_bias(Dtype* bias,  
  320.     const Dtype* input) {  
  321.   caffe_gpu_gemv<Dtype>(CblasNoTrans, num_output_, out_spatial_dim_, 1.,  
  322.       input, bias_multiplier_.gpu_data(), 1., bias);