· 深度学习

反向传播:梯度是怎么从最后一层传回来的

从一个只有两个参数的小例子起步:先算链式法则,再推广到多层网络,最后落到计算图与 PyTorch 的 backward。核心不是某条公式,而是「为什么必须从后往前算」。

深度学习 · 算法拆解

用一个只有两个参数的小例子起步:先算链式法则,再把它推广到多层网络,最后落到计算图和 PyTorch 的 backward 函数。核心不是某条公式,而是「为什么必须从后往前算」。

容易想到的做法

对每个参数单独重新推一遍梯度

每个参数都从损失函数重新走到起点,把沿途的偏导重算一次。参数一多,同一条链上的中间量被反复求值,重复量随网络深度和宽度同时膨胀。

实际的做法

从后往前,把上游的梯度乘进来

先算损失对最后一个输出的偏导,再乘本层局部偏导往下传。每个中间量只算一次,算完立刻被下游复用。

反向传播不是一条新公式,而是求导顺序的优化:识别出链式法则里的公共子表达式,把它缓存下来复用。
01

从一个只有两个参数的模型开始

引例
本节要点
  • 最简模型:输入 x 经过一个线性变换得到 y,即 y = wx + b,待定参数只有 w 和 b。
  • 损失用最小二乘:L = ½(y − y_gt)²,其中 y_gt 是真实值。
  • 取 x = 1.5、y_gt = 0.8、初始 w = 0.8、b = 0.2,整条链路就可以手算了。

先看一个能算到底的线性拟合例子。输入 x 经过一个线性方程得到 y,写成 y = wx + b。整个模型只有两个待定参数:权重 w 和偏置 b。

评价好坏用最小二乘,也就是把预测值与真实值之差平方,再乘 ½:L = ½(y − y_gt)²。这里的 y_gt 是基准数值,即真实值;乘 ½ 是为了求导时能和平方项的 2 抵消,让结果干净。

给定一组具体数值:x = 1.5,真实值 y_gt = 0.8,参数初始化为 w = 0.8、b = 0.2。

模型与损失函数:左侧圆圈是线性变换 w, b,绿色箭头是正向的数据流,右侧是损失函数 L = ½(y − y_gt)²。整条链只有两个可调旋钮。
模型与损失函数:左侧圆圈是线性变换 w, b,绿色箭头是正向的数据流,右侧是损失函数 L = ½(y − y_gt)²。整条链只有两个可调旋钮。
02

先把数字算一遍:预测 1.4,损失 0.18

正向
本节要点
  • 代入得 y = 0.8 × 1.5 + 0.2 = 1.4,离真实值 0.8 还有 0.6 的差距。
  • 损失 L = ½(1.4 − 0.8)² = 0.18,这就是要压低的目标。
  • 此时所有数值都已就位,接下来唯一的问题是:w 和 b 该往哪个方向调。

把数值代进去:y = 0.8 × 1.5 + 0.2 = 1.4。模型预测 1.4,而真实值是 0.8,差了 0.6。

损失函数随之确定:L = ½ × (1.4 − 0.8)² = 0.18。这个 0.18 就是当前参数配置下的「错误程度」,训练的目标就是把它压下去。

现在所有数值都算出来了,问题也随之变得具体:该怎么调整 w 和 b,才能让 L 变小?

数值全部代入:x、w、b、y_gt 四个已知量摆在底部,中间是正向上的箭头链。此时损失还没开始优化。
数值全部代入:x、w、b、y_gt 四个已知量摆在底部,中间是正向上的箭头链。此时损失还没开始优化。
03

往哪调、调多少,都由偏导数决定

梯度下降
本节要点
  • 按梯度下降的做法,先算损失 L 对 w 和 b 的偏导数,再沿梯度的反方向更新参数。
  • 麻烦在于 L 并不直接是 w 的函数——它隔着 y 这一层,没法直接对 w 求导。
  • 于是问题变成:如何把「L 对 w 的偏导」拆成几段能分别求出的部分。

按梯度下降的思路,下一步很明确:计算损失函数 L 对 w 和 b 的梯度,也就是两个偏导数,然后沿着梯度的反方向把参数往下推。

但这里有个结构上的障碍。L 的表达式里只有 y,没有 w。y 才是 w 的函数,L 是 y 的函数。想求 L 对 w 的偏导,中间隔着一层。

