\[ \begin{align}\begin{aligned}\newcommand{\ba}{\boldsymbol{a}} \newcommand{\bb}{\boldsymbol{b}} \newcommand{\be}{\boldsymbol{e}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bw}{\boldsymbol{w}} \newcommand{\bx}{\boldsymbol{x}} \newcommand{\by}{\boldsymbol{y}} \newcommand{\bz}{\boldsymbol{z}} \newcommand{\bd}{\boldsymbol{d}} \newcommand{\bv}{\boldsymbol{v}} \newcommand{\bs}{\boldsymbol{s}}\\\newcommand{\btheta}{\boldsymbol{\theta}} \newcommand{\bbeta}{\boldsymbol{\beta}} \newcommand{\bgamma}{\boldsymbol{\gamma}} \newcommand{\bsigma}{\boldsymbol{\sigma}} \newcommand{\md}{\mbox{d}} \newcommand{\bmu}{\boldsymbol{\mu}} \newcommand{\bone}{\boldsymbol{1}} \newcommand{\bzero}{\boldsymbol{0}} \newcommand{\bepsilon}{\boldsymbol{\epsilon}} \newcommand{\bphi}{\boldsymbol{\phi}} \newcommand{\bh}{\boldsymbol{h}} \newcommand{\bc}{\boldsymbol{c}} \newcommand{\br}{\boldsymbol{r}} \newcommand{\bQ}{\boldsymbol{Q}} \newcommand{\bK}{\boldsymbol{K}} \newcommand{\bV}{\boldsymbol{V}} \newcommand{\bSigma}{\boldsymbol{\Sigma}} \newcommand{\bg}{\boldsymbol{g}} \newcommand{\bxi}{\boldsymbol{\xi}} \newcommand{\bvarepsilon}{\boldsymbol{\varepsilon}} \newcommand{\bdelta}{\boldsymbol{\delta}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bJ}{\boldsymbol{J}} \newcommand{\bp}{\boldsymbol{p}} \newcommand{\bi}{\boldsymbol{i}} \newcommand{\bo}{\boldsymbol{o}} \newcommand{\bE}{\boldsymbol{E}} \newcommand{\bH}{\boldsymbol{H}} \newcommand{\bL}{\boldsymbol{L}} \newcommand{\bu}{\boldsymbol{u}} \newcommand{\bLambda}{\boldsymbol{\Lambda}} \newcommand{\trans}{^{\rm\scriptsize T}} \newcommand{\var}{\mathrm{var}}\\\newcommand{\bA}{\boldsymbol{A}} \newcommand{\bB}{\boldsymbol{B}} \newcommand{\bC}{\boldsymbol{C}} \newcommand{\bD}{\boldsymbol{D}} \newcommand{\bG}{\boldsymbol{G}} \newcommand{\bI}{\boldsymbol{I}} \newcommand{\bM}{\boldsymbol{M}} \newcommand{\bP}{\boldsymbol{P}} \newcommand{\bS}{\boldsymbol{S}} \newcommand{\bU}{\boldsymbol{U}} \newcommand{\bW}{\boldsymbol{W}} \newcommand{\bX}{\boldsymbol{X}} \newcommand{\bY}{\boldsymbol{Y}} \newcommand{\bZ}{\boldsymbol{Z}} \newcommand{\cotp}{\textcolor[RGB]{48,209,88}{TP}} \newcommand{\cotn}{\textcolor[RGB]{100,210,255}{TN}} \newcommand{\cofp}{\textcolor[RGB]{94,92,230}{FP}} \newcommand{\cofn}{\textcolor[RGB]{191,90,242}{FN}}\\\newcommand{\numcotp}{\textcolor[RGB]{48,209,88}{50}} \newcommand{\numcotn}{\textcolor[RGB]{100,210,255}{30}} \newcommand{\numcofp}{\textcolor[RGB]{94,92,230}{10}} \newcommand{\numcofn}{\textcolor[RGB]{191,90,242}{10}} \DeclareMathOperator*{\argmin}{arg\,min}\end{aligned}\end{align} \]

卷积与池化的后向传播#

