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

写在前面

随便翻一翻流行的推理框架(加速器),如NCNNNNPACK等,可以看到,对于卷积层,大家不约而同地采用了Winograd快速卷积算法,该算法出自CVPR 2016的一篇 paper:Fast Algorithms for Convolutional Neural Networks

本文将尝试揭开Winograd算法的神秘面纱。

问题定义

将一维卷积运算定义为\(F(m, r)\),\(m\)为Output Size,\(r\)为Filter Size,则输入信号的长度为\(m+r-1\),卷积运算是对应位置相乘然后求和,输入信号每个位置至少要参与1次乘法,所以乘法数量最少与输入信号长度相同,记为

\[
\mu(F(m, r))=m+r-1
\]

在行列上分别进行一维卷积运算,可得到二维卷积,记为\(F(m\times n, r\times s)\),输出为\(m\times n\),卷积核为\(r\times s\),则输入信号为\((m+r-1)(n+s-1)\),乘法数量至少为

\[
\begin{aligned} \mu(F(m \times n, r \times s)) &=\mu(F(m, r)) \mu(F(n, s)) \\ &=(m+r-1)(n+s-1) \end{aligned}
\]

若是直接按滑动窗口方式计算卷积,一维时需要\(m\times r\)次乘法,二维时需要\(m\times n \times r \times s\)次乘法,远大于上面计算的最少乘法次数

使用Winograd算法计算卷积快在哪里?一言以蔽之:快在减少了乘法的数量,将乘法数量减少至\(m+r-1\)或\((m+r-1)(n+s-1)\)。

怎么减少的?请看下面的例子。

一个例子 F(2, 3)

先以1维卷积为例,输入信号为\(d=\left[ \begin{array}{llll}{d_{0}} & {d_{1}} & {d_{2}} & {d_{3}}\end{array}\right]^{T}\),卷积核为\(g=\left[ \begin{array}{lll}{g_{0}} & {g_{1}} & {g_{2}}\end{array}\right]^{T}\),则卷积可写成如下矩阵乘法形式:

\[
F(2, 3) = \left[ \begin{array}{lll}{d_{0}} & {d_{1}} & {d_{2}} \\ {d_{1}} & {d_{2}} & {d_{3}}\end{array}\right] \left[ \begin{array}{l}{g_{0}} \\ {g_{1}} \\ {g_{2}}\end{array}\right]=\left[ \begin{array}{c}{r_0} \\ {r_1}\end{array}\right]
\]

如果是一般的矩阵乘法,则需要6次乘法和4次加法,如下:

\[
\begin{array}{l}{r_{0}=\left(d_{0} \cdot g_{0}\right)+\left(d_{1} \cdot g_{1}\right)+\left(d_{2} \cdot g_{2}\right)} \\ {r_{1}=\left(d_{1} \cdot g_{0}\right)+\left(d_{2} \cdot g_{1}\right)+\left(d_{3} \cdot g_{2}\right)}\end{array}
\]

但是,卷积运算中输入信号转换成的矩阵不是任意矩阵,其中有规律地分布着大量的重复元素,比如第1行和第2行的\(d_1\)和\(d_2\),卷积转换成的矩阵乘法比一般矩阵乘法的问题域更小,这就让优化存在了可能。

Winograd是怎么做的呢?

\[
F(2,3)=\left[ \begin{array}{lll}{d_{0}} & {d_{1}} & {d_{2}} \\ {d_{1}} & {d_{2}} & {d_{3}}\end{array}\right] \left[ \begin{array}{l}{g_{0}} \\ {g_{1}} \\ {g_{2}}\end{array}\right]=\left[ \begin{array}{c}{m_{1}+m_{2}+m_{3}} \\ {m_{2}-m_{3}-m_{4}}\end{array}\right]
\]

其中,

\[
\begin{array}{ll}{m_{1}=\left(d_{0}-d_{2}\right) g_{0}} & {m_{2}=\left(d_{1}+d_{2}\right) \frac{g_{0}+g_{1}+g_{2}}{2}} \\ {m_{4}=\left(d_{1}-d_{3}\right) g_{2}} & {m_{3}=\left(d_{2}-d_{1}\right) \frac{g_{0}-g_{1}+g_{2}}{2}}\end{array}
\]

乍看上去,为了计算\(\begin{array}{l}{r_{0}=m_1 + m_2 + m_3 } \\ {r_{1}=m_2 - m_3 - m_4}\end{array}\),需要的运算次数分别为:

  • 输入信号\(d\)上:4次加法(减法)
  • 卷积核\(g\)上:3次加法(\(g_1+g_2\)中间结果可保留),2次乘法(除法)
  • 输出\(m\)上:4次乘法,4次加法

