[卷积算子加速] im2col优化
FesianXu 20201121 at UESTC

前言

在深度学习模型中,卷积是非常重要的工具,然而卷积的计算复杂度很高,因此需要对此进行特定的优化,im2colwinograd [5],fourier [4]是非常常见的优化方法,本文介绍基于im2col的优化方法。如有谬误请联系指出,本文遵守 CC 4.0 BY-SA 版权协议,转载请联系作者并注明出处,谢谢

∇\nabla∇ 联系方式:

e-mail: FesianXu@gmail.com

QQ: 973926198

github: https://github.com/FesianXu

知乎专栏: 计算机视觉/计算机图形理论与应用

微信公众号


耗时的卷积算子

卷积操作如Fig 1.1所示,通常涉及到了非常多的浮点运算,示例代码如code 1.1所示。

Fig 1.1 卷积操作的示意图。
for (int batch = 0; batch < B; ++batch) { // batch 批次for (int ox = 0; ox < Xout; ++ox) {   // 输出height大小for (int oy = 0; oy < Yout; ++oy) { // 输出width大小for (int oc = 0; oc < N; ++oc) { // 输出通道大小for (int kx = 0; kx < K; ++kx) { // kernel的height大小for (int ky = 0; ky < K; ++ky) { // kernel的width大小for (int ic = 0; ic < M; ++ic) { // 输入通道大小const int iy = oy * SY + ky * DY - PY;const int ix = ox * SX + kx * DX - PX;if (0 <= ix && ix < X && 0 <= iy && iy <= Y) {output[n][oy][ox][oc] += input[n][iy][ix][ic] * filter[oc][ky][kx][ic];}}}}}}}
}
code 1.1 通常的直接卷积计算可以视为是7层嵌套循环的计算。

code 1.1其中的SYSX是width和height方向的strideDYDX是width和height方向的dilate大小(一般不设置dilate的话都为1), PYPX是width和height方向的padding大小。 显然,这个朴素的直接计算过程存在很多可以优化的地方,比如进行向量化,然而,直接卷积涉及到了很多超参数,比如卷积核大小,步进大小等,单一的优化方式不能对所有的超参数都适用,因此cuDNN [6] , HexagonNNMACE [7] 等神经网络库中,对特定尺寸的卷积核(通常是最为常用的)进行了优化,比如1×51 \times 51×5,5×15 \times 15×1,3×33 \times 33×3,步进为2的卷积等等,如果涉及到其他更为普遍的卷积核,就只能采用原始的未优化的默认实现了。显然,这不是一种通用的做法。

后续提出过很多尝试优化直接卷积的方案,基于傅立叶变换的算法是其中一种[4],称之为快速卷积算法 (Fast Convolution Algorithm),其原理就是通过傅立叶变换将卷积计算转换成乘法计算,从而减少了大量的运算量。但是不幸的是,该算法提速受限于特定的卷积参数(大尺寸的卷积核,单位步进和dilation,足够大的输入尺寸和输入输出通道数等),对于比较小规模的计算就力不从心了,因此也是一种非通用的做法。

比较普遍的通用直接卷积优化方案是通过im2colGEMM实现。GEMM全称General Matrix Multiplication通用矩阵乘法,是BLAS(Basic Linear Algebra subroutine,基础线性代数库)的一部分,其通过很多方式(比如矩阵分区块,多线程,向量化等等)实现了优化,具体我们以后的博文再讨论。总而言之,只要我们将卷积操作以某种方式转换成矩阵相乘的方式,就能从现成的GEMM中获得极大的提速裨益。后文我们谈谈如何通过im2col的方式将卷积操作转换成矩阵乘法。

im2col

正如我们刚才code 1.1的代码所示,我们使用了一大堆嵌套的循环去实现卷积,这对于学习算法而言是很好的,因为这足够直接。但是实际中,计算速度并不够快。im2col(或者im2row,类似因此不独立讨论)将高阶张量的卷积转换成矩阵乘法。我们不妨先进行一个观察:卷积核与输入图片/特征图的某个局部区域(patch)之间进行点乘,并且通过滑动窗口的采样方式,去更新局部区域的信息。如果我们在内存中,把所有可能的局部区域拼成一个矩阵会怎么样呢?然后我们就可以通过矩阵乘法去进行卷积运算了,结合GEMM,可以提供200x以上的加速(取决于特定硬件)。这个就是im2col的基本思路,如Fig 2.1所示。

Fig 2.1 im2col将图片数据按照滑动块(patch)进行展开。

