1.Forward_cpu

conv_layer.cpp

template <typename Dtype>
void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
// blobs_声明在 layer.hpp 中,vector<shared_ptr<Blob<Dtype> > > blobs_;
// 用于存放学习得到的参数权值 weight 和偏置参数 bias
// weight(blobs_[0])和bias(blobs_[1])分别存放在两个blob中
const Dtype* weight = this->blobs_[0]->cpu_data(); // 对bottom中所有blob进行前向卷积运算
for (int i = 0; i < bottom.size(); ++i) {
const Dtype* bottom_data = bottom[i]->cpu_data();
Dtype* top_data = top[i]->mutable_cpu_data(); // 对一个Batch的每一张图片进行前向计算
// num_定义在caffe.proto中,即在caffe.pb.h中,为BatchSize大小
for (int n = 0; n < this->num_; ++n) { // 基类的forward_cpu_gemm函数 base_conv_layer.cpp
// 计算的是top_data[n * this->top_dim_] =
// weights * bottom_data[n * this->bottom_dim_] // bottom_dim_, bias_term和top_dim定义在base_conv_layer.hpp中,
// int bottom_dim_; 大小默认为 C_in*H_in*W_in
// int top_dim_; 大小默认为 C_out*H_out*W_out
// bool bias_term_; 是否使用偏置项
this->forward_cpu_gemm(bottom_data + n * this->bottom_dim_, weight,
top_data + n * this->top_dim_);
if (this->bias_term_) {
const Dtype* bias = this->blobs_[1]->cpu_data();
this->forward_cpu_bias(top_data + n * this->top_dim_, bias);
}
}
}
}

base_conv_layer.cpp

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) {
// 如果没有1x1卷积,也没有skip_im2col
// 则使用conv_im2col_cpu对使用卷积核滑动过程中的
// 每一个kernel大小的三维图像块
// 变成一个列向量,形成一个
// height = C_in * kernel_h * kernel_w
// width = output_h * output_w 的矩阵
conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());
}
// 获取im2col后得到的矩阵。注意col_buff和col_buffer_不同
col_buff = col_buffer_.cpu_data();
} // 使用caffe的cpu_gemm来进行计算
for (int g = 0; g < group_; ++g) {
// g=0,group_=1,所以传递的参数为:
// conv_out_channels_ :卷积层的输出通道
// conv_out_spatial_dim_: H_out*W_out
// kernel_dim_: C_in*H_k*W_k
// weight: 卷积核参数指针
// col_offset:图片展成的列向量
// output: 输出
/*
功能: C = alpha*A*B+beta*C
A,B,C 是输入矩阵(一维数组格式)
所以为:output = 1.*weights*col_buff+0*output
其中weights矩阵的维数为 [C_out,C_in*H_k*W_k]
col_buff矩阵的维数为 [C_in*H_k*W_K, H_out*W_out]
所以得到的结果output的维数为 [C_out, H_out*W_out]
*/ /*
* 滤波器权值没有经行相应的转换是因为,权值和数据都是一维数组存储的
* 但数据是按行存储的,所以需要im2col,而滤波器权值本来就是那样存放的
* 区别在于滤波器权值是学习得到的,而图像是给定的
*/
caffe_cpu_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);
}
}

  

base_conv_layer.hpp

  inline void conv_im2col_cpu(const Dtype* data, Dtype* col_buff) {
if (!force_nd_im2col_ && num_spatial_axes_ == 2) {
// 如果不是计算n维通用卷积且计算的是二维卷积
// num_spatial_axes定义在base_conv_layer.hpp中
// 含义是计算二维卷积还是三维卷积,
// 对于(N, C, H, W)的输入结果为2
// 对于(N, C, D, H, W)的输入,结果为3
im2col_cpu(data, conv_in_channels_,
conv_input_shape_.cpu_data()[1], conv_input_shape_.cpu_data()[2],
kernel_shape_.cpu_data()[0], kernel_shape_.cpu_data()[1],
pad_.cpu_data()[0], pad_.cpu_data()[1],
stride_.cpu_data()[0], stride_.cpu_data()[1],
dilation_.cpu_data()[0], dilation_.cpu_data()[1], col_buff);
} else {
im2col_nd_cpu(data, num_spatial_axes_, conv_input_shape_.cpu_data(),
col_buffer_shape_.data(), kernel_shape_.cpu_data(),
pad_.cpu_data(), stride_.cpu_data(), dilation_.cpu_data(), col_buff);
}
}

  