学习目标与记号#

  1. 准确解释卷积核梯度、输入梯度与偏置梯度;

  2. 根据公式和张量维度分析平均池化与最大池化的后向传播,并识别常见实现错误;

  3. Python 实现或验证 数值梯度检验⁠;在其他条件相同的情况下,只改变一个需要研究的因素进行比较,并根据实验结果分析该因素可能带来的影响。

  本节沿用统一记号:普通小写字母表示标量,粗体小写字母表示向量,粗体大写字母表示矩阵或高阶张量;层编号写作上标 \([l]\)⁠,样本编号写作下标。批量大小记为 \(m\)⁠,程序中的卷积张量采用 NCHW 排列,即四个轴依次表示批量、通道、高度和宽度。正文与练习中的程序都应同时检查数值结果和数组维度。本节练习的参考答案见 卷积与池化的后向传播答案⁠。

  推导以 卷积层⁠、池化层后向传播链式法则 为基础;检查实现时应结合前面介绍的 数值梯度检验⁠。

  卷积层后向传播仍然只是链式法则。参数共享带来的区别是:同一个核元素参与许多空间位置的计算,所以它的梯度必须把这些位置以及小批量中的所有样本贡献相加。

卷积层梯度#

  考虑第 \(l\) 个计算层。设该层的输入、卷积核、偏置和线性运算结果分别为

\[\begin{split}\begin{aligned} \bA^{[l-1]} &\in\mathbb{R}^{m\times d_C^{[l-1]}\times d_H^{[l-1]}\times d_W^{[l-1]}},\\ \bW^{[l]} &\in\mathbb{R}^{d_C^{[l]}\times d_C^{[l-1]}\times f_1^{[l]}\times f_2^{[l]}},\\ \bb^{[l]} &\in\mathbb{R}^{d_C^{[l]}\times1\times1},\\ \bZ^{[l]} &\in\mathbb{R}^{m\times d_C^{[l]}\times d_H^{[l]}\times d_W^{[l]}}. \end{aligned}\end{split}\]

这里,\(d_C^{[l]}\)⁠、\(d_H^{[l]}\)\(d_W^{[l]}\) 分别表示第 \(l\) 层输出的通道数、高度和宽度;\(f_1^{[l]}\)\(f_2^{[l]}\) 分别表示卷积核的高度和宽度。若两个方向的核大小相同,则简写为 \(f^{[l]}\)⁠。

  为避免“卷积还是互相关”的符号歧义,先考虑无填充、步幅为 1 的二维互相关。其下标公式为

\[Z_{i,q,r,s}^{[l]} =b_q^{[l]}+\sum_{c=1}^{d_C^{[l-1]}} \sum_{u=1}^{f_1^{[l]}}\sum_{v=1}^{f_2^{[l]}} W_{q,c,u,v}^{[l]}A_{i,c,r+u-1,s+v-1}^{[l-1]},\]

其中,\(i=1,\ldots,m\) 是样本下标,\(c\)\(q\) 分别是输入通道与输出通道下标,\(r,s\) 是输出位置下标,\(u,v\) 是卷积核位置下标。记后续计算传回该线性运算结果的梯度为

\[\mathrm{d}\bZ^{[l]} :=\frac{\partial\mathcal{J}}{\partial\bZ^{[l]}}, \qquad \mathrm{d}Z_{i,q,r,s}^{[l]} =\frac{\partial\mathcal{J}}{\partial Z_{i,q,r,s}^{[l]}}.\]

  对权重和偏置求导得到

\[\begin{split}\begin{aligned} \frac{\partial\mathcal{J}}{\partial W_{q,c,u,v}^{[l]}} &=\sum_{i=1}^{m}\sum_r\sum_s \mathrm{d}Z_{i,q,r,s}^{[l]} A_{i,c,r+u-1,s+v-1}^{[l-1]},\\ \frac{\partial\mathcal{J}}{\partial b_q^{[l]}} &=\sum_{i=1}^{m}\sum_r\sum_s \mathrm{d}Z_{i,q,r,s}^{[l]}. \end{aligned}\end{split}\]

  卷积核梯度 \(\mathrm{d}\bW^{[l]}\) 的维度与 \(\bW^{[l]}\) 相同;偏置梯度 \(\mathrm{d}\bb^{[l]}\) 对批量和两个空间轴求和,只保留输出通道轴。如果 \(\mathcal{J}\) 已经定义为 \(m\) 个样本损失的平均值,那么因子 \(1/m\) 已经包含在 \(\mathrm{d}\bZ^{[l]}\) 中,不能在上述求和之后再次除以 \(m\)⁠。

  输入位置 \(A_{i,c,a,b}^{[l-1]}\) 可能被多个滑动窗口覆盖,因此

\[\frac{\partial\mathcal{J}}{\partial A_{i,c,a,b}^{[l-1]}} =\sum_q\sum_u\sum_v W_{q,c,u,v}^{[l]} \mathrm{d}Z_{i,q,a-u+1,b-v+1}^{[l]},\]