举例而言,假设输入是227×227×3227 \times 227 \times 3227×227×3的张量,卷积核尺寸是11×11×311 \times 11 \times 311×11×3,stride = 4padding = 0。那么我们先将每个卷积核展开成向量,维度为K2C=11×11×3=363K^2C = 11 \times 11 \times 3 = 363K2C=11×11×3=363,假设输出特征图通道为DDD,那么用im2col展开卷积核后的矩阵A\mathbf{A}A的尺寸为D×363D \times 363D×363。计算有多少个滑动窗口区块,我们有((227−11)/4)+1=55((227-11)/4)+1=55((227−11)/4)+1=55,我们一共有55个区块(在长宽方向上各有55个,整个图片上就是有552=302555^2=3025552=3025个),那么im2col之后的输入特征图矩阵B\mathbf{B}B的尺寸为363×3025363 \times 3025363×3025,最终得出的结果就是矩阵乘法A⋅B\mathbf{A} \cdot \mathbf{B}A⋅B,得出的输出矩阵C\mathbf{C}C的尺寸为D×3025D \times 3025D×3025,通过逆运算col2im将C\mathbf{C}C塑性为55×55×D55 \times 55 \times D55×55×D即完成了最终的卷积输出结果。整个过程如Fig 2.2所示。假设A~\mathbf{\tilde{A}}A~为卷积核,B~\mathbf{\tilde{B}}B~为输入特征图,C\mathbf{C}C为卷积输出结果,那么有:
C=col2im(im2col(A~)⋅im2col(B~))(2.1)\begin{aligned} \mathbf{C} = \mathrm{col2im}(\mathrm{im2col}(\mathbf{\tilde{A}}) \cdot \mathrm{im2col}(\mathbf{\tilde{B}})) \end{aligned} \tag{2.1} C=col2im(im2col(A~)⋅im2col(B~))​(2.1)

Fig 2.2 im2col将卷积核和输入特征图进行展开后,用矩阵乘法取代了卷积操作。

caffe [1]和darknet [2]中都提供了相应的实现,我们主要观察下darknet的实现,如code 2.1所示,主要的是#19行,我们发现其实是通过一系列计算(通过超参数计算区块的地址位置),对输入张量进行访存地址的重定位。

void im2col_cpu(float* data_im,int channels,  int height,  int width,int ksize,  int stride, int pad, float* data_col)
{int c,h,w;int height_col = (height + 2*pad - ksize) / stride + 1;int width_col = (width + 2*pad - ksize) / stride + 1;int channels_col = channels * ksize * ksize;for (c = 0; c < channels_col; ++c) {int w_offset = c % ksize;int h_offset = (c / ksize) % ksize;int c_im = c / ksize / ksize;for (h = 0; h < height_col; ++h) {for (w = 0; w < width_col; ++w) {int im_row = h_offset + h * stride;int im_col = w_offset + w * stride;int col_index = (c * height_col + h) * width_col + w;data_col[col_index] = im2col_get_pixel(data_im, height, width, channels,im_row, im_col, c_im, pad);}}}
}float im2col_get_pixel(float *im, int height, int width, int channels,int row, int col, int channel, int pad)
{row -= pad;col -= pad;if (row < 0 || col < 0 ||row >= height || col >= width) return 0;return im[col + width*(row + height*channel)];
}
code 2.1 darknet中的im2col操作代码。

Reference

[1]. https://github.com/BVLC/caffe/blob/master/src/caffe/layers/im2col_layer.cpp

[2]. https://github.com/pjreddie/darknet/blob/master/src/im2col.c

[3]. Dukhan M. The Indirect Convolution Algorithm[J]. arXiv preprint arXiv:1907.02129, 2019.

[4]. Vasilache N, Johnson J, Mathieu M, et al. Fast convolutional nets with fbfft: A GPU performance evaluation[J]. arXiv preprint arXiv:1412.7580, 2014.

[5]. Andrew Lavin and Scott Gray. Fast algorithms for convolutional neural networks. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, pages 4013–4021, 2016.

[6]. Sharan Chetlur, Cliff Woolley, Philippe Vandermersch, Jonathan Cohen, John Tran, Bryan Catanzaro, and Evan Shelhamer. cudnn: Efficient primitives for deep learning. arXiv preprint arXiv:1410.0759, 2014.

[7]. Xiaomi. MACE. https://github.com/XiaoMi/mace. [Online; accessed 8-April-2019].

