§ 5.2 · Section

前向 & 反向传播

Forward & Backward Propagation

神经网络能"学习",靠的是两个动作反复交替:前向传播算出答案,反向传播算清责任。前者像考试,后者像批卷;一次考试 + 一次批卷,就叫做"训练一步"。上万亿次这种交替,就是 GPT 的训练全过程。

生活场景
🎬 一家人做红烧肉,一次比一次好吃

你第一次做红烧肉,凭感觉放糖、放酱油、放黄酒——端上桌,一家人各自打分:爸说"甜了",妈说"色浅了",你尝完自己也觉得"酒味太冲"。
第二次,你根据大家的反馈微调:少一勺糖、多半勺老抽、少倒点黄酒——再上桌,评价就好一些。
第三次、第十次……每一次你都是"先做菜 → 尝一尝 → 挨骂 → 改方子"。做菜的动作就是前向传播,"根据挨骂调方子"就是反向传播。神经网络学东西的方式,跟你练红烧肉一模一样

先把四个词捋清楚:参数、损失、梯度、传播

这一节会反复出现四个词,它们听着都挺唬人,其实每个都能用一句家常话说明白。我们先把它们钉死,后面就不会晕。

参数(Parameter),说白了就是网络里那一大堆可以拧的旋钮。神经网络里的"权重"和"偏置"合起来统称参数。你把它想成一台老式收音机的背板,上面焊着几百个小旋钮——每个旋钮拧一点,喇叭里出来的声音就变一点。所谓"训练模型",从头到尾就是在拧这些旋钮,一个不多一个不少。GPT-3 有 1750 亿个参数,什么概念?如果你把每个参数的数值抄在一张 A4 纸上,一张纸厚 0.1 毫米,1750 亿张纸摞起来高 17500 公里——地球直径才 12742 公里,也就是说这摞纸能把地球从北极穿到南极,还多出来一截。而这么多旋钮,全靠反向传播一遍一遍自动拧。

损失(Loss)这个词听着玄,其实就是扣分。你考试考了 85 分,也可以说你"损失了 15 分"。模型每答一次题,我们就给它记一次扣分;扣分越少说明它越靠谱。深度学习里所有的努力,最终都指向同一个目标:把这个扣分数字压小

梯度(Gradient)干的活儿,相当于一张"灵敏度清单"。清单上写着:"第 1 号旋钮往右拧一丝,扣分会涨 0.7 分;第 2 号旋钮往右拧一丝,扣分反而降 0.3 分;第 3 号旋钮拧了基本没反应。"有了这张清单,你就知道该动哪个旋钮、往哪边动、动多大。梯度不是一个数,是每个参数各有一个数,1750 亿个参数就有 1750 亿个梯度值。

传播(Propagation)就更朴素了——信息在网络里一层层往下传,跟消息在办公室里一个工位传给下一个工位一样。从输入往输出传,叫前向传播;从输出的错误往回传,叫反向传播。"传播"这个词唯一的信息量就是"接力"两个字,没有任何神秘之处。

前向传播:数据像水一样从左向右流

前向传播(Forward Propagation)是神经网络的"正向计算"过程:你把输入(比如一张图、一句话)从最左边灌进去,它一层一层往右传——每一层的所有神经元都做一遍"加权求和 + 激活函数",把这一层的输出扔给下一层。就像水流经一串水磨,一层磨一层磨,最后从右侧流出一个结果——比如"这张图片有 87% 的概率是猫"。
前向的每一步都清晰、单向、不回头。这个过程不会改变任何权重——它只是"用当前的参数算一遍答案"。

Analogy · 一条流水线上的多道工序

前向传播就像工厂流水线:原材料(输入)从这头进,经过冲压、焊接、喷漆、组装……每道工序(每一层神经元)都在半成品上加工一次,从那头出来的就是成品(模型的预测)。每道工序的机器(权重)是固定的——流水线开动时不许调机器。要改机器,得等下一步"反向"环节。

手算一遍前向传播:一个真的能用纸笔跟下来的例子

光说不算数,我们真的拿笔算一遍。搭一个最小的网络:2 个输入 → 2 个隐藏神经元 → 1 个输出。这是能体现"多层"的最小规模了。所谓隐藏层(Hidden Layer),说白了就是夹在输入和输出中间、你看不见的中间工序——之所以叫"隐藏",纯粹因为它既不是你灌进去的数据,也不是你最后看到的答案,就像饭馆后厨:你只见到菜单(输入)和上桌的菜(输出),中间切配炒制那几道工序都在后厨看不见的地方发生。

