以手写字符集mnist为例介绍caffe卷积层的实现方式。
1.卷积层C1 源码
在卷积层类ConvolutionLayer中,batch(训练 64*3*28*28,测试 100*3*28*28)中每个样本循环调用前向运算ConvolutionLayer::Forward_cpu()函数计算featrue map。
函数源码如下:
template <typename Dtype>
void ConvolutionLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
const Dtype* weight = this->blobs_[0]->cpu_data();
for (int i = 0; i < bottom.size(); ++i) {
const Dtype* bottom_data = bottom[i]->cpu_data();
Dtype* top_data = top[i]->mutable_cpu_data();
for (int n = 0; n < this->num_; ++n) {
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);
}
}
}
}
Forward_cpu()函数内部又调用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_,
(Dtype)1., weights + weight_offset_ * g, col_buff + col_offset_ * g,
(Dtype)0., output + output_offset_ * g);
}
}
其中:
input:输入原始图像指针 【3*28*28】 ;(注意:为了验证多通道卷积原理,此处将mnist数据集改为3通道)
weights:卷积核指针 【 3*5*5】;
output:输出feature map 【20*24*24】
该函数内部又调用conv_im2col_cpu()和caffe_cpu_gemm();
总的调用关系如下:
conv_im2col_cpu()函数实现将多通道图像展开成大的矩阵,有利于后续将卷积操作转化为矩阵相乘操作。caffe_cpu_gemm()主要实现矩阵相乘。
2. caffe多个输入通道和多个输出通道的卷积实现方式
当有Ci个输入通道,Co个输出通道,则需要训练的卷积核为Co*Ci个。其中,Ci个不同的卷积核分别与Ci个图像通道进行卷积(Ci个核为一批),然后将Ci个输出求和输出一个卷积结果。其他卷积层输出与以上操作一致。
conv_im2col_cpu()是关键,函数功能:
将多通道图像展开成矩阵,有利于后续将卷积操作变换为矩阵相乘。展开方式为:按照卷积核的大小展开成列。
- 展开后的大矩阵的行为:Ci个核的总像素数
- 展开后的大矩阵的列为:卷积结果图像像素数