其中只对合法的 \(\mathrm{d}Z_{i,q,a-u+1,b-v+1}^{[l]}\) 下标求和。若使用框架提供的二维互相关算子表达该式,需要旋转卷积核并进行能够覆盖全部位置的填充;这些操作来自下标对应关系。采用步幅、填充或空洞率后,原理不变,但合法位置的对应关系更复杂,实际程序通常交给框架的自动微分和高效卷积算子。

  若卷积之后还有激活函数 \(\bA^{[l]}=\sigma^{[l]}(\bZ^{[l]})\)⁠,应先计算

\[\mathrm{d}\bZ^{[l]} =\mathrm{d}\bA^{[l]} \odot \sigma^{[l]\prime}(\bZ^{[l]}),\]

再把这个梯度送入上述卷积层的后向传播。

池化层梯度#

  池化没有可学习参数,但必须把输出梯度分配回输入窗口。设池化层接收 \(\bA^{[l]}\)⁠,输出记为 \(\bP^{[l+1]}\)⁠;池化通常保持通道数不变。记与输出位置 \((r,s)\) 对应的输入窗口为 \(\Omega_{r,s}^{[l+1]}\)⁠,其高度和宽度分别为 \(f_1^{[l+1]}\)\(f_2^{[l+1]}\)⁠。平均池化的前向传播为

\[P_{i,c,r,s}^{[l+1]} =\frac{1}{f_1^{[l+1]}f_2^{[l+1]}} \sum_{(a,b)\in\Omega_{r,s}^{[l+1]}} A_{i,c,a,b}^{[l]}.\]

  记 \(\mathrm{d}\bP^{[l+1]}:=\partial\mathcal{J}/\partial\bP^{[l+1]}\)⁠。该输出窗口对其中一个输入位置的梯度贡献为

\[\left.\mathrm{d}A_{i,c,a,b}^{[l]}\right|_{(r,s)} =\frac{\mathrm{d}P_{i,c,r,s}^{[l+1]}} {f_1^{[l+1]}f_2^{[l+1]}}, \qquad (a,b)\in\Omega_{r,s}^{[l+1]}.\]

  最大池化的前向传播应缓存每个窗口中实际选中的最大值位置。若该位置记为 \(m_{i,c,r,s}^{[l+1]}\)⁠,则当前窗口对输入位置的梯度贡献为

\[\left.\mathrm{d}A_{i,c,a,b}^{[l]}\right|_{(r,s)} =\mathrm{d}P_{i,c,r,s}^{[l+1]} \mathbb{1}\!\left\{(a,b)=m_{i,c,r,s}^{[l+1]}\right\},\]

其中,\(\mathbb{1}\{\cdot\}\) 是示性函数:条件成立时取 1,否则取 0。因此,最大池化把梯度送回前向传播选中的最大值位置,窗口内其他位置得到 0。无论采用平均池化还是最大池化,只要池化窗口重叠,同一输入位置就可能从多个输出窗口接收梯度,这些贡献必须相加。窗口内存在并列最大值时,应采用框架规定的选择规则,并在前向传播中保存与该规则一致的位置。

备注

  “反池化”不是池化的逆函数:除最大值索引外,池化已经丢失窗口内的其他信息,无法由输出唯一恢复输入。后向传播只是计算局部雅可比的转置与上游梯度之积。

核心推导与实现核验#

核心关系

\[\frac{\partial\mathcal{J}}{\partial W_{q,c,u,v}^{[l]}} =\sum_{i=1}^{m}\sum_r\sum_s \mathrm{d}Z_{i,q,r,s}^{[l]} A_{i,c,r+u-1,s+v-1}^{[l-1]}.\]

  推导路径。 一个核参数在所有样本和空间位置共享,因此它的梯度要累加所有使用该参数的计算路径;偏置梯度则对批量和空间轴全部求和。

关键条件

  由计算图中“共享变量的总导数等于各使用路径偏导之和”,可直接证明权重梯度必须对样本与输出位置求和。

数据规模

  后向传播必须为每个参数和输入位置给出一个对应的梯度。因此,\(\mathrm{d}\bW^{[l]}\) 与卷积核 \(\bW^{[l]}\) 的维度相同,\(\mathrm{d}\bA^{[l-1]}\) 与输入 \(\bA^{[l-1]}\) 的维度相同;\(\mathrm{d}\bb^{[l]}\) 则包含 \(d_C^{[l]}\) 个数。

常见误区

  重叠池化或卷积窗口会让同一输入位置收到多条梯度,必须累加而不能覆盖。最大值并列时梯度约定依框架而异。

动手检查

  按照 数值梯度检验 的步骤,在 float64\(1\times1\times4\times4\) 小张量上逐元素计算数值梯度,分别核对 \(\mathrm{d}\bA^{[l-1]}\)⁠、\(\mathrm{d}\bW^{[l]}\)\(\mathrm{d}\bb^{[l]}\)⁠;重叠窗口位置应收到多条路径梯度之和。ReLU 函数在 0 处不可导,最大池化在窗口内最大值并列时也可能没有唯一导数,数值检验应避开这些位置,或者按照框架采用的导数取值规则解释差异。