【场景】判断一个西瓜甜不甜
   输入 x1 = 敲起来的清脆度(归一到 0~1)= 0.60
   输入 x2 = 瓜藤的干枯程度(0~1)       = 0.90
   真实答案 y = 1(这瓜确实甜)

【当前旋钮值】(训练刚开始,随便给的)
   第一层:h1 的权重 w11=0.50  w21=0.40  偏置 b1=0.10
          h2 的权重 w12=-0.30 w22=0.80  偏置 b2=0.00
   第二层:输出的权重 v1=0.70  v2=0.60   偏置 c=-0.20
   激活函数用 Sigmoid:sigmoid(z) = 1 / (1 + e^(-z))

【第一层:算两个隐藏神经元】
   z_h1 = 0.50×0.60 + 0.40×0.90 + 0.10
        = 0.30 + 0.36 + 0.10 = 0.76
   a_h1 = sigmoid(0.76) = 1/(1+e^-0.76) = 0.681

   z_h2 = (-0.30)×0.60 + 0.80×0.90 + 0.00
        = -0.18 + 0.72 = 0.54
   a_h2 = sigmoid(0.54) = 0.632

【第二层:算输出】
   z_out = 0.70×0.681 + 0.60×0.632 + (-0.20)
         = 0.477 + 0.379 - 0.200 = 0.656
   y_hat = sigmoid(0.656) = 0.658

【算损失】用最简单的平方误差 L = (y - y_hat)² / 2
   L = (1 - 0.658)² / 2 = (0.342)² / 2 = 0.0585

结论:模型说"这瓜 65.8% 的可能是甜的",
     真实答案是"百分百甜",所以扣了 0.0585 分。

注意这一整套算下来,只有加法、乘法和一个指数函数——没有任何一步超过高中数学。GPT 每回答你一句话,干的事情跟这个例子在结构上完全一样,只是把"2 个输入"换成几千维、把"2 个隐藏神经元"换成几万个、把"2 层"换成 96 层,然后重复几百亿次。规模是天壤之别,动作是一模一样

还有一件容易被忽略的事:这个例子里的旋钮值(0.50、0.40、-0.30……)是随机给的。训练刚开始时,网络里所有旋钮都是随机数——就像一个从没做过菜的人第一次进厨房,糖盐酱醋全凭瞎猜。所以第一次前向传播的答案必然很烂,这不是 bug,这是起点。接下来要靠反向传播,把这些瞎猜的数字一步步拧成"懂西瓜的数字"。

损失函数:一把量差距的尺子

前向传出结果之后,我们要问一句:"这答案跟标准答案差多少?" 这就是损失函数(Loss Function)要干的事。它把"模型说的"和"真实标签"做对比,输出一个数字——数字越小说明越准,越大说明越离谱
比如分类任务里常用"交叉熵损失",回归任务里常用"均方误差"。你可以把它想成菜端上桌之后一家人打的总分——0 分是完美复刻大厨,100 分是被扔了。整个训练的唯一目标,就是想尽办法让这个损失数字降下来。

损失函数为什么不能随便挑:两把最常用的尺子

损失函数不是随便定的,它相当于你请来打分的那位裁判。裁判的口味决定了模型往哪个方向努力——请一个只看火候的裁判,厨子就拼命练火候;请一个只看摆盘的裁判,厨子就天天摆花。所以选错损失函数,模型会认认真真地把力气使到错的地方去。

第一把尺子叫均方误差(MSE,Mean Squared Error,均方误差——把每个预测和真值的差取平方再平均)。它的用法适合"答案是个数"的场合:预测明天气温、预测房价、预测明天的用电量。为什么要平方?因为平方会放大大错、宽容小错。差 1 度罚 1 分,差 10 度罚 100 分——它逼模型优先把那些离谱的错误改掉。这就像老板考核你:小失误睁只眼闭只眼,捅了大篓子直接扣半年奖金。

第二把尺子叫交叉熵损失(Cross-Entropy Loss,交叉熵损失——衡量"你给正确答案打的信心分有多低")。它适合"答案是个选项"的场合:这图是猫还是狗、这句话是好评还是差评、下一个字是"的"还是"了"。交叉熵的脾气特别有意思:它不看你答对没答对,只看你对正确答案有多少信心。你说"90% 是猫",真答案是猫,扣分很小;你说"51% 是猫",虽然也答对了,但扣分明显更多;你要是说"1% 是猫"而真答案是猫,扣分会飙到天上去。

用生活场景说:交叉熵像一个严厉的家长在检查你做的判断题。你蒙对了他不夸你——"你自己都不确定,凭什么给你满分?"他要的是"你笃定地答对"。这个设计非常关键,因为它给模型提供了持续的改进压力:哪怕已经答对了,只要信心不足,就还有分可以捞,梯度就不会归零,训练就能继续往前走。

