第 06/24 期:神经网络的反向传播算法:从链式法则到梯度计算

文章目录

一、为什么前向计算容易,而参数梯度需要专门的算法

一个多层神经网络的前向传播可以理解为连续的函数复合:输入经过线性映射、非线性变换,再经过下一层映射,最终得到预测值。对于给定参数,前向计算只需要依次执行这些运算。然而,训练时需要知道损失函数对每个参数的偏导数,问题就发生了变化:某个早期权重对损失的影响,必须经过其后所有层才能传递到输出。

假设网络由 L L L 层构成,第 l l l 层的权重为 W ( l ) W^{(l)} W(l),偏置为 b ( l ) b^{(l)} b(l),输入激活为 a ( l − 1 ) a^{(l-1)} a(l−1)。前向计算定义为

z ( l ) = W ( l ) a ( l − 1 ) + b ( l ) , a ( l ) = ϕ l ( z ( l ) ) . z^{(l)}=W^{(l)}a^{(l-1)}+b^{(l)},\qquad a^{(l)}=\phi_l(z^{(l)}). z(l)=W(l)a(l−1)+b(l),a(l)=ϕl(z(l)).

这里先采用列向量约定: a ( l − 1 ) ∈ R n l − 1 a^{(l-1)}\in\mathbb R^{n_{l-1}} a(l−1)∈Rnl−1, W ( l ) ∈ R n l × n l − 1 W^{(l)}\in\mathbb R^{n_l\times n_{l-1}} W(l)∈Rnl×nl−1, b ( l ) , z ( l ) , a ( l ) ∈ R n l b^{(l)},z^{(l)},a^{(l)}\in\mathbb R^{n_l} b(l),z(l),a(l)∈Rnl。如果最后一层直接输出实数预测,可以取 ϕ L \phi_L ϕL 为恒等映射。设标量损失为 L ( a ( L ) , y ) \mathcal L(a^{(L)},y) L(a(L),y),训练所需的对象是全部梯度 ∇ W ( l ) L \nabla_{W^{(l)}}\mathcal L ∇W(l)L 与 ∇ b ( l ) L \nabla_{b^{(l)}}\mathcal L ∇b(l)L。

最直接的方法是针对每一个参数单独扰动并重新计算损失。但是,参数规模可能达到百万甚至更多;逐参数差分需要大量前向计算,而且差分结果还受到舍入误差与步长选择影响。反向传播(backpropagation)解决的不是"如何定义梯度",而是"如何在复合计算中高效复用中间导数"。它使用链式法则,从输出层向输入层传播一个与损失相关的梯度信号,并在每层形成参数梯度。

上一篇讨论了计算图与自动微分的基本机制。本期进一步把抽象的反向模式落到神经网络层级:推导每个矩阵的梯度,说明误差项究竟是什么,比较单样本与批量形式,并通过实际运行的 NumPy 实验验证公式。重点不是背诵几行更新代码,而是建立可以迁移到其他网络结构的求导方法。

二、链式法则在神经网络中究竟传递什么

先考虑一个只有标量的复合函数: u = w x + b u=wx+b u=wx+b, v = ϕ ( u ) v=\phi(u) v=ϕ(u), L = ℓ ( v , y ) \mathcal L=\ell(v,y) L=ℓ(v,y)。如果 x , y x,y x,y 固定而 w w w 是待训练参数,那么

∂ L ∂ w = ∂ L ∂ v ∂ v ∂ u ∂ u ∂ w = ∂ ℓ ( v , y ) ∂ v ϕ ′ ( u ) x . \frac{\partial\mathcal L}{\partial w} =\frac{\partial\mathcal L}{\partial v} \frac{\partial v}{\partial u} \frac{\partial u}{\partial w} =\frac{\partial\ell(v,y)}{\partial v}\phi'(u)x. ∂w∂L=∂v∂L∂u∂v∂w∂u=∂v∂ℓ(v,y)ϕ′(u)x.

这个表达式可拆成三部分:损失对预测值的敏感度、激活函数的局部导数,以及线性映射对参数的局部导数。三者缺一不可。如果只把输出误差乘上输入,而忽略激活函数的导数,通常不会得到正确梯度。反之,如果只计算激活函数导数而没有上游损失信号,也无法确定参数应该沿哪个方向改变。

对于向量复合 v = f ( u ) v=f(u) v=f(u) 与标量函数 L ( v ) \mathcal L(v) L(v),需要小心矩阵转置。采用列向量梯度约定时,若 J f ( u ) = ∂ v / ∂ u ∈ R m × n J_f(u)=\partial v/\partial u\in\mathbb R^{m\times n} Jf(u)=∂v/∂u∈Rm×n,则

∇ u L = J f ( u ) T ∇ v L . \nabla_u\mathcal L=J_f(u)^\mathsf T\nabla_v\mathcal L. ∇uL=Jf(u)T∇vL.

这一转置不是任意约定,而是由微分的标量不变性决定:

d L = ( ∇ v L ) T d v = ( ∇ v L ) T J f ( u ) d u = ( J f ( u ) T ∇ v L ) T d u . d\mathcal L=(\nabla_v\mathcal L)^\mathsf Tdv =(\nabla_v\mathcal L)^\mathsf TJ_f(u)du =(J_f(u)^\mathsf T\nabla_v\mathcal L)^\mathsf Tdu. dL=(∇vL)Tdv=(∇vL)TJf(u)du=(Jf(u)T∇vL)Tdu.

因此,反向传播中的核心运算是"局部雅可比矩阵的转置乘以上游梯度",而不必显式构造完整雅可比矩阵。对神经网络来说,局部运算往往是矩阵乘法、逐元素乘法和按批次求和,正适合向量化计算。

还要区分两种容易混淆的"误差"。预测残差 y ^ − y \hat y-y y^−y 是输出数值与目标数值之差;反向传播误差项 δ ( l ) \delta^{(l)} δ(l) 则定义为损失对该层预激活的梯度。只有在特定输出激活与损失函数组合下,两者才可能恰好相等。例如,均方误差配合恒等输出时有直接关系,但对一般隐藏层不能直接把 δ ( l ) \delta^{(l)} δ(l) 写成某个预测残差。

三、从输出层开始:构造可以递推的误差项

定义第 l l l 层的误差项为

δ ( l ) ≡ ∇ z ( l ) L = ∂ L ∂ z ( l ) ∈ R n l . \delta^{(l)}\equiv\nabla_{z^{(l)}}\mathcal L =\frac{\partial\mathcal L}{\partial z^{(l)}}\in\mathbb R^{n_l}. δ(l)≡∇z(l)L=∂z(l)∂L∈Rnl.

这是整个算法最重要的定义。注意它是对预激活 z ( l ) z^{(l)} z(l) 的梯度,而不是对激活输出 a ( l ) a^{(l)} a(l) 的梯度。之所以选择这个对象,是因为它与仿射层参数梯度之间存在非常简洁的关系。

在输出层,若损失直接依赖 a ( L ) a^{(L)} a(L),则逐元素激活函数满足

δ ( L ) = ∇ a ( L ) L ⊙ ϕ L ′ ( z ( L ) ) , \delta^{(L)} =\nabla_{a^{(L)}}\mathcal L\odot\phi_L'(z^{(L)}), δ(L)=∇a(L)L⊙ϕL′(z(L)),