解法是把这段距离拆开:既然 L 依赖 y、y 依赖 w,那就分别求出「L 怎么随 y 变」和「y 怎么随 w 变」,再把两段接起来。这正是链式法则要做的事。

04

链式法则:先算那个最关键的中间量

求导
本节要点
  • 先算中间量 ∂L/∂y = y − y_gt,它把「预测与真实的距离」直接量化出来。
  • 再把两段相乘:∂L/∂w = (∂L/∂y) · (∂y/∂w) = (y − y_gt) · x。
  • 对 b 同理:∂L/∂b = (∂L/∂y) · (∂y/∂b),而 ∂y/∂b = 1,所以它直接等于中间量。

为了更容易算出 L 对 w 和 b 的偏导,先算一个中间量:L 对 y 的偏导。对 ½(y − y_gt)² 求导,得到 y − y_gt,也就是预测值与真实值之差。

有了这个中间量,L 对 w 的偏导就可以写成两段的乘积:L 对 y 的偏导,乘以 y 对 w 的偏导。而 y = wx + b 对 w 求导等于 x,所以结果是 (y − y_gt) · x。

对 b 完全同理,只是 y 对 b 求导等于 1,因此 ∂L/∂b 就等于那个中间量 y − y_gt。

这种把复合函数拆成相乘的求导方法,就是高中就学过的链式法则。它的价值在于:把一个够不着的导数,拆成几个够得着的导数相乘。

链式法则在图上展开:黄色箭头表示从输出端反向回推,∂L/∂w 被拆成 (∂L/∂y)·(∂y/∂w);底部一行是当前所有数值。
链式法则在图上展开:黄色箭头表示从输出端反向回推,∂L/∂w 被拆成 (∂L/∂y)·(∂y/∂w);底部一行是当前所有数值。
05

一次反向,两个梯度同时到手

计算
本节要点
  • 代入数值:∂L/∂w = 0.6 × 1.5 = 0.9,∂L/∂b = 0.6。
  • 取学习率 0.1,更新后 w = 0.8 − 0.09 = 0.71,b = 0.2 − 0.06 = 0.14。
  • 新预测 y = 1.205,比原来的 1.4 更接近 0.8;损失从 0.18 降到 0.082。

把数值代进去。y − y_gt = 1.4 − 0.8 = 0.6,乘以 x = 1.5,得到 ∂L/∂w = 0.9;∂L/∂b 直接等于 0.6。

注意这两个梯度是「一趟算出来」的:中间量 y − y_gt 只算了一次,两个参数各自乘上自己的局部偏导就分别得到了梯度。这种复用,正是后面所有优化的来源。

两个梯度值:∂L/∂w = 0.9、∂L/∂b = 0.6。同一趟反向里,中间量只求了一次。
两个梯度值:∂L/∂w = 0.9、∂L/∂b = 0.6。同一趟反向里,中间量只求了一次。

设学习率为 0.1,参数沿反梯度方向更新:w = 0.8 − 0.1 × 0.9 = 0.71,b = 0.2 − 0.1 × 0.6 = 0.14。

更新后的效果:w 变 0.71、b 变 0.14,新预测 y = 1.205,损失降到 0.082——只走了一步,误差就减少了超过一半。
更新后的效果:w 变 0.71、b 变 0.14,新预测 y = 1.205,损失降到 0.082——只走了一步,误差就减少了超过一半。

用新参数重新前向:y = 0.71 × 1.5 + 0.14 = 1.205。与真实值 0.8 的距离从 0.6 缩到 0.405,损失函数也同步变小到 0.082。一次迭代,方向是对的。

06

「反向传播」这个名字从哪来

命名
本节要点
  • 上面那套沿着数据流的反方向、由输出向输入逐层求导的流程,就是反向传播算法。
  • 「反向」指的不是数值取负,而是计算顺序与正向传播相反。
  • 在图上,它表现为一串从右往左的箭头,把输出的误差一层层分配回去。

回头看刚才做的事:正向是数据从左往右流,x 进去、y 出来、误差出来;而求梯度的过程恰好相反,从损失函数出发,一路往左退回到 x 和参数。

这种沿着黄色箭头、从后向前计算参数梯度的方法,就是反向传播算法。「反向」说的不是符号取负,而是计算方向与正向传播相反。

它的效果可以这样理解:输出端的误差被逐层「分配」回每个参数,每个参数分到的份额,就是它对这个误差该负的责任大小。