反向传播:把责任按贡献分摊回去

反向传播(Backpropagation,简称 BP)是深度学习的"灵魂算法"。它做的事情听起来很难,但道理很朴素:既然结果错了,就得沿着来时路,一层一层往回追问——"这一层的每个权重,各自对最终错误负多少责任?" 谁责任大,就多调它一点;谁基本没参与,就少动或不动。
数学上,这靠的是"链式法则"——把最终的损失,对每个权重求"偏导数"。导数就是"这个权重变一点点,损失会跟着变多少"的斜率。斜率大的权重就是"祸首",斜率小的就是"路人"。

Analogy · 项目做砸了开复盘会

反向传播就是一场逆向复盘会:项目失败了(损失很高),项目经理不能只骂最后一个交付的人——他得从终点回溯:这次交付出错,是因为上一环节没接好;上一环节没接好,是因为再上一环节的输入就是错的……一层层追责,直到找到源头。每个环节该承担多少改进量,都算得清清楚楚。神经网络就靠这套自动复盘机制,让百亿参数各自认领自己那一份责任。

链式法则与一次手算:反向传播的数学内核

链式法则(Chain Rule)这个名字听着像高数期末考的噩梦,其实它说的道理你每天都在用:一连串环节里,最上游变一点,最下游会变多少?把每一环的"变化倍数"乘起来就是答案

举个菜市场的例子。猪肉涨价 → 你家红烧肉成本上升 → 你饭馆的菜价上调 → 你的客流下降。问:猪肉每斤涨 1 块钱,你一天少来几个客人?你会这么算:猪肉涨 1 块,一份红烧肉成本涨 0.3 块(第一环倍数 0.3);成本涨 0.3 块,你会把菜价提 0.5 块(第二环倍数约 1.67);菜价提 0.5 块,客流少 4 个人(第三环倍数 8)。把倍数乘起来:0.3 × 1.67 × 8 ≈ 4,猪肉每涨 1 块,你一天少 4 个客人。这就是链式法则,一字不差。

神经网络里的链条更长一点,但结构完全一样:某个权重变一丝 → 这一层的加权和变一点 → 过激活函数后输出变一点 → 下一层的加权和变一点 → …… → 最终损失变多少。把这条链上每一环的"变化倍数"(也就是偏导数)乘起来,就得到"这个权重对最终损失的责任",也就是它的梯度。

为什么这件事在 1986 年之前是个大难题?因为如果你笨着算——对每个权重单独走一遍完整的链条——一个有一百万个权重的网络就要算一百万遍,算到宇宙毁灭都算不完。反向传播的天才之处在于它发现了:这些链条的后半段是共享的。就像一栋楼里所有住户去地铁站的路,最后一公里都走同一条街——你只要把那一公里算一次,所有人共用。反向传播从输出往回走一遍,顺手就把所有共享路段的结果缓存下来,于是一次反向传播的成本,大约只等于两次前向传播。一百万个权重的梯度,一遍全算完。

光说不算数,我们把责任数字真的算出来。接着上面那个西瓜的例子。当时模型说"65.8% 甜",真答案是 100% 甜,扣了 0.0585 分。现在我们要追责:输出层的权重 v1(连接 h1 到输出的那个旋钮,当前值 0.70)该负多少责任?

【要算的是】L 对 v1 的偏导数 dL/dv1
  意思:v1 往右拧一丝,扣分 L 会跟着变多少

【这条责任链有三环】
  v1  →  z_out  →  y_hat  →  L

第一环:L 对 y_hat 的敏感度
  L = (y - y_hat)²/2  →  dL/dy_hat = -(y - y_hat)
                       = -(1 - 0.658) = -0.342
  读法:y_hat 每涨一丝,扣分降 0.342 —— 合理,
        因为真答案是 1,答案越接近 1 扣分越少

第二环:y_hat 对 z_out 的敏感度(Sigmoid 的导数)
  sigmoid 的导数有个漂亮性质:s'(z) = s(z)×(1-s(z))
  dy_hat/dz_out = 0.658 × (1 - 0.658) = 0.658 × 0.342 = 0.225

第三环:z_out 对 v1 的敏感度
  z_out = v1×a_h1 + v2×a_h2 + c
  对 v1 求导,其它项都是常数,直接得
  dz_out/dv1 = a_h1 = 0.681

【三环相乘 = 责任】
  dL/dv1 = (-0.342) × 0.225 × 0.681 = -0.0524

