代码阅读2

http://blog.csdn.net/u014568921/article/details/53995455

首先,要知道caffe里的卷积核都是三维的

在caffe中卷积核是三维的还是二维的?


下面分割线之间的内容来自http://blog.csdn.NET/u014114990/article/details/51125776

/******************************************************************************************/

下面讲一下,caffe中的实现。


Caffe中的卷积计算是将卷积核矩阵和输入图像矩阵变换为两个大的矩阵A与B,然后A与B进行矩阵相乘得到结果C(利用GPU进行矩阵相乘的高效性),三个矩阵的说明如下:

(1)在矩阵A中

        M为卷积核个数,K=k*k,等于卷积核大小,即第一个矩阵每行为一个卷积核向量(是将二维的卷积核转化为一维),总共有M行,表示有M个卷积核。

(2)在矩阵B中

        N=((image_h + 2*pad_h – kernel_h)/stride_h+ 1)*((image_w +2*pad_w – kernel_w)/stride_w + 1)

        image_h:输入图像的高度

        image_w:输入图像的宽度

        pad_h:在输入图像的高度方向两边各增加pad_h个单位长度(因为有两边,所以乘以2)

        pad_w:在输入图像的宽度方向两边各增加pad_w个单位长度(因为有两边,所以乘以2)

        kernel_h:卷积核的高度

        kernel_w:卷积核的宽度

        stride_h:高度方向的滑动步长;

        stride_w:宽度方向的滑动步长。

        因此,N为输出图像大小的长宽乘积,也是卷积核在输入图像上滑动可截取的最大特征数。

        K=k*k,表示利用卷积核大小的框在输入图像上滑动所截取的数据大小,与卷积核大小一样大。

(3)在矩阵C中

        矩阵C为矩阵A和矩阵B相乘的结果,得到一个M*N的矩阵,其中每行表示一个输出图像即feature map,共有M个输出图像(输出图像数目等于卷积核数目)


 (在Caffe中是使用src/caffe/util/im2col.cu中的im2col和col2im来完成矩阵的变形和还原操作)

 

 举个例子(方便理解):

     假设有两个卷积核为,因此M=2,kernel_h=2,kernel_w=2,K= kernel_h * kernel_w=4

     输入图像矩阵为,因此image_h=3,image_w=3,令边界扩展为0即pad_h=0,pad_w=0,滑动步长为1,即stride_h=1,stride_w=1

     故N=[(3+2*0-2)/1+1]*[ (3+2*0-2)/1+1]=2*2=4

 

    A矩阵(M*K)为(一行为一个卷积核),B矩阵(K*N)为(B矩阵的每一列为一个卷积核要卷积的大小)

    A 矩阵的由来:::    

    B矩阵的由来:(caffe 有 imtocol.cpp代码,专门用于实现) 



    C=A*B=*=

    C中的分别为两个输出特征图像即feature map。验证了 有几个卷积核就有几个feature map

 

    在Caffe源码中,src/caffe/util/math_functions.cu(如果使用CPU则是src/util/math_functions.cpp)中的caffe_gpu_gemm()函数,其中有两个矩阵A(M*K)

    与矩阵    B(K*N),大家可以通过输出M、K、N的值即相应的矩阵内容来验证上述的原理,代码中的C矩阵与上述的C矩阵不一样,代码中的C矩阵存储的是偏置bias,

    是A  与B相乘后得到M*N大小的矩阵,然后再跟这个存储偏置的矩阵C相加完成卷积过程。如果是跑Mnist训练网络的话,可以看到第一个卷积层卷积过程中,

    M=20,K=25,N=24*24=576。

  (caffe中涉及卷积具体过程的文件主要有:src/caffe/layers/conv_layer.cu、src/caffe/layers/base_conv_layer.cpp、                src/caffe/util/math_functions.cu、src/caffe/util/im2col.cu)

    另外大家也可以参考知乎上贾扬清大神的回答,帮助理解http://www.zhihu.com/question/28385679

  (对于他给出的ppt上的C表示图像通道个数,如果是RGB图像则通道数为3,对应于caffe代码中的变量为src/caffe/layers/base_conv_layer.cpp中

     函数forward_gpu_gemm中的group_)

/********************************************************************************************************/


梳理caffe代码im2col(十七)

caffe中卷积计算详解

Caffe源码解析5:Conv_Layer

Caffe 代码阅读-卷积

Caffe Convolutional Layer 记录

Caffe源码学习系列二----卷积层

caffe卷积层代码阅读笔记

卷积运算转换为矩阵乘法

github上关于卷积操作的可视化介绍

A guide to convolution arithmetic for deep learning

在 Caffe 中如何计算卷积?


Caffe源码(四):base_conv_layer 分析

梳理caffe代码base_conv_layer(十八)

其中caffe_cpu_gemm是对cblas_dgemm函数的封装


Caffe Convolutional Layer 记录






先看base_conv_layer

成员数据如下

/// @brief The spatial dimensions of a filter kernel.

Blob<int> kernel_shape_;
  /// @brief The spatial dimensions of the stride.
  Blob<int> stride_;
  /// @brief The spatial dimensions of the padding.
  Blob<int> pad_;
  /// @brief The spatial dimensions of the dilation.
  Blob<int> dilation_;
  /// @brief The spatial dimensions of the convolution input.
  Blob<int> conv_input_shape_;
  /// @brief The spatial dimensions of the col_buffer.
  vector<int> col_buffer_shape_;
  /// @brief The spatial dimensions of the output.
  vector<int> output_shape_;
  const vector<int>* bottom_shape_;


  int num_spatial_axes_;
  int bottom_dim_;
  int top_dim_;


  int channel_axis_;
  int num_;
  int channels_;
  int group_;
  int out_spatial_dim_;
  int weight_offset_;
  int num_output_;
  bool bias_term_;
  bool is_1x1_;
  bool force_nd_im2col_;



对于卷积层中的卷积操作,还有一个group的概念要说明一下,groups是代表filter 组的个数。引入gruop主要是为了选择性的连接卷基层的输入端和输出端的channels,否则参数会太多。每一个group 和1/ group的input 通道和 1/group 的output通道进行卷积操作。比如有4个input, 8个output,那么1-4属于第一组,5-8属于第二个gruop。



主要函数

