反向传播算法详解:四步推导加一次手算,把前馈神经网络的梯度讲透
【免费下载链接】nndl邱锡鹏《神经网络与深度学习》第二版与通识版:电子书、章节目录、学习资源与勘误。项目地址: https://gitcode.com/GitHub_Trending/nn/nndl
训练前馈神经网络时,如果一次预测"考砸了",反向传播就是那套复盘流程:把损失拆成责任,一层一层追到每个参数头上,然后按各自的责任大小调整。本文结合邱锡鹏《神经网络与深度学习》仓库 nndl 的配套资源,带你走完这个过程。读完你会收获:
- 不看公式也能说清的误差分摊直觉
- 输出层、隐藏层误差项与参数梯度的完整推导
- 一个 1-2-1 小网络的逐步手算
- 梯度消失/爆炸、学习率问题的排查清单
先建立直觉:误差是怎么分摊的
把一次前向计算想象成一场考试:输出层交卷,分数(损失)出来了。现在要复盘,做法不是重做整张卷子,而是沿计算链从后往前追责。
- 输出层的误差,直接由"预测值 − 真实值"衡量;
- 往前一层走一步,上游的误差要乘以该层与下游之间连接权重的转置,再乘上本层激活函数的导数——前者回答"我通过哪条连线影响了下游",后者回答"我当时对输入的敏感度有多高";
- 每个权重拿到的误差份额,就是上游误差和本层输出的乘积。
追责完毕,每个参数就知道该往哪个方向、迈多大步去更新:
参数 ← 参数 − 学习率 × 该参数的梯度记住这个"转置传误差、导数调敏感度、外积算份额"的三步口诀,下面的公式就只是把它写成符号而已。
数学拆解:从损失到参数梯度
符号约定与损失函数
第 l 层(输入层记为 0 层)的计算是:
z⁽ˡ⁾ = W⁽ˡ⁾a⁽ˡ⁻¹⁾ + b⁽ˡ⁾ a⁽ˡ⁾ = σ(z⁽ˡ⁾)z 是加权求和后的输入,a 是过完激活函数 σ 的输出。训练用均方误差衡量差距:
L = ½ ||t − a⁽ᴸ⁾||²我们的目标只有一个:把 ∂L/∂W⁽ˡ⁾ 和 ∂L/∂b⁽ˡ⁾ 算出来。直接对整条链求导会指数级膨胀,反向传播的聪明之处在于引入中间量误差项 δ⁽ˡ⁾ = ∂L/∂z⁽ˡ⁾,让梯度像接力一样逐层传递。
输出层误差怎么算
最后一层的误差项一步到位:
δ⁽ᴸ⁾ = (a⁽ᴸ⁾ − t) ⊙ σ′(z⁽ᴸ⁾)白话说:预测超真实值多少,再乘以输出激活函数在该点的斜率。⊙ 表示逐元素相乘,σ′ 是激活函数的导数。
隐藏层误差如何回传
对任意隐藏层 l,误差项由上一层"倒推"回来:
δ⁽ˡ⁾ = (W⁽ˡ⁺¹⁾)ᵀ δ⁽ˡ⁺¹⁾ ⊙ σ′(z⁽ˡ⁾)白话说:下游的误差先沿连接权重转置传回,再按本层激活函数的敏感度缩放。注意这里只用到相邻两层的信息,所以整个网络 O(层数) 次矩阵乘法就扫完了。
参数梯度怎么算
拿到各层 δ,权重和偏置的梯度就是两行式子:
∂L/∂W⁽ˡ⁾ = δ⁽ˡ⁾ (a⁽ˡ⁻¹⁾)ᵀ ∂L/∂b⁽ˡ⁾ = δ⁽ˡ⁾白话说:一个权重的责任 = 下游传回的误差 × 本层的输出;偏置没有"输入"可分摊,直接等于误差本身。
手算走一遍:一个 1-2-1 的网络
网络结构:1 个输入、2 个隐藏神经元、1 个输出,隐藏层用 Sigmoid(Sigmoid 把任意实数压到 0~1 之间,它的导数是 a(1−a)),输出层不加激活函数(回归任务常用)。损失取 L = ½(t − a³)²。
取一组好算的数据:x = 0.5,目标 t = 1;参数 w1 = 0.4,w2 = −0.2,w3 = 0.6,b = 0.1。
前向传播:
| 步骤 | 计算 | 结果 |
|---|---|---|
| 隐藏层输入 | z¹ = [w1x, w2x] = [0.4×0.5, −0.2×0.5] | [0.2, −0.1] |
| 隐藏层输出 | a¹ = σ(z¹) | [0.55, 0.475] |
| 输出层 | z² = 0.6×0.55 + (−0.2)×0.475 + 0.1 | 0.225 |
| 损失 | L = ½(1 − 0.225)² | ≈ 0.300 |
反向传播:
- 输出层误差(输出层是线性,σ′ ≡ 1):δ² = a³ − t = 0.225 − 1 =−0.775
- 隐藏层误差:先传回再缩放。δ¹ = [0.6, −0.2]ᵀ × (−0.775) ⊙ [0.55×0.45, 0.475×0.525] ≈ [−0.153, 0.089] ⊙ [0.2475, 0.2494] ≈[−0.038, 0.022]
- 参数梯度:
- ∂L/∂w3 = δ² × a1¹ ≈ −0.775 × 0.55 =−0.426
- ∂L/∂b = δ² =−0.775
- ∂L/∂w1 = δ¹[0] × x ≈ −0.038 × 0.5 =−0.019
- ∂L/∂w2 = δ¹[1] × x ≈ 0.022 × 0.5 =0.011
看一个细节:预测偏低,输出层的误差是负的,所以 w3 和 b 的梯度都指向"把输出调大"的方向——符号和直觉对上了。👍
代码要点:20 行以内抓住关键
反向传播最该先验证的不是"能不能跑",而是"梯度算没算错"。一个通用的数值梯度检验:
import numpy as np def gradcheck(loss_fn, params, eps=1e-6): num, ana = np.zeros_like(params), params.grad # ana 为你的反向传播结果 for i in np.ndindex(params.shape): old = params[i] params[i] = old + eps lp = loss_fn(params) params[i] = old - eps lm = loss_fn(params) params[i] = old num[i] = (lp - lm) / (2 * eps) return np.allclose(num, ana, rtol=1e-4)几条实用建议:
- 梯度检验只在小数据上做:数值法每个参数要跑两次前向,参数一多就慢得受不了;
- 激活导数优先用输出表示,如 Sigmoid 写
a * (1 - a),避免重复指数运算; - 向量化 + mini-batch:把一批样本写成矩阵一次算完,别写 Python for 循环;
- 在 PyTorch 里反向传播就是
loss.backward()一行,但它算的还是上面这套 δ——理解推导后,你才能看懂 autograd 出错时的方向问题。
避坑清单:训练出问题的排查表
| 现象 | 常见成因 | 对策 |
|---|---|---|
| 梯度消失:浅层参数几乎不动 | Sigmoid 导数最大只有 0.25,连乘后层层缩水 | 换 ReLU(负区间为 0 的分段线性函数)、用合适的初始化、加残差连接 |
| 梯度爆炸:损失变 NaN | 权重初始化过大或网络过深,连乘指数增长 | 梯度裁剪、Xavier/He 初始化、换自适应优化器 |
| 收敛震荡不下降 | 学习率过大,一步迈过谷底 | 学习率衰减、Adam 类自适应方法 |
| 收敛极慢 | 学习率过小,或误差项本身太小 | 预热后提学习率、检查初始化尺度 |
资源直达:去哪儿继续学
- 本书第二版入口(章节目录、习题与勘误):nndl-v2/index.md
- 书中关键概念的可视化动图(按章节组织):viz/index.md
- 配套实战教程《案例与实践》,含 PyTorch 可运行案例:nndl-practice/index.md
- 四本书的阅读路径与选读建议:reading-path/index.md
进阶文献两篇:
- Rumelhart, Hinton, Williams (1986),Learning representations by back-propagating errors——反向传播的经典论文,推导和本文同构;
- Glorot & Bengio (2010),Understanding the difficulty of training deep feedforward neural networks——解释为什么初始化尺度决定训练成败,与上面避坑表直接相关。
写在最后
📌 框架再好用,梯度出错时也得靠你自己的推导去定位。拿一支笔把上面的 1-2-1 例子重新算一遍,比读完三遍公式更有用。下一步如果想继续深挖,可以看卷积网络里权值共享如何改写梯度公式——那是反向传播原理的另一个经典应用。
【免费下载链接】nndl邱锡鹏《神经网络与深度学习》第二版与通识版:电子书、章节目录、学习资源与勘误。项目地址: https://gitcode.com/GitHub_Trending/nn/nndl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考