分享

美图---问题---深度学习笔记4:卷积层的实现

 雪柳花明 2017-08-07

卷积层的推导

卷积层的前向计算

如下图,卷积层的输入来源于输入层或者pooling层。每一层的多个卷积核大小相同,在这个网络中,我使用的卷积核均为5*5。


如图输入为28*28的图像,经过5*5的卷积之后,得到一个(28-5+1)*(28-5+1) = 24*24、的map。卷积层2的每个map是不同卷积核在前一层每个map上进行卷积,并将每个对应位置上的值相加然后再加上一个偏置项。


每次用卷积核与map中对应元素相乘,然后移动卷积核进行下一个神经元的计算。如图中矩阵C的第一行第一列的元素2,就是卷积核在输入map左上角时的计算结果。在图中也很容易看到,输入为一个4*4的map,经过2*2的卷积核卷积之后,结果为一个(4-2+1) *(4-2+1) = 3*3的map。

卷积层的后向计算

之前的笔记中我有写到:在反向传播过程中,若第x层的a节点通过权值W对x+1层的b节点有贡献,则在反向传播过程中,梯度通过权值W从b节点传播回a节点。不管下面的公式推导,还是后面的卷积神经网络,在反向传播的过程中,都是遵循这样的一个规律。

卷积层的反向传播过程也是如此,我们只需要找出卷积层L中的每个单元和L+1层中的哪些单元相关联即可。我们还用上边的图片举例子。

在上图中,我们的矩阵A11通过权重B11与C11关联。而A12与2个矩阵C中2个元素相关联,分别是通过权重B12和C11关联,和通过权重B11和C12相关联。矩阵A中其他元素也类似。

那么,我们有没有简单的方法来实现这样的关联呢。答案是有的。可以通过将卷积核旋转180度,再与扩充后的梯度矩阵进行卷积。扩充的过程如下:如果卷积核为k*k,待卷积矩阵为n*n,需要以n*n原矩阵为中心扩展到(n+2(k-1))*(n+2(k-1))。具体过程如下:

假设D为反向传播到卷积层的梯度矩阵,则D应该与矩阵C的大小相等,在这里为3*3。我们首先需要将它扩充到(3+2*(2-1))* (3+2*(2-1)) = 5*5大小的矩阵,


同时将卷积核B旋转180度:


将旋转后的卷积核与扩充后的梯度矩阵进行卷积:



Caffe中卷积层的实现

在caffe的配置文件中,我们的网络定义了2个卷积层,下面是第二个卷积层的配置信息:

  1. layer {  
  2.   name: "conv2"  
  3.   type: "Convolution"  
  4.   bottom: "pool1"  
  5.   top: "conv2"  
  6.   param {  
  7.     lr_mult: 1  
  8.   }  
  9.   param {  
  10.     lr_mult: 2  
  11.   }  
  12.   convolution_param {  
  13.     num_output: 50  
  14.     kernel_size: 5  
  15.     stride: 1  
  16.     weight_filler {  
  17.       type: "xavier"  
  18.     }  
  19.     bias_filler {  
  20.       type: "constant"  
  21.     }  
  22.   }  
  23. }  

我们可以看到,该层的类型为Convolution,即卷积层,bottom表示上一层为pool1,是一个池化层,top表示该层的输出为conv2,即本层卷积层的输出。

lr_mult是2个学习速率,这个在权值更新部分在说。

接下来可以看到num_output表示该层有50个输出map,kernel_size卷积核大小为5*5,stride表示卷积步长为1,weight_filler表示权值初始化方式, 默认为“constant",值全为0,很多时候我们也可以用"xavier"或者”gaussian"来进行初始化。bias_filler表示偏置值的初始化方式,该参数的值和weight_filler类似,一般设置为"constant",值全为0。



前向过程

在看代码前,我们先看一下caffe中卷积的实现。

Caffe中卷积的实现十分巧妙,详细可以参考一下这篇论文: https://hal./file/index/docid/112631/filename/p1038112283956.pdf

下面是一张论文中的图片,看这张图片可以很清楚理解。从图中可以看出,卷积之前将输入的多个矩阵和多个卷积核先展开再组合成2个大的矩阵,用展开后的矩阵相乘。