在神经网络的推理阶段,卷积核上的元素是固定的,因此\(g\)上的运算可以提前算好预测阶段只需计算一次,可以忽略,所以一共所需的运算次数为\(d\)与\(m\)上的运算次数之和,即4次乘法和8次加法

与直接运算的6次乘法和4次加法相比,乘法次数减少,加法次数增加。在计算机中,乘法一般比加法慢,通过减少减法次数,增加少量加法,可以实现加速。

1D winograd

上一节中的计算过程写成矩阵形式如下:
\[
Y=A^{T}\left[(G g) \odot\left(B^{T} d\right)\right]
\]

其中,\(\odot\)为element-wise multiplication(Hadamard product)对应位置相乘,

\[
B^{T}=\left[ \begin{array}{cccc}{1} & {0} & {-1} & {0} \\ {0} & {1} & {1} & {0} \\ {0} & {-1} & {1} & {0} \\ {0} & {1} & {0} & {-1}\end{array}\right]
\]

\[
G=\left[ \begin{array}{ccc}{1} & {0} & {0} \\ {\frac{1}{2}} & {\frac{1}{2}} & {\frac{1}{2}} \\ {\frac{1}{2}} & {-\frac{1}{2}} & {\frac{1}{2}} \\ {0} & {0} & {1}\end{array}\right]
\]

\[
A^{T}=\left[ \begin{array}{llll}{1} & {1} & {1} & {0} \\ {0} & {1} & {-1} & {-1}\end{array}\right]
\]

\[
g=\left[ \begin{array}{lll}{g_{0}} & {g_{1}} & {g_{2}}\end{array}\right]^{T}
\]

\[
d=\left[ \begin{array}{llll}{d_{0}} & {d_{1}} & {d_{2}} & {d_{3}}\end{array}\right]^{T}
\]

  • \(g\):卷积核
  • \(d\):输入信号
  • \(G\):Filter transform矩阵,尺寸\((m+r-1)\times r\)
  • \(B^T\):Input transform矩阵,尺寸\((m+r-1)\times (m+r-1)\)
  • \(A^T\):Output transform矩阵,尺寸\(m \times (m+r-1)\)

整个计算过程在逻辑上可以分为4步:

  • Input transform
  • Filter transform
  • Hadamar product
  • Output transform

注意,这里写成矩阵形式,并不意味着实现时要调用矩阵运算的接口,一般直接手写计算过程速度会更快,写成矩阵只是为了数学形式。

1D to 2D,F(2, 3) to F(2x2, 3x3)

上面只是看了1D的一个例子,2D怎么做呢?

论文中一句话带过:

A minimal 1D algorithm F(m, r) is nested with itself to obtain a minimal 2D algorithm,F(m×m, r×r).

\[
Y=A^{T}\left[\left[G g G^{T}\right] \odot\left[B^{T} d B\right]\right] A
\]

其中,\(g\)为\(r \times r\) Filter,\(d\)为\((m+r-1)\times (m+r-1)\)的image tile。

问题是:怎么nested with itself

这里继续上面的例子\(F(2, 3)\),扩展到2D,\(F(2\times 2, 3 \times 3)\),先写成矩阵乘法,见下图,图片来自SlideShare,注意数学符号的变化,


将卷积核的元素拉成一列,将输入信号每个滑动窗口中的元素拉成一行。注意图中红线划分成的分块矩阵,每个子矩阵中重复元素的位置与一维时相同,同时重复的子矩阵也和一维时相同,如下所示

令\(D_0 = [k_0, k_1, k_2, k_3]^T\),即窗口中的第0行元素,\(D_1 \ D_2 \ D_3\)表示第1、2、3行;\(W_0=[w_0, w_1, w_2]^T\),