其中 ⊙ \odot ⊙ 表示逐元素乘法。若 ϕ L \phi_L ϕL 不是逐元素函数(例如 softmax),应使用对应雅可比转置乘积,不能直接套用上式。对于单输出平方损失 L = 1 2 ( a ( L ) − y ) 2 \mathcal L=\frac12(a^{(L)}-y)^2 L=21(a(L)−y)2 且输出层激活为恒等映射,有

δ ( L ) = a ( L ) − y . \delta^{(L)}=a^{(L)}-y. δ(L)=a(L)−y.

若使用的是没有 1 / 2 1/2 1/2 系数的平方损失,导数会多一个系数 2 2 2;若对 N N N 个样本取均值,梯度还会包含 1 / N 1/N 1/N。这些系数不是无关紧要的装饰,必须与代码的损失定义保持一致。

对隐藏层,先看下一层的仿射变换: z ( l + 1 ) = W ( l + 1 ) a ( l ) + b ( l + 1 ) z^{(l+1)}=W^{(l+1)}a^{(l)}+b^{(l+1)} z(l+1)=W(l+1)a(l)+b(l+1)。根据链式法则,损失对本层激活输出的梯度为

∇ a ( l ) L = ( W ( l + 1 ) ) T δ ( l + 1 ) . \nabla_{a^{(l)}}\mathcal L =(W^{(l+1)})^\mathsf T\delta^{(l+1)}. ∇a(l)L=(W(l+1))Tδ(l+1).

再经过本层激活函数的局部导数,得到递推式