假设我们一次训练16张图片(即batch_size为16)。通过之前的推导,我们知道该层的输入为20个12*12的特征图,所以bottom的维度16*20*12*12,则该层的输出top的维度为16*50*8*8。

 

下面我们来看一下caffe中对于卷积层的实现代码。在caffe中,GPU上的卷积层对应的文件为\src\caffe\layers\conv_layer.cu

我们先看一下前向过程的代码:

  1. void ConvolutionLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,  
  2.       const vector<Blob<Dtype>*>& top) {  
  3.   const Dtype* weight = this->blobs_[0]->gpu_data();  
  4.   for (int i = 0; i < bottom.size(); ++i) {  
  5.     const Dtype* bottom_data = bottom[i]->gpu_data();  
  6.     Dtype* top_data = top[i]->mutable_gpu_data();  
  7.     for (int n = 0; n < this->num_; ++n) {  
  8.     //bottom_data为上一层传入的数据,与weight作卷积,结果保存到top_data中  
  9.       this->forward_gpu_gemm(bottom_data + n * this->bottom_dim_, weight,  
  10.           top_data + n * this->top_dim_);  
  11.       if (this->bias_term_) {  
  12.       //加上偏置值  
  13.         const Dtype* bias = this->blobs_[1]->gpu_data();  
  14.         this->forward_gpu_bias(top_data + n * this->top_dim_, bias);  
  15.       }  
  16.     }  
  17.   }  
  18. }  

其中,卷积的运算用到了这个函数forward_gpu_gemm(),我们展开看一下这个函数的代码:

  1. void BaseConvolutionLayer<Dtype>::forward_gpu_gemm(const Dtype* input,  
  2.        const Dtype* weights, Dtype* output, bool skip_im2col) {  
  3.     const Dtype* col_buff = input;  
  4.     //若为1x1,不进行卷积操作  
  5.     if (!is_1x1_) {  
  6.         if (!skip_im2col) {  
  7.                 //将输入矩阵展开  
  8.                 conv_im2col_gpu(input, col_buffer_.mutable_gpu_data());  
  9.               }  
  10.         col_buff = col_buffer_.gpu_data();  
  11.     }  
  12.     //对weights与col_buffer作卷积,卷积的结果放入output  
  13.     caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, conv_out_channels_ /  
  14.             group_, conv_out_spatial_dim_, kernel_dim_,  
  15.             (Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,  
  16.             (Dtype)0., output + output_offset_ * g);  
  17.         }  
  18. }  

反向传播

Caffe中反向传播的代码如下:

  1. void ConvolutionLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,  
  2.       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {  
  3.   const Dtype* weight = this->blobs_[0]->gpu_data();  
  4.   Dtype* weight_diff = this->blobs_[0]->mutable_gpu_diff();  
  5.   for (int i = 0; i < top.size(); ++i) {  
  6.     const Dtype* top_diff = top[i]->gpu_diff();  
  7.     // Bias gradient, if necessary.  
  8.     if (this->bias_term_ && this->param_propagate_down_[1]) {  
  9.       Dtype* bias_diff = this->blobs_[1]->mutable_gpu_diff();  
  10.       for (int n = 0; n < this->num_; ++n) {  
  11.           //对一个batch中每一个map,计算其偏置的偏导  
  12.         this->backward_gpu_bias(bias_diff, top_diff + n * this->top_dim_);  
  13.       }  
  14.     }  
  15.     if (this->param_propagate_down_[0] || propagate_down[i]) {  
  16.       const Dtype* bottom_data = bottom[i]->gpu_data();  
  17.       Dtype* bottom_diff = bottom[i]->mutable_gpu_diff();  
  18.       for (int n = 0; n < this->num_; ++n) {  
  19.         // gradient w.r.t. weight. Note that we will accumulate diffs.  
  20.         if (this->param_propagate_down_[0]) {  
  21.           this->weight_gpu_gemm(bottom_data + n * this->bottom_dim_,  
  22.               top_diff + n * this->top_dim_, weight_diff);  
  23.         }  
  24.         // gradient w.r.t. bottom data, if necessary.  
  25.         if (propagate_down[i]) {  
  26.           this->backward_gpu_gemm(top_diff + n * this->top_dim_, weight,  
  27.               bottom_diff + n * this->bottom_dim_);  
  28.         }  
  29.       }  
  30.     }  
  31.   }  
  32. }  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多