10 从 MLP 到 LeNet:反向传播到底在做什么?为什么梯度能一层层传回来(附三层网络手写示例)
Python 深度学习入门:反向传播到底在做什么?为什么梯度能一层层传回来(附三层网络手写示例)
很多初学者在学完“梯度下降到底在做什么”之后,都会遇到一个很自然的问题:
既然训练时要根据损失去更新参数,
那么网络里这么多层、这么多参数,它们各自的梯度到底是怎么来的?
这其实就是反向传播要解决的问题。
前面我们已经知道:
- 机器学习不是抽象地“让机器变聪明”,而是在学习从输入到输出的映射关系
- 模型真正能调整的是参数,参数决定了模型如何对输入做出反应
- 损失函数的作用,是把预测结果和真实答案之间的差距,变成一个可以计算、可以优化的数值
但当模型从单层变成多层之后,一个新的问题就出现了:
最后的损失,为什么能反过来指导前面每一层参数的更新?
这就是反向传播的核心。
很多人一听“反向传播”,就会想到复杂公式。
但从本质上说,它做的事情其实很明确:
从最终损失出发,沿着网络的计算路径,一层层算出每个参数对损失的影响。
本文围绕“反向传播到底在做什么”这一主题,介绍:
- 为什么训练多层网络一定会用到反向传播
- 反向传播和梯度下降到底有什么区别
- 为什么反向传播离不开链式法则
- 如何用一个三层小网络手写反向传播过程
- 如何通过代码和结果,真正看懂“梯度是怎么传回来的”
摘要
反向传播是神经网络训练中的核心机制之一,它解决的不是“参数怎么更新”,而是“每个参数的梯度怎么得到”。训练的目标本质上是不断调整参数,让损失函数变小 ;而在多层网络中,参数往往不会直接影响损失,而是通过中间层间接影响输出,因此必须借助链式法则,把损失对各层参数的影响一层层算回去。本文以一个三层线性网络为例,结合公式、手写 Python 代码和结果分析,说明反向传播到底在做什么,以及为什么多层网络的训练离不开它。
一、为什么要学习反向传播?
如果只看模型训练的表面流程,事情好像很简单:
- 输入数据进入模型
- 模型给出预测结果
- 预测结果和真实答案进行比较
- 然后更新参数
但问题是,“更新参数”这一步并不是一句话就能完成的。
因为在真实模型里,参数通常不止一个。
它们可能分布在不同层里,彼此之间还通过很多中间变量连接在一起。
而前面已经讲过,训练的目标不是直接去“提高准确率”,而是不断调整参数,让损失函数变小 。
所以训练时真正要回答的问题其实是:
每个参数变化一点点,损失会怎么变?
如果是一个很简单的模型,这件事也许还能手算。
但一旦模型变成多层网络,参数数量一多,关系一复杂,就必须有一种系统的方法,把这些梯度算出来。
这就是为什么要学习反向传播。
二、反向传播到底在解决什么问题?
一句话概括:
反向传播解决的是“梯度怎么来”的问题。
这一点一定要和梯度下降区分开。
梯度下降解决的是
- 已经知道梯度以后,参数该怎么更新
比如:
w←w−η∂L∂w w \leftarrow w - \eta \frac{\partial L}{\partial w} w←w−η∂w∂L
这里说的是:
如果已经得到了 ∂L∂w\frac{\partial L}{\partial w}∂w∂L,那就沿着让损失减小的方向去更新参数。
反向传播解决的是
- 这个梯度 ∂L∂w\frac{\partial L}{\partial w}∂w∂L 到底怎么求出来
所以可以把两者理解成分工关系:
- 反向传播负责算梯度
- 梯度下降负责用梯度更新参数
如果没有反向传播,梯度下降就没有“材料”可用。
三、为什么多层网络一定会遇到这个问题?
在单层模型里,一个参数对输出的影响路径比较短。
但在多层网络里,一个前面层的参数,往往不会直接影响损失,而是要经过很多中间步骤。
比如这样一条链:
输入 x
→ 第1层输出 z1
→ 第2层输出 z2
→ 最终输出 y_hat
→ 损失 L
如果我们关心的是第1层某个参数 w1w_1w1 对损失 LLL 的影响,
那它不是直接作用在损失上的,而是:
- 先影响第1层输出
- 再影响第2层输出
- 再影响最终预测
- 最后才影响损失
所以:
越靠前的参数,离损失越远,影响链条也越长。
这就决定了,多层网络训练时不能只看最后一层。
必须有一种方法,把最终损失对前面参数的影响一层层传回来。
这正是反向传播存在的意义。
另外,前面也已经讨论过,单层模型表达能力有限,多层结构的关键不只是“更大”,而是开始具备中间表示和更复杂的组合关系 。
但多层网络要真正学起来,不能只靠“结构存在”,还必须能训练,而训练就离不开反向传播。
四、反向传播为什么离不开链式法则?
如果只用一句话概括反向传播的数学基础,那就是:
链式法则。
原因很简单:
在神经网络里,一个参数通常不会直接影响最终损失,
而是通过多个中间变量,间接地影响最终结果。
所以要计算“参数对损失的影响”,就必须把这条路径拆成一段一段,然后把每一段的影响连起来。
比如:
w1→z1→z2→yhat→L w_1 \rightarrow z_1 \rightarrow z_2 \rightarrow y_{\text{hat}} \rightarrow L w1→z1→z2→yhat→L
那么 w1w_1w1 对损失的影响,就不能跳着算,而必须沿着路径写成:
∂L∂w1=∂L∂yhat⋅∂yhat∂z2⋅∂z2∂z1⋅∂z1∂w1 \frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_{\text{hat}}} \cdot \frac{\partial y_{\text{hat}}}{\partial z_2} \cdot \frac{\partial z_2}{\partial z_1} \cdot \frac{\partial z_1}{\partial w_1} ∂w1∂L=∂yhat∂L⋅∂z2∂yhat⋅∂z1∂z2⋅∂w1∂z1
这就是链式法则。
所以反向传播并不是某种神秘技巧,
它只是把“链式法则在多层网络里系统地执行了一遍”。
五、案例:一个三层小网络里,梯度是怎么传回来的?
为了把这个过程讲清楚,我们先不引入激活函数,也不引入复杂结构,
只看一个最简单的三层网络:
z1=w1x z_1 = w_1 x z1=w1x
z2=w2z1 z_2 = w_2 z_1 z2=w2z1
yhat=w3z2 y_{\text{hat}} = w_3 z_2 yhat=w3z2
L=(yhat−y)2 L = (y_{\text{hat}} - y)^2 L=(yhat−y)2
这里:
- xxx 是输入
- yyy 是真实标签
- w1,w2,w3w_1, w_2, w_3w1,w2,w3 是三层参数
- LLL 是损失函数
这个例子虽然简单,但已经足够把反向传播的主线讲清楚。
前向传播在做什么?
先按顺序一层层算:
- 输入 xxx 经过第一层,得到 z1z_1z1
- z1z_1z1 再经过第二层,得到 z2z_2z2
- z2z_2z2 再经过第三层,得到预测值 yhaty_{\text{hat}}yhat
- 最后根据 yhaty_{\text{hat}}yhat 和真实值 yyy,计算损失 LLL
反向传播在做什么?
从最后的损失开始,沿着相反方向一层层往回求导:
- 先算损失对输出的导数
- 再算输出对上一层的导数
- 再算上一层对更前一层的导数
- 最后得到每个参数对损失的导数
这就是“反向传播”。
六、把具体数字代进去,会更容易看清楚
下面给这个三层网络代入一组具体数值:
x=2,y=1 x = 2,\quad y = 1 x=2,y=1
w1=0.5,w2=1.5,w3=2.0 w_1 = 0.5,\quad w_2 = 1.5,\quad w_3 = 2.0 w1=0.5,w2=1.5,w3=2.0
第一步:先做前向传播
先一层层算:
z1=w1x=0.5×2=1 z_1 = w_1 x = 0.5 \times 2 = 1 z1=w1x=0.5×2=1
z2=w2z1=1.5×1=1.5 z_2 = w_2 z_1 = 1.5 \times 1 = 1.5 z2=w2z1=1.5×1=1.5
yhat=w3z2=2.0×1.5=3 y_{\text{hat}} = w_3 z_2 = 2.0 \times 1.5 = 3 yhat=w3z2=2.0×1.5=3
L=(yhat−y)2=(3−1)2=4 L = (y_{\text{hat}} - y)^2 = (3 - 1)^2 = 4 L=(yhat−y)2=(3−1)2=4
也就是说:
- 第一层输出是 1
- 第二层输出是 1.5
- 最终预测值是 3
- 损失是 4
第二步:开始反向传播
现在从损失开始,往回求导。
先看损失对预测值的导数:
∂L∂yhat=2(yhat−y)=2(3−1)=4 \frac{\partial L}{\partial y_{\text{hat}}} = 2(y_{\text{hat}}-y)=2(3-1)=4 ∂yhat∂L=2(yhat−y)=2(3−1)=4
接下来分别看后面各段关系。
第三层相关导数
∂yhat∂w3=z2=1.5 \frac{\partial y_{\text{hat}}}{\partial w_3} = z_2 = 1.5 ∂w3∂yhat=z2=1.5
∂yhat∂z2=w3=2.0 \frac{\partial y_{\text{hat}}}{\partial z_2} = w_3 = 2.0 ∂z2∂yhat=w3=2.0
第二层相关导数
∂z2∂w2=z1=1 \frac{\partial z_2}{\partial w_2} = z_1 = 1 ∂w2∂z2=z1=1
∂z2∂z1=w2=1.5 \frac{\partial z_2}{\partial z_1} = w_2 = 1.5 ∂z1∂z2=w2=1.5
第一层相关导数
∂z1∂w1=x=2 \frac{\partial z_1}{\partial w_1} = x = 2 ∂w1∂z1=x=2
第三步:把链条乘起来
对第三层参数求导
∂L∂w3=∂L∂yhat⋅∂yhat∂w3=4⋅1.5=6 \frac{\partial L}{\partial w_3} = \frac{\partial L}{\partial y_{\text{hat}}} \cdot \frac{\partial y_{\text{hat}}}{\partial w_3} = 4 \cdot 1.5 = 6 ∂w3∂L=∂yhat∂L⋅∂w3∂yhat=4⋅1.5=6
对第二层参数求导
∂L∂w2=∂L∂yhat⋅∂yhat∂z2⋅∂z2∂w2=4⋅2.0⋅1=8 \frac{\partial L}{\partial w_2} = \frac{\partial L}{\partial y_{\text{hat}}} \cdot \frac{\partial y_{\text{hat}}}{\partial z_2} \cdot \frac{\partial z_2}{\partial w_2} = 4 \cdot 2.0 \cdot 1 = 8 ∂w2∂L=∂yhat∂L⋅∂z2∂yhat⋅∂w2∂z2=4⋅2.0⋅1=8
对第一层参数求导
∂L∂w1=∂L∂yhat⋅∂yhat∂z2⋅∂z2∂z1⋅∂z1∂w1=4⋅2.0⋅1.5⋅2=24 \frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial y_{\text{hat}}} \cdot \frac{\partial y_{\text{hat}}}{\partial z_2} \cdot \frac{\partial z_2}{\partial z_1} \cdot \frac{\partial z_1}{\partial w_1} = 4 \cdot 2.0 \cdot 1.5 \cdot 2 = 24 ∂w1∂L=∂yhat∂L⋅∂z2∂yhat⋅∂z1∂z2⋅∂w1∂z1=4⋅2.0⋅1.5⋅2=24
这时候就能很直观地看出:
- 越靠后的参数,离损失越近,梯度链越短
- 越靠前的参数,离损失越远,梯度链越长
- 所谓“反向传播”,就是把这些影响一层层乘回去
七、Python 实战代码:手写一个最简单的反向传播过程
下面把上面的过程完整写成 Python 代码。
x = 2.0
y = 1.0
w1 = 0.5
w2 = 1.5
w3 = 2.0
# =========================
# 1. 前向传播
# =========================
z1 = w1 * x
z2 = w2 * z1
y_hat = w3 * z2
L = (y_hat - y) ** 2
# =========================
# 2. 反向传播
# =========================
dL_dyhat = 2 * (y_hat - y)
dyhat_dw3 = z2
dyhat_dz2 = w3
dz2_dw2 = z1
dz2_dz1 = w2
dz1_dw1 = x
dL_dw3 = dL_dyhat * dyhat_dw3
dL_dw2 = dL_dyhat * dyhat_dz2 * dz2_dw2
dL_dw1 = dL_dyhat * dyhat_dz2 * dz2_dz1 * dz1_dw1
# =========================
# 3. 输出结果
# =========================
print("前向传播结果:")
print("z1 =", z1)
print("z2 =", z2)
print("y_hat =", y_hat)
print("L =", L)
print("\n反向传播结果:")
print("dL/dw3 =", dL_dw3)
print("dL/dw2 =", dL_dw2)
print("dL/dw1 =", dL_dw1)
八、代码结果怎么看?
运行后会得到:
前向传播结果:
z1 = 1.0
z2 = 1.5
y_hat = 3.0
L = 4.0
反向传播结果:
dL/dw3 = 6.0
dL/dw2 = 8.0
dL/dw1 = 24.0
下面重点看这几个结果分别说明了什么。
1. z1、z2、y_hat 表示前向传播一层层算出来的中间结果
它们说明,网络不是一下子直接得到损失的,
而是先经过若干层计算,最后才得到预测值和损失。
这一步很重要,因为反向传播不是凭空出现的,
它一定是建立在前向传播已经完成的基础上。
2. dL/dw3、dL/dw2、dL/dw1 是各层参数的梯度
这三个值就是训练真正需要的东西。
因为训练的目标本质上是让损失函数变小 ,
而要想让损失变小,就必须知道每个参数变化时,损失会怎么变。
也就是说:
dL/dw3 = 6.0表示第3层参数对损失的影响dL/dw2 = 8.0表示第2层参数对损失的影响dL/dw1 = 24.0表示第1层参数对损失的影响
这些导数不是随便算出来的,而是通过链式法则,一层层乘回来的。
3. 为什么最前面的 dL/dw1 最大?
这里要特别提醒一点:
梯度数值大,不代表这一层就“最重要”。
在这个例子里,dL/dw1 更大,只是因为它离损失更远,
它的梯度需要把后面多层的影响一起乘回来,所以数值会受到整条链的共同影响。
这正好说明了反向传播的核心特征:
前面的参数梯度,往往依赖后面所有层。
也正因为如此,网络一旦变深,梯度传播这件事就会变得更值得关注。
九、把这个过程画成一条链,会更容易理解“反向”是什么意思
这个三层网络可以写成下面这样的结构:
前向传播:
x → z1 → z2 → y_hat → L
如果把数值带进去,就是:
前向传播:
x = 2
→ z1 = 1
→ z2 = 1.5
→ y_hat = 3
→ L = 4
而反向传播则是沿着相反方向,把梯度一层层传回来:
反向传播:
L
→ dL/dy_hat = 4
→ dL/dw3 = 6
→ dL/dw2 = 8
→ dL/dw1 = 24
这时候“反向传播”这个名字就很容易理解了:
- 前向传播:从输入走到损失
- 反向传播:从损失回到参数
所以它的“反向”,反的不是数据本身,
而是导数的计算方向。
十、为什么反向传播对多层网络特别重要?
前面已经讲过,单层模型能力有限,而多层网络之所以有意义,是因为它开始具备更复杂的中间表示和组合关系 。
但“有结构”不等于“能训练”。
多层网络之所以能学起来,不是因为它自己会变聪明,
而是因为训练过程中,损失能够通过反向传播,把每一层参数的梯度算出来,然后再交给梯度下降去更新。
所以从训练机制上看:
- 多层网络提供表达能力
- 损失函数提供优化目标
- 反向传播提供梯度
- 梯度下降完成参数更新
这几步合在一起,才构成了完整训练过程。
十一、学习反向传播时常见的误区
1. 把反向传播和梯度下降当成同一个东西
不是。
反向传播负责算梯度,梯度下降负责用梯度更新参数。
2. 以为反向传播是在“倒着传输入”
不是。
输入在前向传播中已经用过了,反向传播传回去的是导数信息,而不是原始输入。
3. 只会背链式法则,但不知道它为什么会出现
链式法则不是为了考试才写的公式,
而是因为在多层网络里,一个参数影响损失本来就必须经过很多中间变量,所以只能一段一段地连起来算。
4. 觉得反向传播很神秘
其实它并不神秘。
它做的事情很朴素:
从最终损失出发,把损失对每个参数的影响一层层算回去。
十二、总结
反向传播是神经网络训练中的核心步骤之一。
它解决的不是“参数怎么更新”,而是:
每个参数的梯度到底怎么得到。
通过本文的三层网络案例,可以建立以下几个基本认识:
- 训练的目标本质上是让损失函数变小
- 多层网络中,参数通常不会直接影响损失,而是通过中间层间接影响输出
- 因此必须借助链式法则,把损失对参数的影响一层层算回去
- 这整个过程,就是反向传播
从入门角度看,可以把反向传播概括为一句话:
反向传播并不是神秘算法,它只是把损失对参数的影响,沿着网络结构一层层算回来。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)