\[\begin{aligned}
\left[ \begin{array}{c}{r_0} \\ {r_1} \\ {r_2} \\ {r_3}\end{array}\right] &=
\left[ \begin{array}{c}{R_0} \\ {R_1}\end{array}\right] =
\left[ \begin{array}{c}{K_0 W_0 + K_1 W_1 + K_2 W_2} \\ {K_1 W_0 + K_2 W_1 + K_3 W_2} \end{array} \right] \\
&= \left[ \begin{array}{c} {A^{T}\left[(G W_0) \odot\left(B^{T} D_0 \right)\right] + A^{T}\left[(G W_1) \odot\left(B^{T} D_1 \right)\right] + A^{T}\left[(G W_2) \odot\left(B^{T} D_2 \right)\right]} \\ {A^{T}\left[(G W_0) \odot\left(B^{T} D_1 \right)\right] + A^{T}\left[(G W_1) \odot\left(B^{T} D_2 \right)\right] + A^{T}\left[(G W_2) \odot\left(B^{T} D_3 \right)\right]} \end{array} \right] \\
\\
&=A^{T}\left[\left[G [W_0 \ W_1 \ W_2 ] G^{T}\right] \odot\left[B^{T} [d_0 \ d_1 \ d_2 \ d_3] B\right]\right]A \\
\\
&=A^{T}\left[\left[G g G^{T}\right] \odot\left[B^{T} d B\right]\right] A
\end{aligned}
\]

卷积运算为对应位置相乘再相加,上式中,\(A^{T}\left[(G W_0) \odot\left(B^{T} D_0 \right)\right]\)为列向量\(W_0\)与\(D_0\)的卷积,结果为长度为2的列向量,而\(A^{T}\left[(G W_0) \odot\left(B^{T} D_0 \right)+ (G W_1) \odot\left(B^{T} D_1 \right) + (G W_2) \odot\left(B^{T} D_2 \right)\right]\)方括号内对应位置相乘再相加,相当于在构成的行向量上卷积,据此,上面的推导就不难看出了。

所谓的nested with itself如下图所示,

此时,Winograd算法的乘法次数为16(上图\(4\times 4\)),而直接卷积的乘法次数为36,降低了2.25倍的乘法计算复杂度

卷积神经网络中的Winograd

要将Winograd应用在卷积神经网络中,还需要回答下面两个问题:

  • 上面我们仅仅是针对一个小的image tile,但是在卷积神经网络中,feature map的尺寸可能很大,难道我们要实现\(F(224, 3)\)吗?
  • 在卷积神经网络中,feature map是3维的,卷积核也是3维的,3D的winograd该怎么做?

第一个问题,在实践中,会将input feature map切分成一个个等大小有重叠的tile,在每个tile上面进行winograd卷积。

第二个问题,3维卷积,相当于逐层做2维卷积,然后将每层对应位置的结果相加,下面我们会看到多个卷积核时更巧妙的做法。

这里直接贴上论文中的算法流程:

整体仍可分为4步,

  • Input transform
  • Filter transform
  • Batched-GEMM(批量矩阵乘法)
  • Output transform

算法流程可视化如下,图片出自论文Sparse Winograd Convolutional neural networks on small-scale systolic arrays,与算法对应着仔细推敲还是挺直观的。

注意图中的Matrix Multiplication,对应3维卷积中逐channel卷积后的对应位置求和,相当于\((m+r-1)^2\)个矩阵乘积,参与乘积的矩阵尺寸分别为\(\lceil H / m\rceil\lceil W / m\rceil \times C\)和\(C \times K\),把Channel那一维消掉。

总结

  • Winograd算法通过减少乘法次数来实现提速,但是加法的数量会相应增加,同时需要额外的transform计算以及存储transform矩阵,随着卷积核和tile的尺寸增大,就需要考虑加法、transform和存储的代价,而且tile越大,transform矩阵越大,计算精度的损失会进一步增加,所以一般Winograd只适用于较小的卷积核和tile(对大尺寸的卷积核,可使用FFT加速),在目前流行的网络中,小尺寸卷积核是主流,典型实现如\(F(6\times 6, 3\times 3)\)、\(F(4\times 4, 3\times 3)\)、\(F(2\times 2, 3\times 3)\)等,可参见NCNNFeatherCNNARM-ComputeLibrary等源码实现。
  • 就卷积而言,Winograd算法和FFT类似,都是先通过线性变换将input和filter映射到新的空间,在那个空间里简单运算后,再映射回原空间。
  • 与im2col+GEMM+col2im相比,winograd在划分时使用了更大的tile,就划分方式而言,\(F(1\times 1, r\times r)\)与im2col相同。

参考