δ ( l ) = ( ( W ( l + 1 ) ) T δ ( l + 1 ) ) ⊙ ϕ l ′ ( z ( l ) ) . \boxed{\delta^{(l)}= \big((W^{(l+1)})^\mathsf T\delta^{(l+1)}\big) \odot\phi_l'(z^{(l)})}. δ(l)=((W(l+1))Tδ(l+1))⊙ϕl′(z(l)).

这个公式体现了两个连续操作:先将下一层的梯度经权重矩阵转置传播回来,再乘以本层非线性的局部导数。权重矩阵负责混合不同神经元的影响,激活函数导数负责调节局部敏感度。对于非逐元素运算,第二步应替换为相应的向量雅可比乘积。

四、为什么权重梯度是误差项与输入的外积

对第 l l l 层,预激活的第 i i i 个分量是

z i ( l ) = ∑ j = 1 n l − 1 W i j ( l ) a j ( l − 1 ) + b i ( l ) . z_i^{(l)}=\sum_{j=1}^{n_{l-1}}W_{ij}^{(l)}a_j^{(l-1)}+b_i^{(l)}. zi(l)=j=1∑nl−1Wij(l)aj(l−1)+bi(l).

求某个权重 W i j ( l ) W_{ij}^{(l)} Wij(l) 的偏导时,其余参数和输入保持不变。因为 ∂ z k ( l ) / ∂ W i j ( l ) \partial z_k^{(l)}/\partial W_{ij}^{(l)} ∂zk(l)/∂Wij(l) 只有 k = i k=i k=i 时非零,所以

∂ L ∂ W i j ( l ) = ∂ L ∂ z i ( l ) ∂ z i ( l ) ∂ W i j ( l ) = δ i ( l ) a j ( l − 1 ) . \frac{\partial\mathcal L}{\partial W_{ij}^{(l)}} =\frac{\partial\mathcal L}{\partial z_i^{(l)}} \frac{\partial z_i^{(l)}}{\partial W_{ij}^{(l)}} =\delta_i^{(l)}a_j^{(l-1)}. ∂Wij(l)∂L=∂zi(l)∂L∂Wij(l)∂zi(l)=δi(l)aj(l−1).

把所有 i , j i,j i,j 合并为矩阵,就得到

∇ W ( l ) L = δ ( l ) ( a ( l − 1 ) ) T . \boxed{\nabla_{W^{(l)}}\mathcal L =\delta^{(l)}(a^{(l-1)})^\mathsf T}. ∇W(l)L=δ(l)(a(l−1))T.

其维度是 n l × n l − 1 n_l\times n_{l-1} nl×nl−1,恰好与 W ( l ) W^{(l)} W(l) 相同。偏置的梯度更直接,因为 ∂ z i ( l ) / ∂ b j ( l ) \partial z_i^{(l)}/\partial b_j^{(l)} ∂zi(l)/∂bj(l) 是 Kronecker delta:

∇ b ( l ) L = δ ( l ) . \boxed{\nabla_{b^{(l)}}\mathcal L=\delta^{(l)}}. ∇b(l)L=δ(l).

这些公式解释了为什么实现反向传播时必须保存每一层的输入激活。如果没有 a ( l − 1 ) a^{(l-1)} a(l−1),即使已经算出 δ ( l ) \delta^{(l)} δ(l),也不能形成权重梯度。当然,可以在反向计算时重新计算部分前向激活以节省内存,但那是以额外计算量换取存储空间的工程策略。

对权重梯度的物理式直觉可以这样理解:某个连接的梯度由"下游损失对接收神经元的敏感度"与"该连接输入的大小"共同决定。输入为零时,该连接在该样本上的局部权重梯度为零;输入较大时,并不意味着一定需要大幅更新,因为误差项可能很小,甚至为零。梯度是局部一阶变化率,不等于最终应该采用的参数更新幅度;学习率和优化器仍会参与更新。

五、一个完整的两层网络推导

为了将所有符号落到具体矩阵上,考虑输入维度为 d d d、隐藏层宽度为 h h h、输出维度为 q q q 的网络:

z ( 1 ) = W ( 1 ) x + b ( 1 ) , a ( 1 ) = tanh ⁡ ( z ( 1 ) ) , z^{(1)}=W^{(1)}x+b^{(1)},\quad a^{(1)}=\tanh(z^{(1)}), z(1)=W(1)x+b(1),a(1)=tanh(z(1)),

y ^ = W ( 2 ) a ( 1 ) + b ( 2 ) . \hat y=W^{(2)}a^{(1)}+b^{(2)}. y^=W(2)a(1)+b(2).

这里 W ( 1 ) ∈ R h × d W^{(1)}\in\mathbb R^{h\times d} W(1)∈Rh×d、 W ( 2 ) ∈ R q × h W^{(2)}\in\mathbb R^{q\times h} W(2)∈Rq×h。采用单样本平方损失 L = 1 2 ∥ y ^ − y ∥ 2 2 \mathcal L=\frac12\|\hat y-y\|_2^2 L=21∥y^−y∥22。由于输出层是恒等映射,首先有

δ ( 2 ) = y ^ − y . \delta^{(2)}=\hat y-y. δ(2)=y^−y.

接着得到输出层参数梯度:

∇ W ( 2 ) L = δ ( 2 ) ( a ( 1 ) ) T , ∇ b ( 2 ) L = δ ( 2 ) . \nabla_{W^{(2)}}\mathcal L=\delta^{(2)}(a^{(1)})^\mathsf T, \qquad\nabla_{b^{(2)}}\mathcal L=\delta^{(2)}. ∇W(2)L=δ(2)(a(1))T,∇b(2)L=δ(2).

隐藏层的局部导数为 tanh ⁡ ′ ( z ) = 1 − tanh ⁡ 2 ( z ) \tanh'(z)=1-\tanh^2(z) tanh′(z)=1−tanh2(z),因此

δ ( 1 ) = ( ( W ( 2 ) ) T δ ( 2 ) ) ⊙ ( 1 − ( a ( 1 ) ) ⊙ 2 ) . \delta^{(1)}= \big((W^{(2)})^\mathsf T\delta^{(2)}\big) \odot\big(1-(a^{(1)})^{\odot2}\big). δ(1)=((W(2))Tδ(2))⊙(1−(a(1))⊙2).

最后得到

∇ W ( 1 ) L = δ ( 1 ) x T , ∇ b ( 1 ) L = δ ( 1 ) . \nabla_{W^{(1)}}\mathcal L=\delta^{(1)}x^\mathsf T, \qquad\nabla_{b^{(1)}}\mathcal L=\delta^{(1)}. ∇W(1)L=δ(1)xT,∇b(1)L=δ(1).

这里的 ( a ( 1 ) ) ⊙ 2 (a^{(1)})^{\odot2} (a(1))⊙2 表示逐元素平方。每一步的维度均可直接核对: δ ( 2 ) ∈ R q \delta^{(2)}\in\mathbb R^q δ(2)∈Rq, δ ( 1 ) ∈ R h \delta^{(1)}\in\mathbb R^h δ(1)∈Rh,而两个权重梯度分别为 q × h q\times h q×h 与 h × d h\times d h×d。这种维度检查可以发现大量常见错误,例如忘记转置、把外积写成内积,或者在错误的轴上求和。

激活函数导数在反向传播中扮演关键角色。下图展示 tanh ⁡ \tanh tanh 及其局部导数:输入绝对值较大时,导数接近零,意味着梯度在穿过这一局部变换时可能被显著缩小。这里观察的是单层局部敏感度,并不直接证明某个完整网络必然出现梯度消失。

图1: tanh ⁡ \tanh tanh 函数及其导数随预激活输入变化的曲线。

六、批量训练时矩阵方向为什么经常写反

许多 NumPy 与 PyTorch 实现采用"每行一个样本"的批量约定,与前文单样本列向量约定不同。令 X ∈ R N × d X\in\mathbb R^{N\times d} X∈RN×d 表示 N N N 个样本,参数矩阵取 W 1 ∈ R d × h W_1\in\mathbb R^{d\times h} W1∈Rd×h、 W 2 ∈ R h × q W_2\in\mathbb R^{h\times q} W2∈Rh×q,则前向计算变为

Z 1 = X W 1 + 1 N b 1 , A 1 = tanh ⁡ ( Z 1 ) , P = A 1 W 2 + 1 N b 2 . Z_1=XW_1+\mathbf1_Nb_1,\quad A_1=\tanh(Z_1),\quad P=A_1W_2+\mathbf1_Nb_2. Z1=XW1+1Nb1,A1=tanh(Z1),P=A1W2+1Nb2.

这里 b 1 ∈ R 1 × h b_1\in\mathbb R^{1\times h} b1∈R1×h、 b 2 ∈ R 1 × q b_2\in\mathbb R^{1\times q} b2∈R1×q,广播相当于将每一行加上同一偏置。为了与代码一致,取

L = 1 2 N ∑ i = 1 N ∥ P i , : − Y i , : ∥ 2 2 . \mathcal L=\frac{1}{2N}\sum_{i=1}^N\|P_{i,:}-Y_{i,:}\|_2^2. L=2N1i=1∑N∥Pi,:−Yi,:∥22.

定义批量输出误差 D 2 = ( P − Y ) / N D_2=(P-Y)/N D2=(P−Y)/N。由矩阵微分可以直接得到

d L = tr ⁡ ( D 2 T d P ) , d P = d A 1 W 2 + A 1 d W 2 + 1 N d b 2 . d\mathcal L=\operatorname{tr}(D_2^\mathsf T dP), \qquad dP=dA_1W_2+A_1dW_2+\mathbf1_Ndb_2. dL=tr(D2TdP),dP=dA1W2+A1dW2+1Ndb2.

利用迹运算的循环性质,可读出

∇ W 2 L = A 1 T D 2 , ∇ b 2 L = ∑ i = 1 N D 2 , i , : . \nabla_{W_2}\mathcal L=A_1^\mathsf TD_2, \qquad\nabla_{b_2}\mathcal L=\sum_{i=1}^ND_{2,i,:}. ∇W2L=A1TD2,∇b2L=i=1∑ND2,i,:.

隐藏层误差为

D 1 = ( D 2 W 2 T ) ⊙ ( 1 − A 1 ⊙ 2 ) , D_1=(D_2W_2^\mathsf T)\odot(1-A_1^{\odot2}), D1=(D2W2T)⊙(1−A1⊙2),

因此

∇ W 1 L = X T D 1 , ∇ b 1 L = ∑ i = 1 N D 1 , i , : . \boxed{\nabla_{W_1}\mathcal L=X^\mathsf TD_1, \qquad\nabla_{b_1}\mathcal L=\sum_{i=1}^ND_{1,i,:}}. ∇W1L=XTD1,∇b1L=i=1∑ND1,i,:.

注意这里 W 1 W_1 W1 的形状为 d × h d\times h d×h,与单样本列向量写法的 h × d h\times d h×d 不同。这不是两个算法相互矛盾,而是同一线性映射在两种数据布局下的不同表达。判断公式是否正确,应先明确样本轴和特征轴,而不是直接照搬别人的转置位置。

批量平均的系数应只引入一次。若已定义 D 2 = ( P − Y ) / N D_2=(P-Y)/N D2=(P−Y)/N,后续 A 1 T D 2 A_1^\mathsf TD_2 A1TD2 不应再次除以 N N N。相反,如果将 D 2 D_2 D2 定义为未归一化的残差,就应在形成最终梯度时统一除以 N N N。两种写法都可以,但混用会使学习率的实际作用与预期不一致。

七、梯度校验:让解析公式接受数值检验

反向传播公式看起来正确,并不代表代码实现一定正确。一个非常有效的检查是中心差分。对于参数向量 θ \theta θ 的第 j j j 个分量,使用

g j F D ( h ) = L ( θ + h e j ) − L ( θ − h e j ) 2 h g_j^{\mathrm{FD}}(h)= \frac{\mathcal L(\theta+he_j)-\mathcal L(\theta-he_j)}{2h} gjFD(h)=2hL(θ+hej)−L(θ−hej)

近似解析梯度 g j = ∂ L / ∂ θ j g_j=\partial\mathcal L/\partial\theta_j gj=∂L/∂θj。在函数足够光滑、运算精度充分的条件下,中心差分的截断误差通常为 O ( h 2 ) O(h^2) O(h2);但当 h h h 过小时,两次几乎相等的损失相减会放大浮点舍入误差。因此,不能简单地认为步长越小越好。

在本期实验中,我们对第一个权重矩阵的一个元素实施中心差分,并扫描多个步长。图2展示了绝对误差随步长变化的情况。它既可以验证链式法则的实现,也说明数值检验本身具有精度边界。

图2:中心差分步长与解析梯度校验误差的关系。

需要强调,梯度校验只是局部一致性检验。它不能证明网络具有良好的泛化能力,也不能证明训练过程一定收敛。对于 ReLU 等不可微点附近的模型,数值差分还可能跨越不同的分段区域,导致与所采用的次梯度约定不一致。实际测试时应避开不可微点,并采用多个参数位置和不同随机输入进行检查。

八、梯度计算正确之后,训练仍然可能失败

得到正确梯度只是优化的必要条件之一。最简单的梯度下降更新为

θ t + 1 = θ t − η ∇ θ L ( θ t ) , \theta_{t+1}=\theta_t-\eta\nabla_\theta\mathcal L(\theta_t), θt+1=θt−η∇θL(θt),

其中 η > 0 \eta>0 η>0 是学习率。梯度给出当前点的局部一阶下降方向,但有限步长是否降低损失还取决于损失曲面的曲率、参数尺度和更新幅度。如果学习率过大,更新可能跨过低损失区域,甚至产生震荡;如果学习率过小,下降可能非常缓慢。

我们使用相同初始化、相同数据和相同解析梯度,对三种学习率执行固定次数的全批量梯度下降。图3展示了损失轨迹。实验是特定合成数据和网络配置下的观察,不应据此推断某个学习率在所有任务中都最优。

图3:相同初始参数下,不同学习率对应的训练损失变化。

还应区分损失下降与梯度消失。训练损失变化缓慢,可能是梯度很小,也可能是学习率设置不当、数据尺度不合适、优化目标存在平台区,或者当前参数接近某个驻点。只看损失曲线无法唯一确定原因。更有解释力的诊断应同时观察各层梯度范数、激活分布、参数更新量以及必要的数值梯度校验。

九、多层链式乘积与梯度消失、梯度爆炸的联系

反向传播通过逐层雅可比转置相乘,把输出损失的敏感度传到早期层。忽略偏置并简化记号,可将某个早期激活的梯度写成

∇ a ( l ) L = J l + 1 T J l + 2 T ⋯ J L T ∇ a ( L ) L . \nabla_{a^{(l)}}\mathcal L= J_{l+1}^\mathsf TJ_{l+2}^\mathsf T\cdots J_L^\mathsf T \nabla_{a^{(L)}}\mathcal L. ∇a(l)L=Jl+1TJl+2T⋯JLT∇a(L)L.

由矩阵范数的次乘性,有上界

∥ ∇ a ( l ) L ∥ 2 ≤ ( ∏ k = l + 1 L ∥ J k ∥ 2 ) ∥ ∇ a ( L ) L ∥ 2 . \|\nabla_{a^{(l)}}\mathcal L\|2 \leq\left(\prod{k=l+1}^L\|J_k\|2\right) \|\nabla{a^{(L)}}\mathcal L\|_2. ∥∇a(l)L∥2≤(k=l+1∏L∥Jk∥2)∥∇a(L)L∥2.

这个上界说明:若沿路径的局部算子范数普遍较小,梯度存在衰减风险;若某些方向上的增益持续较大,梯度也可能被放大。但上界本身不能反向证明实际梯度一定爆炸。矩阵方向、奇异值结构、激活状态和不同路径的相互作用都会影响真实梯度。

为了单独观察连乘效应,图4使用理想化的标量局部增益 s s s,绘制 s L s^L sL 随链长变化的曲线。它不是实际神经网络的训练结果,而是对链式乘积的数学示意: s < 1 s<1 s<1 时指数衰减, s > 1 s>1 s>1 时指数增长, s = 1 s=1 s=1 时保持不变。真实网络中,每一层的局部雅可比通常都不相同。

图4:不同固定局部增益的连乘结果,展示深度对梯度尺度的潜在影响。

这也解释了为什么权重初始化、残差连接、归一化和激活函数的选择会影响深层网络的训练行为。不过,这些机制各自改变的是不同的数学结构,不能把它们简单理解为"把梯度放大"或"把梯度缩小"。后续关于初始化与梯度问题的专题将进一步讨论这些条件。

十、从公式到代码:NumPy 实现反向传播

以下实验使用纯 NumPy 实现两层网络的前向传播、反向传播、梯度校验与梯度下降。输入数据由固定随机种子生成,网络采用 2 个输入特征、8 个隐藏单元和 1 个输出,隐藏层激活为 tanh ⁡ \tanh tanh。目标数据是带有非线性结构的合成回归信号,主要用于验证求导与训练流程,而不是比较不同网络在真实任务上的泛化能力。

实验的核心函数可以概括为:

python 复制代码
def forward(params):
    w1, b1, w2, b2 = params
    z1 = X @ w1 + b1
    a1 = np.tanh(z1)
    prediction = a1 @ w2 + b2
    return z1, a1, prediction


def gradients(params):
    w1, b1, w2, b2 = params
    z1, a1, prediction = forward(params)
    d2 = (prediction - y) / len(X)
    grad_w2 = a1.T @ d2
    grad_b2 = d2.sum(axis=0, keepdims=True)
    d1 = (d2 @ w2.T) * (1 - a1**2)
    grad_w1 = X.T @ d1
    grad_b1 = d1.sum(axis=0, keepdims=True)
    return [grad_w1, grad_b1, grad_w2, grad_b2]

代码采用批量行向量约定。 X X X 的形状为 ( 64 , 2 ) (64,2) (64,2), w 1 w_1 w1 为 ( 2 , 8 ) (2,8) (2,8), a 1 a_1 a1 为 ( 64 , 8 ) (64,8) (64,8), w 2 w_2 w2 为 ( 8 , 1 ) (8,1) (8,1),最终预测为 ( 64 , 1 ) (64,1) (64,1)。这里 d2 已经包含样本均值系数,所以计算权重梯度时不再额外除以 64。

完整可执行程序包含绘图、学习率对照、梯度差分检验和图像保存,随本期文章一并提供。运行环境为 Python 3、NumPy 和 Matplotlib,执行命令为 python experiment.py。该程序使用固定随机种子,并将图片输出到 images/ 目录。实验只依赖 CPU,不需要深度学习框架或外部数据集。

实际运行时,初始半均方误差为约 0.662627 0.662627 0.662627,对指定权重的中心差分校验在扫描的步长中取得约 6.48 × 10 − 12 6.48\times10^{-12} 6.48×10−12 的最小绝对误差。这个结果表明被检查位置的解析梯度与数值差分在浮点精度范围内高度一致,但不能代替对全部参数、不同输入和其他网络结构的系统测试。

除了单个参数,还可以观察不同参数块的梯度范数。图5绘制初始时刻四组参数梯度的 Frobenius 范数,便于比较不同层和偏置项的梯度尺度。由于参数块包含的元素数量不同,范数大小不能直接作为"哪一层更重要"的证据,也不应仅凭这一张图判断是否存在训练故障。

图5:同一初始参数下,两个权重矩阵与两个偏置向量的梯度范数。

十一、实现反向传播时最容易遗漏的细节

首先是损失函数的归一化。对于回归问题, 1 2 N ∑ i ∥ y ^ i − y i ∥ 2 \frac{1}{2N}\sum_i\|\hat y_i-y_i\|^2 2N1∑i∥y^i−yi∥2 与 1 N ∑ i ∥ y ^ i − y i ∥ 2 \frac1N\sum_i\|\hat y_i-y_i\|^2 N1∑i∥y^i−yi∥2 只差一个常数因子,但梯度也相应差一倍。对于多输出情形,是否再除以输出维度 q q q,同样取决于所采用的损失定义。把某个框架默认的 mean 规约直接搬到手写推导中,容易造成系数不一致。

其次是激活函数导数的求值位置。对于 tanh ⁡ \tanh tanh,可以利用已经保存的激活输出计算 1 − a 2 1-a^2 1−a2;对于 sigmoid,可使用 a ( 1 − a ) a(1-a) a(1−a);对于 ReLU,则要依据预激活是否大于零确定局部导数,并明确在零点采用的次梯度约定。不能把不同激活函数的导数公式混用,更不能把导数直接作用于错误的张量。

再次是偏置的广播。前向传播中一个偏置向量被复制到所有样本,因此反向传播时必须沿样本轴累加其梯度。sum(axis=0, keepdims=True) 在行样本约定下对应这一操作。如果误写成 axis=1,要么得到错误形状,要么在某些广播情况下产生难以察觉的逻辑错误。

第四是参数更新顺序。计算一次完整梯度时,所有层的梯度都应基于同一组参数。若在尚未完成其他层梯度计算前就原地更新某一层权重,后续反传可能使用新旧参数混合的状态,破坏链式法则对应的计算图。简单的实现应先计算全部梯度,再统一更新参数。

第五是数值稳定性。对于 sigmoid 或 softmax 与交叉熵的组合,实际实现通常会融合运算并使用稳定的 logits 形式,以避免指数溢出和对极小概率直接取对数。对这种融合损失,应推导整体梯度,而不是在数值不稳定的中间概率上机械拆分。对于深层网络,梯度裁剪等策略可以限制更新相关的梯度幅值,但不会自动修复错误的梯度公式。

最后是梯度检查的范围。只检验一个权重元素是一项快速冒烟测试,不是完整的正确性证明。更稳妥的流程是:对所有参数块随机抽取多个元素,使用不同差分步长,检查绝对误差与相对误差,并在不含不可微点的多个输入上重复。对参数量较小的教学网络,还可以逐元素检查所有参数;对大型网络则需要在成本与覆盖率之间折中。

十二、从反向传播走向更一般的网络结构

标准全连接层的反向传播公式之所以简洁,是因为其局部结构只有矩阵乘法、偏置相加和逐元素非线性。对于卷积层、归一化层、注意力层与残差连接,原则仍然相同:确定局部计算的输入输出,写出上游梯度如何经过局部雅可比转置传播,并对共享参数的所有贡献求和。

例如,当某个张量沿两条路径流向同一损失时,它收到的总梯度是两条路径贡献之和。这来自微分的线性性,而不是网络结构的特殊技巧。若 u u u 同时参与 f ( u ) f(u) f(u) 与 g ( u ) g(u) g(u),损失为 L = f ( u ) + g ( u ) \mathcal L=f(u)+g(u) L=f(u)+g(u),则 ∇ u L = ∇ u f + ∇ u g \nabla_u\mathcal L=\nabla_uf+\nabla_ug ∇uL=∇uf+∇ug。残差网络中的跳跃连接正是利用了这种多路径结构,但是否改善训练还要结合具体雅可比和优化条件分析。

参数共享同样遵循贡献累加。若同一个权重在不同时间步或不同空间位置被重复使用,其梯度是所有使用位置的局部贡献之和。这一点连接了全连接网络反向传播与循环神经网络的时间反向传播,以及卷积核的共享权重求导。换言之,复杂网络并不需要一套完全不同的微积分规则,而是需要正确管理更复杂的计算图与张量布局。

反向传播也不等于优化器。它负责给出给定参数处的梯度;梯度下降、动量法、Adam 等优化算法负责利用梯度决定参数更新。两者可以组合,却不能相互替代。一个梯度实现完全正确的网络,仍可能因为优化超参数、数据分布或模型结构而训练不理想;反过来,优化器再复杂,也无法补救系统性错误的梯度计算。

十三、用微分形式重新验证矩阵梯度

前面的分量推导已经足以得到正确结果,但在网络层数和张量维度增加后,逐元素写偏导会迅速变得繁琐。矩阵微分提供了一种更稳定的推导方法。考虑单层仿射变换 z = W a + b z=Wa+b z=Wa+b,设上游梯度为 δ = ∇ z L \delta=\nabla_z\mathcal L δ=∇zL。在输入 a a a 固定时,参数微小变化导致

d z = ( d W ) a + d b . dz=(dW)a+db. dz=(dW)a+db.

由于 L \mathcal L L 为标量,损失的一阶变化为

d L = δ T d z = δ T ( d W ) a + δ T d b . d\mathcal L=\delta^\mathsf T dz =\delta^\mathsf T(dW)a+\delta^\mathsf Tdb. dL=δTdz=δT(dW)a+δTdb.

利用迹的恒等式 u T M v = tr ⁡ ( v u T M ) u^\mathsf TMv=\operatorname{tr}(vu^\mathsf TM) uTMv=tr(vuTM),可将第一项写为

δ T ( d W ) a = tr ⁡ ( ( δ a T ) T d W ) . \delta^\mathsf T(dW)a =\operatorname{tr}\big((\delta a^\mathsf T)^\mathsf T dW\big). δT(dW)a=tr((δaT)TdW).

按照矩阵梯度的定义 d L = tr ⁡ ( ( ∇ W L ) T d W ) + ( ∇ b L ) T d b d\mathcal L=\operatorname{tr}((\nabla_W\mathcal L)^\mathsf TdW)+(\nabla_b\mathcal L)^\mathsf Tdb dL=tr((∇WL)TdW)+(∇bL)Tdb,再次得到 ∇ W L = δ a T \nabla_W\mathcal L=\delta a^\mathsf T ∇WL=δaT 与 ∇ b L = δ \nabla_b\mathcal L=\delta ∇bL=δ。这不仅复现了分量法结论,还表明公式来自标量微分对所有微小参数扰动的线性响应,而不是对矩阵求导符号的机械记忆。

如果输入也需要梯度,不能再把 a a a 视为常量。完整微分为 d z = ( d W ) a + W d a + d b dz=(dW)a+Wda+db dz=(dW)a+Wda+db,因此额外出现 δ T W d a \delta^\mathsf TWda δTWda。由此读出 ∇ a L = W T δ \nabla_a\mathcal L=W^\mathsf T\delta ∇aL=WTδ,恰好就是向前一层传播的梯度。可见,同一个局部微分同时给出参数梯度与输入梯度,只是关注的变量不同。自动微分框架对一个算子编写反向规则时,通常也需要返回其各个可微输入的梯度贡献。

这个观点对调试特别有用。如果某层的参数梯度形状正确而输入梯度形状错误,问题很可能出现在转置或张量轴的处理上;如果输入梯度正确但参数梯度异常,则应重点检查前向输入是否保存正确,以及批量累加是否符合损失规约。矩阵微分把这两类错误清楚地分开。

十四、为什么反向传播能够节省大量重复计算

考虑一个标量输出的深层网络,参数总数为 P P P。如果通过对每个参数分别扰动求导,中心差分需要约 2 P 2P 2P 次完整前向计算;若用符号求导为每个参数单独展开链式法则,许多中间子表达式也会反复出现。反向传播则先完成一次前向计算,保存必要的中间激活,然后从输出端沿计算图逆向传播梯度,避免为每个参数重新求值整张图。

对于标量损失,反向模式自动微分的成本通常与若干次前向计算处于同一量级,而不是简单地随参数数目增加为 P P P 次前向计算。当然,这不是对所有程序、所有算子都成立的固定倍数结论。实际开销还受到中间激活存储、算子实现、硬件并行、控制流和梯度计算是否稀疏等因素影响。这里的核心优势在于共享中间计算,而非某个普适的运行时间常数。

前向模式和反向模式的选择还与输入输出维度有关。若函数从少量参数映射到大量输出,前向模式的雅可比向量积可能更合适;若函数从大量参数映射到一个标量损失,反向模式的向量雅可比积通常更有优势。神经网络训练恰好具有"参数多、最终目标是标量"的典型结构,因此反向模式成为主流。

但节省计算不意味着没有存储成本。反向传播通常需要访问前向阶段的某些中间结果。例如,计算 tanh ⁡ \tanh tanh 的梯度需要知道激活输出或预激活,计算权重梯度需要知道该层输入。随着网络深度、批量大小与特征图尺寸增加,这些激活可能占据大量显存。梯度检查点技术选择性地不保存部分中间结果,反向阶段再重新计算,从而用额外计算换取较低峰值内存。是否划算取决于计算与存储的实际瓶颈。

十五、从单个样本的梯度到总体训练目标

训练集包含多个样本时,目标函数一般写成经验风险

R N ( θ ) = 1 N ∑ i = 1 N ℓ ( f θ ( x i ) , y i ) . \mathcal R_N(\theta)=\frac1N\sum_{i=1}^N\ell(f_\theta(x_i),y_i). RN(θ)=N1i=1∑Nℓ(fθ(xi),yi).

在可交换求导与有限求和的条件下,梯度满足

∇ θ R N ( θ ) = 1 N ∑ i = 1 N ∇ θ ℓ ( f θ ( x i ) , y i ) . \nabla_\theta\mathcal R_N(\theta) =\frac1N\sum_{i=1}^N\nabla_\theta\ell(f_\theta(x_i),y_i). ∇θRN(θ)=N1i=1∑N∇θℓ(fθ(xi),yi).

因此,全批量反向传播得到的是各样本梯度的平均。小批量训练只是从训练集中选取一部分样本形成近似梯度,通常不是对完整经验风险梯度的精确计算。若样本独立均匀抽取,且采样方案满足相应条件,小批量梯度可以成为完整梯度的无偏估计;但分层采样、非均匀加权、数据增强或样本相关性都可能改变这一性质。

这种区别影响对训练曲线的解释。全批量梯度下降在固定数据上使用确定性梯度,而随机小批量训练的更新方向会随抽样而变化。即使代码与解析求导完全正确,相邻两步的训练损失也不一定单调下降。观察到损失波动时,首先应核对记录的是当前小批量损失还是整个训练集损失,然后再判断波动是否超出合理范围。

如果损失含有正则化项,例如 J ( θ ) = R N ( θ ) + λ 2 ∥ W ∥ F 2 \mathcal J(\theta)=\mathcal R_N(\theta)+\frac\lambda2\|W\|_F^2 J(θ)=RN(θ)+2λ∥W∥F2,总梯度还包含 λ W \lambda W λW。这一项不是由数据残差经过神经网络反向传播得到的,而是正则项本身对参数的直接导数。若采用只对权重、不对偏置施加正则的设计,就必须区分不同参数块。反向传播负责组合所有可微路径上的贡献,最终梯度才是优化器使用的完整梯度。

十六、输出层的不同损失组合不能混为一谈

平方损失配合恒等输出的情况比较简单,但分类任务经常使用 sigmoid 或 softmax 与交叉熵。考虑二分类 logit z z z、预测概率 p = σ ( z ) = 1 / ( 1 + e − z ) p=\sigma(z)=1/(1+e^{-z}) p=σ(z)=1/(1+e−z),以及标签 y ∈ { 0 , 1 } y\in\{0,1\} y∈{0,1}。二元交叉熵为

ℓ ( p , y ) = − y log ⁡ p − ( 1 − y ) log ⁡ ( 1 − p ) . \ell(p,y)=-y\log p-(1-y)\log(1-p). ℓ(p,y)=−ylogp−(1−y)log(1−p).

先对 p p p 求导,得到 ∂ ℓ / ∂ p = − y / p + ( 1 − y ) / ( 1 − p ) \partial\ell/\partial p=-y/p+(1-y)/(1-p) ∂ℓ/∂p=−y/p+(1−y)/(1−p);再乘以 d p / d z = p ( 1 − p ) dp/dz=p(1-p) dp/dz=p(1−p),可化简为

∂ ℓ ∂ z = p − y . \frac{\partial\ell}{\partial z}=p-y. ∂z∂ℓ=p−y.

这个简洁结果依赖于 sigmoid 与二元交叉熵的组合,不能因此得出"任何输出层的误差项都等于预测减标签"的结论。如果输出使用 sigmoid,但损失换成平方损失,梯度还会包含 p ( 1 − p ) p(1-p) p(1−p) 因子;如果输出层不使用 sigmoid 而直接把 logit 送入稳定的二元交叉熵实现,框架会在内部完成等价但更稳定的计算。

对多分类 softmax,令 p k = e z k / ∑ j e z j p_k=e^{z_k}/\sum_j e^{z_j} pk=ezk/∑jezj,类别标签采用 one-hot 向量 y y y,交叉熵为 ℓ = − ∑ k y k log ⁡ p k \ell=-\sum_k y_k\log p_k ℓ=−∑kyklogpk。利用 softmax 的雅可比

∂ p i ∂ z j = p i ( 1 i = j − p j ) , \frac{\partial p_i}{\partial z_j}=p_i(\mathbf1_{i=j}-p_j), ∂zj∂pi=pi(1i=j−pj),

并假设 ∑ k y k = 1 \sum_k y_k=1 ∑kyk=1,可得到

∂ ℓ ∂ z j = p j − y j . \frac{\partial\ell}{\partial z_j}=p_j-y_j. ∂zj∂ℓ=pj−yj.

这一推导与二分类形式相似,但内部机制是向量雅可比乘积而非单个 sigmoid 的逐元素导数。对于标签平滑或软标签,只要目标分布各分量之和为一,上述结果仍然成立;如果目标向量不是规范化分布,则必须重新检查化简条件。实际数值实现通常先减去 logits 的最大值,或直接使用稳定的 log-sum-exp 形式,避免指数上溢。

这些例子说明,反向传播框架并不强制使用某种特定损失。真正需要的是从最终标量损失到输出层变量的正确局部导数。一旦输出层误差项求对,后续隐藏层的链式递推就可以保持相同结构。因而,在实现新任务时,优先核对损失函数、输出激活和标签编码的匹配关系,比盲目调整学习率更有价值。

十七、参数共享、分支与广播的梯度累加

考虑一个简单分支结构,输入 x x x 分别经过 u = f ( x ) u=f(x) u=f(x) 和 v = g ( x ) v=g(x) v=g(x),最终损失为 L = h ( u , v ) \mathcal L=h(u,v) L=h(u,v)。由于 x x x 同时影响两条路径,链式法则给出

∇ x L = J f ( x ) T ∇ u L + J g ( x ) T ∇ v L . \nabla_x\mathcal L =J_f(x)^\mathsf T\nabla_u\mathcal L +J_g(x)^\mathsf T\nabla_v\mathcal L. ∇xL=Jf(x)T∇uL+Jg(x)T∇vL.

这里必须求和。如果只保留最后一次反向传播的结果,就会丢失另一条路径的梯度。这是计算图实现中常见的逻辑错误,也解释了为什么自动微分系统需要在一个节点被多个后继节点使用时累积梯度。

再考虑共享权重 W W W 在两个位置被调用: u = W x 1 u=Wx_1 u=Wx1、 v = W x 2 v=Wx_2 v=Wx2,损失同时依赖 u u u 和 v v v。设对应上游梯度为 δ u \delta_u δu、 δ v \delta_v δv,则

∇ W L = δ u x 1 T + δ v x 2 T . \nabla_W\mathcal L=\delta_ux_1^\mathsf T+\delta_vx_2^\mathsf T. ∇WL=δux1T+δvx2T.

不能因为代码中只有一个权重对象就只计算其中一次贡献。卷积网络中一个卷积核在多个空间位置反复使用,循环网络中同一权重在多个时间步反复使用,均遵循这一累加规则。其复杂之处主要在于索引与张量重排,而不是梯度原理发生变化。

广播也可以理解为一种隐式共享。对批量数据执行 Z = X W + b Z=XW+b Z=XW+b 时,同一偏置 b b b 被每个样本使用,因此偏置梯度等于各样本误差项沿批量维度的和。类似地,如果某个参数沿时间维或空间维广播,它的梯度就需要沿所有被广播的维度累加。正确理解这一点,可以在没有框架自动求导的情况下推导复杂张量表达式。

十八、梯度检查中绝对误差与相对误差的取舍

数值校验常报告绝对误差 ∣ g j − g j F D ∣ |g_j-g_j^{\mathrm{FD}}| ∣gj−gjFD∣,但当真实梯度尺度差异很大时,单一绝对阈值可能不公平。例如,一个梯度本身为 10 − 2 10^{-2} 10−2,误差 10 − 6 10^{-6} 10−6 相对较小;另一个梯度为 10 − 8 10^{-8} 10−8,同样误差就十分严重。因此,可以结合相对误差

r j = ∣ g j − g j F D ∣ max ⁡ ( ε , ∣ g j ∣ + ∣ g j F D ∣ ) r_j=\frac{|g_j-g_j^{\mathrm{FD}}|}{\max(\varepsilon,|g_j|+|g_j^{\mathrm{FD}}|)} rj=max(ε,∣gj∣+∣gjFD∣)∣gj−gjFD∣

进行检查,其中 ε \varepsilon ε 是防止分母接近零的稳定常数。实际报告时应说明阈值和数值精度,不应将某个固定容差当成所有任务都适用的数学定理。

校验之前还要确保损失函数在两次扰动计算中是确定性的。若模型包含随机失活、随机数据增强或随机采样,而每次扰动使用不同随机状态,差分中就混入了随机变化,无法单独反映参数扰动的作用。对于批归一化等依赖批次统计量的算子,也应保持相同数据与计算模式。若使用混合精度或低精度浮点数,舍入误差可能明显增大,最好先在双精度、较小网络和固定输入上验证核心公式。

另外,检查结果必须与损失定义对应。把平均损失的解析梯度与求和损失的数值差分比较,会出现近似等于批量大小的比例差;把训练模式下的前向结果与推理模式下的反向公式混用,也会导致不一致。遇到系统性比例错误时,优先检查损失规约和梯度累加次数;遇到个别参数块错误时,优先检查转置、广播和激活导数。

十九、把一次反向传播拆成可审计的计算步骤

一个可靠的手写反向传播实现,应当能够回答每个中间数组从何而来。以本期网络为例,前向阶段首先得到输入矩阵 X X X、隐藏层预激活 Z 1 Z_1 Z1、隐藏层输出 A 1 A_1 A1 和最终预测 P P P。在这些对象中, Z 1 Z_1 Z1 与 A 1 A_1 A1 的保存策略可以有所不同:对 tanh ⁡ \tanh tanh,使用 A 1 A_1 A1 就能计算局部导数;若采用其他激活或融合算子,可能需要保存不同中间量。

反向阶段的第一步是计算损失对预测矩阵的梯度。在当前半均方误差定义下, D 2 = ( P − Y ) / N D_2=(P-Y)/N D2=(P−Y)/N。第二步利用 D 2 D_2 D2 与隐藏层输出 A 1 A_1 A1 形成 W 2 W_2 W2 的梯度,并沿样本轴累加形成 b 2 b_2 b2 的梯度。第三步将 D 2 D_2 D2 乘以 W 2 W_2 W2 的转置,把梯度从输出空间传播回隐藏层激活空间。第四步乘以 tanh ⁡ \tanh tanh 的局部导数,得到隐藏层预激活的梯度 D 1 D_1 D1。最后一步使用 X T D 1 X^\mathsf TD_1 XTD1 与样本轴求和,得到第一层权重和偏置梯度。

这一顺序看起来简单,但其内在约束很重要。形成 D 1 D_1 D1 时使用的必须是当前前向计算对应的 W 2 W_2 W2,而不能是已经更新过的权重。计算 W 2 W_2 W2 梯度时使用的必须是当前输入对应的 A 1 A_1 A1,而不是前一个批次缓存的激活。计算偏置梯度时必须沿样本轴聚合,不能错误地沿特征轴聚合。这些检查可以直接写成断言,例如 grad_w1.shape == w1.shape,以及所有梯度均为有限数值。

对于可审计的实验,还应记录随机种子、数据规模、模型结构、损失定义、学习率、迭代次数和浮点精度。随机种子保证在相同软件环境下可重复生成初始化和合成数据,但它不保证跨平台所有浮点运算都逐位一致。尤其在 GPU 并行、不同 BLAS 实现或非确定性算子参与时,结果可能存在微小差异。科研实验更应关注结果是否在声明的容差内一致,而不是将逐位相同作为唯一复现标准。

二十、局部梯度、参数敏感性与因果解释的边界

梯度刻画的是当前参数点附近,损失对一个无穷小参数扰动的一阶响应。若某个权重的梯度很大,可以说在该点附近、其他参数保持不变时,损失对这个权重的局部变化较敏感;但不能直接说这个权重对模型预测"最重要"。原因在于不同层的参数尺度、冗余表示和重参数化方式会影响梯度数值,而不一定改变网络表示的函数。

例如,考虑两个相邻的线性层且中间没有非线性: f ( x ) = W 2 W 1 x f(x)=W_2W_1x f(x)=W2W1x。在可逆矩阵 S S S 存在时,将 W 1 W_1 W1 改为 S W 1 SW_1 SW1,同时将 W 2 W_2 W2 改为 W 2 S − 1 W_2S^{-1} W2S−1,整体映射不变。但这两组参数的梯度分布和尺度一般不同。因此,比较不同参数化下的梯度范数时,需要避免将参数坐标中的敏感性直接等同于函数空间中的重要性。

同样,梯度接近零也有多种解释。它可能意味着已经接近局部驻点,可能意味着激活处于饱和区域,也可能意味着不同路径的梯度贡献发生抵消,或者数据分布使某些参数暂时没有得到有效激励。仅凭一次梯度范数无法区分这些情况。较可靠的诊断会结合多个训练时刻、不同批次和模型输出行为,必要时还要考察 Hessian 或局部曲率信息。

如果将梯度用于解释模型决策,还需要明确求导对象。损失对权重的梯度反映参数优化敏感性;输出对输入的梯度反映输入局部扰动对输出的影响;损失对输入的梯度则常用于对抗扰动或输入敏感性分析。这三者都可以由反向模式计算,但回答的是不同问题。将它们混为一谈,容易造成对模型机制的错误解释。

二十一、从计算复杂度看反向传播的实际瓶颈

以批量全连接层 Z = X W + b Z=XW+b Z=XW+b 为例,设 X X X 为 N × d N\times d N×d、 W W W 为 d × h d\times h d×h。前向矩阵乘法的主要算术量级为 O ( N d h ) O(Ndh) O(Ndh)。反向阶段计算 ∇ W L = X T D \nabla_W\mathcal L=X^\mathsf TD ∇WL=XTD 和 ∇ X L = D W T \nabla_X\mathcal L=DW^\mathsf T ∇XL=DWT,各自也具有相近的矩阵乘法量级。由此可以理解:对于这种稠密层,计算所有参数梯度并不需要对每个权重分别执行一次完整网络。

不过,时间复杂度只是一个方面。训练期间需要保存输入激活,权重梯度本身也需要存储,优化器可能还会为每个参数维护额外状态。以普通动量或 Adam 为例,优化器状态可能与参数规模处于相同数量级;这部分存储并不是反向传播的激活缓存,但会共同决定训练内存需求。对于大批量、高分辨率输入或很深的网络,激活存储甚至可能超过权重存储。

计算设备还会影响瓶颈位置。矩阵乘法通常能够利用高性能线性代数库,而逐元素激活、张量转置和数据复制可能受内存带宽限制。如果为了节省几行代码而频繁生成临时数组,实际运行时间未必理想。反之,融合算子可以减少中间张量的物化和内存访问,但融合后的梯度必须保持与原数学表达式一致。优化实现之前,首先应有一个数值正确、可独立测试的基线。

对于大规模网络,梯度累积也是常见策略。假设内存只能容纳较小的微批次,可以依次计算多个微批次的梯度并累加,再执行一次参数更新。若目标是模拟一个更大的批量均值损失,必须按各微批次样本数量正确加权;如果最后一个微批次大小不同,简单平均各微批次的均值梯度就不一定等于整个大批次的均值梯度。这个细节再次说明,梯度计算中的规约方式需要与数学目标保持一致。

二十二、如何判断一个反向传播实验是否真正说明问题

教学实验经常展示训练损失下降,但仅凭一条下降曲线不足以证明反向传播公式正确。一个错误梯度实现有时也能使损失下降,尤其是在简单数据或特定初始化下。因此,本期把证据拆成三个层次:首先用解析推导确定应有的梯度形式;然后用中心差分对局部参数梯度进行独立数值校验;最后在固定数据和初始化下观察实际优化轨迹。三者相互补充,却不能相互替代。

第一层证据的重点是可检查性。公式必须说明变量维度、损失规约和激活导数,而不是只给最终更新式。第二层证据的重点是局部数值一致性。有限差分使用与反向传播不同的计算路径,因而能发现部分实现错误,但受浮点精度与可微性限制。第三层证据的重点是算法行为:训练损失能否下降、学习率变化如何影响轨迹,以及不同参数块是否得到有限且合理的梯度。

实验还需要说明它没有证明什么。本期使用的是合成回归数据、浅层网络和固定随机种子,不能据此证明算法在真实图像、文本或时序任务上的表现,也不能据此比较各种优化器的优劣。局部增益连乘图只是数学示意,不能当作实际深层网络的梯度统计。梯度范数图展示某次初始化的局部数值,不构成参数重要性排名。

如果希望进一步扩展实验,可以改变隐藏层宽度、网络深度和激活函数,记录每层梯度范数的时间序列;也可以加入带噪声数据和独立验证集,区分训练优化与泛化性能;还可以对比不同参数初始化对局部雅可比尺度的影响。扩展时必须保持控制变量清晰,否则多个因素同时改变,就难以判断观测差异来自哪里。以上方向分别与后续激活函数、初始化和梯度稳定性专题衔接,本期不提前替代那些主题。

二十三、结语

神经网络反向传播可以归结为三个相互衔接的公式:输出层先形成损失对预激活的梯度,隐藏层通过权重转置与激活导数递推误差项,每一层再利用误差项和前向输入形成参数梯度。对单样本列向量,权重梯度表现为外积;对批量行样本,表现为输入矩阵转置乘以误差矩阵。这两种写法本质相同。

理解反向传播的关键不在于记住某一种代码风格,而在于明确每个梯度是对哪个变量求导、上游梯度来自哪里、局部雅可比如何作用、张量维度是否一致,以及多条计算路径的贡献如何累加。结合数值差分校验和实际训练曲线,可以把公式推导、代码实现与实验现象连接起来,为后续分析激活函数、初始化、梯度消失与优化算法建立可靠基础。

参考文献

1 Rumelhart, D. E., Hinton, G. E., & Williams, R. J. (1986). Learning representations by back-propagating errors. Nature, 323, 533--536. https://doi.org/10.1038/323533a0

2 Werbos, P. J. (1974). Beyond Regression: New Tools for Prediction and Analysis in the Behavioral Sciences. PhD dissertation, Harvard University.

3 Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press, Chapter 6. https://www.deeplearningbook.org/

4 Baydin, A. G., Pearlmutter, B. A., Radul, A. A., & Siskind, J. M. (2018). Automatic Differentiation in Machine Learning: a Survey. Journal of Machine Learning Research, 18(153), 1--43. https://jmlr.org/papers/v18/17-468.html

相关推荐
武子康9 分钟前
单卡 A6000 跑 Qwen-Drive:怎样留下可比较的第一条基线?
linux·人工智能·深度学习
落羽的落羽22 分钟前
【C++】C++17的主要特性
linux·服务器·开发语言·c++·算法·机器学习
后端小肥肠28 分钟前
公众号漫画又挖到长尾赛道:家常菜美食,流量稳定长期有收益
人工智能·aigc·agent
泯泷1 小时前
从"检索管道"到"主动助理"——Agentic RAG 与双层记忆(五)
人工智能·llm·agent
【心态好不摆烂】1 小时前
数据结构基础精讲3
数据结构·算法
武子康1 小时前
ComfyUI 加载了动作 LoRA,H3-World 的时间绑定就接好了吗?
人工智能·机器学习·claude
腾科IT教育1 小时前
HCIE-AI认证全部解析:方向选择、考试门槛与职业适配指南
人工智能·华为认证·hcie-ai·华为ai
QXWZ_IA1 小时前
具身智能在油气行业规模化应用面临哪些技术挑战?
人工智能·安全