数值稳定性与规模

  先用整数公式验证输出尺寸非负;卷积累加采用足够精度。后向检查用 float64 小张量和 中心差分⁠,窗口重叠处必须累加而不是覆盖。

本节小结#

  1. 共享参数的梯度要沿所有使用路径累加。

  2. 每一种梯度都应与对应变量的维度相同。

  3. 中心差分 适合在小张量上验证后向实现。

综合练习#

  前四题用于推导、证明和具体计算,第 5--7 题用于编程实现与核验,第 8--10 题要求在其他条件相同的情况下,只改变一个需要研究的因素进行比较,并根据实验结果分析该因素可能带来的影响。程序题应固定随机种子、写出维度断言并报告运行环境;比较题还应固定数据划分、随机种子集合和训练预算。全部参考答案见 卷积与池化后向传播答案⁠。

  1. 卷积核梯度推导。\(Z_{i,q,r,s}^{[l]}=b_q^{[l]}+\sum_{c,u,v}A_{i,c,r+u-1,s+v-1}^{[l-1]}W_{q,c,u,v}^{[l]}\) 出发,推导 \(\partial\mathcal J/\partial W_{q,c,u,v}^{[l]}\)⁠,并解释为什么共享权重的梯度必须对 \(m\) 个样本和所有输出位置求和。若 \(\mathcal J\) 已经是批量平均损失,还应说明为什么不能在最后再次除以 \(m\)⁠。

  2. 卷积梯度具体计算。 单通道输入 \(\bA^{[l-1]}\)\(\begin{bmatrix}1&2&3\\4&5&6\\7&8&9\end{bmatrix}\)⁠,使用 \(2\times2\) 全 1 核、步幅 1、无填充,且 \(\mathrm{d}\bZ^{[l]}\)\(2\times2\) 全 1 矩阵。计算 \(\mathrm{d}\bW^{[l]}\)\(\mathrm{d}\bA^{[l-1]}\)⁠。

  3. 池化梯度计算。 对输入窗口 \(\begin{bmatrix}1&4\\3&2\end{bmatrix}\)⁠,设池化输出收到的梯度 \(\mathrm{d}P_{i,c,r,s}^{[l+1]}=6\)⁠。分别计算最大池化与平均池化对四个输入的梯度;说明最大值并列时实现必须预先规定怎样选择接收梯度的位置。

  4. 有限差分误差。 根据 数值梯度检验 中的中心差分公式,用 Taylor 展开说明截断误差为 \(O(h^2)\)⁠,并结合浮点舍入误差约为 \(O(\varepsilon_{\mathrm{mach}}/h)\)⁠,解释相对误差随 \(h\) 变化通常呈 U 形。

  5. 手工后向实现。 在第 5.1 节 NumPy 卷积前向的基础上,实现对输入、卷积核和偏置的后向传播;支持批量与多通道,并确保窗口重叠处使用累加而不是覆盖。

  6. 梯度核验。 按照 数值梯度检验 的步骤,对随机 float64 小张量抽查若干输入与卷积核元素,再把全部解析梯度与 PyTorch 自动微分结果 对照;报告最大绝对误差和相对误差。

  7. 测试设计。 至少测试:全 1 核的输入梯度呈正确重叠计数、偏置梯度等于 \(\mathrm{d}\bZ^{[l]}\) 求和、多样本与多通道梯度的维度分别与原变量相同、最大池化与平均池化的梯度分配正确、全零 \(\mathrm{d}\bZ^{[l]}\) 产生全零梯度,以及非法维度会失败;另用非对称卷积核检查程序是否采用了正确的核方向。

  8. 后向实现效率比较。 在同一小型 CNN 中比较 NumPy 朴素后向、向量化后向和 框架自动微分⁠。固定数据划分、随机种子集合、训练预算、初始化和硬件;报告任务性能、梯度差异、训练时间、固定批量预测时间、参数量及峰值内存。

  9. 池化梯度比较。 在结构相同的网络中仅替换最大池化与平均池化,记录池化前激活梯度的非零比例和范数。固定数据划分、随机种子集合和训练预算;报告任务性能、训练时间、固定批量预测时间、参数量及峰值内存。

  10. 数值精度比较。 比较 float64float32 和带损失缩放的混合精度卷积训练。固定数据划分、随机种子集合、模型和训练预算;报告任务性能、梯度有限性、训练时间、固定批量预测时间、参数量及峰值内存,并用 float64 小例作为梯度参考。