卷积神经网络中的Winograd快速卷积算法

博客: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+r1,卷积运算是对应位置相乘然后求和,输入信号每个位置至少要参与1次乘法,所以乘法数量最少与输入信号长度相同,记为

μ(F(m,r))=m+r1

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

μ(F(m×n,r×s))=μ(F(m,r))μ(F(n,s))=(m+r1)(n+s1)

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

使用Winograd算法计算卷积快在哪里?一言以蔽之:快在减少了乘法的数量,将乘法数量减少至m+r1(m+r1)(n+s1)

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

一个例子 F(2, 3)

先以1维卷积为例,输入信号为d=[d0d1d2d3]T,卷积核为g=[g0g1g2]T,则卷积可写成如下矩阵乘法形式:

F(2,3)=[d0d1d2d1d2d3][g0g1g2]=[r0r1]

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

r0=(d0g0)+(d1g1)+(d2g2)r1=(d1g0)+(d2g1)+(d3g2)

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

Winograd是怎么做的呢?

F(2,3)=[d0d1d2d1d2d3][g0g1g2]=[m1+m2+m3m2m3m4]

其中,

m1=(d0d2)g0m2=(d1+d2)g0+g1+g22m4=(d1d3)g2m3=(d2d1)g0g1+g22

乍看上去,为了计算r0=m1+m2+m3r1=m2m3m4,需要的运算次数分别为:

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

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

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

1D winograd

上一节中的计算过程写成矩阵形式如下:

Y=AT[(Gg)(BTd)]

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

BT=[1010011001100101]

G=[100121212121212001]

AT=[11100111]

g=[g0g1g2]T

d=[d0d1d2d3]T

  • g:卷积核
  • d:输入信号
  • G:Filter transform矩阵,尺寸(m+r1)×r
  • BT:Input transform矩阵,尺寸(m+r1)×(m+r1)
  • AT:Output transform矩阵,尺寸m×(m+r1)

整个计算过程在逻辑上可以分为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=AT[[GgGT][BTdB]]A

其中,gr×r Filter,d(m+r1)×(m+r1)的image tile。

问题是:怎么nested with itself

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

nested 1D winograd algorithm
将卷积核的元素拉成一列,将输入信号每个滑动窗口中的元素拉成一行。注意图中红线划分成的分块矩阵,每个子矩阵中重复元素的位置与一维时相同,同时重复的子矩阵也和一维时相同,如下所示
nested 1D winograd algorithm
D0=[k0,k1,k2,k3]T,即窗口中的第0行元素,D1 D2 D3表示第1、2、3行;W0=[w0,w1,w2]T

[r0r1r2r3]=[R0R1]=[K0W0+K1W1+K2W2K1W0+K2W1+K3W2]=[AT[(GW0)(BTD0)]+AT[(GW1)(BTD1)]+AT[(GW2)(BTD2)]AT[(GW0)(BTD1)]+AT[(GW1)(BTD2)]+AT[(GW2)(BTD3)]]=AT[[G[W0 W1 W2]GT][BT[d0 d1 d2 d3]B]]A=AT[[GgGT][BTdB]]A

卷积运算为对应位置相乘再相加,上式中,AT[(GW0)(BTD0)]为列向量W0D0的卷积,结果为长度为2的列向量,而AT[(GW0)(BTD0)+(GW1)(BTD1)+(GW2)(BTD2)]方括号内对应位置相乘再相加,相当于在构成的行向量上卷积,据此,上面的推导就不难看出了。

卷积运算为对应位置相乘再相加,上式中,AT[(GW0)(BTD0)]表示长度为4的D0与长度为3的W0卷积结果,结果为长度为2的列向量,其中,(GW0)(BTD0)均为长度为4的列向量,进一步地,[(GW0)(BTD0)+(GW1)(BTD1)+(GW2)(BTD2)]可以看成3对长度为4的列向量两两对应位置相乘再相加,结果为长度为4的列向量,也可以看成是4组长度为3的行向量的点积运算,同样,[(GW0)(BTD1)+(GW1)(BTD2)+(GW2)(BTD3)]也是4组长度为3的行向量的内积运算,考虑两者的重叠部分(BTD1)(BTD2),恰好相当于G[W0 W1 W2]的每一行在BT[d0 d1 d2 d3]的对应行上进行1维卷积,上面我们已经进行了列向量卷积的Winograd推导,行向量的卷积只需将所有左乘的变换矩阵转置后变成右乘就可以了,至此,上面的推导结果就不难得出了。

所谓的nested with itself如下图所示,
nested 1D winograd algorithm

此时,Winograd算法的乘法次数为16(上图4×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维卷积,然后将每层对应位置的结果相加,下面我们会看到多个卷积核时更巧妙的做法。

这里直接贴上论文中的算法流程:
convnet layer winograd algorithm
整体仍可分为4步,

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

算法流程可视化如下,图片出自论文Sparse Winograd Convolutional neural networks on small-scale systolic arrays,与算法对应着仔细推敲还是挺直观的。
An overview of Winograd convolution layer
注意图中的Matrix Multiplication,对应3维卷积中逐channel卷积后的对应位置求和,相当于(m+r1)2个矩阵乘积,参与乘积的矩阵尺寸分别为H/mW/m×CC×K,把Channel那一维消掉。

总结

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

参考

posted @   shine-lee  阅读(57608)  评论(5编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
51La
点击右上角即可分享
微信分享提示