在 ConvNet 中, 大部分的计算耗费在计算卷积的过程中, 尤其是在端上设备中, 对于性能的要求更为苛刻. 程序的性能优化是一个复杂而庞大的话题. 高性能计算就像系统设计一样, 虽然有一些指导原则, 但是, 对于不同的场景需要有不同的设计方案, 因此, 对于同一个优化问题, 不同的人可能会给出完全不同的优化方案. 本文不是探讨硬件和代码级的优化, 仅针对计算卷积的一个特定方法 — Winograd 方法做一个简单的记述.
首先要明确一点, 这里说的卷积是是指 ConvNet 中默认的卷积, 而不是数学意义上的卷积. 其实, ConvNet 中的卷积对于与数学中的 cross correlation.
计算卷积的方法有很多种, 常见的有以下几种方法:
Winograd 方法简单来讲, 就是用更多的加法计算来减少乘法计算. 因此, 一个前提就是, 在处理器中, 乘法计算的时钟周期数要大于加法计算的时钟周期数.
Winograd 计算卷积需要完成的乘法的次数为:
μ(F(m×n,r×s))=(m+n−1)×(n+s−1)μ(F(m×n,r×s))=(m+n−1)×(n+s−1)
r×sr×s 表示卷积核的大小, m×nm×n 表示输出大小.
所以, 做一个简单的对比计算: 3×33×3 的卷积核, 输出为 2×22×2, 那么, 滑窗或者 im2col 需要的乘法计算次数为 3×3×2×2=363×3×2×2=36, Winograd 需要的乘法计算次数为 (3+2−1)×(3+2−1)=16(3+2−1)×(3+2−1)=16.
Winograd 的证明方法较为复杂, 要用到数论中的一些知识, 但是, 使用起来很简单. 只需要按照如下公式计算:
Y=AT[[GgGT]⊙[BTdB]]AY=AT[[GgGT]⊙[BTdB]]A
其中, ⊙⊙ 表示 element-wise multiplication. A,G,BA,G,B 根据输出大小和卷积核大小不同有不同的定义, 并且是提前确定了的. 每种输出大小和卷积核的 A,G,BA,G,B 具体是多少, 可以通过 https://github.com/andravin/wincnn 的脚本计算. gg 表示的是卷积核, dd 表示要进行卷积计算的 data. gg 的 shape 为 r×rr×r, dd 的 shape 为 (m+r−1)×(m+r−1)(m+r−1)×(m+r−1)