【更新这个旋钮】学习率取 0.5
  v1_new = v1 - 学习率 × 梯度
         = 0.70 - 0.5 × (-0.0524)
         = 0.70 + 0.0262 = 0.7262

读法:梯度是负的,说明"v1 变大能让扣分变小",
     所以更新规则里的减号把它变成了"往大调"。
     旋钮从 0.70 拧到 0.7262 —— 一次只拧这么一丝。

同样的算法继续往回走,就能算出 v2、c,再穿过第二层算到第一层的 w11、w21、b1……整张网络的每个旋钮,都能拿到自己那份责任数字。这里有个细节值得记住:算第一层权重的梯度时,前面那些环(-0.342、0.225)是直接复用的,不需要重算——这就是刚才说的"共享最后一公里"。

我们验证一下这一步有没有用。把 v1 从 0.70 改成 0.7262,重新前向算一遍:z_out = 0.7262×0.681 + 0.60×0.632 − 0.20 = 0.4945 + 0.3792 − 0.20 = 0.6737,y_hat = sigmoid(0.6737) = 0.662,损失 L = (1−0.662)²/2 = 0.0571。扣分从 0.0585 降到了 0.0571。降得很少?对,就是这么少。但把这个动作重复一千万次,模型就从"瞎猜西瓜"变成"比瓜农还准"。深度学习的全部奇迹,就建立在这种极其微小、极其大量的改进上。

为什么叫"反向":信息真的是倒着流的

很多人第一次听"反向传播"会以为是"网络倒着跑一遍"——不是的。数据本身没有倒流,倒流的是"责任"这个信息。前向传播时,从左往右流动的是"数值";反向传播时,从右往左流动的是"敏感度"。两趟走的是同一条路,运的是不同的货。

用快递来打比方。前向传播像发货:包裹从上海仓库出发,经过杭州中转、南昌中转、长沙中转,最后送到你手上。反向传播像投诉追溯:你收到的箱子被压坏了,投诉打到客服,客服往回查——先问长沙站"你交出去时坏了没",长沙说"我收到就坏了",再问南昌,南昌说"我收到时好的",那就锁定问题在南昌到长沙这一段。投诉信息严格沿着包裹走过的路线倒着走了一遍,每一站都要交代自己的责任比例。这就是反向传播的字面意思。

还有一层原因是数学上的必然:链式法则的乘积必须从"离损失最近的那一环"开始算,才能把中间结果缓存复用。如果从输入端开始往前算,每个参数都得独立走完全程,成本立刻爆炸。所以"反向"不是设计者的审美偏好,是唯一算得起的顺序。这个从后往前算的方式在数学上有个专门名字,叫反向模式自动微分(reverse-mode automatic differentiation)——说白了就是"倒着求导数"。

计算图与 loss.backward():框架替你干了什么

计算图(Computational Graph)这个词听着抽象,说白了就是一张记录"谁是谁算出来的"的流程图。你在 PyTorch 里写 y = w * x + b,框架并不只是算出一个数就完事,它同时在后台画了一张图:这里有一个乘法节点,两个输入是 w 和 x;上面接一个加法节点,另一个输入是 b。

这就像装修工地的施工记录本。刷完墙、铺完地、装完柜,工头把每道工序的先后顺序、谁干的、用了什么材料,全记在本子上。等到验收时发现柜门关不严,工头翻开本子往前查,一眼就能看出是柜体安装那道工序出了问题,还能顺着查到是不是墙面不平导致的。没有这本记录,就只能砸墙重来;有了这本记录,就能精准追责

有了这张图,自动微分(Automatic Differentiation,自动微分——让程序自己算导数,不用人手推公式)就变得机械化了:框架里预先给每种基本运算(加、乘、sigmoid、矩阵乘法……)都写好了"我的导数长什么样",然后沿着图从输出往输入倒着走,每碰到一个节点就查表拿它的导数,一路乘下去。你写模型时完全不需要手推任何求导公式,这是深度学习能被普通工程师用起来的核心原因之一。1990 年代的人要训一个新结构的网络,得先在纸上把梯度公式推一遍再手写代码——推错一个符号就 debug 一星期。

知道了这套机制,我们看看它在真实代码里长什么样。在 PyTorch 里训练一个模型,核心就四行代码。这四行看着轻描淡写,底下每一行都在干重活。我们逐行拆开。

for x, y in dataloader:          # 一批一批取数据
    optimizer.zero_grad()        # ① 清空上一轮攒下的梯度
    y_hat = model(x)             # ② 前向传播,顺手建计算图
    loss  = criterion(y_hat, y)  # ③ 算扣分
    loss.backward()              # ④ 反向传播,把所有梯度填进去
    optimizer.step()             # ⑤ 按梯度拧一遍所有旋钮

