ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

反向传播指南:手推一个2-3-1网络的梯度,避开梯度消失与爆炸两个坑

反向传播指南:手推一个2-3-1网络的梯度,避开梯度消失与爆炸两个坑 反向传播指南手推一个2-3-1网络的梯度避开梯度消失与爆炸两个坑【免费下载链接】nndl邱锡鹏《神经网络与深度学习》第二版与通识版电子书、章节目录、学习资源与勘误。项目地址: https://gitcode.com/GitHub_Trending/nn/nndl训练一个五层MLPloss卡在1.38不动了打开梯度监控前几层是接近0的小数后几层直接弹出NaN。这类症状几乎都出在梯度计算环节也就是反向传播Backpropagation这一步。本文从这两个症状切入先建立直觉再手推一遍小网络的完整梯度最后把代码和排障清单整理给你。读完这篇文章你能做到把反向传播每个公式的符号翻译成一句大白话手算一个2-3-1网络的逐层梯度数字可复核写一份最小反向传播实现并用数值梯度验证它对着训练日志判断梯度消失、爆炸、学习率失当训练不动时先看这两个信号信号一loss平台期。前几十步loss下降很快之后曲线变平。常见原因是深层的梯度已经小到乘不进参数里网络学不动了。信号二梯度出现0或NaN。打印各层梯度如果数值整体趋零多半是误差在逐层衰减如果出现NaN多半是某一步的指数运算溢出或学习率把参数甩出了定义域。两个信号的排查顺序都一样先确认反向传播算得对不对再怀疑数据和学习率。下面从它在算什么讲起。用产线追责理解反向传播在算什么把前馈网络想象成一条装配线x 是原料每一层 W、b 是一道工序y 是成品loss 是质检打分。质检不合格时追责是从末端往回走的成品站先认领责任我的输出离目标差多少这就是输出层误差然后把责任往前一道工序传传多少取决于上一道工序的输出乘以我自己的工序敏感度每一站拿到自己那份额乘以自己环节的导数才算出本站真正该改多少。整个过程只有一条规则责任沿反向路径回传每经过一层就乘一次该层的局部导数。这条规则就是链式法则它就是反向传播的全部。前向原料 → 成品 x ──[W1,b1]──▶ h ──[W2,b2]──▶ a ──▶ loss 反向追责回传 loss ◀── δ_out ◀── δ_h ◀── 各参数拿到自己的梯度 每一站上游责任 × 本站导数从损失函数到参数梯度反向传播公式下面按 损失 → 输出层误差 → 层间传递 → 参数梯度 的顺序推导。设网络有 L 层第 l 层z^(l) W^(l) a^(l-1) b^(l) a^(l) σ(z^(l))白话z 是加权求和的半成品a 是过激活函数后的成品σ 是逐元素作用的非线性函数。损失函数怎么定取均方误差L 1/2 (y - a^(L))^2白话成品 y 和输出 a^(L) 的差距平方乘 1/2 是为了求导时消掉系数。输出层误差最后一站先认领δ^(L) (a^(L) - y) ⊙ σ(z^(L))白话输出层的责任 我离目标差多少 × 我这一站对 z 有多敏感。⊙ 表示逐元素相乘σ 是激活函数在 z 处的导数。层间误差传递责任往上游递对中间层 lL-1 ≥ l ≥ 2δ^(l) ( W^(l1)^T δ^(l1) ) ⊙ σ(z^(l))白话上游把责任乘上连接我的权重递给我我再乘上本站导数才是我真正认领的那份。注意 W^(l1)^T 这个转置——责任是沿着权重反着走的。参数梯度各站写整改单有了每层的 δ参数梯度一步得出∂L/∂W^(l) δ^(l) (a^(l-1))^T ∂L/∂b^(l) δ^(l)白话权重该改多少 本站认领的责任 × 上游递进来的激活值偏置该改多少 本站责任原样照收。手推走查一个2-3-1网络的全部梯度换一组和常见教材不同的结构和数据。网络为 2 输入 → 3 隐藏 → 1 输出激活均为 Sigmoidσ(z)a(1-a)损失为上文的 MSE。参数W1 [[ 0.40, 0.60], b1 [ 0.00, 0.20, 0.10] [ 0.20, 0.30], [-0.10, -0.20]] W2 [ 0.50, -0.25, 0.30] b2 [ 0.10 ] x [1, 1]目标 y 0.8前向部分算出 loss隐藏层z1 0.40*1 0.60*1 0.00 1.00 a1 σ(1.00) 0.7311 z2 0.20*1 0.30*1 0.20 0.70 a2 σ(0.70) 0.6682 z3 -0.10*1 - 0.20*1 0.10 -0.20 a3 σ(-0.20) 0.4502输出层z 0.50*0.7311 - 0.25*0.6682 0.30*0.4502 0.10 0.4483 a σ(0.4483) 0.6103 L 1/2 (0.8 - 0.6103)^2 0.0181反向部分从 δ 传到参数输出层误差δ (a - y) * a * (1 - a) (0.6103 - 0.8) * 0.6103 * 0.3897 -0.0447隐藏层误差逐神经元σ(z1) 0.7311*0.2689 0.1966 σ(z2) 0.6682*0.3318 0.2217 σ(z3) 0.4502*0.5498 0.2475 δ1 (0.50 * -0.0447) * 0.1966 -0.0044 δ2 (-0.25 * -0.0447) * 0.2217 0.0025 δ3 (0.30 * -0.0447) * 0.2475 -0.0033写出参数梯度∂L/∂W2 δ * [a1, a2, a3] [-0.0327, -0.0299, -0.0201] ∂L/∂b2 δ [-0.0447] ∂L/∂W1 δ ⊙ x^Tx[1,1]两列相同 [[-0.0044, -0.0044], [ 0.0025, 0.0025], [-0.0033, -0.0033]] ∂L/∂b1 [δ1, δ2, δ3] [-0.0044, 0.0025, -0.0033]你可以拿上面的每一步单独复核前向只依赖 z→a 的代换反向只依赖 δ 的逐层回传。注意 δ2 是正的——第 2 个隐藏神经元出力方向和输出误差相反它该往反方向调这类符号信息手推时最容易被忽略。用30行代码实现反向传播矩阵写法一次算完一批样本手推用标量代码里全部换成矩阵乘法。对 m 个样本组成的 batch下面用单样本演示batch 时在相应位置除以 mimport numpy as np def sigmoid(z): return 1 / (1 np.exp(-z)) x np.array([[1.0, 1.0]]) y np.array([[0.8]]) W1 np.array([[0.40, 0.20, -0.10], [0.60, 0.30, -0.20]]) # 转置存法2×3 b1 np.array([0.00, 0.20, 0.10]) W2 np.array([[0.50, -0.25, 0.30]]) # 1×3 b2 np.array([0.10]) # 前向 a1 sigmoid(x W1 b1) a2 sigmoid(a1 W2 b2) L 0.5 * ((a2 - y) ** 2).sum() # 反向对应手推的四步 delta_out (a2 - y) * a2 * (1 - a2) # δ^(L) dW2 a1.T delta_out db2 delta_out delta1 (delta_out W2) * a1 * (1 - a1) # 层间传递 dW1 x.T delta1 db1 delta1对照手推结果dW2 约 [-0.0327, -0.0299, -0.0201]dW1 的三行分别约为 [-0.0044, -0.0044]、[0.0025, 0.0025]、[-0.0033, -0.0033]完全一致。几个容易出错的点存储布局W1 按输入维×输出维存放前向用矩阵乘反向用转置矩阵乘别混成输出维×输入维。激活导数复用缓存a*(1-a)里直接用前向存下的 a别重算 σ(z)数值上也更稳。batch 场景除 m 放在 δ^(L) 之后统一做保证 dL/dW 是平均值。用数值梯度验证你的实现解析梯度写错了不会报错只会安静地学错。标准做法是数值梯度对照def loss(): h sigmoid(x W1 b1) return 0.5 * ((sigmoid(h W2 b2) - y) ** 2).sum() def num_grad(f, p, eps1e-6): g np.zeros_like(p) for i in range(p.size): old p.flat[i] p.flat[i] old eps; up f() p.flat[i] old - eps; dn f() p.flat[i] old g.flat[i] (up - dn) / (2 * eps) return g # np.allclose(num_grad(loss, W1), dW1, atol1e-5) 应返回 True白话把每个参数往左右各推一小步看 loss 各差多少用对称差分近似偏导。解析梯度和它逐项接近你的实现才算可信对不上时优先检查维度转置和激活导数这是两个最高发的错误点。排障清单梯度消失、爆炸与学习率现象深层梯度趋近0loss平台期成因Sigmoid 导数上限只有 0.25每传一层最多乘 0.25十层之后误差项缩小几个数量级再加偏置项逐层累加中间层激活容易饱和导数进一步变小。动作隐藏层换 ReLU 或其变体导数在正区间恒为 1责任不再衰减权重初始化用 Xavier 或 He 方案避免第一层就把梯度打小深网络加残差连接给误差开一条直通通道仍不够再考虑 Batch Normalization把各层激活拉回舒适区现象梯度暴涨、loss变NaN成因初始权重过大或含饱和的非线性叠加时逐层相乘会指数放大大学习率一步更新后参数飞出正常范围exp 溢出产生 inf/NaN。动作做梯度裁剪clip norm把范数压回安全值检查初始化尺度用 Xavier/He 而不是拍脑袋的均匀大值NaN 出现后回滚到上一步参数快照别在污染的参数上继续更新现象loss震荡甚至发散 / 收敛极慢成因学习率过大时参数在最优解附近来回跳步长超过曲率能承受的范围过小时每步位移小于数值噪声看起来像没在动。动作震荡 → 学习率减半重跑或用余弦、阶梯衰减把步长逐步压下来慢 → 直接上自适应算法Adam、RMSprop它们对每个参数维护自己的有效步长对初始学习率不敏感仓库的可视化页面里收录了这组动图配合上面的三条规则看轨迹会更直观优化算法对比。收尾手算的终点自动微分的起点反向传播就是一条规则——误差沿计算图反向累积、每层乘以局部导数——掌握它你就有了判断梯度异常、审计自定义算子的底稿。下一步建议直接进入自动微分PyTorch 的 autograd 等它替你完成了上面 30 行代码的工作但当你遇到自定义算子梯度异常时本文的手推流程就是最快的定位工具。系统的推导和更多结构变体卷积、循环网络的反向传播见 《神经网络与深度学习》第二版 · 第4章 前馈神经网络 与配套 案例与实践教程。延伸阅读奠基性文献按原文引用Rumelhart, Hinton Williams (1986)Learning representations by back-propagating errorsGlorot Bengio (2010)Understanding the difficulty of training deep feedforward neural networks【免费下载链接】nndl邱锡鹏《神经网络与深度学习》第二版与通识版电子书、章节目录、学习资源与勘误。项目地址: https://gitcode.com/GitHub_Trending/nn/nndl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进