0 · 导读:为什么 CNN 的反向传播值得单独一篇
《神经网络》篇的结论可以压成一句话:反向传播 = 链式法则沿计算图逆拓扑序走一遍,每个密集层只出两种形态——误差回传 δ(l)=(W(l+1))⊤δ(l+1)⊙φ′(z(l))\delta^{(l)} = (W^{(l+1)})^\top\delta^{(l+1)}\odot\varphi'(z^{(l)})δ(l)=(W(l+1))⊤δ(l+1)⊙φ′(z(l)),权重梯度 ∂L/∂W=(a(l−1))⊤δ\partial\mathcal{L}/\partial W = (a^{(l-1)})^\top\delta∂L/∂W=(a(l−1))⊤δ。卷积层并没有推翻这个框架,它只是把「密集矩阵乘」换成了带权重共享的稀疏线性算子。由此产生三个 MLP 时代不存在的新问题:
- 权重梯度要累加,不是平均。一个 3×33\times33×3 核被 Ho×WoH_o\times W_oHo×Wo 个空间位置共享,每个位置都对同一份 WWW 产生一份梯度——∂L/∂W\partial\mathcal{L}/\partial W∂L/∂W 是对全部位置的求和。写丢一个 ∑\sum∑ 是最常见的静默错误;
- 输入梯度是 full convolution(转置卷积)。感受野重叠意味着同一个输入像素被多个输出位置共享,回传时同样要累加;而「前向算子的转置」在卷积这里的标准形式是 full conv——「翻转」第一次出现在反向传播里,这是全文的核心;
- stride / padding / dilation 各自变形。stride sss 在反向里变成对上游梯度 δ\deltaδ 的 dilation(插零);padding ppp 变成 full conv 之后的 裁剪;不能整除时的余数 rrr 变成 PyTorch 的
output_padding——三个超参在反向里各有归宿,记混任何一个都会得到「形状对、数值错」的梯度。
全文路线图:§1 把前向约定钉死(互相关 vs 真卷积、线性算子视角、im2col 视角)→ §2 给出统御全篇的「VJP / 转置」观点 → §3 卷积层三条公式的完整推导 + 3×3 手算全数字 → §4 其余算子(池化/激活/FC/softmax)→ §5 端到端微型 CNN(666 个参数全部过数值梯度检验)→ §6 常见错误清单 → §7 计算量与内存 → §8 框架映射与高性能内核。
1 · 前向传播速查:先把约定钉死
1.1 互相关 vs 真卷积:一切混乱的根源
一维情形,两个「卷积」的定义:
互相关: Y[i]=∑u=0k−1X[i⋅s+u−p]⋅W[u]真卷积: Y[i]=∑u=0k−1X[i⋅s+u−p]⋅W[k−1−u] \text{互相关:}\ Y[i] = \sum_{u=0}^{k-1} X[i\cdot s + u - p]\cdot W[u] \qquad \text{真卷积:}\ Y[i] = \sum_{u=0}^{k-1} X[i\cdot s + u - p]\cdot W[k-1-u] 互相关: Y[i]=u=0∑k−1X[i⋅s+u−p]⋅W[u]真卷积: Y[i]=u=0∑k−1X[i⋅s+u−p]⋅W[k−1−u]
差别只有一个:核要不要翻转 180°180°180°。PyTorch nn.Conv2d、TensorFlow、cuDNN 用的都是互相关(翻转对可学习的核是冗余操作,学出来的权重自然适配);而 Goodfellow《Deep Learning》教材、经典信号处理文献用的是真卷积。同一个公式在两套约定下长得不一样,是网上 CNN 反向传播推导互相矛盾的唯一根源。
本文约定表(后文所有公式都基于互相关约定):
| 约定 | 前向 | ∂L/∂W\partial\mathcal{L}/\partial W∂L/∂W | ∂L/∂X\partial\mathcal{L}/\partial X∂L/∂X |
|---|---|---|---|
| 互相关(PyTorch,本文) | Y=X⋆WY = X \star WY=X⋆W | XXX 与 GGG 的互相关 | GGG 与 WWW 的 full convolution(有翻转) |
| 真卷积(Goodfellow 教材) | Y=X∗WY = X * WY=X∗W | XXX 与 GGG 的 full convolution | GGG 与 WWW 的互相关(无翻转) |
两条约定数学上完全等价(把对方的核 rot180 即可互换),§3.3 末尾给出了这一点的机械证明。读任何推导前先问一句:他的卷积翻转吗?
1.2 前向公式与输出尺寸
batch 形式(NNN 样本、CCC 输入通道、FFF 输出通道、核 kH×kWk_H\times k_WkH×kW、stride sss、padding ppp):
Y[n,f,i,j]=b[f]+∑c=1C∑u=0kH−1∑v=0kW−1X[n,c, i⋅s+u−p, j⋅s+v−p]⋅W[f,c,u,v] Y[n,f,i,j] = b[f] + \sum_{c=1}^{C}\sum_{u=0}^{k_H-1}\sum_{v=0}^{k_W-1} X\big[n,c,\; i\cdot s + u - p,\; j\cdot s + v - p\big]\cdot W[f,c,u,v] Y[n,f,i,j]=b[f]+c=1∑Cu=0∑kH−1v=0∑kW−1X[n,c,i⋅s+u−p,j⋅s+v−p]⋅W[f,c,u,v]
输出尺寸(向下取整;不可整除时余数 r=(H+2p−kH) mod sr=(H+2p-k_H)\bmod sr=(H+2p−kH)mods,记住它,§3.3 和 §6 都要用):
Ho=⌊H+2p−kHs⌋+1 H_o = \left\lfloor \frac{H + 2p - k_H}{s}\right\rfloor + 1 Ho=⌊sH+2p−kH⌋+1
记上游梯度 G=∂L/∂YG = \partial\mathcal{L}/\partial YG=∂L/∂Y(形状与 YYY 相同)。反向传播要产出三样东西:dXdXdX(形状同 XXX,回传给前层)、dWdWdW(形状同 WWW)、dbdbdb(形状同 bbb)。
1.3 线性算子视角:conv 就是稀疏矩阵乘
把一维例子写成就知道了。X=(x0,…,x4)X=(x_0,\dots,x_4)X=(x0,…,x4) 长 5,W=(w0,w1,w2)W=(w_0,w_1,w_2)W=(w0,w1,w2) 长 3,p=1p=1p=1,s=1s=1s=1,输出长 5。前向 Y=MxY = MxY=Mx,其中
M=(0w1w200w0w1w2000w0w1w2000w0w1w2000w0w1) M = \begin{pmatrix} 0 & w_1 & w_2 & 0 & 0\\ w_0 & w_1 & w_2 & 0 & 0\\ 0 & w_0 & w_1 & w_2 & 0\\ 0 & 0 & w_0 & w_1 & w_2\\ 0 & 0 & 0 & w_0 & w_1 \end{pmatrix} M= 0w0000w1w1w000w2w2w1w0000w2w1w0000w2w1
——一条带状 Toeplitz 矩阵(每条对角线上的元素相同)。三个立刻到手的事实:
- conv = 稀疏矩阵乘:非零元只有 5×3=155\times3=155×3=15 个,密度 15/25=60%15/25=60\%15/25=60%;核再大些(k=7k=7k=7、H=64H=64H=64)密度降到 ≈11%\approx 11\%≈11%。所谓「局部连接 + 权重共享」在矩阵语言里就是「同一组 www 在对角线上反复出现」;
- BP = 矩阵转置:dX=M⊤GdX = M^\top GdX=M⊤G——这就是「转置卷积」这个名字的来历。M⊤M^\topM⊤ 对应的核正是 WWW 的 180°180°180° 翻转(§3.3 会把这个对应关系算实);
- 若 XXX 是循环补零(circular padding),MMM 变成循环矩阵,可被 FFT 对角化——这是 FFT 卷积的全部理论(§8)。
用 numpy 验证过:MxMxMx 与直接卷积逐元素一致(最大相对误差 2.1×10−152.1\times10^{-15}2.1×10−15),M⊤GM^\top GM⊤G 与直接索引累加完全相等(误差 0)。
1.4 im2col 视角:把 conv 变成一次 GEMM
把每个感受野窗口拉成一列,Xcol[n]∈RCkHkW×HoWoX_{\text{col}}[n]\in\mathbb{R}^{Ck_Hk_W \times H_oW_o}Xcol[n]∈RCkHkW×HoWo,核摊平 Wmat∈RF×CkHkWW_{\text{mat}}\in\mathbb{R}^{F\times Ck_Hk_W}Wmat∈RF×CkHkW,前向就是一次矩阵乘:
Y[n]=Wmat Xcol[n]+b Y[n] = W_{\text{mat}}\,X_{\text{col}}[n] + b Y[n]=WmatXcol[n]+b
这就是 im2col(Chellapilla, Puri & Simard, 2006):卷积 → GEMM,直接吃 cuBLAS 的高度优化。代价是显存:重叠窗口被复制 kHkWk_Hk_WkHkW 倍,XcolX_{\text{col}}Xcol 比 XXX 大 CkHkW/CCk_Hk_W/CCkHkW/C 倍(3×33\times33×3、C=64C=64C=64 时是 9 倍)。现代框架用 implicit GEMM(在 GEMM 内部按索引取数,不落地拷贝)绕开这个开销,但数学恒等不变——§3.5 的反向传播公式在 im2col 视图下是三行矩阵运算,这是手写实现最不容易错的形态。
2 · 反向传播总纲:每个算子只需交出 VJP
统御全篇的观点:前向是算子 fff,反向就是它的转置(伴随算子)fˉ\bar ffˉ——给定上游梯度 GGG,产出对各输入的梯度。自动微分里这叫 VJP(vector-Jacobian product),loss.backward() 干的就是给计算图每个节点挂上 VJP。
| 算子 | 前向 | VJP(输入 G=∂L/∂YG=\partial\mathcal{L}/\partial YG=∂L/∂Y) |
|---|---|---|
| conv | Y=X⋆W+bY = X\star W + bY=X⋆W+b | 三条:dX=⋆‾(G;W)dX = \overline{\star}(G;W)dX=⋆(G;W),dW=X⋆GdW = X\star GdW=X⋆G,db=∑Gdb=\sum Gdb=∑G(§3) |
| max pool | 窗口取 max | dXdXdX 只路由到 argmax 位置(§4.1) |
| avg pool | 窗口取均值 | dXdXdX 窗口内均分 G/(kHkW)G/(k_Hk_W)G/(kHkW)(§4.2) |
| ReLU | max(0,z)\max(0,z)max(0,z) | dZ=G⊙1[Z>0]dZ = G\odot\mathbb{1}[Z>0]dZ=G⊙1[Z>0](§4.3) |
| FC | Z=AW+bZ = A W + bZ=AW+b | dA=GW⊤dA = GW^\topdA=GW⊤,dW=A⊤GdW = A^\top GdW=A⊤G,db=∑Gdb=\sum Gdb=∑G(§4.5) |
| softmax+CE | p=softmax(z)p=\mathrm{softmax}(z)p=softmax(z) | dZ=(P−Y)/ndZ = (P-Y)/ndZ |

本文系统阐述卷积神经网络(CNN)反向传播的数学原理与工程实现,聚焦卷积层梯度计算三大公式:对bias求和、对权重的互相关累加、对输入的full convolution(转置卷积)。明确互相关约定下dX需经dilation、rot180翻转、padding裁剪等操作,并统一于VJP(向量-雅可比积)视角。涵盖池化、ReLU、FC及softmax+CE等算子的反向规则,通过3×3手算实例与666参数微型CNN端到端数值验证,揭示常见错误根源与计算/内存开销规律。
6679

被折叠的 条评论
为什么被折叠?