1. LayerSetUp 函数:

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {CHECK_EQ(4, bottom[0]->num_axes()) << "Input must have 4 axes, "<< "corresponding to (num, channels, height, width)";// Configure the kernel size, padding, stride, and inputs.ConvolutionParameter conv_param = this->layer_param_.convolution_param();CHECK(!conv_param.has_kernel_size() !=!(conv_param.has_kernel_h() && conv_param.has_kernel_w()))<< "Filter size is kernel_size OR kernel_h and kernel_w; not both";CHECK(conv_param.has_kernel_size() ||(conv_param.has_kernel_h() && conv_param.has_kernel_w()))<< "For non-square filters both kernel_h and kernel_w are required.";CHECK((!conv_param.has_pad() && conv_param.has_pad_h()&& conv_param.has_pad_w())|| (!conv_param.has_pad_h() && !conv_param.has_pad_w()))<< "pad is pad OR pad_h and pad_w are required.";CHECK((!conv_param.has_stride() && conv_param.has_stride_h()&& conv_param.has_stride_w())|| (!conv_param.has_stride_h() && !conv_param.has_stride_w()))<< "Stride is stride OR stride_h and stride_w are required.";if (conv_param.has_kernel_size()) {kernel_h_ = kernel_w_ = conv_param.kernel_size();} else {kernel_h_ = conv_param.kernel_h();kernel_w_ = conv_param.kernel_w();}//用户自定义kernel size 的两种方式CHECK_GT(kernel_h_, 0) << "Filter dimensions cannot be zero.";CHECK_GT(kernel_w_, 0) << "Filter dimensions cannot be zero.";if (!conv_param.has_pad_h()) {pad_h_ = pad_w_ = conv_param.pad();} else {pad_h_ = conv_param.pad_h();pad_w_ = conv_param.pad_w();}if (!conv_param.has_stride_h()) {stride_h_ = stride_w_ = conv_param.stride();} else {stride_h_ = conv_param.stride_h();stride_w_ = conv_param.stride_w();}// Special case: im2col is the identity for 1x1 convolution with stride 1// and no padding, so flag for skipping the buffer and transformation.is_1x1_ = kernel_w_ == 1 && kernel_h_ == 1&& stride_h_ == 1 && stride_w_ == 1 && pad_h_ == 0 && pad_w_ == 0;// Configure output channels and groups.channels_ = bottom[0]->channels();num_output_ = this->layer_param_.convolution_param().num_output();CHECK_GT(num_output_, 0);group_ = this->layer_param_.convolution_param().group();CHECK_EQ(channels_ % group_, 0);CHECK_EQ(num_output_ % group_, 0)<< "Number of output should be multiples of group.";//channel 和 输出 feature map 个数必须为group的整数倍,每个group中只用本group的featrue mapif (reverse_dimensions()) {conv_out_channels_ = channels_;conv_in_channels_ = num_output_;} else {conv_out_channels_ = num_output_;//用户指定输出feature map的数量conv_in_channels_ = channels_;}// Handle the parameters: weights and biases.// - blobs_[0] holds the filter weights// - blobs_[1] holds the biases (optional)bias_term_ = this->layer_param_.convolution_param().bias_term();//默认为 trueif (this->blobs_.size() > 0) {LOG(INFO) << "Skipping parameter initialization";} else {if (bias_term_) {this->blobs_.resize(2);} else {this->blobs_.resize(1);}// Initialize and fill the weights:// output channels x input channels per-group x kernel height x kernel widththis->blobs_[0].reset(new Blob<Dtype>(conv_out_channels_, conv_in_channels_ / group_, kernel_h_, kernel_w_));shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(this->layer_param_.convolution_param().weight_filler()));weight_filler->Fill(this->blobs_[0].get()); //用weight_filler初始化// If necessary, initialize and fill the biases.if (bias_term_) {vector<int> bias_shape(1, num_output_);this->blobs_[1].reset(new Blob<Dtype>(bias_shape));shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(this->layer_param_.convolution_param().bias_filler()));bias_filler->Fill(this->blobs_[1].get());}}// Propagate gradients to the parameters (as directed by backward pass).this->param_propagate_down_.resize(this->blobs_.size(), true);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

2.Reshape 函数:

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {CHECK_EQ(4, bottom[0]->num_axes()) << "Input must have 4 axes, "<< "corresponding to (num, channels, height, width)";//blob是四维数组num_ = bottom[0]->num();height_ = bottom[0]->height();width_ = bottom[0]->width();CHECK_EQ(bottom[0]->channels(), channels_) << "Input size incompatible with"" convolution kernel.";// TODO: generalize to handle inputs of different shapes.for (int bottom_id = 1; bottom_id < bottom.size(); ++bottom_id) {CHECK_EQ(num_, bottom[bottom_id]->num()) << "Inputs must have same num.";CHECK_EQ(channels_, bottom[bottom_id]->channels())<< "Inputs must have same channels.";CHECK_EQ(height_, bottom[bottom_id]->height())<< "Inputs must have same height.";CHECK_EQ(width_, bottom[bottom_id]->width())<< "Inputs must have same width.";}//有多少个bottom 就有多少个top输出,要求每个bottom有相同的shape,因为用的是同一组filter// Shape the tops.compute_output_shape();//在conv_layer中定义,计算输出feature map 的shapefor (int top_id = 0; top_id < top.size(); ++top_id) {top[top_id]->Reshape(num_, num_output_, height_out_, width_out_);}if (reverse_dimensions()) {conv_in_height_ = height_out_;//根据pad情况计算所得输出top 的height,具体如何计算在conv_layer的compute_output_shape()中定义conv_in_width_ = width_out_;conv_out_spatial_dim_ = height_ * width_;} else {conv_in_height_ = height_; //输入bottom 的heightconv_in_width_ = width_;   //输入bottom 的widthconv_out_spatial_dim_ = height_out_ * width_out_;}kernel_dim_ = conv_in_channels_ * kernel_h_ * kernel_w_;//对应一个输出的feature mapweight_offset_ = conv_out_channels_ * kernel_dim_ / group_ / group_;col_offset_ = kernel_dim_ * conv_out_spatial_dim_ / group_;output_offset_ = conv_out_channels_ * conv_out_spatial_dim_ / group_;// The im2col result buffer will only hold one image at a time to avoid// overly large memory usage. In the special case of 1x1 convolution// it goes lazily unused to save memory.if (reverse_dimensions()) {col_buffer_.Reshape(1, kernel_dim_, height_, width_);} else {col_buffer_.Reshape(1, kernel_dim_, height_out_, width_out_);}// Set up the all ones "bias multiplier" for adding biases by BLASif (bias_term_) {vector<int> bias_multiplier_shape(1, height_out_ * width_out_);bias_multiplier_.Reshape(bias_multiplier_shape);caffe_set(bias_multiplier_.count(), Dtype(1),bias_multiplier_.mutable_cpu_data());}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

3.forward_cpu_gemm 函数:


template <typename Dtype>
void BaseConvolutionLayer<Dtype>::forward_cpu_gemm(const Dtype* input,const Dtype* weights, Dtype* output, bool skip_im2col) {const Dtype* col_buff = input;if (!is_1x1_) {if (!skip_im2col) {conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());}col_buff = col_buffer_.cpu_data();}for (int g = 0; g < group_; ++g) {caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, conv_out_channels_ /group_, conv_out_spatial_dim_, kernel_dim_ / group_,(Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,(Dtype)0., output + output_offset_ * g);}
}// 实现卷积操作

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.forward_cpu_bias 函数:

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::forward_cpu_bias(Dtype* output,const Dtype* bicas) {caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, num_output_,height_out_ * width_out_, 1, (Dtype)1., bias, bias_multiplier_.cpu_data(),(Dtype)1., output);
}//卷积后加bias
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4.backward_cpu_gemm函数:

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::backward_cpu_gemm(const Dtype* output,const Dtype* weights, Dtype* input) {Dtype* col_buff = col_buffer_.mutable_cpu_data();if (is_1x1_) {col_buff = input;}for (int g = 0; g < group_; ++g) {caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_ / group_,conv_out_spatial_dim_, conv_out_channels_ / group_,(Dtype)1., weights + weight_offset_ * g, output + output_offset_ * g,(Dtype)0., col_buff + col_offset_ * g);}if (!is_1x1_) {conv_col2im_cpu(col_buff, input);}计算关于bottom data的导数以便传给下一层
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