红框标出的就是反向传播:四条黄色箭头从损失函数出发,逆着绿箭头的数据流方向,逐级回推到 w 和 b。
红框标出的就是反向传播:四条黄色箭头从损失函数出发,逆着绿箭头的数据流方向,逐级回推到 w 和 b。
07

加一层:参数一多,为什么必须换算法

推广
本节要点
  • 把模型改成两级:x 经第一个线性变换得到 y₁,再经第二个得到 y₂,最后算损失。
  • 待求参数变成四个——w₁、b₁、w₂、b₂,链路变长,重复计算的机会也变多。
  • 关键观察:求某个参数的梯度时,链上的后半段是公共的,前面已经算过,不必重来。

现在加一点难度。x 先经过一个线性变换得到 y₁,再经过另一个线性变换得到 y₂,最后用损失函数评价 y₂。这是正向传播的过程。

要得到 w₁、b₁、w₂、b₂ 这四个参数的梯度,如果每个参数都独立地从损失函数往回推一遍,那条公共的链路就要被反复走多次。

而从后往前依次计算就能避免这件事:先算损失对 y₂ 的偏导,再算对 y₁ 的偏导,往下逐级传递。每一步用到的上游梯度,都是上一步刚算出来的结果,直接在缓存里取,不需要第二次求值。

两级网络的梯度链:黄色标注的项就是上一步已经算出的梯度,被下一步直接复用。链路越长,这种复用省下的计算越多。
两级网络的梯度链:黄色标注的项就是上一步已经算出的梯度,被下一步直接复用。链路越长,这种复用省下的计算越多。
08

反向传播的实质:一个加速梯度计算的工程手段

本质
本节要点
  • 反向传播算法就是神经网络中加速计算参数梯度值的方法。
  • 它没有改变梯度的数学定义,改变的是求值的顺序与复用方式。
  • 一次正向、一次反向,就能拿到全部参数的梯度,而不是每个参数各跑一遍。

由此可以给反向传播一个准确的定义:它就是神经网络中加速计算参数梯度值的方法。

注意这句话的重心在「加速」。梯度该是多少,数学上早就定好了;反向传播没有改变定义,它改变的是求这些值的顺序——把链式法则里的公共部分提到前面算一次,然后一路乘下去。

这个区别在网络只有两个参数时微不足道,在真实网络里却是决定性的:一次正向传播加一次反向传播,就能拿到全部参数的梯度,而不是每个参数各跑一遍完整链路。

09

但计算机不这么算:先拆成最小的运算单元

模块化
本节要点
  • 真实框架里,前向不会直接算 w₁x + b₁,而是拆成两个最小单元:先乘、再加。
  • 定义中间变量 u₁ = w₁ × x,于是 y₁ = u₁ + b₁;第二个线性变换同理。
  • 拆分后的特点是:每个方块里的运算都极简单且可复制,只有输入的变量不同。

上面那种「一个线性变换一步到位」的写法,方便人理解,却不是计算机里的真实做法。框架中的运算是更加模块化的。

以刚才的例子为例,先有三个叶子变量:x、w₁、b₁。然后定义一个新的中间变量 u₁,令 u₁ = w₁ × x。这样第一个线性变换的结果就可以写成 y₁ = u₁ + b₁。第二个线性变换用同样的方式再拆一遍。

拆完之后能看出一个明显的特点:每个方块里只做一件事——要么相乘,要么相加,输入变量不同而已,但运算本身高度统一、可复制。

拆成最小单元之后的形状:乘法方块与加法方块交替出现,每个方块只认两条输入。这种「小方块可复制」的结构,是后面能自动求导的前提。
拆成最小单元之后的形状:乘法方块与加法方块交替出现,每个方块只认两条输入。这种「小方块可复制」的结构,是后面能自动求导的前提。
10

计算图:由单元运算和变量构成的流程图

计算图
本节要点
  • 把变量当作数据、把单元运算当作节点连起来,得到的流程图叫计算图。
  • 计算图上的每条边都代表一个有名字的变量,正向传播就是沿边把数值填进去。
  • 一旦结构固定,求导就可以在图上机械地执行,不需要人再推导公式。

这种由单元运算和变量构成的计算流程图,被称为计算图。

它的结构与神经网络本身一一对应:方块是运算,箭头是数据流,每个箭头上流动的都是一个有名字的变量。正向传播就是沿着箭头,把已知数值一个个填进去,最终算出损失。