第 ② 行 model(x) 不只是算出预测值,它同时在内存里悄悄搭起了那张计算图,并且缓存了每一层的中间结果(前面例子里的 a_h1=0.681、a_h2=0.632 这些)。为什么要缓存?因为算梯度时用得上——你看第三环 dz_out/dv1 就直接等于 a_h1。这也是训练比推理吃显存得多的根本原因:推理算完就扔,训练必须把每一层的中间结果都留着等反向传播来取。一个模型推理只要 16GB 显存,训练可能要 80GB,差距就在这儿。

第 ④ 行 loss.backward() 是真正的主角。它沿着计算图从 loss 这个节点往回走,逐个节点应用链式法则,把算出的梯度累加到每个参数自带的 .grad 属性里。注意是"累加"不是"覆盖"——这就是第 ① 行必须先清零的原因。忘了写 zero_grad() 是初学者最常见的 bug:梯度会一轮一轮越积越大,训练看起来"莫名其妙就炸了"。这就像你用一个没擦干净的量杯量面粉,第二次量出来的必然比实际多。

第 ⑤ 行 optimizer.step() 才是真正动手拧旋钮的那一步。它遍历所有参数,按 参数 = 参数 − 学习率 × 梯度 更新一遍。backward 只负责算清责任,step 才负责执行处罚——这两件事在框架里是刻意分开的,因为算责任的方式只有一种(链式法则),而执行处罚的方式有很多种(SGD、Adam、AdamW……),分开才能自由组合。

梯度是什么、梯度下降怎么走:顺着斜坡下山

"梯度"这个词是初学者的第一个大坎。教科书写"梯度是多元函数各偏导数组成的向量"——这句话正确,但对零基础的人来说等于没说。我们换个说法。

想象你面前是一台老式的调音台,上面有 20 个推子。现在音箱里放出来的声音很糟,你要调好它。你会怎么办?你会一个一个试:把第 1 个推子推上去一点点,听——变糟了;推下去一点点,听——好了一点。记下来:"第 1 个推子往下推,效果变好,敏感度中等。"然后试第 2 个推子……20 个全试完,你手里就有一张表,记着每个推子的方向和敏感度。这张表就是梯度

神经网络里的梯度就是这么个东西,只有两点不同。第一,网络里的"推子"是几亿几百亿个,不可能一个一个试;反向传播的作用就是一次性把所有推子的敏感度全算出来,不用逐个试。第二,梯度里的数字有正负号:正号意思是"这个旋钮往大调,扣分会涨",所以你要往小调;负号意思是"往大调扣分会降",所以要往大调。这就是更新公式里那个减号的全部含义——参数 = 参数 − 学习率 × 梯度,减号自动帮你选了正确的方向。

再补一个直觉:梯度的大小告诉你这个旋钮有多"要紧"。梯度是 0.5 的旋钮,比梯度是 0.001 的旋钮重要五百倍——同样拧一丝,前者对结果的影响大五百倍。所以梯度下降天然就是"优先解决主要矛盾"的策略:责任大的多改,责任小的少改,没参与的干脆不动。这跟一个好的管理者复盘项目的做法一模一样。

算清了责任,接下来就要用它去修正权重。修正规则叫梯度下降(Gradient Descent)权重 = 原权重 − 学习率 × 梯度
你可以把损失函数想象成一片山地:模型现在站在山上某个位置,梯度就是"最陡下坡方向的箭头"。你顺着箭头走一小步("一小步"的大小由"学习率"决定),就离山谷(损失最小值)更近一点。走几百万步之后,模型基本就落到山谷里了——这时候各种权重的组合,就是训练出来的"聪明模型"。

Analogy · 大雾天的盲人下山

梯度下降的完整画面是这样的:你被空投到一座陌生的大山上,四周浓雾弥漫,能见度不到一米,任务是走到山谷最低处。你看不见远处的地形,唯一能做的是用脚探一探:往前踩踩,比现在低还是高?往左踩踩呢?往右呢?探完一圈,你找到"最陡的下坡方向",朝那个方向迈一步,然后在新位置重新探一圈,再迈一步。
这里面每个动作都能对上号:用脚探周围地形 = 反向传播算梯度(你只知道脚下这一小块的坡度,不知道全山的地形);迈一步的大小 = 学习率(步子大走得快但容易一脚踩空,步子小稳当但天黑了还在半山腰);反复迈步 = 训练迭代
这个类比还解释了两个让人困惑的现象。第一,为什么模型可能卡在"局部最优"——你走到了一个小山坳,四周探一圈全是上坡,你以为到底了,其实真正的山谷在隔壁山头下面,你只是被一个小坑困住了。第二,为什么随机性有好处——如果你偶尔喝点酒走得踉踉跄跄(这就是随机梯度下降的"抖"),反而可能一脚踉跄跨出小坑,找到更深的谷底。深度学习里刻意保留噪声,正是这个道理。