im2col.cpp

template <typename Dtype>
void im2col_cpu(const Dtype* data_im, const int channels,
const int height, const int width, const int kernel_h, const int kernel_w,
const int pad_h, const int pad_w,
const int stride_h, const int stride_w,
const int dilation_h, const int dilation_w,
Dtype* data_col) {
// 通用公式,其中包含了卷积核的膨胀操作
const int output_h = (height + 2 * pad_h -
(dilation_h * (kernel_h - 1) + 1)) / stride_h + 1;
const int output_w = (width + 2 * pad_w -
(dilation_w * (kernel_w - 1) + 1)) / stride_w + 1;
const int channel_size = height * width;
// 先遍历每一个通道的图像矩阵(不是顺序遍历像素)
// 然后遍历每一个kernel(遍历kernel,而不是遍历kernel内的元素)
// 不是依次遍历kernel中的所有元素,而是每次只取kernel中某一个位置的元素
// 需要遍历多次所有的kernel,每遍历一次所有的kernel
// 所有kernel中的某个位置的元素被遍历,假如kernel大小为3*3,
// 那么需要遍历9次所有的kernel才能遍历完图像矩阵的所有像素
// 因为kernel有9个元素,每次kernel的遍历只取一个位置,
// 那么如果遍历某个kernel中的所有元素,那么就需要9次遍历
for (int channel = channels; channel--; data_im += channel_size) {
for (int kernel_row = 0; kernel_row < kernel_h; kernel_row++) {
for (int kernel_col = 0; kernel_col < kernel_w; kernel_col++) {
// 计算将要访问的元素在输入矩阵中的行数(此矩阵为pad操作后的矩阵)
int input_row = -pad_h + kernel_row * dilation_h;
for (int output_rows = output_h; output_rows; output_rows--) {
// is_a_ge_zero_and_a_lt_b(a, b) 含义为
// 判断 0<= a < b 是否成立
// 如果input_row和height不满足条件,说明input_row位于pad行
if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
for (int output_cols = output_w; output_cols; output_cols--) {
*(data_col++) = 0;
}
} else {
// 计算将要访问的元素在输入矩阵中的列数(此矩阵为pad操作后的矩阵)
int input_col = -pad_w + kernel_col * dilation_w;
for (int output_col = output_w; output_col; output_col--) {
if (is_a_ge_zero_and_a_lt_b(input_col, width)) {
// 依次复制该行所有kernel中的固定位置的元素
*(data_col++) = data_im[input_row * width + input_col];
} else {
// 如果input_col和width不满足条件,说明input_col位于pad列
*(data_col++) = 0;
}
// 依次遍历input_row行中所有kernel
input_col += stride_w;
}
}
// 遍历下一行的kernel组
input_row += stride_h;
}
}
}
}
}

  

math_functions.cpp

/*
功能: 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的列数(不做转置)行数(做转置)
A:[M,K]
B:[K,N]
*/
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);
}

  

cblas

void cblas_sgemm(const enum CBLAS_ORDER Order, const enum CBLAS_TRANSPOSE TransA,
const enum CBLAS_TRANSPOSE TransB, const int M, const int N,
const int K, const float alpha, const float *A,
const int lda, const float *B, const int ldb,
const float beta, float *C, const int ldc)
/*
* 功能为计算 C = alpha*op(A)*op(B) + beta*C
*
* const enum CBLAS_ORDER Order,这是指的数据的存储形式,
* 在CBLAS的函数中无论一维还是二维数据都是用一维数组存储,
* 这就要涉及是行主序还是列主序,在C语言中数组是用行主序,fortran中是列主序
*
* CblasNoTrans表示矩阵是否转置
* 不转置则 op(A) = A
* 转置则 op(A) = A'
*
* const int M,矩阵A的行,矩阵C的行
* const int N,矩阵B的列,矩阵C的列
* const int K,矩阵A的列,矩阵B的行
*
* const float alpha,const float beta,计算公式中的两个参数值
*
* const float* A, const float* B, const float* C,矩阵A、B、C的数据
*
* const int lda, const int ldb, const int ldc
* 不转置 ld* = max(1, 列数)
* 转置 ld* = max(1, 行数)
*/

  

