深度学习基础知识(4):卷积层的实现

来源:互联网 发布:最短路径的算法 编辑:程序博客网 时间:2024/06/10 16:19

版权声明:本文系转载,已获得作者授权
原文网址:http://blog.csdn.net/l691899397/article/details/52240421
作者:佳寧

卷积层的推导

            ①卷积层的前向计算

如下图,卷积层的输入来源于输入层或者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个卷积层,下面是第二个卷积层的配置信息:

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个大的矩阵,用展开后的矩阵相乘。


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


阅读全文
0 0
原创粉丝点击