当前位置:   article > 正文

深度学习笔记4:卷积层的实现_给定一个输入图像尺寸为28x28,应用一个5x5的卷积核,步长为2,填充为2,那么输出图像

给定一个输入图像尺寸为28x28,应用一个5x5的卷积核,步长为2,填充为2,那么输出图像

卷积层的推导

卷积层的前向计算

如下图,卷积层的输入来源于输入层或者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.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

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

  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. }



声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/787220
推荐阅读
相关标签
  

闽ICP备14008679号