2.Backward_cpu

conv_layer.cpp

template <typename Dtype>
void ConvolutionLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { const Dtype* weight = this->blobs_[0].cpu_data();
Dtype* weight_diff = this->blobs_[0].mutable_cpu_diff(); for (int i = 0; i < top.size(); i++) { // 上一层传下来的导数
const Dtype* top_diff = top[i].cpu_diff();
const Dtype* bottom_data = bottom[i].cpu_data(); //传给下一层的导数
Dtype* bottom_diff = bottom[i].mutable_cpu_diff(); // 如果有bias项,计算Bias导数
if (this->bias_term_ && this->param_propagate_down_[1]) {
for (int n = 0; n < this->num_; ++n) {
this->Backward_cpu_bias(bias_diff, top_diff + n * this->top_dim_);
}
} // 计算weight
// param_propagate_down_定义在layer.hpp中,vector<bool> param_propagate_down_;
// 标志该层每个可学习参数blob是否需要计算反向传递的梯度值
// param_propagate_down_[0] - weight; param_propagate_down_[1] - bias if (this->param_propagate_down_[0] || propagate_down[i]) {
for (int n = 0; n < this->num_; ++n) {
// 计算对于权值的梯度
if (this->param_propagate_down_[0]) {
// weight_diff = bottom_data * top_diff
this->weight_cpu_gemm(bottom_data + n * this->bottom_dim_,
top_diff + n * this->top_dim_, weight_diff);
}
// 计算传播到下一层的梯度,即对于下一层来说是top_diff
// 对于这个层来说是 bottom_diff
if (propagate_down[i]) {
// bottom_diff = top_diff * weight
// bottom_diff [C_in*H_k*W_k, H_out*W_out]
// top_diff [C_out, H_out*W_out]
// weight [C_out, C_in*H_k*W_K]
this->backward_cpu_gemm(top_diff + n * this->top_dim_, weight,
bottom_diff _ n * this->bottom_dim_);
}
}
}
}
}

  

base_conv_layer.cpp

template <typename Dtype>
void BaseConvolutionLayer<Dtype>::weight_cpu_gemm(const Dtype* input,
const Dtype* output, Dtype* weights) {
const Dtype* col_buff = input; // 不使用 1*1 卷积
if (!is_1x1_) {
// 矩阵转换
conv_im2col_cpu(input, col_buffer_.mutable_cpu_data());
col_buff = col_buffer_.cpu_data();
}
// 前向:output = weights * col_buff
// 反向:weights = output * col_buff^T
// 这是对滤波器权值求导
for (int g = 0; g < group_; ++g) {
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, conv_out_channels_ / group_,
kernel_dim_, conv_out_spatial_dim_,
(Dtype)1., output + output_offset_ * g, col_buff + col_offset_ * g,
(Dtype)1., weights + weight_offset_ * g);
}
}

  

base_conv_layer.cpp

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(); // 使用1*1卷积
if (is_1x1_) {
col_buff = input;
} // 前向:output = weights * col_buff
// 反向:col_buff = weights^T * output
// 这是对bottom_data即输入数据求梯度
for (int g = 0; g < group_; ++g) {
caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, kernel_dim_,
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_) {
// Blob中数据是row-major存储的,W(列)是变化最快的维度
conv_col2im_cpu(col_buff, input);
}
}

  

  

  