前向 + 反向 = 一次完整训练迭代

epoch、batch、iteration:三个最容易搞混的词

这三个词在任何一份训练日志里都会出现,混淆它们会让你完全读不懂别人在说什么。用批改作业这个场景,一次讲清。

假设你是老师,班上有 1000 份作业要批。你的习惯是一次抱 50 份到办公室,批完这 50 份之后,才根据这批作业里发现的普遍问题去调整教案。那么:

关系式记住一句就够:1 epoch = 数据总量 ÷ batch size 次 iteration。举个真实数字:ImageNet 有 128 万张图,batch size 设 256,那么 1 个 epoch = 5000 次 iteration;训 90 个 epoch,总共要走 45 万步。每一步都是一次完整的"前向 + 反向 + 拧旋钮"

batch size 这个数字的选择很有讲究。太小(比如 1):每次只看一份作业就改教案,被单个学生的偶然错误带偏,教案改得东倒西歪;但好处是偶尔的"带偏"能帮你跳出局部最优。太大(比如 1000,一次全批完):教案改得非常稳,方向准,但改一次要等很久,而且太"平均"了,容易一头钻进离起点最近的那个小坑里出不来。实践中一般取 32 到 512 之间,一半是数学考虑,一半是显存装得下多少的现实约束。

学习率:走多大一步是门艺术,设错了会怎样

学习率(learning rate)这个数字看着不起眼,其实是训练里最难调的超参数之一。步子太大——每次跨过山谷两侧,永远不落底,甚至越训越糟("发散");步子太小——训练龟速,几天几夜都下不了山。
实践中人们会用"学习率衰减"、"Adam / AdamW 优化器"之类的技巧,让开头步子大、后面步子小——粗调加精调结合。你写代码调不好,往往就是这个数字设得离谱。

下面给出症状对照表——你训模型时看到损失曲线长什么样,基本就能反推学习率出了什么问题。这跟看病一样:知道症状对应哪个毛病,才知道该吃什么药。

【病症一】学习率大得离谱(比如该用 0.001 你写了 10)
  损失曲线:第 2 步就变成 NaN(Not a Number,非数值)
  发生了什么:一步跨出十万八千里,数值直接溢出
  生活类比:你在山上一脚跨出去,直接跨到大气层外了
  处方:学习率除以 100 再试

【病症二】学习率偏大(该用 0.001 你写了 0.1)
  损失曲线:上下剧烈震荡,忽高忽低,不往下走
  发生了什么:每一步都跨过谷底落到对面山坡上,
             来回蹦,永远落不到底
  生活类比:你想走到房间正中央,但每步都迈两米——
           永远在中心两侧来回横跳
  处方:学习率除以 10,或者开启学习率衰减

【病症三】学习率偏小(该用 0.001 你写了 0.00001)
  损失曲线:极其缓慢地下降,一条几乎水平的线
  发生了什么:每步只挪一丝,训练要跑到明年
  生活类比:搬家时你一次只搬一双袜子
  处方:学习率乘 10,或者用带自适应的 Adam

【病症四】学习率还行但训练后期卡住
  损失曲线:先漂亮地下降,然后在某个值上摊平不动了
  发生了什么:接近谷底了,但步子相对于剩余距离还是太大,
             在谷底附近来回小幅晃动
  生活类比:绣花针眼要穿线,手抖幅度大于针眼
  处方:学习率衰减(cosine 退火 / 阶梯下降),
       让后期的步子自动变小

行业里有一套非常实用的经验数字可以直接抄:用 Adam 优化器时,学习率从 1e-3(0.001)起步;微调一个预训练大模型时,用 1e-5 到 5e-5 这个量级。为什么微调要小 100 倍?因为预训练模型的旋钮已经被别人调得很好了,你只是想微微修一下——你请人来给你家的钢琴调音,他不会把每根弦都松掉重装,只会拧一丝丝。学习率大了,等于把人家辛苦调好的琴给拆了,这个现象在业内叫"灾难性遗忘"。

还有一个几乎所有大模型训练都会用的技巧叫学习率预热(warmup):训练最开始的几百步,故意把学习率从 0 慢慢升到目标值,而不是一上来就用满。原因很实在——训练刚开始时所有旋钮都是随机数,梯度方向极不可靠,这时候大步跨出去很容易把网络带进死胡同。这就像开车上高速:不能一脚油门到底,得先在匝道上慢慢加速。

