您现在的位置是: 网站首页 >> 深度学习 深度学习

【转】深度学习笔记4:卷积层的实现

发布时间:2018年8月27日 21:55 作者:Master 来源:转载 点击:508

    卷积层的推导

    卷积层的前向计算

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

    20160817182320843.png

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

    20160818140552357.png


    每次用卷积核与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大小的矩阵,

    20160818140730428.png

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

    20160818140809616.png


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

    20160818140849564.png



    Caffe中卷积层的实现


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

    layer {
      name: "conv2"
      type: "Convolution"
      bottom: "pool1"
      top: "conv2"
      param {
        lr_mult: 1
      }
      param {
        lr_mult: 2
      }
      convolution_param {
        num_output: 50
        kernel_size: 5
        stride: 1
        weight_filler {
          type: "xavier"
        }
        bias_filler {
          type: "constant"
        }
      }
    }

    我们可以看到,该层的类型为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.archives-ouvertes.fr/file/index/docid/112631/filename/p1038112283956.pdf

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

    20160818141025738.png


    假设我们一次训练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

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

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

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

    void BaseConvolutionLayer<Dtype>::forward_gpu_gemm(const Dtype* input,
           const Dtype* weights, Dtype* output, bool skip_im2col) {
        const Dtype* col_buff = input;
        //若为1x1,不进行卷积操作
        if (!is_1x1_) {
      if (!skip_im2col) {
                    //将输入矩阵展开
                    conv_im2col_gpu(input, col_buffer_.mutable_gpu_data());
                  }
            col_buff = col_buffer_.gpu_data();
        }
        //对weights与col_buffer作卷积,卷积的结果放入output
        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);
            }
    }

    反向传播


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

    void ConvolutionLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
          const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
      const Dtype* weight = this->blobs_[0]->gpu_data();
      Dtype* weight_diff = this->blobs_[0]->mutable_gpu_diff();
      for (int i = 0; i < top.size(); ++i) {
        const Dtype* top_diff = top[i]->gpu_diff();
        // Bias gradient, if necessary.
        if (this->bias_term_ && this->param_propagate_down_[1]) {
          Dtype* bias_diff = this->blobs_[1]->mutable_gpu_diff();
          for (int n = 0; n < this->num_; ++n) {
        //对一个batch中每一个map,计算其偏置的偏导
            this->backward_gpu_bias(bias_diff, top_diff + n * this->top_dim_);
          }
        }
        if (this->param_propagate_down_[0] || propagate_down[i]) {
          const Dtype* bottom_data = bottom[i]->gpu_data();
          Dtype* bottom_diff = bottom[i]->mutable_gpu_diff();
          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_gpu_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_gpu_gemm(top_diff + n * this->top_dim_, weight,
                  bottom_diff + n * this->bottom_dim_);
            }
          }
        }
      }
    }



    注:本博文为转载文章,已征得原博主同意

    来源:https://blog.csdn.net/l691899397/article/details/52240421