博客:blog.shinelee.me | 博客园 | CSDN

im2col实现

如何将卷积运算转为矩阵相乘?直接看下面这张图,以下图片来自论文High Performance Convolutional Neural Networks for Document Processing



上图为3D卷积的传统计算方式与矩阵乘法计算方式的对比,传统卷积运算是将卷积核以滑动窗口的方式在输入图上滑动,当前窗口内对应元素相乘然后求和得到结果,一个窗口一个结果。相乘然后求和恰好也是向量内积的计算方式,所以可以将每个窗口内的元素拉成向量,通过向量内积进行运算,多个窗口的向量放在一起就成了矩阵,每个卷积核也拉成向量,多个卷积核的向量排在一起也成了矩阵,于是,卷积运算转化成了矩阵运算。

下图为转化后的矩阵尺寸,padding为0:



代码上怎么实现呢?这里参看一下SeetaFaceEngine/FaceIdentification/src/conv_net.cpp 中的代码,与上面的图片对照着看比较直观。

int dst_h = (src_h - kernel_h) / stride_h_ + 1; // int src_h = input->height(); int kernel_h = weight->height();
int dst_w = (src_w - kernel_w) / stride_w_ + 1; // int src_w = input->width(); int kernel_w = weight->width();
int end_h = src_h - kernel_h + 1;
int end_w = src_w - kernel_w + 1;
int dst_size = dst_h * dst_w;
int kernel_size = src_channels * kernel_h * kernel_w; const int src_num_offset = src_channels * src_h * src_w; // int src_channels = input->channels();
float* const dst_head = new float[src_num * dst_size * dst_channels];
float* const mat_head = new float[dst_size * kernel_size]; const float* src_data = input->data().get();
float* dst_data = dst_head;
int didx = 0; for (int sn = 0; sn < src_num; ++sn) {
float* mat_data = mat_head;
for (int sh = 0; sh < end_h; sh += stride_h_) {
for (int sw = 0; sw < end_w; sw += stride_w_) {
for (int sc = 0; sc < src_channels; ++sc) {
int src_off = (sc * src_h + sh) * src_w + sw;
for (int hidx = 0; hidx < kernel_h; ++hidx) {
memcpy(mat_data, src_data + src_off,
sizeof(float) * kernel_w);
mat_data += kernel_w;
src_off += src_w;
}
} // for sc
} // for sw
} // for sh
src_data += src_num_offset; const float* weight_head = weight->data().get();
// int dst_channels = weight->num();
matrix_procuct(mat_head, weight_head, dst_data, dst_size, dst_channels,
kernel_size, true, false); dst_data += dst_channels * dst_size;
} // for sn

src_num 个输入,每个尺寸为 src_channels * src_h * src_w,卷积核尺寸为kernel_size = src_channels * kernel_h * kernel_w,将每个输入转化为二维矩阵,尺寸为(dst_h * dst_w) * (kernel_size),可以看到最内层循环在逐行拷贝当前窗口内的元素,窗口大小与卷积核大小相同,一次拷贝kernel_w个元素,一个窗口内要拷贝src_channels*kernel_h次,因此一个窗口共拷贝了kernel_size个元素,共拷贝dst_h * dst_w个窗口,因此输入对应的二维矩阵尺寸为(dst_h * dst_w) * (kernel_size)。对于卷积核,有dst_channels= weight->num();个卷积核,因为是行有先存储,卷积核对应的二维矩阵尺寸为dst_channels*(kernel_size)逻辑上虽然为矩阵乘法,实现时两个矩阵逐行内积即可

优缺点分析

将卷积运算转化为矩阵乘法,从乘法和加法的运算次数上看,两者没什么差别,但是转化成矩阵后,运算时需要的数据被存在连续的内存上,这样访问速度大大提升(cache),同时,矩阵乘法有很多库提供了高效的实现方法,像BLAS、MKL等,转化成矩阵运算后可以通过这些库进行加速。

缺点呢?这是一种空间换时间的方法,消耗了更多的内存——转化的过程中数据被冗余存储。

参考

最新文章

  1. 以小时候玩的贪吃蛇为例,对于Java图像界面的学习感悟
  2. 第三周作业--Word Counter
  3. secure erase 时必须umount
  4. error C3861: “LOG4CPLUS_DEBUG”: 找不到标识
  5. 蓝牙BLE ATT剖析(二)-- PDU
  6. SqlSever基础 delete 删除一个表中的所有数据
  7. SqlServer2005基于已有表创建分区
  8. hdu2847(暴力)
  9. Swiper Usage&amp;&amp;API
  10. How to: Host and Run a Basic Windows Communication Foundation Service
  11. delphi 默认字体修改
  12. Qt中widget重新setParent需要注意的问题
  13. hdu 1530 Maximum Clique
  14. MFC学习之CWinApp类
  15. Lambda 表达式,Java中应用Lambda 表达式
  16. HDU 1014 Uniform Generator【GCD,水】
  17. Vue的组件为什么要export default
  18. node.js异步编程的几种模式
  19. 性能瓶颈之Session
  20. [HAOI2007] 修筑绿化带

热门文章

  1. [转]在Windows下编译ffmpeg完全手册
  2. python3学习笔记3---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/
  3. codeforces——961C. Chessboard
  4. manifold tangent classifier
  5. JAVA未来前景还能持续多久
  6. GenyMotion 配合 Android Studio 的安装
  7. 前端开发APP,从HBuilder开始~ 【转】
  8. 用Django做一个团队介绍
  9. Mysql服务启动与关闭
  10. eclipse中javadoc给项目生成api文档