反向传播的真实身世:1974 的博士论文与 1986 的引爆

上面那段说 1986 年 Rumelhart 等人"发明"了反向传播,其实这个说法不太准确,历史真相更曲折,也更能说明科学史上"发明"和"被看见"是两件不同的事。

1974 年,哈佛大学一位叫 Paul Werbos 的博士生在他的学位论文里就已经完整写出了用链式法则训练多层网络的方法。他把这套方法叫"动态反馈",用它做社会经济系统的预测。这比 1986 年那篇著名论文早了整整 12 年。但当时是什么时候?1969 年 Minsky 那本《Perceptrons》刚把神经网络研究判了死刑,整个领域处在第一次 AI 冬天的最深处,没人愿意听"神经网络还有救"这种话。Werbos 后来回忆说,他甚至找不到愿意认真读这篇论文的人。

再往前追,其实还能追到更早:1960 年代控制论领域的 Henry Kelley、Arthur Bryson 在研究火箭轨迹最优控制时,就用过数学上等价的"反向递推"技术;1970 年芬兰的 Seppo Linnainmaa 在硕士论文里写出了通用的反向模式自动微分。换句话说,这套数学工具在不同领域被独立发明了至少四五次——但每次都因为"不在对的圈子里"而没能引起注意。这是科学史上极常见的一幕。

1986 年 10 月,Nature 杂志刊出了 David Rumelhart、Geoffrey Hinton、Ronald Williams 的《Learning representations by back-propagating errors》。这篇论文的技术内容并不比 Werbos 的更高深,但它做了三件关键的事:第一,把方法讲得极其清楚,任何研究者读完都能自己实现;第二,用具体实验证明多层网络确实能学到有意义的内部表示——比如网络自己在隐藏层里发现了"家族关系"的抽象概念,这一点极具说服力;第三,发在 Nature 上,让整个科学界都看到了。它同时打破了 Minsky 那本书留下的"多层网络训不了"的心理魔咒。这一年通常被视为神经网络研究第二春的起点。

这段历史值得记住的教训是:一项技术能否改变世界,不只取决于它有多聪明,还取决于它出现的时机、写得清不清楚、发在哪里、以及有没有人愿意为它站台。 反向传播躺了 12 年才被引爆,之后又躺了 20 年(因为算力和数据不够)才真正掀起深度学习浪潮——它 2012 年才等到自己的时代。

所以为什么说反向传播是"神来一笔"?1986 年之前的神经网络训练极其低效——人们不知道怎么高效地把误差摊回去。Rumelhart、Hinton 等人把链式法则用到神经网络上,让一次反向计算就能同时算出所有权重的梯度,算力复杂度和一次前向相当。这一下把"训练百亿参数"从纸面变成工程可行。可以说,没有反向传播,就没有今天的深度学习——就像没有蒸汽机就没有工业革命一样重要。

批量、随机、小批量——三种下山姿势

梯度下降真正在工程里落地时,有三种玩法:批量梯度下降(Batch GD)——每一步都用整个数据集算梯度,稳但极慢,几百万条数据算一次就要跑半天;随机梯度下降(SGD)——每次只用一条样本算梯度,快但抖,像在山坡上边喝酒边走路;小批量梯度下降(Mini-batch GD)——每次用几十上百条,兼顾稳定和速度,是今天的标配。
再往上走,就是 Adam、AdamW、Adagrad 这些"聪明版优化器"——它们不但看当前梯度,还看历史梯度的动量、每个参数的自适应学习率,让下山过程更聪明。同一个模型,只换一个优化器,训练结果可能天差地别——这是深度学习工程里非常见的"魔法"。

梯度消失与梯度爆炸:0.25 连乘十次 vs 1.5 连乘十次

上面那段是结论,这里给你数字。梯度消失和梯度爆炸的全部原因,就藏在"一串数字连乘"这个动作里,任何人拿计算器都能验证。

【梯度消失】每层乘 0.25(这是 Sigmoid 导数的最大值!)

  第 1 层往回:0.25
  第 2 层:    0.25 × 0.25       = 0.0625
  第 3 层:                       = 0.0156
  第 5 层:                       = 0.000977
  第 10 层:                      = 0.00000095   (九千五百万分之一)
  第 20 层:                      = 0.0000000000009  (九千亿分之一)

  含义:第 20 层收到的责任是第 1 层的九千亿分之一。
       用 float32 精度存储,这个数已经接近 0,
       更新时乘上学习率之后彻底变成 0 ——
       前面 20 层的旋钮一动不动,等于根本没在训练。

