图像卷积的实现
矩阵相乘
卷积是乘累加运算,把图像和卷积核展开成列,可用矩阵相乘加速运算。比如,输入feature map是32*32*3,卷积核是5*5*3*20,padding是1,stride是1,则展开后得到的feature map矩阵宽度是5*5*3,高度是((32+2*1-5)/1+1)*((32+2*1-5)/1+1),卷积核矩阵高度是5*5*3,宽度是20。
Caffe中相关代码在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的第一个元素开始
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++) {
int input_row = -pad_h + kernel_row * dilation_h;
for (int output_rows = output_h; output_rows; output_rows--) {
if (!is_a_ge_zero_and_a_lt_b(input_row, height)) {
//Padding
for (int output_cols = output_w; output_cols; output_cols--) {
*(data_col++) = 0;
}
} else {
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)) {
*(data_col++) = data_im[input_row * width + input_col];
} else {
*(data_col++) = 0;
}
input_col += stride_w;
}
}
input_row += stride_h;
}
}
}
}
}
手写前馈网络
前馈网络实际就是一些矩阵运算。这里我们选用Armadillo作为矩阵库,这是个操作比较接近matlab的库。首先我们把Caffe中的一些基础的层剥离出来。然后再实现我们自己的前馈网络。代码放在github上:https://github.com/goodluckcwl/simple-cnn
最终的速度是:i5 CPU上单张55*47的图,一次前馈20-30ms