【caffe】卷积层代码解析
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】卷积层代码解析的更多相关文章
- caffe卷积层代码阅读笔记
卷积的实现思想: 通过im2col将image转为一个matrix,将卷积操作转为矩阵乘法运算 通过调用GEMM完毕运算操作 以下两个图是我在知乎中发现的,"盗"用一下,确实非常好 ...
- caffe卷积层实现
下图是jiayangqing在知乎上的回答,其实过程就是把image转换成矩阵,然后进行矩阵运算 卷积的实现在conv_layer层,conv_layer层继承了base_conv_layer层,ba ...
- caffe 卷积层的运算
贾清扬寻找快速算法之路:https://github.com/Yangqing/caffe/wiki/Convolution-in-Caffe:-a-memo 卷积运算图文并茂:http://www. ...
- 卷积神经网络CNN的原理(三)---代码解析
卷积神经网络在几个主流的神经网络开源架构上面都有实现,我这里不是想实现一个自己的架构,主要是通过分析一个最简单的卷积神经网络实现代码,来达到进一步的加深理解卷积神经网络的目的. 笔者在github上找 ...
- caffe之(一)卷积层
在caffe中,网络的结构由prototxt文件中给出,由一些列的Layer(层)组成,常用的层如:数据加载层.卷积操作层.pooling层.非线性变换层.内积运算层.归一化层.损失计算层等:本篇主要 ...
- caffe源码 卷积层
通俗易懂理解卷积 图示理解神经网络的卷积 input: 3 * 5 * 5 (c * h * w) pading: 1 步长: 2 卷积核: 2 * 3 * 3 * 3 ( n * c * k * k ...
- TensorFlow与caffe中卷积层feature map大小计算
刚刚接触Tensorflow,由于是做图像处理,因此接触比较多的还是卷及神经网络,其中会涉及到在经过卷积层或者pooling层之后,图像Feature map的大小计算,之前一直以为是与caffe相同 ...
- caffe Python API 之卷积层(Convolution)
1.Convolution层: 就是卷积层,是卷积神经网络(CNN)的核心层. 层类型:Convolution lr_mult: 学习率的系数,最终的学习率是这个数乘以solver.prototxt配 ...
- tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图
tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图 因为很多 demo 都比较复杂,专门抽出这两个函数,写的 demo. 更多教程:http://www.tensorflown ...
随机推荐
- Q: Why can't I access the Site Settings of my SharePoint site? 'File Not Found'
Q: I am trying to access the Site Settings of my SharePoint site, but I get a File Not Found error, ...
- 【BZOJ1132】[POI2008]Tro 几何
[BZOJ1132][POI2008]Tro Description 平面上有N个点. 求出所有以这N个点为顶点的三角形的面积和 N<=3000 Input 第一行给出数字N,N在[3,3000 ...
- 【BZOJ1007】[HNOI2008]水平可见直线 半平面交
[BZOJ1007][HNOI2008]水平可见直线 Description 在xoy直角坐标平面上有n条直线L1,L2,...Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为可见 ...
- fedora找开ftpd服务器并以root登陆
工作原因需要在federal中弄个vsftpd再用root去登陆(我知道这样不太安全) 确认系统的版本 [root@localhost ~]# uname -a Linux localhost.loc ...
- POJ 2856 Y2K Accounting Bug【简单暴力】
链接: http://poj.org/problem?id=2586 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=26733#probl ...
- spring属性注入方式
一.使用有参构造注入属性 配置文件 constructor-arg标签是需注入属性的名字 User类 生成了User的有参构造函数 测试类 结果 打印出了name属性的值 二.使用set方法注入属性 ...
- 变分推断(Variational Inference)
变分 对于普通的函数f(x),我们可以认为f是一个关于x的一个实数算子,其作用是将实数x映射到实数f(x).那么类比这种模式,假设存在函数算子F,它是关于f(x)的函数算子,可以将f(x)映射成实数F ...
- Python赋值原理:Python无变量,万物皆对象
有几个和以前的常见语言,比如c语言不同 改变变量数据不覆盖原来的 name = '苍老师' print(id(name)) name = '志玲' print(id(name)) 运行结果 73955 ...
- maven 手动加载第三方jar、zip包
使用maven搭建工程时,难免要加载大量的第三方的jar包.zip包比较少用,而maven的官网提供的jar往往不能满足需求,这时需要我们手动加载到我们本地或nexus私服的仓库中. 1.加载jar包 ...
- PullToRefresh下拉刷新
https://github.com/chrisbanes/Android-PullToRefresh