[卷积算子加速] im2col优化相关推荐

  1. DL之CNN可视化:利用SimpleConvNet算法【3层,im2col优化】基于mnist数据集训练并对卷积层输出进行可视化

    DL之CNN可视化:利用SimpleConvNet算法[3层,im2col优化]基于mnist数据集训练并对卷积层输出进行可视化 导读 利用SimpleConvNet算法基于mnist数据集训练并对卷 ...

  2. DL之CNN:自定义SimpleConvNet【3层,im2col优化】利用mnist数据集实现手写数字识别多分类训练来评估模型

    DL之CNN:自定义SimpleConvNet[3层,im2col优化]利用mnist数据集实现手写数字识别多分类训练来评估模型 目录 输出结果 设计思路 核心代码 更多输出 输出结果 设计思路 核心 ...

  3. php编译优化,浅析使用Turck-mmcache编译来加速、优化PHP代码

    php_screw确实很不错,但是只能起到加密的作用,并没有加速和优化的作用. 下面我们来看看:Turck MMCache它的作用是通过对php代码的编译来加速.优化php代码,我们如果发布的php代 ...

  4. FFmpeg在Intel GPU上的硬件加速与优化

    英特尔提供了一套基于VA-API/Media SDK的硬件加速方案,通过在FFmpeg中集成Intel GPU的媒体硬件加速能力,为用户提供更多的收益.本文来自英特尔资深软件开发工程师赵军在LiveV ...

  5. Hexo博客使用腾讯云CDN加速及优化

    原文地址:Hexo博客使用腾讯云CDN加速及优化 使用公共 CDN 加载部分资源 修改主题(我的是NexT主题)配置文件,使用公共CDN加载部分CSS.JS文件,我选择的是 jsDelivr 这个公共 ...

  6. 什么是BBR加速,Centos7如何开启BBR加速,优化服务器的传输速度,避免堵塞

    Centos7开启BBR加速,优化你的传输速度,避免堵塞 了解BBR加速 什么是BBR加速? BBR是一种加速TCP的拥堵算法技术.BBR 利用瓶颈带宽和往返传播时间,被认为是迄今为止跨越不同路由发送 ...

  7. Linux实战教学笔记36:PHP服务缓存加速深度优化实践

    一,PHP缓存加速器介绍与环境准备 1.1 PHP缓存加速器介绍 1.1.1 操作码介绍及缓存原理 当客户端请求一个PHP程序时,服务器的PHP引擎会解析该PHP程序,并将其编译为特定的操作码(Ope ...

  8. 为你的应用加速 - 安卓优化指南

    为你的应用加速 - 安卓优化指南 原则 每当我遇到性能问题,或者尝试发现性能问题的时候,我会遵循如下原则: 坚持性能测试 - 不要用你的眼睛去优化性能.也许在你盯着同一个动画看了几次之后,你会开始相信 ...

  9. 广域网加速及优化解决方案

    广域网加速及优化解决方案能够在以下领域帮助您的公司或机构更好的利用带宽,降低成本,提高工作效率,增加销售和利润: 动态网站加速解决方案 适用于社交网站,博客,B2C/B2B 服务网站, 网络游戏, 和 ...

最新文章

  1. 实现迷你解析器把字符串解析成NestInteger类 Mini Parser
  2. 西安网络推广教大家如何提高网站抓取量?
  3. Redis进阶 - 因异常断电导致的Redis Cluster Fail故障处理
  4. 阿里大数据技术如何进化?资深技术专家带你回顾
  5. 写MySQL存储过程实现动态执行SQL
  6. 精通Android开发 1
  7. Python自动化之列表
  8. php 不申明构造函数,PHP的构造函数和同类名函数同时申明时调用的情况
  9. php中pdo连接数据库,PHP使用PDO连接数据库
  10. 【转】MongoDB资料汇总专题
  11. TensorFlow推出命令式、可定义的运行接口Eager Execution
  12. 自定义办法设置 localStorage 过期时间
  13. 推荐几款好用的Chrome扩展插件
  14. linux glibc升级
  15. 小度杀入《最强大脑》总决赛,除了阴谋论还有什么值得关注的?
  16. SSM项目实战之一:项目创建
  17. cad怎样弄出放线的坐标_怎么把图纸上的坐标输入CAD详细步骤?
  18. 计算机课怎么加水印,word文档怎么加水印
  19. Cordova app中点击打开微信功能
  20. express 热更新

热门文章

  1. 利用Scrapy爬取伯乐在线文章并存取到mysql数据库
  2. 我在她心中到底是什么呢?
  3. 题解7:球弹跳高度的计算
  4. 计算机视觉 特征检测与匹配 线和消失点
  5. 3月Java岗华为校招+阿里巴巴社招完整面经
  6. JavaScript数据结构(3):单向链表与双向链表
  7. VNC如何进行IPV6连接
  8. XDR与SOC的关系
  9. 1-4 传统操作系统的分类
  10. oracle 韩国整形医院,韩国整形医院前十名公布:都是本地人去的整形医院!