【梯度爆炸】每层乘 1.5

  第 1 层:  1.5
  第 3 层:  1.5³   = 3.375
  第 5 层:  1.5⁵   = 7.59
  第 10 层: 1.5¹⁰  = 57.7
  第 20 层: 1.5²⁰  = 3325
  第 50 层: 1.5⁵⁰  = 637,621,500  (六亿多)

  含义:本来应该拧一丝的旋钮,被拧了六亿丝。
       参数瞬间变成天文数字,下一步前向传播
       算出 inf(无穷大),再一步就是 NaN,训练报废。

为什么恰好是 0.25 这个数?因为 Sigmoid 函数的导数最大值就是 0.25(在 z=0 处取到),其它位置更小。也就是说,用 Sigmoid 做激活函数的深层网络,梯度衰减是数学上的必然,不是运气不好。这一个数字,就解释了为什么 1990 年代人们死活训不动超过 5 层的网络——他们不是不努力,是被一个高中生就能算的乘法卡住了 15 年。

用生活场景感受这两个魔咒。梯度消失像一场传话游戏:一百个人排成队,第一个人听到一句完整的话,往后传,每个人只听清 25%。传到第十个人,剩下的信息量是九千五百万分之一,他听到的完全是噪音——最前面几个人(也就是网络的浅层)永远不知道自己该改什么。梯度爆炸像谣言传播:一个人说"隔壁小区有点小雨",传一手变成"下暴雨",传两手变成"发洪水了",传到第二十手已经是"世界末日"——每一手都夸大 1.5 倍,二十手之后就是三千倍的失真。

知道原因,药方就清楚了,这也解释了现代深度学习为什么长成今天的样子:

补一句总结:反向传播的思想虽然简单,但工程上曾经栽过大跟头。当神经网络堆到几十层时,反向传播的梯度沿着链式法则一层层相乘——每一层都乘一个小于 1 的数,几十层乘完,梯度几乎变成 0,前面几层的权重根本更新不动,这叫梯度消失;反之,如果每层都乘一个大于 1 的数,梯度就会指数级放大,训练直接飞了,这叫梯度爆炸
这个问题让 1990 年代到 2006 年间的深度网络训练几乎是"不可能任务"。后来靠着 ReLU 激活函数、Batch Normalization、残差连接(ResNet)、更好的初始化方案(Xavier / He),才把这两个魔咒基本解掉。今天你能训练 175B 参数的 GPT-3,背后是几十年间无数工程师和研究员在跟这两个魔咒搏斗积累下的经验。

训练完之后,权重就"冻结"了

很多人以为模型上线之后还会继续学习——其实不然。训练推理是深度学习里两个截然分开的阶段:训练时,前向 + 反向 + 更新三步循环几百万次,把权重调到位;训练完成后,模型被"打包"存起来,所有权重固定不变;推理时,只做前向传播——你输一个问题,它算一遍答案,权重丝毫不动。
这就是为什么 ChatGPT 不会因为你今天骂了它就"记仇"——它每次对话都是从冻结的权重开始跑的。它有的只是"上下文窗口"内的短期对话记忆,一旦对话结束、窗口关闭,什么都不会留下。想要模型真的"学新东西",就得单独走一遍"微调"(fine-tune)——本质上是让训练循环再跑一次,用新数据把权重再微调一下。前向 = 使用;反向 = 学习——两个阶段泾渭分明

过拟合:反向传播太"卷"的后遗症

反向传播这么高效,反而带来一个副作用——模型可能会把训练数据"背下来",包括数据里的噪声和偶然性——这就叫过拟合(overfitting)。表现是训练集上准确率 99%,一到测试集就掉到 60%。
工程上的对抗招式很多:早停(early stopping)——一发现验证集损失开始上升就停训;Dropout——训练时随机让一部分神经元罢工,防止过度依赖某几个;权重衰减(weight decay)——给损失函数加个惩罚项,逼权重别长太大;数据增强——让训练数据更多样。这些技巧本质都是在"给反向传播打拳击手套"——让它别把训练数据咬得太死。

Recap · 收束

前向 = 用当前参数算答案;反向 = 用误差回溯,给每个参数分摊责任并修正。训练一个神经网络,就是让它做上亿次"考试 → 批卷 → 改题"的循环。理解了这两个动作,你就理解了 GPT、Sora、AlphaGo 是怎么"从随机瞎猜"训练成"世界高手"的——它们没有什么魔法,就是循环得比你多、算得比你快而已。

☰ 主页
Xue Hai Wu Ya · § 5.2 · Propagation