快速卷积算法详解:img2col vs Winograd vs FFT
🎯 为什么需要快速卷积算法?
传统卷积的问题
直接卷积计算复杂度:O(N × H × W × C × R × S × M)
其中:N=批次,H×W=输入尺寸,C=输入通道,R×S=卷积核,M=输出通道
例子:ResNet-50的一层卷积
输入:56×56×256,卷积核:3×3×256×256
计算量:56 × 56 × 256 × 3 × 3 × 256 ≈ 9.2亿次乘法!
现代深度学习需要更高效的算 法!
1. img2col 算法
📖 基本思想
将卷积转换为矩阵乘法,利用高度优化的矩阵乘法库(如cuBLAS)。
🔧 算法原理
Step 1: 重排输入数据 (im2col)
原始输入:4×4×1
[1 2 3 4]
[5 6 7 8]
[9 10 11 12]
[13 14 15 16]
3×3卷积核在2×2输出上移动,提取所有窗口:
位置(0,0): 位置(0,1): 位置(1,0): 位置(1,1):
[1 2 3] [2 3 4] [5 6 7] [6 7 8]
[5 6 7] → [6 7 8] → [9 10 11] → [10 11 12]
[9 10 11] [10 11 12] [13 14 15] [14 15 16]
重排成矩阵 (每列是一个卷积窗口):
pos(0,0) pos(0,1) pos(1,0) pos(1,1)
[1 2 5 6 ] ← 卷积核位置(0,0)
[2 3 6 7 ] ← 卷积核位置(0,1)
[3 4 7 8 ] ← 卷积核位置(0,2)
[5 6 9 10 ] ← 卷积核位置(1,0)
[6 7 10 11 ] ← 卷积核位置(1,1)
[7 8 11 12 ] ← 卷积核位置(1,2)
[9 10 13 14 ] ← 卷积核位置(2,0)
[10 11 14 15 ] ← 卷积核位置(2,1)
[11 12 15 16 ] ← 卷积核位置(2,2)
Step 2: 重排卷积核
原始卷积核 3×3:
[w1 w2 w3]
[w4 w5 w6]
[w7 w8 w9]
重排成行向量:
[w1 w2 w3 w4 w5 w6 w7 w8 w9]
Step 3: 矩阵乘法
输出 = 卷积核矩阵 × img2col矩阵
[w1 w2 w3 w4 w5 w6 w7 w8 w9] × [重排后的输入矩阵]
= [4个输出值] → 重排成 2×2 输出
📊 多通道情况
输入: H×W×C
卷积核: R×S×C×M
img2col矩阵大小: (R×S×C) × (输出位置数)
权重矩阵大小: M × (R×S×C)
输出矩阵大小: M × (输出位置数)
✅ img2col的优点
- 简单易实现:逻辑清晰,容易理解和调试
- 充分利用BLAS库:可以使用高度优化的矩阵乘法
- 通用性强:适用于任意卷积核大小和步长
- 硬件友好:适合GPU和专用AI芯片
❌ img2col的缺点
- 内存开销大:需要额外存储重排后的数据
- 内存带宽瓶颈:大量数据重复和传输
🎯 适用场景
- 大多数深度学习框架的默认选择
- 中等大小的卷积核(3×3, 5×5)
- GPU加速场景
2. Winograd 算法
📖 基本思想
利用数学变换减少乘法次数,通过预计算和后处理,用更多加法换取更少乘法。
🧮 数学原理
一维Winograd F(2,3)示例
目标:计算长度为4的输入与长度为3的卷积核的卷积,输出长度为2
输入: d = [d0, d1, d2, d3]
卷积核: g = [g0, g1, g2]
输出: F(2,3) 表示输出2个点,卷积核3个点
传统方法需要 2×3 = 6 次乘法
Winograd只需要 4 次乘法!
变换矩阵
输入变换矩阵 B^T: 权重变换矩阵 G: 输出变换矩阵 A^T:
[1 0 -1 0] [1 0 0] [1 1 1 0]
[0 1 1 0] [1/2 1/2 1/2] [0 1 -1 -1]
[0 -1 1 0] [1/2 -1/2 1/2]
[0 1 0 -1] [0 0 1]
计算过程:
1. 输入变换: U = B^T × d
2. 权重变换: V = G × g × G^T
3. 点乘: M = U ⊙ V (逐元素相乘)
4. 输出变换: Y = A^T × M × A
🔧 二维Winograd F(2×2, 3×3)
最常用的情况:3×3卷积核,2×2输出块
输入块: 4×4 卷积核: 3×3 输出块: 2×2
[d00 d01 d02 d03] [g00 g01 g02] [y00 y01]
[d10 d11 d12 d13] [g10 g11 g12] [y10 y11]
[d20 d21 d22 d23] [g20 g21 g22]
[d30 d31 d32 d33]
变换过程:
1. U = B^T × d × B (输入变换)
2. V = G × g × G^T (权重变换,可预计算)
3. M = U ⊙ V (逐元素乘法,16次)
4. Y = A^T × M × A (输出变换)
复杂度对比:
传统卷积: 2×2×3×3 = 36次乘法
Winograd: 16次乘法,节省55%!
⚡ Winograd的性能分析
计算复杂度(理论):
传统卷积: O(n²m²) (n=输出大小,m=卷积核大小)
Winograd F(n,m): O(n²·乘法复杂度)
对于F(2,3): 乘法复杂度 = n+m-1 = 4
对于F(4,3): 乘法复杂度 = 6
对于F(6,3): 乘法复杂度 = 8
✅ Winograd的优点
- 显著减少乘法:对3×3卷积特别有效
- 理论加速比高:可达2-3倍
- 适合特定硬件:AI芯片可以专门优化
❌ Winograd的缺点
- 数值不稳定:变换矩阵可能放大数值误差
- 内存需求:需要额外的变换存储空间
- 实现复杂:需要仔细处理边界和多通道情况
- 限制较多:主要适用于3×3卷积核,步长为1
🎯 适用场景
- 3×3卷积核,步长=1
- 对计算效率要求极高的场景
- 专用硬件加速
- 数值精度要求不极严格的应用
3. FFT卷积算法
📖 基本思想
利用卷积定理:时域卷积等于频域点乘,通过FFT转到频域计算。
🧮 数学原理
卷积定理
时域: y = x * h (卷积)
频域: Y = X · H (点乘)
其中: X = FFT(x), H = FFT(h), Y = FFT(y)
所以: y = IFFT(FFT(x) · FFT(h))
算法流程
输入: 输入特征图 x[H×W×C], 卷积核 h[R×S×C×M]
Step 1: 零填充
将x和h填充到合适大小 (H+R-1) × (W+S-1)
Step 2: FFT变换
X[k,l,c] = FFT(x_padded[h,w,c]) 对每个通道做2D FFT
H[k,l,c,m] = FFT(h_padded[r,s,c,m])
Step 3: 频域点乘
Y[k,l,m] = Σ X[k,l,c] · H[k,l,c,m] (对通道求和)
c
Step 4: 逆FFT
y[h,w,m] = IFFT(Y[k,l,m])
Step 5: 提取有效部分
从y中提取 (H-R+1) × (W-S+1) 的有效卷积结果
📊 复杂度分析
传统卷积: O(H·W·R·S·C·M)
FFT卷积: O((H+R)·(W+S)·C·M·log((H+R)·(W+S)))
当卷积核很大时,FFT更有优势:
- 小卷积核(3×3): 传统方法更快
- 大卷积核(11×11以上): FFT更快