赞
踩
如下图,卷积层的输入来源于输入层或者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的配置文件中,我们的网络定义了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_);
- }
- }
- }
- }
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。