卷积神经网络中的Winograd快速卷积算法的更多相关文章

  1. 卷积神经网络中的通道 channel

    卷积神经网络中 channels 分为三种:    (1):最初输入的图片样本的 channels ,取决于图片类型,比如RGB, channels=3    (2):卷积操作完成后输出的 out_c ...

  2. tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图

    tensorflow CNN 卷积神经网络中的卷积层和池化层的代码和效果图 因为很多 demo 都比较复杂,专门抽出这两个函数,写的 demo. 更多教程:http://www.tensorflown ...

  3. 卷积神经网络中的channel 和filter

    在深度学习的算法学习中,都会提到 channels 这个概念.在一般的深度学习框架的 conv2d 中,如 tensorflow .mxnet,channels 都是必填的一个参数. channels ...

  4. 理解卷积神经网络中的输入与输出形状(Keras实现)

    即使我们从理论上理解了卷积神经网络,在实际进行将数据拟合到网络时,很多人仍然对其网络的输入和输出形状(shape)感到困惑.本文章将帮助你理解卷积神经网络的输入和输出形状. 让我们看看一个例子.CNN ...

  5. 理解卷积神经网络中的channel

    在一般的深度学习框架的 conv2d 中,如 tensorflow.mxnet,channel 都是必填的一个参数 在 tensorflow 中,对于输入样本中 channels 的含义,一般是RGB ...

  6. [DeeplearningAI笔记]卷积神经网络1.4-1.5Padding与卷积步长

    4.1卷积神经网络 觉得有用的话,欢迎一起讨论相互学习~Follow Me 1.4Padding 一张\(6*6\)大小的图片,使用\(3*3\)的卷积核设定步长为1,经过卷积操作后得到一个\(4*4 ...

  7. 【转载】 【Tensorflow】卷积神经网络中strides的参数

    原文地址: https://blog.csdn.net/TwT520Ly/article/details/79540251 http://blog.csdn.net/TwT520Ly -------- ...

  8. tensorflow学习笔记——图像识别与卷积神经网络

    无论是之前学习的MNIST数据集还是Cifar数据集,相比真实环境下的图像识别问题,有两个最大的问题,一是现实生活中的图片分辨率要远高于32*32,而且图像的分辨率也不会是固定的.二是现实生活中的物体 ...

  9. 卷积神经网络概念及使用 PyTorch 简单实现

    卷积神经网络 卷积神经网络(CNN)是深度学习的代表算法之一 .具有表征学习能力,能够按其阶层结构对输入信息进行平移不变分类,因此也被称为“平移不变人工神经网络”.随着深度学习理论的提出和数值计算设备 ...

随机推荐

  1. leetCode 65.Valid Number (有效数字)

    Valid Number  Validate if a given string is numeric. Some examples: "0" => true " ...

  2. poj 2828 Buy Tickets 【线段树点更新】

    题目:id=2828" target="_blank">poj 2828 Buy Tickets 题意:有n个人排队,每一个人有一个价值和要插的位置,然后当要插的位 ...

  3. C++经典面试题解析

    1. // BlankTest.cpp : 定义控制台应用程序的入口点. //题目:将一个文件中的一组整数排序后输出到另一个文件中 #include "stdafx.h" #inc ...

  4. 走入asp.net mvc不归路:[2]控制器概览

    asp.net mvc中最灵活的地方就是控制器,这里可以验证数据,可以跳转视图,还可以访问数据库等等.所以,我们要先从这里说起. 1 控制器就是继承了Controller的类,一般来说,类名后面都会增 ...

  5. bzoj 1030: [JSOI2007]文本生成器 (ac自己主动机上的dp)

    1030: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 162 MB Submit: 2635  Solved: 1090 [id=1030&qu ...

  6. System V 信号量使用相关函数

    System V 信号量 在提到Posix 信号量时,指的是二值信号量或计数信号量,而System V信号量指的是入了计数信号量集 二值信号量:其值为0或1,类似于互斥锁,资源被锁住时为0,资源可用为 ...

  7. 李洪强iOS开发之-实现点击单行View显示和隐藏Cell

    李洪强iOS开发之-实现点击单行View显示和隐藏Cell 实现的效果:  .... ....

  8. Redis HyperLogLog及应用

    参考:http://www.runoob.com/redis/redis-hyperloglog.html Redis 在 2.8.9 之后的版本中,添加了 HyperLogLog 结构,用来做基数统 ...

  9. Boom!!!计算机系统,从理解到爆炸,Bomblab

    进入文件夹下 ./bomb 開始执行炸弹 对于炸弹command not found之类的鬼畜情况: chmod 777 bomb 然后再执行炸弹 objdump -d bomb > bomb. ...

  10. UICollectionViewController xcode6.1 自定义Cell

    本文转载至 http://blog.csdn.net/daleiwang/article/details/40423219 UICollectionViewContAutolayoutstoryboa ...