计算图的意义在于:它把「求导」这件依赖数学推导的事,变成了一件可以在图上按规则执行的机械操作。只要图结构确定,梯度就能沿着图自动算出来,不需要人再对每个网络手写公式。

完整的计算图:从左侧 x、w₁、b₁ 出发,经 u₁、y₁、u₂、y₂ 抵达 L。两级线性变换在图上只是四个方块的重复。
完整的计算图:从左侧 x、w₁、b₁ 出发,经 u₁、y₁、u₂、y₂ 抵达 L。两级线性变换在图上只是四个方块的重复。
11

反向的起点:损失对最后一个输出的偏导

起点
本节要点
  • 反向传播的第一步没有可复用的上游,必须直接算出 ∂L/∂y₂。
  • 这个值靠损失函数的解析式得到,是整个反向链路的种子。
  • 后面所有变量的梯度,都由这个种子逐级乘出来。

现在用计算图反向求出损失函数对每一个变量的梯度表达式。第一步必须手算:损失函数 L 对最后输出变量 y₂ 的偏导,记作 ∂L/∂y₂。

这一步没有捷径,因为它位于链路的末端,没有上游梯度可以借。它只能由损失函数的形式直接求出来,也正因为如此,它是整条反向链路的种子。

把这一步做对之后,往下的每一步都有固定套路:拿上游传来的梯度,乘上本地的局部偏导,传给下游。

反向的起点:∂L/∂y₂ 从指向 L 的那条边上开始往回走,逆着正向方向逐级推进。
反向的起点:∂L/∂y₂ 从指向 L 的那条边上开始往回走,逆着正向方向逐级推进。
12

一个值得注意的细节:加法节点的偏导等于 1

链式法则
本节要点
  • 沿反向方向看 u₂ 这个变量,按链式法则:∂L/∂u₂ = (∂L/∂y₂) · (∂y₂/∂u₂)。
  • 因为 y₂ = u₂ + b₂,加法对 u₂ 求偏导恒等于 1。
  • 于是 ∂L/∂u₂ 直接等于 ∂L/∂y₂——加法节点把上游梯度原封不动地传下去。

沿着反向传播的方向继续走,下一个遇到的是 u₂。按链式法则,L 对 u₂ 的偏导等于 L 对 y₂ 的偏导,再乘以 y₂ 对 u₂ 的偏导。

关键在于后一项。y₂ = u₂ + b₂,加法运算对 u₂ 求偏导,结果恒等于 1。这不是巧合,而是加法节点的普遍性质:它对任何一个加数求导,导数都是 1。

所以最终的 ∂L/∂u₂ 就等于 ∂L/∂y₂。用工程的眼光看:加法节点在反向时相当于一根直通管道,梯度进来多少,出去还是多少,不做任何缩放。

红框标注的就是这个结论:∂L/∂u₂ = ∂L/∂y₂,加法节点对上游梯度不做衰减。
红框标注的就是这个结论:∂L/∂u₂ = ∂L/∂y₂,加法节点对上游梯度不做衰减。
13

全部梯度:黄色是历史,绿色是已知

全景
本节要点
  • 同样的做法可以推到图上所有变量,得到完整的梯度表达式集合。
  • 黄色项来自前一步的反向计算,绿色项来自正向传播时存下的数值。
  • 真正需要的只是四个参数的梯度,其余中间量的梯度都只是过路的中间结果。

用同样的道理,可以为图中所有变量的梯度写出表达式。写完之后会看到一个很整齐的规律:每一项要么是前面步骤已经得到的梯度,要么是正向传播时求出的数值。

式子里的黄色量,就是通过之前步骤得到的梯度;绿色量则是正向传播中已经算出的值。这两类都是已知的,直接相乘即可,不需要任何新的求导。

而我们真正需要的结果只有一个:w₁、b₁、w₂、b₂ 这四个参数的梯度。拿到它们,就可以交给梯度下降去更新参数了。其余中间变量的梯度,只是串起这条链的过路点。

梯度全景:整张计算图的每条边都挂上了自己的偏导表达式,黄色为反向步骤中已得的梯度,绿色为正向传播的已知数值。
梯度全景:整张计算图的每条边都挂上了自己的偏导表达式,黄色为反向步骤中已得的梯度,绿色为正向传播的已知数值。
14

在 PyTorch 里,它长这样