【caffe】卷积层代码解析的更多相关文章

  1. caffe卷积层代码阅读笔记

    卷积的实现思想: 通过im2col将image转为一个matrix,将卷积操作转为矩阵乘法运算 通过调用GEMM完毕运算操作 以下两个图是我在知乎中发现的,"盗"用一下,确实非常好 ...

  2. caffe卷积层实现

    下图是jiayangqing在知乎上的回答,其实过程就是把image转换成矩阵,然后进行矩阵运算 卷积的实现在conv_layer层,conv_layer层继承了base_conv_layer层,ba ...

  3. caffe 卷积层的运算

    贾清扬寻找快速算法之路:https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo 卷积运算图文并茂:http://www. ...

  4. 卷积神经网络CNN的原理(三)---代码解析

    卷积神经网络在几个主流的神经网络开源架构上面都有实现,我这里不是想实现一个自己的架构,主要是通过分析一个最简单的卷积神经网络实现代码,来达到进一步的加深理解卷积神经网络的目的. 笔者在github上找 ...

  5. caffe之(一)卷积层

    在caffe中,网络的结构由prototxt文件中给出,由一些列的Layer(层)组成,常用的层如:数据加载层.卷积操作层.pooling层.非线性变换层.内积运算层.归一化层.损失计算层等:本篇主要 ...

  6. caffe源码 卷积层

    通俗易懂理解卷积 图示理解神经网络的卷积 input: 3 * 5 * 5 (c * h * w) pading: 1 步长: 2 卷积核: 2 * 3 * 3 * 3 ( n * c * k * k ...

  7. TensorFlow与caffe中卷积层feature map大小计算

    刚刚接触Tensorflow,由于是做图像处理,因此接触比较多的还是卷及神经网络,其中会涉及到在经过卷积层或者pooling层之后,图像Feature map的大小计算,之前一直以为是与caffe相同 ...

  8. caffe Python API 之卷积层(Convolution)

    1.Convolution层: 就是卷积层,是卷积神经网络(CNN)的核心层. 层类型:Convolution lr_mult: 学习率的系数,最终的学习率是这个数乘以solver.prototxt配 ...

  9. tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图

    tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图 因为很多 demo 都比较复杂,专门抽出这两个函数,写的 demo. 更多教程:http://www.tensorflown ...

随机推荐

  1. 打包合并多个dll

    复杂项目中会引用大量的第三方dll文件,为了便于管理会尝试把相关打包合并成一个dll文件. 推荐使用ILMerge,如需使用网上自行下载. 使用方法: cd 安装目录 ILmerge /target: ...

  2. Springboot整合日志时候出现的问题

    上图是问题,按照路径去找下,发现其实是jar包重复导致的! 在对应的项目上,右键--->属性(Properties)--->JavaBuild Path  然后选择Libraries 页签 ...

  3. Java基础 - 变量的定义和使用

    变量定义 public class Main { public static void main(String[] args) { // 定义byte类型的变量 byte b = 10; System ...

  4. Virtualbox报错------> VirtualBox虚拟机下鼠标不正常的解决方法

    在Virtualbox虚拟机下,突然发现鼠标使用不正常.出现2个鼠标,一个是Ubuntu主机下面的鼠标,一个是Window7下的鼠标,但是Win7下的鼠标不可以看得到,但是点击鼠标左右键可以看到有反应 ...

  5. linux c编程:进程控制(二)_竞争条件

    前面介绍了父子进程,如果当多个进程企图对共享数据进行处理.而最后的结果又取决于进程运行的顺序时,就认为发生了竞争关系.通过下面的例子来看下 在这里标准输出被设置为不带缓冲的,于是父子进程每输出一个字符 ...

  6. 销售订单、外向交货单、交货 bapi

    转自[http://www.cnblogs.com/elegantok/archive/2009/10/18/1585398.html]***********SALES ORDER INPUT CRE ...

  7. tomcat调试页面的时候,不刷新

    1.调试一个页面的时候,js文件不刷新,也就是相当于没有改(cache) 2.解决:在Server中将当前tomcat的模式改为debug即可

  8. sql获取数组指定元素

    需求:获取字符数组1,2,3的第2个元素 方法:通过自定义函数来实现 /* 获取字符串数组某个元素 */ from sysobjects where id = object_id('Get_StrAr ...

  9. 模块化(CommonJs、AMD、CMD、UMD)发展历史与优缺点

    全文主要整理自摘自<Webpack中文指南>(好文,建议直接去看,以下仅对该系列文章中的<历史发展>篇幅进行备份——也整理了点其他内容) 模块化 模块化是老生常谈了,这里不做阐 ...

  10. c#命名规则参考

    命名规则参考:1.从组件类型名中移去T前缀.例如TButton变成Button.2.除了第一个元音,删去所有元音字母.例如,Button变成bttn,Edit变成edt.3.压缩双字母.例如,bttn ...