5.weight_cpu_gemm 函数:

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::weight_cpu_gemm(const Dtype* input,const Dtype* output, Dtype* weights) {const Dtype* col_buff = input;if (!is_1x1_) {conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());col_buff = col_buffer_.cpu_data();}for (int g = 0; g < group_; ++g) {caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,kernel_dim_ / group_, conv_out_spatial_dim_,(Dtype)1., output + output_offset_ * g, col_buff + col_offset_ * g,(Dtype)1., weights + weight_offset_ * g);}
}//计算关于weight的导数用于更新。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

6.backward_cpu_bias 函数:

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::backward_cpu_bias(Dtype* bias,const Dtype* input) {caffe_cpu_gemv<Dtype>(CblasNoTrans, num_output_, height_out_ * width_out_, 1.,input, bias_multiplier_.cpu_data(), 1., bias);
} 计算关于bias的导数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其中用到的一些矩阵运算函数在math_functions.cpp里实现


  • 目录
    • 主要函数
      • caffe_cpu_gemm 函数
      • caffe_cpu_gemv 函数
      • caffe_axpy 函数
      • caffe_set 函数
      • caffe_add_scalar 函数
      • caffe_copy 函数
      • caffe_scal 函数
      • caffeine_cup_axpby 函数
      • caffe_add caffe_sub caffe_mul caffe_div 函数
      • caffe_powx caffe_sqr caffe_exp caffe_abs 函数
      • int caffe_rng_rand 函数
      • caffe_nextafer 函数
      • caffe_cpu_strided_dot 函数
      • caffe_cpu_hamming_distance 函数
      • caffe_cpu_asum 函数
      • caffe_cpu_scale 函数

主要函数

math_function 定义了caffe 中用到的一些矩阵操作和数值计算的一些函数,这里以float类型为例做简单的分析

1. caffe_cpu_gemm 函数:

template<>
void caffe_cpu_gemm<float>(const CBLAS_TRANSPOSE TransA,const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K,const float alpha, const float* A, const float* B, const float beta,float* C) {int lda = (TransA == CblasNoTrans) ? K : M;int ldb = (TransB == CblasNoTrans) ? N : K;cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B,ldb, beta, C, N);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

功能: C=alpha*A*B+beta*C 
A,B,C 是输入矩阵(一维数组格式) 
CblasRowMajor :数据是行主序的(二维数据也是用一维数组储存的) 
TransA, TransB:是否要对A和B做转置操作(CblasTrans CblasNoTrans) 
M: A、C 的行数 
N: B、C 的列数 
K: A 的列数, B 的行数 
lda : A的列数(不做转置)行数(做转置) 
ldb: B的列数(不做转置)行数(做转置)

2. caffe_cpu_gemv 函数:

template <>
void caffe_cpu_gemv<float>(const CBLAS_TRANSPOSE TransA, const int M,const int N, const float alpha, const float* A, const float* x,const float beta, float* y) {cblas_sgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

功能: y=alpha*A*x+beta*y 
其中X和Y是向量,A 是矩阵 
M:A 的行数 
N:A 的列数 
cblas_sgemv 中的 参数1 表示对X和Y的每个元素都进行操作

3.caffe_axpy 函数:

template <>
void caffe_axpy<float>(const int N, const float alpha, const float* X,float* Y) { cblas_saxpy(N, alpha, X, 1, Y, 1); }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

功能: Y=alpha*X+Y 
N:为X和Y中element的个数

4.caffe_set 函数:

template <typename Dtype>
void caffe_set(const int N, const Dtype alpha, Dtype* Y) {if (alpha == 0) {memset(Y, 0, sizeof(Dtype) * N);  // NOLINT(caffe/alt_fn)return;}for (int i = 0; i < N; ++i) {Y[i] = alpha; }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

功能:用常数 alpha 对 Y 进行初始化 
函数 void *memset(void *buffer, char c, unsigned count) 一般为新申请的内存做初始化,功能是将buffer所指向内存中的每个字节的内容全部设置为c指定的ASCII值, count为块的大小

5.caffe_add_scalar 函数:

template <>
void caffe_add_scalar(const int N, const float alpha, float* Y) {for (int i = 0; i < N; ++i) {Y[i] += alpha;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

功能: 给 Y 的每个 element 加上常数 alpha

6.caffe_copy 函数:

template <typename Dtype>
void caffe_copy(const int N, const Dtype* X, Dtype* Y) {if (X != Y) {if (Caffe::mode() == Caffe::GPU) {
#ifndef CPU_ONLY// NOLINT_NEXT_LINE(caffe/alt_fn)CUDA_CHECK(cudaMemcpy(Y, X, sizeof(Dtype) * N, cudaMemcpyDefault));
#elseNO_GPU;
#endif} else {memcpy(Y, X, sizeof(Dtype) * N);  // NOLINT(caffe/alt_fn)}}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

函数 void *memcpy(void *dest, void *src, unsigned int count) 把src所指向的内存区域 copy到dest所指向的内存区域, count为块的大小

7.caffe_scal 函数:

template <>
void caffe_scal<float>(const int N, const float alpha, float *X) {cblas_sscal(N, alpha, X, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

功能:X = alpha*X 
N: X中element的个数

8.caffeine_cup_axpby 函数:

template <>
void caffe_cpu_axpby<float>(const int N, const float alpha, const float* X,const float beta, float* Y) {cblas_saxpby(N, alpha, X, 1, beta, Y, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

功能:Y= alpha*X+beta*Y

9.caffe_add、 caffe_sub、 caffe_mul、 caffe_div 函数:

template <>
void caffe_add<float>(const int n, const float* a, const float* b,float* y) {vsAdd(n, a, b, y);
}
template <>
void caffe_sub<float>(const int n, const float* a, const float* b,float* y) {vsSub(n, a, b, y);
}template <>
void caffe_mul<float>(const int n, const float* a, const float* b,float* y) {vsMul(n, a, b, y);
}template <>
void caffe_div<float>(const int n, const float* a, const float* b,float* y) {vsDiv(n, a, b, y);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

功能:这四个函数分别实现element-wise的加减乘除(y[i] = a[i] + - * \ b[i])

10.caffe_powx、 caffe_sqr、 caffe_exp、 caffe_abs 函数:

template <>
void caffe_powx<float>(const int n, const float* a, const float b,float* y) {vsPowx(n, a, b, y);
}template <>
void caffe_sqr<float>(const int n, const float* a, float* y) {vsSqr(n, a, y);
}template <>
void caffe_exp<float>(const int n, const float* a, float* y) {vsExp(n, a, y);
}template <>
void caffe_abs<float>(const int n, const float* a, float* y) {vsAbs(n, a, y);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

功能 : 同样是element-wise操作,分别是y[i] = a[i] ^ b, y[i] = a[i]^2,y[i] = exp(a[i] ),y[i] = |a[i] |

11.int caffe_rng_rand 函数:

unsigned int caffe_rng_rand() {return (*caffe_rng())();
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

功能:返回一个随机数

12.caffe_nextafer 函数:

template <typename Dtype>
Dtype caffe_nextafter(const Dtype b) {return boost::math::nextafter<Dtype>(b, std::numeric_limits<Dtype>::max());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

功能 : 返回 b 最大方向上可以表示的最接近的数值。

13.caffe_cpu_strided_dot 函数:

template <>
double caffe_cpu_strided_dot<double>(const int n, const double* x,const int incx, const double* y, const int incy) {return cblas_ddot(n, x, incx, y, incy);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

功能: 返回 vector X 和 vector Y 的内积。 
incx, incy : 步长,即每隔incx 或 incy 个element 进行操作。

14.caffe_cpu_hamming_distance 函数:

template <>
int caffe_cpu_hamming_distance<float>(const int n, const float* x,const float* y) {int dist = 0;for (int i = 0; i < n; ++i) {dist += __builtin_popcount(static_cast<uint32_t>(x[i]) ^static_cast<uint32_t>(y[i]));}return dist;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

功能:返回 x 和 y 之间的海明距离。(两个等长字符串之间的海明距离是两个字符串对应位置的不同字符的个数。)

15. caffe_cpu_asum 函数:

template <>
float caffe_cpu_asum<float>(const int n, const float* x) {return cblas_sasum(n, x, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

功能:计算 vector x 的所有element的绝对值之和。

16.caffe_cpu_scale 函数:

template <>
void caffe_cpu_scale<float>(const int n, const float alpha, const float *x,float* y) {cblas_scopy(n, x, 1, y, 1);cblas_sscal(n, alpha, y, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

功能:y = alpha*x




关于deconv

这个概念很混乱,没有统一的定义,在不同的地方出现,意义却不一样。

上采样的卷积层有很多名字:全卷积(full convolution),网络内上采样( in-network upsampling),微步幅卷积(fractionally-strided convolution),反向卷积(backwards convolution),去卷积(deconvolution),上卷积(upconvolution),以及转置卷积(transposed convolution)。用「去卷积」这个术语是非常不推荐的,因为这是一个过载的术语:在数学运算或计算机视觉中的其他应用有着完全不同的含义。



神经网络中,怎样计算caffe中反卷积层(deconv)的感受野(receptive field)

What are deconvolutional layers?



deconv_layer.cpp

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #include <vector>  
  2.   
  3. #include "caffe/layers/deconv_layer.hpp"  
  4.   
  5. namespace caffe {  
  6.   
  7. template <typename Dtype>  
  8. void DeconvolutionLayer<Dtype>::compute_output_shape() {  
  9.   const int* kernel_shape_data = this->kernel_shape_.cpu_data();  
  10.   const int* stride_data = this->stride_.cpu_data();  
  11.   const int* pad_data = this->pad_.cpu_data();  
  12.   const int* dilation_data = this->dilation_.cpu_data();  
  13.   this->output_shape_.clear();  
  14.   for (int i = 0; i < this->num_spatial_axes_; ++i) {  
  15.     // i + 1 to skip channel axis  
  16.     const int input_dim = this->input_shape(i + 1);  
  17.     const int kernel_extent = dilation_data[i] * (kernel_shape_data[i] - 1) + 1;  
  18.     const int output_dim = stride_data[i] * (input_dim - 1)  
  19.         + kernel_extent - 2 * pad_data[i];  
  20.     this->output_shape_.push_back(output_dim);  
  21.   }  
  22. }  
  23.   
  24. template <typename Dtype>  
  25. void DeconvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,  
  26.       const vector<Blob<Dtype>*>& top) {  
  27.   const Dtype* weight = this->blobs_[0]->cpu_data();  
  28.   for (int i = 0; i < bottom.size(); ++i) {  
  29.     const Dtype* bottom_data = bottom[i]->cpu_data();  
  30.     Dtype* top_data = top[i]->mutable_cpu_data();  
  31.     for (int n = 0; n < this->num_; ++n) {  
  32.       this->backward_cpu_gemm(bottom_data + n * this->bottom_dim_, weight,  
  33.           top_data + n * this->top_dim_);  
  34.       if (this->bias_term_) {  
  35.         const Dtype* bias = this->blobs_[1]->cpu_data();  
  36.         this->forward_cpu_bias(top_data + n * this->top_dim_, bias);  
  37.       }  
  38.     }  
  39.   }  
  40. }  
  41.   
  42. template <typename Dtype>  
  43. void DeconvolutionLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,  
  44.       const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {  
  45.   const Dtype* weight = this->blobs_[0]->cpu_data();  
  46.   Dtype* weight_diff = this->blobs_[0]->mutable_cpu_diff();  
  47.   for (int i = 0; i < top.size(); ++i) {  
  48.     const Dtype* top_diff = top[i]->cpu_diff();  
  49.     const Dtype* bottom_data = bottom[i]->cpu_data();  
  50.     Dtype* bottom_diff = bottom[i]->mutable_cpu_diff();  
  51.     // Bias gradient, if necessary.  
  52.     if (this->bias_term_ && this->param_propagate_down_[1]) {  
  53.       Dtype* bias_diff = this->blobs_[1]->mutable_cpu_diff();  
  54.       for (int n = 0; n < this->num_; ++n) {  
  55.         this->backward_cpu_bias(bias_diff, top_diff + n * this->top_dim_);  
  56.       }  
  57.     }  
  58.     if (this->param_propagate_down_[0] || propagate_down[i]) {  
  59.       for (int n = 0; n < this->num_; ++n) {  
  60.         // Gradient w.r.t. weight. Note that we will accumulate diffs.  
  61.         if (this->param_propagate_down_[0]) {  
  62.           this->weight_cpu_gemm(top_diff + n * this->top_dim_,  
  63.               bottom_data + n * this->bottom_dim_, weight_diff);  
  64.         }  
  65.         // Gradient w.r.t. bottom data, if necessary, reusing the column buffer  
  66.         // we might have just computed above.  
  67.         if (propagate_down[i]) {  
  68.           this->forward_cpu_gemm(top_diff + n * this->top_dim_, weight,  
  69.               bottom_diff + n * this->bottom_dim_,  
  70.               this->param_propagate_down_[0]);  
  71.         }  
  72.       }  
  73.     }  
  74.   }  
  75. }  
  76.   
  77. #ifdef CPU_ONLY  
  78. STUB_GPU(DeconvolutionLayer);  
  79. #endif  
  80.   
  81. INSTANTIATE_CLASS(DeconvolutionLayer);  
  82. REGISTER_LAYER_CLASS(Deconvolution);  
  83.   
  84. }  // namespace caffe  


http://imbinwang.github.io/blog/inside-caffe-code-layer

Layer(层)是Caffe中最庞大最繁杂的模块,它是网络的基本计算单元。由于Caffe强调模块化设计,因此只允许每个layer完成一类特定的计算,例如convolution操作、pooling、非线性变换、内积运算,以及数据加载、归一化和损失计算等。

模块说明

每个layer的输入数据来自一些’bottom’ blobs, 输出一些’top’ blobs。Caffe中每种类型layer的参数说明定义在caffe.proto文件中,具体的layer参数值则定义在具体应用的protocals buffer网络结构说明文件中。例如,卷积层(ConvolutionLayer)的参数说明在caffe.proto中是如下定义的,

// in caffe.proto
// Message that stores parameters used by ConvolutionLayer
message ConvolutionParameter {optional uint32 num_output = 1; // The number of outputs for the layer
  optional bool bias_term = 2 [default = true]; // whether to have bias terms
  // Pad, kernel size, and stride are all given as a single value for equal
  // dimensions in height and width or as Y, X pairs.
  optional uint32 pad = 3 [default = 0]; // The padding size (equal in Y, X)
  optional uint32 pad_h = 9 [default = 0]; // The padding height
  optional uint32 pad_w = 10 [default = 0]; // The padding width
  optional uint32 kernel_size = 4; // The kernel size (square)
  optional uint32 kernel_h = 11; // The kernel height
  optional uint32 kernel_w = 12; // The kernel width
  optional uint32 group = 5 [default = 1]; // The group size for group conv
  optional uint32 stride = 6 [default = 1]; // The stride (equal in Y, X)
  optional uint32 stride_h = 13; // The stride height
  optional uint32 stride_w = 14; // The stride width
  optional FillerParameter weight_filler = 7; // The filler for the weight
  optional FillerParameter bias_filler = 8; // The filler for the bias
  enum Engine {DEFAULT = 0;CAFFE = 1;CUDNN = 2;}optional Engine engine = 15 [default = DEFAULT];
}

其中的参数说明包括卷积核的个数、大小和步长等。在examples\mnist\lenet_train_test.prototxt网络结构说明文件中,具体一个卷积层(ConvolutionLayer)是这样定义的,

# in examples\mnist\lenet_train_test.prototxt
layer {name: "conv1" // 层的名字
  type: "Convolution" // 层的类型,说明具体执行哪一种计算
  bottom: "data" // 层的输入数据Blob的名字
  top: "conv1" // 层的输出数据Blob的名字
  param { // 层的权值和偏置相关参数
    lr_mult: 1}param {lr_mult: 2}convolution_param { // 卷积层卷积运算相关的参数
    num_output: 20kernel_size: 5stride: 1weight_filler {type: "xavier"}bias_filler {type: "constant"}}
}

层的输入输出结构,图示是这样的,

layer_img

每种类型的layer需要定义三种关键操作LayerSetUp, Forward, Backward:

  • LayerSetUp: 网络构建时初始化层和层的连接
  • Forward: 网络数据前向传递,给定bottom输入数据,计算输出到top
  • Backward: 网络误差反向传递,给定top的梯度,计算bottom的梯度并存储到bottom blob

实现细节

Caffe中与Layer相关的头文件有7个,

  • layer.hpp: 父类Layer,定义所有layer的基本接口。
  • data_layers.hpp: 继承自父类Layer,定义与输入数据操作相关的子Layer,例如DataLayer,HDF5DataLayer和ImageDataLayer等。
  • vision_layers.hpp: 继承自父类Layer,定义与特征表达相关的子Layer,例如ConvolutionLayer,PoolingLayer和LRNLayer等。
  • neuron_layers.hpp: 继承自父类Layer,定义与非线性变换相关的子Layer,例如ReLULayer,TanHLayer和SigmoidLayer等。
  • loss_layers.hpp: 继承自父类Layer,定义与输出误差计算相关的子Layer,例如EuclideanLossLayer,SoftmaxWithLossLayer和HingeLossLayer等。
  • common_layers.hpp: 继承自父类Layer,定义与中间结果数据变形、逐元素操作相关的子Layer,例如ConcatLayer,InnerProductLayer和SoftmaxLayer等。
  • layer_factory.hpp: Layer工厂模式类,负责维护现有可用layer和相应layer构造方法的映射表。

每个Layer根据自身需求的不同,会定义CPU或GPU版本的实现,例如ConvolutionLayer的CPU和GPU实现就定义在了两个文件中conv_layer.cpp, conv_layer.cu

父类Layer

layer.hpp中定义了Layer的基本接口,成员变量,

protected:/** The protobuf that stores the layer parameters */// 层说明参数,从protocal buffers格式的网络结构说明文件中读取
  LayerParameter layer_param_;/** The phase: TRAIN or TEST */// 层状态,参与网络的训练还是测试
  Phase phase_;/** The vector that stores the learnable parameters as a set of blobs. */// 层权值和偏置参数,使用向量是因为权值参数和偏置是分开保存在两个blob中的
  vector<shared_ptr<Blob<Dtype> > > blobs_;/** Vector indicating whether to compute the diff of each param blob. */// 标志每个top blob是否需要计算反向传递的梯度值
  vector<bool> param_propagate_down_;/** The vector that indicates whether each top blob has a non-zero weight in*  the objective function. */// 非LossLayer为零,LossLayer中表示每个top blob计算的loss的权重
  vector<Dtype> loss_;

构造和析构函数,

/*** You should not implement your own constructor. Any set up code should go* to SetUp(), where the dimensions of the bottom blobs are provided to the* layer.*/
// 显示的构造函数不需要重写,任何初始工作在SetUp()中完成
// 构造方法只复制层参数说明的值,如果层说明参数中提供了权值和偏置参数,也复制
  explicit Layer(const LayerParameter& param): layer_param_(param) {// Set phase and copy blobs (if there are any).
      phase_ = param.phase();if (layer_param_.blobs_size() > 0) {blobs_.resize(layer_param_.blobs_size());for (int i = 0; i < layer_param_.blobs_size(); ++i) {blobs_[i].reset(new Blob<Dtype>());blobs_[i]->FromProto(layer_param_.blobs(i));}}}
// 虚析构
  virtual ~Layer() {}

初始化函数SetUp,每个Layer对象都必须遵循固定的调用模式,

  /*** @brief Implements common layer setup functionality.* @brief 实现每个layer对象的setup函数* @param bottom the preshaped input blobs* @param bottom 层的输入数据,blob中的存储空间已申请* @param top*     the allocated but unshaped output blobs, to be shaped by Reshape* @param top 层的输出数据,blob对象以构造但是其中的存储空间未申请,*     具体空间大小需根据bottom blob大小和layer_param_共同决定,具体在Reshape函数现实** Checks that the number of bottom and top blobs is correct.* Calls LayerSetUp to do special layer setup for individual layer types,* followed by Reshape to set up sizes of top blobs and internal buffers.* Sets up the loss weight multiplier blobs for any non-zero loss weights.* This method may not be overridden.* 1. 检查输入输出blob个数是否满足要求,每个层能处理的输入输出数据不一样* 2. 调用LayerSetUp函数初始化特殊的层,每个Layer子类需重写这个函数完成定制的初始化* 3. 调用Reshape函数为top blob分配合适大小的存储空间* 4. 为每个top blob设置损失权重乘子,非LossLayer为的top blob其值为零** 此方法非虚函数,不用重写,模式固定*/void SetUp(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {CheckBlobCounts(bottom, top);LayerSetUp(bottom, top);Reshape(bottom, top);SetLossWeights(top);}

每个子类Layer必须重写的初始化函数LayerSetUp

  /*** @brief Does layer-specific setup: your layer should implement this function*        as well as Reshape.* @brief 定制初始化,每个子类layer必须实现此虚函数** @param bottom*     the preshaped input blobs, whose data fields store the input data for*     this layer* @param bottom*     输入blob, 数据成员data_和diff_存储了相关数据* @param top*     the allocated but unshaped output blobs* @param top*     输出blob, blob对象已构造但数据成员的空间尚未申请** This method should do one-time layer specific setup. This includes reading* and processing relevent parameters from the <code>layer_param_</code>.* Setting up the shapes of top blobs and internal buffers should be done in* <code>Reshape</code>, which will be called before the forward pass to* adjust the top blob sizes.* 此方法执行一次定制化的层初始化,包括从layer_param_读入并处理相关的层权值和偏置参数,* 调用Reshape函数申请top blob的存储空间*/virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {}

每个子类Layer必须重写的Reshape函数,完成top blob形状的设置并为其分配存储空间,

  /*** @brief Adjust the shapes of top blobs and internal buffers to accomodate*        the shapes of the bottom blobs.* @brief 根据bottom blob的形状和layer_param_计算top blob的形状并为其分配存储空间** @param bottom the input blobs, with the requested input shapes* @param top the top blobs, which should be reshaped as needed** This method should reshape top blobs as needed according to the shapes* of the bottom (input) blobs, as well as reshaping any internal buffers* and making any other necessary adjustments so that the layer can* accomodate the bottom blobs.*/virtual void Reshape(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) = 0;

前向传播函数Forward反向传播函数Backward

inline Dtype Forward(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top);
inline void Backward(const vector<Blob<Dtype>*>& top,const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom);

这两个函数非虚函数,它们内部会调用如下虚函数完成数据前向传递和误差反向传播,根据执行环境的不同每个子类Layer必须重写CPU和GPU版本,

virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) = 0;
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {// LOG(WARNING) << "Using CPU code as backup.";
    return Forward_cpu(bottom, top);}virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom) = 0;virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom) {// LOG(WARNING) << "Using CPU code as backup.";
    Backward_cpu(top, propagate_down, bottom);}

Layer的序列化函数,将layer的层说明参数layer_param_,层权值和偏置参数blobs_复制到LayerParameter对象,便于写到磁盘,

// Serialize LayerParameter to protocol buffer
template <typename Dtype>
void Layer<Dtype>::ToProto(LayerParameter* param, bool write_diff) {param->Clear();param->CopyFrom(layer_param_); // 复制层说明参数layer_param_
  param->clear_blobs();// 复制层权值和偏置参数blobs_
  for (int i = 0; i < blobs_.size(); ++i) {blobs_[i]->ToProto(param->add_blobs(), write_diff);}
}

子类Data Layers

数据经过date layers进入Caffe的数据处理流程,他们位于网络Net最底层。数据可以来自高效的数据库(LevelDB或LMDB),直接来自内存,或者对效率不太关注时,可以来自HDF5格式的或常见图片格式的磁盘文件。Data Layers继承自Layer,继承关系如图所示,

data_layers_class_diagram

最终的子类层包括DataLayer,ImageDataLayer,WindowDataLayer,MemoryDataLayer,HDF5DataLayer,HDF5OutputLayer,DummyDataLayer。这里只分析DataLayer,其它数据层类似。

首先,来看DataLayer的LayerSetUp实现过程,DataLayer直接从父类BasePrefetchingDataLayer继承此方法,

// in base_data_layer.cpp
template <typename Dtype>
void BasePrefetchingDataLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {// 1. 调用父父类BaseDataLayer构造方法,
  BaseDataLayer<Dtype>::LayerSetUp(bottom, top);// Now, start the prefetch thread. Before calling prefetch, we make two
  // cpu_data calls so that the prefetch thread does not accidentally make
  // simultaneous cudaMalloc calls when the main thread is running. In some
  // GPUs this seems to cause failures if we do not so.
  // 2. 访问预取数据空间,这里是为了提前分配预取数据的存储空间
  this->prefetch_data_.mutable_cpu_data();if (this->output_labels_) {this->prefetch_label_.mutable_cpu_data();}// 3. 创建用于预取数据的线程
  DLOG(INFO) << "Initializing prefetch";this->CreatePrefetchThread();DLOG(INFO) << "Prefetch initialized.";
}

执行流程大致为:

  1. 调用父父类BaseDataLayer构造方法,

    // in base_data_layer.cpp
    template <typename Dtype>
    void BaseDataLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {if (top.size() == 1) {output_labels_ = false;} else {output_labels_ = true;}// The subclasses should setup the size of bottom and top
      DataLayerSetUp(bottom, top);data_transformer_.reset(new DataTransformer<Dtype>(transform_param_, this->phase_));data_transformer_->InitRand();
    }
    

    根据top blob的个数判断是否输出数据的label,对output_labels_赋值,接下来调用自己的DataLayerSetUp方法,

    // in data_layer.cpp
    template <typename Dtype>
    void DataLayer<Dtype>::DataLayerSetUp(const vector<Blob<Dtype>*>& bottom,const vector<Blob<Dtype>*>& top) {// Initialize DB
      // 打开源数据库
      db_.reset(db::GetDB(this->layer_param_.data_param().backend()));db_->Open(this->layer_param_.data_param().source(), db::READ);cursor_.reset(db_->NewCursor());// Check if we should randomly skip a few data points
      if (this->layer_param_.data_param().rand_skip()) {unsigned int skip = caffe_rng_rand() %this->layer_param_.data_param().rand_skip();LOG(INFO) << "Skipping first " << skip << " data points.";while (skip-- > 0) {cursor_->Next();}}// Read a data point, and use it to initialize the top blob.
      // 读取一个数据对象, 用于分析数据对象的存储空间大小,并未输出到top blob
      Datum datum;datum.ParseFromString(cursor_->value());bool force_color = this->layer_param_.data_param().force_encoded_color();if ((force_color && DecodeDatum(&datum, true)) ||DecodeDatumNative(&datum)) {LOG(INFO) << "Decoding Datum";}// image
      // 对数据对象进行预处理
      int crop_size = this->layer_param_.transform_param().crop_size();if (crop_size > 0) {// 为top blob分配存储空间,同时为预取数据分配存储空间
        top[0]->Reshape(this->layer_param_.data_param().batch_size(),datum.channels(), crop_size, crop_size);this->prefetch_data_.Reshape(this->layer_param_.data_param().batch_size(),datum.channels(), crop_size, crop_size);this->transformed_data_.Reshape(1, datum.channels(), crop_size, crop_size);} else {top[0]->Reshape(this->layer_param_.data_param().batch_size(), datum.channels(),datum.height(), datum.width());this->prefetch_data_.Reshape(this->layer_param_.data_param().batch_size(),datum.channels(), datum.height(), datum.width());this->transformed_data_.Reshape(1, datum.channels(),datum.height(), datum.width());}LOG(INFO) << "output data size: " << top[0]->num() << ","<< top[0]->channels() << "," << top[0]->height() << ","<< top[0]->width();// label
      if (this->output_labels_) {vector<int> label_shape(1, this->layer_param_.data_param().batch_size());top[1]->Reshape(label_shape);this->prefetch_label_.Reshape(label_shape);}
    }
    

    打开数据源数据库,读取一个数据对象,对数据对象进行预处理,为top blob分配存储空间,同时为预取数据分配存储空间。

  2. 访问预取数据空间,为了提前分配预取数据的存储空间。
  3. 调用CreatePrefetchThread方法,创建用于预取数据的线程。

层初始化的工作完成。接下来看DataLayer的Forward实现过程,因为DataLayer位于网络最底层,因此无需实现Backward。DataLayer直接从父类BasePrefetchingDataLayer继承Forward方法,且只实现了CPU版本Forward_cpu

// in base_data_layer.cpp
template <typename Dtype>
void BasePrefetchingDataLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {// First, join the thread
  // 等待线程的数据预取结束
  JoinPrefetchThread();DLOG(INFO) << "Thread joined";// Reshape to loaded data.
  top[0]->Reshape(this->prefetch_data_.num(), this->prefetch_data_.channels(),this->prefetch_data_.height(), this->prefetch_data_.width());// Copy the data
  // 将预取的数据复制到top blobs
  caffe_copy(prefetch_data_.count(), prefetch_data_.cpu_data(),top[0]->mutable_cpu_data());DLOG(INFO) << "Prefetch copied";if (this->output_labels_) {caffe_copy(prefetch_label_.count(), prefetch_label_.cpu_data(),top[1]->mutable_cpu_data());}// Start a new prefetch thread
  // 创建新线程完成数据预取
  DLOG(INFO) << "CreatePrefetchThread";CreatePrefetchThread();
}

可以看到,DataLayer的Forward_cpu就是通过另一个线程预先取得数据源中的数据,需要时将预取的数据复制到top blobs,完成数据的前向传播。

P.S. 注意到在data_layer.cpp文件的最后,有下面两句宏函数,

INSTANTIATE_CLASS(DataLayer);
REGISTER_LAYER_CLASS(Data);

它们被用来做什么了?看看它们的定义,

// ------ in common.hpp ------
// Instantiate a class with float and double specifications.
#define INSTANTIATE_CLASS(classname) \char gInstantiationGuard##classname; \template class classname<float>; \template class classname<double>
// ------ in common.hpp ------

// ------ in layer_factory.hpp ------
#define REGISTER_LAYER_CREATOR(type, creator)                                  \static LayerRegisterer<float> g_creator_f_##type(#type, creator<float>);     \static LayerRegisterer<double> g_creator_d_##type(#type, creator<double>)    \#define REGISTER_LAYER_CLASS(type)                                             \template <typename Dtype>                                                    \shared_ptr<Layer<Dtype> > Creator_##type##Layer(const LayerParameter& param) \{                                                                            \return shared_ptr<Layer<Dtype> >(new type##Layer<Dtype>(param));           \}                                                                            \REGISTER_LAYER_CREATOR(type, Creator_##type##Layer)
// ------ in layer_factory.hpp ------

其中,INSTANTIATE_CLASS(DataLayer)被用来实例化DataLayer的类模板,REGISTER_LAYER_CLASS(Data)被用来向layer_factory注册DataLayer的构造方法,方便直接通过层的名称(Data)直接获取层的对象。Caffe中内置的层在实现的码的最后都会加上这两个宏。

子类Vision Layers

Vision Layers, 暂时将其翻译成特征表达层,它通常接收“图像”作为输入,输出结果也是“图像”。这里的“图像”可以是真实世界的单通道灰度图像,或RGB彩色图像, 或多通道2D矩阵。在Caffe的上下文环境下,“图像”的显著性特征是它的空间结构:宽w>1,高h>1,这个2D的性质导致Vision Layers具有局部区域操作的性质,例如卷积,池化等。Vision Layers继承自也Layer,继承关系如图所示,

vision_layers_class_diagram

最终的子类层包括ConvolutionLayer,CuDNNConvolutionLayer,PoolingLayer,CuDNNPoolingLayer,LRNLayer,DeconvolutionLayer,还有若干辅助的功能子类层Im2colLayer,SplitLayer。这里会详细分析ConvolutionLayer。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/313188.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文带你了解华为云DevCloud为何能全面领跑中国DevOps云服务市场

近日&#xff0c;国际权威调研机构IDC发布了《IDC MarketScape&#xff1a;中国DevOps云服务市场2019厂商评估》报告&#xff0c;该报告从战略和能力两个维度对国内主流DevOps云厂商进行了评估&#xff0c;报告显示&#xff0c;华为云位于 IDC MarketScape “中国DevOps云服务 …

[电子书制作]Excel催化剂输出内容汇总PDF及Word版本分享

Excel催化剂在2018年开始&#xff0c;陆续写出了230篇高质量原创性文章&#xff0c;将Excel催化剂插件的开发过程及使用方法全方位地通过文字的方式给广大网友们分享了。电子书下载方式同样地&#xff0c;为了减少大家过多繁琐的资料下载途径&#xff0c;电子书的下载路径和之前…

深入理解.NET Core的基元: deps.json, runtimeconfig.json, dll文件

原文链接&#xff1a; Deep-dive into .NET Core primitives: deps.json, runtimeconfig.json, and dlls作者&#xff1a; Nate McMasterC#编译器(The C# Compiler)C#的编译器可以将cs文件转换为dll文件, 即程序集文件。程序集文件是一个便携的可执行格式文件, 借助.NET Core,它…

C# 8.0 的默认接口方法

例子直接看例子有这样一个接口&#xff1a; 然后有三个它的实现类&#xff1a; 然后在main方法里面调用&#xff1a; 截至目前&#xff0c;程序都可以成功的编译和运行。 IPerson接口变更 突然&#xff0c;我想对所有的人类添加一个新的特性&#xff0c;例如&#xff0c;添加一…

convolutional layer 源代码

http://blog.csdn.net/xizero00/article/details/51049858 一、 卷积层的作用简介 卷积层是深度神经网络中的一个重要的层&#xff0c;该层实现了局部感受野&#xff0c;通过这种局部感受野&#xff0c;可以有效地降低参数的数目。我们将结合caffe来讲解具体是如何实现卷积层的…

在.net core3.0中使用SignalR实现实时通信

最近用.net core3.0重构网站&#xff0c;老大想做个站内信功能&#xff0c;就是有些耗时的后台任务的结果需要推送给用户。一开始我想简单点&#xff0c;客户端每隔1分钟调用一下我的接口&#xff0c;看看是不是有新消息&#xff0c;有的话就告诉用户有新推送&#xff0c;但老大…

活动最后72小时:购书优惠劵,折后再折,赶紧来抢啊

1024程序员节当当网计算机图书每满100减50&#xff01;满200减100&#xff01;满300-150&#xff01;机械工业出版社华章公司联合当当网特意为【DotNET技术圈】用户申请了一批可与满减叠加使用的“满200减30”的图书优惠码&#xff0c;优惠码使用后相当于&#xff1a;400减230 …

C# 8 新特性 - using 声明

using语句块 尽管.NET Core运行时有垃圾收集器&#xff08;GC&#xff09;来负责内存清理工作&#xff0c;但是我们还是要自己确保当非托管资源不再使用的时候应该被清理掉。以前针对实现了IDisposable接口的对象&#xff0c;我们经常会使用using 语句块来这样做&#xff1a; 这…

.Net Core3.0依赖注入DI

构建ASP.NET Core应用程序的时候&#xff0c;依赖注入已成为了.NET Core的核心&#xff0c;这篇文章&#xff0c;我们理一理依赖注入的使用方法。不使用依赖注入首先&#xff0c;我们创建一个ASP.NET Core Mvc项目&#xff0c;定义个表达的爱服务接口&#xff0c;中国小伙类实现…

.Net轻量状态机Stateless

很多业务系统开发中&#xff0c;不可避免的会出现状态变化&#xff0c;通常采用的情形可能是使用工作流去完成&#xff0c;但是对于简单场景下&#xff0c;用工作流有点大财小用感觉&#xff0c;比如订单业务中&#xff0c;订单状态的变更&#xff0c;涉及到的状态量不是很多&a…

Asp.net Core全局异常监控和记录日志

前言系统异常监控可以说是重中之重&#xff0c;系统不可能一直运行良好&#xff0c;开发和运维也不可能24小时盯着系统&#xff0c;系统抛异常后我们应当在第一时间收到异常信息。在Asp.net Core里我使用拦截器和中间件两种方式来监控异常。全局异常监控的数据最好还是写入数据…

SiteServer CMS 新版本 V6.13(2019年11月1日发布)

欢迎来到 SiteServer CMS V6.13 版本&#xff0c;经过两个月的连续迭代开发&#xff0c;V6.13版本新增了几项重要功能&#xff0c;我们希望你会喜欢&#xff0c;一些关键的亮点包括&#xff1a;。新增功能及BUG 修复经过两个月的连续迭代开发&#xff0c;V6.13 版本新增了部分功…

CUDA的global内存访问的问题

http://blog.csdn.net/OpenHero/article/details/3520578 关于CUDA的global内存访问的问题&#xff0c;怎么是访问的冲突&#xff0c;怎样才能更好的访问内存&#xff0c;达到更高的速度。下面先看几张图&#xff0c;这些图都是CUDA编程手册上的图&#xff0c;然后分别对这些…

C# 8 新特性 - 异步流 Asynchronous Streams

异步流 Asynchronous Streams例子 这是一个很简单的控制台程序。它有一个NumberFactory&#xff0c;它可以根据传递的参数来产生一串数字&#xff08;IEnumerable<int>&#xff09;。然后在这个程序中把每个数字都打印出来&#xff0c;同时在前边显示出当前的线程ID。 这…

__syncthreads()

http://www.cnblogs.com/dwdxdy/p/3215136.html __syncthreads()是cuda的内建函数&#xff0c;用于块内线程通信. __syncthreads() is you garden variety thread barrier. Any thread reaching the barrier waits until all of the other threads in that block also reach i…

互联网50周年!这有它的一张“出生证明”

2019 年 10 月 29 日是互联网的 50 周年&#xff0c;50 年前(1969 年 10 月 29 日)&#xff0c;加州大学洛杉矶分校的计算机将一个只有两个字母(LO)的数据包发送到斯坦福研究所的计算机上&#xff0c;这是互联网史上的第一个数据包&#xff0c;从此开启互联网时代的第一步。 当…

Eltwise_layer简介

http://www.voidcn.com/blog/thy_2014/article/p-6117416.html common_layer&#xff1a; ArgMaxLayer类&#xff1b; ConcatLayer类&#xff1a; EltwiseLayer类&#xff1b; FlattenLayer类&#xff1b; InnerProductLayer类&#xff1b; MVNLayer类&#xff1b; SilenceLaye…

PowerBI 秒级实时大屏展示方案 全面助力双十一

双十一来了&#xff0c;你准备好了吗&#xff1f;不管你是否准备完毕&#xff0c;我们带来了全网首发的 PowerBI 秒级实时大屏展示方案&#xff0c;你可以直接用来展示双十一的实时状况。我们一步步来说明这个套件模板教程。真实效果功能如下&#xff1a;全实时展示 双十一 当天…

优化 .net core 应用的 dockerfile

优化 .net core 应用的 dockerfileIntro在给 .net core 应用的写 dockerfile 的时候一直有个苦恼&#xff0c;就是如果有很多个项目&#xff0c;在 dockerfile 里写起来就会很繁琐&#xff0c;有很多项目文件要 copy&#xff0c;dockerfile 还不支持直接批量复制项目文件&#…

C# 8 新特性 - 静态本地方法

从C# 8 开始&#xff0c;本地方法就可以是静态的了。 与其他的本地方法不同&#xff0c;静态的本地方法无法捕获任何本地状态量。 直接看例子&#xff1a; 这段代码里有两个本地方法&#xff0c;他们分别对实例的一个字段和方法里的一个本地变量进行了修改操作&#xff0c;也就…