很多同学能看懂前向传播:图片进入模型,经过一层层计算,最后得到预测结果。可一到 loss.backward(),过程突然变得抽象。模型已经算出了损失,为什么还要从后往前再走一遍?损失变大时,直接让优化器修改参数不行吗?
关键问题在于,损失只能告诉模型"这次错了多少",却没有说明几百万个参数分别该承担多少责任。反向传播要完成的工作,就是把输出端的误差沿计算路径逐层传回去,算出损失对每个可训练参数的梯度。本文从一个只有单个权重的例子讲起,再扩展到多层网络、链式法则和 PyTorch 的 loss.backward(),把模型从"知道错了"到"知道怎么改"的过程连起来。
一、只有前向传播,模型为什么学不会
神经网络在训练开始时,大量权重通常处于随机初始化状态。输入一张图片后,模型按照当前权重完成卷积、激活、特征融合和预测,这个从输入走向输出的过程叫前向传播。
前向传播能回答两个问题:按照当前参数,模型会给出什么预测;这个预测与真实标签相差多少。损失函数负责把第二个问题压缩成一个或几个数值。例如,分类错误会带来分类损失,检测框偏离目标会带来定位损失。
但前向传播本身没有学习动作。只要输入和参数不变,模型下一次仍会得到相同的结果。即使损失已经高得很明显,所有权重也会停在原来的位置。模型想从错误中进步,必须知道该修改哪些参数,以及每个参数应该往哪个方向移动。
如果网络只有一个权重,这件事还算简单。现代深度学习模型往往包含数百0万甚至数十亿个参数,一个输出结果又是许多层共同计算的产物。某次预测出错后,很难凭最终损失直接判断是最后一层的问题,还是前面某个卷积核提取了错误特征。反向传播由此进入训练过程。
二、损失函数只能打分,不能直接修改参数
可以把损失看成一张成绩单。分数高低能说明当前预测好不好,却不能指出每一道题该怎样订正。
假设一个模型有 100 万个参数,某个 Batch 的损失为 2.3。这个数字描述了整体误差,但它没有回答下面这些问题:
- 哪些参数让损失升高了;
- 哪些参数对当前结果几乎没有影响;
- 某个参数应该增大还是减小;
- 参数改动多少比较合适。
直接给所有参数减去同一个值显然不合理。有的权重需要增大,有的需要减小,还有一些权重在当前样本上几乎不用动。随机尝试参数也很低效,因为参数空间的维度太高,一次随机改动很可能同时破坏许多已经学到的特征。
训练真正需要的,是一份比损失更细的反馈。对每个参数,都要估计它稍微变化一点时,损失会朝哪个方向变化、变化有多快。这份反馈就是梯度。
三、梯度怎样告诉参数应该往哪里走
梯度可以理解为损失对参数变化的敏感程度。梯度的正负给出方向,绝对值反映当前局部范围内的敏感程度。
先看一个只有一个权重的简单模型。设输入 x=2,模型输出为 预测值=w×x,真实答案是 10。当前权重 w=3,所以模型给出的预测值为 6。采用平方误差后,损失为:
损失 =(6-10)² = 16
现在要判断权重 w 应该怎样调整。损失对 w 的梯度可以写成:
梯度 = 2×(预测值-真实值)×输入
代入数字后,梯度为:
2×(6-10)×2 = -16
梯度是负数,说明在当前这个位置增大 w,损失会下降。假设学习率为 0.01,梯度下降会按下面的方式更新参数:
新权重 = 旧权重 - 学习率×梯度
因此,新权重变成 3-0.01×(-16)=3.16。再次前向计算,预测值变成 6.32,损失约为 13.54,已经低于原来的 16。
这个例子展示了训练最核心的一步。损失给出当前结果有多差,梯度把这个整体评价变成参数的调整依据,优化器再根据学习率等规则完成更新。只有一个参数时,可以直接手算;网络一旦加深,梯度的计算会迅速变复杂。
四、多层网络怎样把错误分到每一层
多层网络中的一个早期权重,并不会直接决定最终损失。它先改变当前层输出,当前层输出又影响下一层,随后继续影响更深的特征和最终预测。损失与早期权重之间隔着一长串计算。
假设某个权重 w₁ 影响中间特征 z₁,z₁ 又影响 z₂,最终由 z₂ 产生损失 L。想知道 w₁ 对损失的影响,需要把整条路径上的局部变化率连接起来:
损失对 w₁ 的梯度 = L 对 z₂ 的变化率 × z₂ 对 z₁ 的变化率 × z₁ 对 w₁ 的变化率
这就是链式法则在神经网络中的作用。每一层只需要处理自己这一小段关系,再把上游传来的梯度继续传给更早的节点。网络有多少层,误差信息就沿计算图向后经过多少层。
"反向传播"这个名字也由此而来。前向传播从输入出发,逐层计算特征和预测;反向传播从损失出发,沿着前向计算留下的路径反向计算梯度。传播回去的是损失对各个中间变量和参数的导数,既不是原始预测结果,也不是平均分配给每一层的损失值。
最后一层距离损失最近,梯度可以直接计算。更早的层会接收后面传回来的梯度,再乘上本层操作的局部导数。这样处理后,分类头、特征融合层和主干网络中的每个可训练参数,都能得到与自身计算路径对应的梯度。
链式法则还有一个实际后果。许多局部导数连续相乘时,结果可能越来越小,也可能迅速增大,这分别对应梯度消失和梯度爆炸。激活函数、参数初始化、归一化和残差连接之所以重要,其中一个原因就是它们会影响梯度能否稳定地传过深层网络。
五、为什么不逐个参数试一遍
既然梯度描述参数变化对损失的影响,最直接的想法是:每次把一个参数稍微增大一点,重新计算损失,再比较前后差异。这种方法叫数值微分,在参数很少时可以用来检查梯度是否正确。
问题出在计算成本。模型有 100 万个参数,就要对大量参数分别施加扰动并重复前向计算。使用双边差分时,每个参数通常还要向两个方向各试一次。模型越大,这种方法越难承担,而且扰动步长太大或太小都会引入数值误差。
符号求导也不适合直接处理大型神经网络。它尝试先写出完整的导数表达式,网络结构复杂后,表达式容易快速膨胀,实际执行时还可能重复计算相同的中间结果。
反向传播采用的是反向模式自动微分。前向计算时,框架记录张量之间执行过的操作和必要的中间结果;反向阶段再沿计算图逆序应用链式法则。一个标量损失对应大量参数时,这种方式可以在一次反向遍历中得到所有参数的梯度,所需计算量与若干次网络计算处在相近量级。
深度学习训练依赖反向传播,主要原因就在这里:它能把一个整体损失高效地拆成每个参数的局部梯度,让大规模网络具备可执行的学习路径。它并不保证模型一定找到全局最优解,也不能替代合理的数据、损失函数和优化策略,但没有这一步,常见的梯度优化方法就拿不到更新参数所需的信息。
六、反向传播、梯度下降和优化器是什么关系
这几个概念经常出现在同一段训练代码里,因此很容易被混为一谈。可以按照一次训练迭代的执行顺序理解它们:
- 前向传播根据当前参数产生预测;
- 损失函数衡量预测与标签之间的差距;
- 反向传播计算损失对各个参数的梯度;
- 优化器读取梯度,并按照更新规则修改参数;
- 梯度清零后,模型开始处理下一个 Batch。
反向传播的任务停在"算出梯度"这一步。真正修改权重的是优化器。最基础的梯度下降会让参数沿梯度的反方向移动;SGD、Adam、AdamW 等优化器还会结合动量、历史梯度或权重衰减决定实际更新量。
学习率同样属于更新规则。梯度指出当前局部的方向和敏感程度,学习率控制一次迈多大。学习率太小,参数移动缓慢;学习率太大,更新可能跨过合适区域,损失随之震荡甚至发散。反向传播能提供正确梯度,也无法替训练过程自动选出合适的学习率。
在 YOLO 训练中,模型可能同时计算框回归损失、分类损失和分布焦点损失。多个损失按配置组合成训练目标后,反向传播会从这些损失出发,把梯度传入检测头、特征融合部分和主干网络。某个模块能否学到有效特征,既取决于前向时提供了什么信息,也取决于反向时梯度能否顺利到达这里。
七、PyTorch 中的 loss.backward() 做了什么
PyTorch 会在前向计算时建立动态计算图。参与梯度计算的张量带有 requires_grad=True,张量经过乘法、卷积、激活函数等操作后,新结果会记录对应的梯度函数。整个前向过程由此留下了一条可追踪的计算路径。
调用 loss.backward() 后,自动微分引擎从损失节点开始,逆着计算图依次执行各个操作的反向规则。链式法则所需的局部导数在这个过程中被组合起来,最终得到损失对各个叶子参数的梯度,并累积到参数的 .grad 属性中。
这里有两个细节很重要。第一,loss.backward() 只负责计算和累积梯度,参数更新仍要等到 optimizer.step()。第二,PyTorch 默认会累积梯度,所以进入下一轮反向传播前通常要调用 optimizer.zero_grad() 或其他清零方式。忘记清零后,当前 Batch 的梯度会叠加到之前的结果上,训练行为也会随之改变。
模型推理时只需要前向结果,不需要计算参数梯度。此时可以使用 torch.no_grad() 或推理模式关闭梯度跟踪,减少计算图带来的时间和内存开销。冻结某些网络层时,也可以让这些参数停止记录梯度,只训练剩余部分。
因此,训练代码中的几行命令各有明确分工:前向传播负责得到预测,损失函数负责评价,loss.backward() 负责追责,优化器负责执行更新。框架替我们完成了复杂的求导过程,但理解这条分工后,遇到梯度没有生成、梯度持续为零、忘记清零或网络不收敛等问题时,才知道应该检查哪个环节。
结语
深度学习需要反向传播,是因为模型只有得到每个参数对应的梯度,才能把一次预测错误转化为具体的参数调整。前向传播告诉我们模型算出了什么,损失函数说明结果差了多少,反向传播沿计算图和链式法则把误差分配到各个参数,优化器随后完成更新。把这四个环节分清后,loss.backward() 就不再是一行神秘命令,而是模型从错误中学习时不可缺少的求导过程。
项目咨询
如果你正在做深度学习毕设、模型训练或论文实验,遇到选题、数据集、代码调试、模型改进、论文写作等问题,可以通过评论或私信咨询主包。