落地
本节要点
  • 框架里每个单元运算都实现为一对函数:forward 做前向计算,backward 把上游梯度乘回本地偏导。
  • 以乘法为例,forward 返回 x × y;backward 收到 grad_z 后,分别算出 grad_x = grad_z · y、grad_y = grad_z · x。
  • 这就是链式法则的代码形态:本地偏导写死在 backward 里,上游梯度由参数传进来。

在深度学习框架里,计算图中元素的定义和使用都很直接。以乘法运算为例,在 PyTorch 中就可以写成下面这样:

python · multiply.py
class Multiply(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, y):
        ctx.save_for_backward(x, y)      # 前向的输入留到反向用
        z = x * y
        return z

    @staticmethod
    def backward(ctx, grad_z):
        x, y = ctx.saved_tensors
        grad_x = grad_z * y             # dL/dx = dL/dz * dz/dx, dz/dx = y
        grad_y = grad_z * x             # dL/dy = dL/dz * dz/dy, dz/dy = x
        return grad_x, grad_y

forward 以 x 和 y 作为输入,输出二者的乘积,这就是前向传播的计算;它顺手把输入存进 ctx,因为反向时还要用。

backward 的入参 grad_z,就是损失函数 L 对 z 的偏导数,也就是上游传下来的梯度。而 grad_x 和 grad_y 要算的,是 L 对 x 和 y 的偏导数——按链式法则,用 grad_z 分别乘上本地的偏导即可。乘法节点的两个本地偏导正好是「对方的值」,所以代码里就是乘 y 和乘 x。

代码与图并排:左侧是 z = x × y 的计算图,右侧是同一件事的 PyTorch 实现。框架把图结构和求导规则封装成了类。
代码与图并排:左侧是 z = x × y 的计算图,右侧是同一件事的 PyTorch 实现。框架把图结构和求导规则封装成了类。
15

总结:为什么亿级参数也能训得动

收束
本节要点
  • 反向传播(backpropagation,BP 算法)是深度学习最核心的算法之一。
  • 它用一次反向遍历快速算出全部梯度,再由梯度下降完成参数更新。
  • 在计算机中,它以计算图的方式模块化实现:所有单元运算都定义了正向与反向函数。

完整回顾这条链路。反向传播的英文是 backpropagation,也常被称为 BP 算法,是深度学习最核心的算法之一。

它的作用可以压成一句话:利用反向传播快速计算梯度值,然后再利用梯度下降算法训练整个神经网络。前者负责算出「每个参数该负多少责任」,后者负责「照着这个责任去调整」。

而在计算机中,反向传播一般是使用计算图的方式进行模块化运算的:所有单元运算都配有一对 forward 和 backward 函数,图一搭好,梯度就能自动流过整张图。

这正是深度网络能训练起来的原因。参数规模达到数以亿计的时候,没有任何人能手推出每个参数的梯度;但只要每个基础运算的反向规则都被写好一次,剩下的工作就可以交给图去重复执行。

收束:算法名、核心作用、实现方式三层,构成对反向传播的完整定义。
收束:算法名、核心作用、实现方式三层,构成对反向传播的完整定义。

术语速查

按出现顺序排列。

反向传播backpropagation / BP
从损失函数出发,逆着正向数据流逐层回推,复用上游梯度算出全部参数梯度的算法。
链式法则Chain Rule
复合函数求导时把它拆成若干段偏导相乘,是反向传播全部推导的基础。
正向传播Forward Pass
把输入沿计算图一步步算到损失函数,同时把中间结果保存下来供反向使用。
计算图Computational Graph
以变量为边、单元运算为节点的有向流程图,把求导变成可在图上机械执行的操作。
梯度下降Gradient Descent
算出梯度后沿其反方向按学习率更新参数,逐步逼近损失函数的最小值。
偏导数Partial Derivative
多元函数中固定其余变量只对某一个变量求导,表示该方向上的变化率。
中间变量Intermediate Variable
为拆分运算而引入的临时量,如 u = w × x,是反向传播复用的对象。
学习率Learning Rate
控制每次参数更新幅度的系数,本例取 0.1,过大震荡、过小收敛慢。
自动微分Autograd
框架为每个单元运算预写好正反向函数,使梯度可沿图自动计算。
最小二乘Least Squares
以预测值与真实值之差的平方作为损失,本例取 L = ½(y − y_gt)²。
分享:
返回文章列表

相关文章

前向传播与反向传播概述

微调场景下的前向产物、反向传播机制,以及冻结参数与 LoRA 等不同策略对反向计算图的实际影响。