一、先接上第 5 讲的悬念
第 5 讲拼出了一个完整的函数:矩阵乘法、ReLU、softmax,层层叠加,理论上能表达相当复杂的映射关系。但留了一个没解决的问题:W 和 b 里的每一个数字,都是随便设的。随机参数喂进去,只会吐出随机的、没有意义的概率分布。
这一讲要解决的问题就一句话:怎么让这些参数,从"随机的乱码",自动变成"能把下一个词猜对"的正确值?
二、打个比方:蒙着眼睛下山
想象你站在一片高低起伏的山地里,蒙着眼睛,目标是走到山谷最低点。你看不见地形全貌,但你能感觉到脚下这一小块地面往哪个方向倾斜、倾斜得有多陡。
一个合理的策略是:每一步都朝"脚下感觉最陡的下坡方向"迈一小步,然后重新感觉一次坡度,再迈下一步。走的步数够多,你大概率会停在某个谷底附近——即便你从头到尾都没看到过整片地形。
⭐ 这就是这一讲要讲的算法的全部直觉。“山地的高度"对应损失函数(衡量模型当前错得有多离谱);“脚下的坡度"对应梯度;“朝最陡下坡方向迈一小步"对应梯度下降这个更新参数的规则。
三、把直觉钉成数学
损失函数:把"错得有多离谱"变成一个数字
要下山,先得知道"当前站的地方海拔多高”。对神经网络来说,这个"海拔"叫损失(loss)——一个衡量"模型现在的输出,和正确答案差多少"的数字,数字越大,模型错得越离谱。
第 4 讲已经埋好了这个工具:交叉熵——那一讲用它给困惑度打分,这一讲它换一个身份,变成训练时要主动去缩小的目标。这不是两个不同的量,是同一个量的两种用法:算完之后拿来"评估”,就是第 4 讲的困惑度;算完之后拿来"调参数”,就是这一讲的损失函数。
导数:挪一点点参数,损失会变化多少
要知道"脚下往哪边倾斜",需要一个衡量"改变这个参数一点点,损失会跟着变化多少"的工具——这就是导数。
用一个具体到可以逐步手算的小例子建立直觉。假设某个参数 w 只有一个,损失函数是:
L(w) = (w − 3)²
(这是虚构的一个简化损失,只是为了让"下山"这件事可以被完整地画出来——真实的损失函数由整个网络加交叉熵算出来,形状复杂得多,但梯度下降处理它的方式完全一样。)
这个函数的导数是 dL/dw = 2(w−3)——不需要深究求导的规则,只需要知道导数告诉你,在当前这一点,w 往哪个方向挪,L 会变大还是变小,变化得有多快。
从 w=0 开始,每一步走"学习率" η=0.1 乘以负梯度的距离:
| 步数 | w | dL/dw = 2(w−3) | 更新:w ← w − η·(dL/dw) |
|---|---|---|---|
| 0 | 0 | −6.0 | 0 − 0.1×(−6.0) = 0.6 |
| 1 | 0.6 | −4.8 | 0.6 − 0.1×(−4.8) = 1.08 |
| 2 | 1.08 | −3.84 | 1.08 − 0.1×(−3.84) = 1.464 |
| 3 | 1.464 | −3.072 | 1.464 + 0.307 = 1.771 |
| … | … | … | 逐步逼近 3 |
⭐ 每一步都往梯度的反方向挪(梯度是"变大最快"的方向,要下降就要反着走),w 一路从 0 逼近损失最小的地方——也就是 w=3(这一点上 dL/dw = 2×(3-3) = 0,坡度为零,“脚下已经是平地”,这就是梯度下降判断"到底了"的信号)。
梯度下降(gradient descent):沿着损失函数对参数的梯度的反方向,反复对参数做微小更新,逐步逼近损失更小的参数取值的一种迭代算法。更新规则:
w ← w − η · (dL/dw),η叫学习率,控制每一步迈多大。
⚠️ 学习率不是越大越好,也不是越小越好:η 太大,每一步迈得太猛,可能直接越过谷底冲到对面的坡上,损失不降反升,甚至越走越离谱(发散);η 太小,每一步挪动量微乎其微,要走天文数字那么多步才能到谷底,训练慢得不现实。第 13、17 讲会看到,“怎么设置和调整学习率"本身就是训练一个大模型时最重要的工程决策之一。
多参数:梯度是一个向量
真实的网络不止一个参数,是成千上万甚至几十亿个 W、b 里的数字。这时候"梯度"不再是一个数字,而是每个参数各自的偏导数排成的一串数字——回忆第 3 讲:这正是一个向量。梯度下降的更新规则完全不变,只是从"一个数字减一个数字"变成"一个向量减一个向量(对应位置分别相减)":
所有参数 ← 所有参数 − η × 梯度向量
反向传播:高效算出每个参数的梯度
问题来了:一个真实的网络有很多层,损失是最后一层才算出来的,但需要调整的参数散布在每一层里。第一层的某个参数,怎么知道自己该往哪个方向挪,才能让好几层之后才算出来的损失变小?
答案是微积分的链式法则——把一个复合函数的导数,拆成一串局部导数相乘:
如果 L 是 x 经过 g 再经过 f 算出来的:L = f(g(x))
那么:dL/dx = (dL/dg) × (dg/dx)
⚠️ 这里要停一下澄清一个容易混淆的地方:这个名字你不是第一次见——第 4 讲讲概率时也用了"链式法则"这个词。两者名字相同,指的是完全不同的两条规则:第 4 讲的链式法则是概率论里的恒等式,把一整句话的联合概率拆成一串条件概率相乘;这一讲的链式法则是微积分里的求导规则,把一个复合函数的导数拆成一串局部导数相乘。两者不是巧合,都是"把一个复杂问题拆成一串简单步骤,再把结果组合起来"这同一种数学哲学的体现,但计算的对象完全不是一回事,不要混着用。
神经网络刚好是一长串函数的复合(一层接一层),所以链式法则可以从最后一层的损失开始,一层一层往输入方向倒着算回去——这正是"反向传播(backpropagation)“这个名字的来源:先正向算一遍输出(前向传播),再从输出端的误差出发,反着方向、一层一层往回传,把每一层的梯度都算出来。
反向传播(backpropagation):利用微积分链式法则,从输出层的损失开始,逐层向输入方向反向计算,从而高效算出损失对网络中每一个参数的梯度的算法。
一个完整的手算例子
把整个流程走一遍,用第 5 讲那种规模的小网络,全部换成具体数字。网络结构:
z₁ = w₁x + b₁
h = ReLU(z₁)
y = w₂h + b₂
L = (y − t)² ← t 是目标值(真实答案)
取 x=2,目标 t=10,当前参数 w₁=1, b₁=0, w₂=3, b₂=1。
前向传播(正向算一遍,顺便把每一步的中间结果都记下来,反向传播要用):
z₁ = 1×2 + 0 = 2
h = ReLU(2) = 2
y = 3×2 + 1 = 7
L = (7 − 10)² = 9
反向传播(从 L 出发,反着往回算每一个参数的梯度):
① dL/dy = 2(y − t) = 2×(7−10) = −6
② dL/dw₂ = dL/dy × dy/dw₂ = dL/dy × h = −6 × 2 = −12
dL/db₂ = dL/dy × dy/db₂ = dL/dy × 1 = −6
③ dL/dh = dL/dy × dy/dh = dL/dy × w₂ = −6 × 3 = −18
④ dh/dz₁ = ReLU'(z₁) = 1 (因为 z₁=2 > 0,ReLU 在正数区间导数恒为 1)
dL/dz₁ = dL/dh × dh/dz₁ = −18 × 1 = −18
⑤ dL/dw₁ = dL/dz₁ × dz₁/dw₁ = dL/dz₁ × x = −18 × 2 = −36
dL/db₁ = dL/dz₁ × dz₁/db₁ = dL/dz₁ × 1 = −18
⭐ 注意②到⑤每一步都在复用上一步已经算好的结果(dL/dy 算一次,后面全部拿来接着用),不需要为每个参数从头重新算一遍完整的链条——这正是反向传播比"对每个参数分别暴力求导"高效得多的原因,网络越深,这个效率差距越大。
拿学习率 η=0.01 更新一次全部参数:
w₂ ← 3 − 0.01×(−12) = 3.12
b₂ ← 1 − 0.01×(−6) = 1.06
w₁ ← 1 − 0.01×(−36) = 1.36
b₁ ← 0 − 0.01×(−18) = 0.18
这就是训练神经网络唯一在做的事情——不断重复"前向算一遍、反向传播算梯度、按梯度小幅更新参数"这三步,跑过足够多的例子之后,参数会逐渐从随机值,收敛到能让损失变小的取值。
⚠️ 现实训练几乎从不会真的每算一个例子就更新一次,也不会攒够全部训练数据才更新一次,而是每次取一小批数据(mini-batch)算平均梯度再更新,叫随机梯度下降(SGD);实践中用得更多的是在 SGD 基础上加了些技巧(比如记住之前几步的更新方向)的改良版本(如 Adam)。这些是重要的工程细节,但核心机制和这一讲手算的例子完全相同,不是另一套原理。
四、这一讲机制自带的代价
- 梯度消失/爆炸,第 2 讲的旧账:第 2 讲讲 RNN 时提到,训练信号要经过很多步连乘会指数级衰减或爆炸。现在可以把这句话说得更准确:这正是这一讲反向传播链式法则连乘的直接后果——网络(或序列)越深,梯度要连续相乘的局部导数就越多,只要这些局部导数持续小于 1(或大于 1),乘积就会指数级衰减(或爆炸)。这不是 RNN 独有的毛病,是反向传播这个机制在"层数/步数很深"时的通用风险,只是 RNN 因为按时间步展开后天生"很深”,撞得最早、最明显。
- 没有找到全局最优的保证:损失函数的"地形"极其复杂,梯度下降只能保证每一步都在往"脚下的下坡方向"走,不能保证走到的是整片地形里最低的谷底,也可能停在一个局部的小坑里。⚠️ 好消息是:经验上,现代大规模神经网络的损失地形,在极高维参数空间里,糟糕的局部小坑相对没那么常见(更常见的困扰是大片近乎平坦、走得很慢的区域),但这依然是一个没有理论保证、只能靠经验和实践验证的说法。
- 训练要占用大量显存:反向传播要求前向传播时把每一层的中间结果(比如手算例子里的
z₁、h)都存下来,反向传播时才有东西可用来计算局部导数。网络越深、批量数据越大,需要存的中间结果就越多——这是训练大模型需要巨大显存的直接原因之一,第 22 讲讲推理阶段的显存优化时,你会看到类似的存储-计算权衡以另一种形式重新出现。
五、和后面课程的关系
- 第 13 讲(预训练目标):把这一讲的"损失函数"具体钉死成交叉熵,把"训练数据"换成海量文本,本质上就是把这一讲的三步循环(前向、反向、更新)在极大规模上重复运行。
- 第 2 讲(RNN 的瓶颈):梯度消失问题在这一讲被正式讲清楚了数学来源——反向传播链式法则的连乘衰减,回头看第 2 讲会更清楚为什么 LSTM 的"闸门"是在直接对抗这个数学性质。
- 第 17 讲(RLHF):强化学习里的"奖励信号"要转换成能驱动参数更新的梯度,转换的方式和这一讲不完全相同,但最终"用梯度下降更新参数"这一步,和这一讲讲的机制是同一套。
- Lab 1:会把这一讲手算的例子,原封不动地写成一段可以运行、可以验证的纯 NumPy 代码——自己实现一次前向传播和反向传播,比看多少遍公式都更能建立直觉。
六、本讲小结
- 损失函数把"模型错得有多离谱"变成一个数字;梯度告诉你,微调某个参数,这个数字会怎么变化。
- ⭐ 梯度下降:沿梯度反方向,反复对参数做小幅更新,
w ← w − η·(dL/dw),像蒙眼下山一样一步步逼近损失更低的地方;学习率η太大会冲过头发散,太小会慢得不现实。 - 多参数时梯度是一个向量——每个参数各自的偏导数排成一串,衔接回第 3 讲的向量概念。
- ⭐ 反向传播用微积分链式法则,从输出端的损失开始,逐层往回传,高效算出每一个参数的梯度,避免了对每个参数从头暴力求导——本讲给出的手算例子完整走过了前向、反向、更新三步,每一个中间数字都可以自己验算。
- ⚠️ 这一讲的"链式法则"是微积分规则,和第 4 讲概率论的"链式法则"同名不同物,两者共享"拆解复杂问题、组合简单步骤"的哲学,但计算对象完全不同。
- 代价:梯度消失/爆炸是反向传播连乘的通用风险(第 2 讲 RNN 的问题在这里有了准确的数学解释);没有全局最优的理论保证;训练需要存储大量中间结果,是显存开销的直接来源。
思考题
- 在第三节的手算下山例子(
L(w)=(w-3)²,从w=0开始,η=0.1)里,继续手算第 4、5 步,观察w是不是在持续逼近 3。如果把学习率换成η=1.1,从w=0开始走一步,w会变成多少?这说明了什么? - 手算例子里,如果
x=−2(其他参数不变:w₁=1,b₁=0),重新算一遍z₁和h。这时候dh/dz₁还等于 1 吗?为什么(提示:ReLU 在负数区间的导数)?这对dL/dw₁会有什么影响? - 为什么反向传播要"复用上一步算好的结果"这件事,会让计算量随网络层数增长得比"暴力对每个参数分别求导"慢得多?试着数一数:一个 10 层的网络,暴力法大概要重复计算多少次"前 9 层"的部分?
- 结合本讲第四节和第 2 讲的内容,解释一下:为什么 RNN 按时间步展开后,“网络的深度"会随着序列长度增长,而普通的前馈网络(比如第 5 讲的 MLP)深度是固定的、不随输入变化?
- 第四节提到"损失地形里可能存在局部小坑,梯度下降不保证找到全局最优”——结合第三节"脚下感觉最陡的方向"这个比方,你能想到蒙眼下山这个策略在什么样的地形里会被"骗"到一个不是最低点的地方吗?