梯度下降:大语言模型如何从“胡乱猜词”学会生成文本

面向没有机器学习背景的普通程序员,解释大语言模型训练中最核心的概念之一:梯度下降。

本文讨论的是公开、通用的大语言模型训练原理,不涉及任何未公开的模型架构、训练数据或训练配方。

摘要

很多程序员第一次接触大语言模型训练时,都会遇到一串看起来很抽象的术语:参数、损失函数、梯度、反向传播、梯度下降、优化器。

把这些概念串起来之后,大语言模型最核心的训练过程其实可以压缩成一句话:

让模型预测下一个 token,计算它错得有多严重,然后稍微修改模型内部的参数,使它下一次更有可能预测正确。

这个过程反复执行,模型便会从最初近似随机的预测,逐渐学会语言、代码以及训练文本中存在的大量规律。公开资料通常将语言模型预训练描述为“根据已有 token 预测下一个 token”,而训练本身则是在不断调整模型内部的大量数值参数。12

负责回答“这些参数下一步应该往哪个方向修改”的核心工具,就是本文的主角:梯度下降


目录


先不要把大语言模型想得太神秘

从程序员的角度,可以先把大语言模型理解成一个非常复杂的函数:

输入的一串 token
   大语言模型
下一个 token 的概率分布

用一个简化的数学表达式表示:

$$ P(\text{下一个 token}) = f(\text{已有 token}; \theta) $$

这里的 $\theta$ 代表模型内部所有可以通过训练改变的参数。

你可以暂时把这些参数理解成一个极其庞大的浮点数数组:

parameters = [
    0.0182,
    -0.7314,
    1.2057,
    # ...
]

这些数字并不是一句句人工编写的规则,也不是下面这种代码:

if user_asks_about_france:
    return "Paris"

模型的语言能力来自大量参数共同参与计算后形成的行为。训练的主要工作,就是不断调整这些参数。OpenAI 的公开说明把模型的权重或参数描述为模型内部的大量数值;训练会根据数据中发现的关系,对这些数值进行细微调整。1

你可以把模型参数类比成一个大型程序中的所有配置值和函数系数。单独看某一个数字,往往很难说它“保存了什么知识”;但所有数字共同工作时,模型就能表现出复杂能力。


大语言模型在训练时究竟做什么题

假设训练数据中有一句话:

法国的首都是巴黎。

文本首先会被 tokenizer 转换成 token。为了方便理解,我们暂时假设它被切分成:

[法国, 的, 首都, 是, 巴黎, 。]

真实 tokenizer 的切分方式可能不同。一个 token 可能对应:

  • 一个完整单词;
  • 单词的一部分;
  • 一个汉字或若干汉字;
  • 标点符号;
  • 其他常见的文本片段。

训练时,可以把上面这句话自动变成多道“预测下一个 token”的题:

看到:法国
预测:的

看到:法国 的
预测:首都

看到:法国 的 首都
预测:是

看到:法国 的 首都 是
预测:巴黎

因此,预训练数据通常不需要人工为每句话单独编写答案。

文本后面的 token,本身就是前面文本的训练答案。

在实现层面,可以把同一段 token 序列错开一位:

输入: [法国, 的, 首都, 是]
答案: [的, 首都, 是, 巴黎]

模型一次前向计算,就可以在多个位置上同时接受训练。

GPT-4 技术报告明确把其预训练目标描述为“预测文档中的下一个 token”。2


模型输出的不是一个词,而是一组概率

当模型看到:

法国的首都是

它不会在内部直接得到唯一答案“巴黎”。

模型首先会为词表中的大量候选 token 计算分数,再把这些分数转换成概率分布。下面是一个仅用于说明原理的虚构结果:

巴黎    10%
伦敦     8%
东京     5%
城市     4%
北京     3%
其他    70%

训练数据告诉模型,当前位置的正确答案是:

巴黎

但是模型只给了“巴黎”10%的概率。

接下来,训练系统需要把这次预测的质量压缩成一个数字:

模型这一次究竟错得有多严重?

这个数字就是 损失值(Loss)


损失函数:给模型的错误打分

语言模型训练通常会使用交叉熵损失。对于一个位置上的正确 token,可以先把它简化理解为:

$$ L = -\log P(\text{正确 token}) $$

其中,$P$ 是模型分配给正确答案的概率。

假设正确答案是“巴黎”。

当模型只给出 1% 的概率时:

$$ L = -\log(0.01) \approx 4.61 $$

损失比较大。

当模型给出 80% 的概率时:

$$ L = -\log(0.8) \approx 0.22 $$

损失比较小。

因此,可以先记住这个关系:

正确答案的概率越低 → Loss 越大
正确答案的概率越高 → Loss 越小

实际训练时,系统通常会计算一个 batch 中大量 token 的损失,再进行求和或求平均。PyTorch 的官方训练教程也把优化描述为:不断调整模型参数,使模型在每个训练步骤中的误差下降。3

于是,大语言模型训练的目标可以暂时写成:

$$ \text{寻找一组参数 } \theta,\text{使 Loss 尽可能小} $$

问题随之而来:

模型中有大量参数,我们怎么知道应该修改哪些参数?每个参数应该调大还是调小?

答案就是:梯度


梯度究竟是什么

先把真实模型缩小成一个玩具模型。假设它只有三个参数:

w₁ = 0.5
w₂ = -0.8
w₃ = 1.2

计算完当前的 Loss 后,我们想知道:

w₁ 稍微增加一点,Loss 会怎样变化?
w₂ 稍微增加一点,Loss 会怎样变化?
w₃ 稍微增加一点,Loss 会怎样变化?

数学上,这些变化率写成:

$$ \frac{\partial L}{\partial w_1}, \quad \frac{\partial L}{\partial w_2}, \quad \frac{\partial L}{\partial w_3} $$

假设计算结果是:

∂L/∂w₁ = +0.7
∂L/∂w₂ = -0.2
∂L/∂w₃ = +0.05

把所有参数对应的变化率放在一起:

$$ \nabla L = [0.7, -0.2, 0.05] $$

这个向量就是梯度

可以用程序员更容易理解的语言解释:

梯度是一份针对全部参数的局部修改信息。它告诉我们:在当前位置附近,每个参数发生微小变化时,Loss 会朝什么方向、以多快的速度变化。

对于其中一个参数:

梯度为正:
参数增大时,Loss 倾向于增大
为了让 Loss 下降,参数应该减小

梯度为负:
参数增大时,Loss 倾向于减小
为了让 Loss 下降,参数应该增大

这里有一个非常容易误解的地方:

梯度下降中的“下降”,指的是让 Loss 下降,并不是让所有参数的数值都下降。

一次更新中,有些参数可能变小,有些参数可能变大。


梯度下降:沿着能让错误减小的方向走

解释梯度下降时,最常用的是“下山”比喻。

假设你站在一座山上,但四周都是浓雾,你无法看到整座山,只能感受到脚下地面的倾斜方向。

在这个比喻中:

你当前所在的位置    → 当前模型参数
当前位置的海拔      → 当前 Loss
脚下山坡的倾斜情况  → 梯度
每一步走多远        → 学习率
较低的山谷          → 较低的 Loss

梯度指向当前位置附近上升最快的方向。

因此,沿着梯度的反方向移动,通常会让 Loss 下降:

梯度方向:局部上山方向
负梯度方向:局部下山方向

最基础的梯度下降更新公式是:

$$ \theta_{t+1} = \theta_t - \eta \nabla_\theta L $$

其中:

  • $\theta_t$:更新之前的模型参数;
  • $\nabla_\theta L$:Loss 对模型参数的梯度;
  • $\eta$:学习率;
  • $\theta_{t+1}$:更新之后的模型参数。

用代码风格表示就是:

parameters = parameters - learning_rate * gradients

这就是“梯度下降”最核心的含义:

根据梯度给出的局部方向,对参数进行一小步调整,使损失函数倾向于下降。

深度学习中的训练,通常就是把学习问题转换成一个优化问题,再利用梯度信息寻找能够降低损失的参数。4


用一个只有一个参数的例子算一次

假设模型只有一个参数 $w$,损失函数是:

$$ L(w) = (w - 3)^2 $$

显然,当:

$$ w = 3 $$

损失最低,为 0。

但模型一开始并不知道答案是 3。假设它从下面的位置开始:

$$ w = 0 $$

此时损失为:

$$ L(0) = (0 - 3)^2 = 9 $$

损失函数对 $w$ 的梯度是:

$$ \frac{dL}{dw} = 2(w - 3) $$

代入 $w=0$:

$$ \frac{dL}{dw} = -6 $$

假设学习率为:

$$ \eta = 0.1 $$

执行一次梯度下降:

$$ \begin{aligned} w_{\text{new}} &= w - \eta \frac{dL}{dw} \\ &= 0 - 0.1 \times (-6) \\ &= 0.6 \end{aligned} $$

更新后的损失变成:

$$ L(0.6) = (0.6 - 3)^2 = 5.76 $$

Loss 从 9 下降到了 5.76。

下一次继续计算梯度并更新:

0
→ 0.6
→ 1.08
→ 1.464
→ 1.7712
→ ……
→ 逐渐接近 3

模型并没有突然“理解正确答案是 3”。

它只是不断重复:

查看当前位置的坡度
→ 沿着下坡方向走一小步
→ 到新位置后重新计算坡度

真实的大语言模型不是只有一个参数,而是在极高维的参数空间里同时调整大量参数,但核心思想相同。


反向传播和梯度下降不是同一件事

这两个术语经常一起出现,所以特别容易混淆。

可以把一次训练步骤拆成四个阶段。

前向传播:模型先做一次预测

logits = model(input_tokens)

输入 token 经过模型的各层计算,最终得到每个位置上候选 token 的预测分数。

损失函数:判断这次预测有多差

loss = cross_entropy(logits, target_tokens)

损失函数把预测结果和正确 token 进行比较,得到一个 Loss。

反向传播:计算每个参数对 Loss 的影响

loss.backward()

反向传播从最终 Loss 开始,沿着计算图反向经过模型的各层,利用链式法则计算:

$$ \frac{\partial L}{\partial \theta} $$

也就是 Loss 对全部可训练参数的梯度。

优化器更新:真正修改参数

optimizer.step()

优化器读取刚刚计算出的梯度,并依据某种更新规则修改参数。

PyTorch 官方资料把 autograd 描述为支持神经网络训练的自动微分引擎,它能够在计算图上自动计算梯度。5

可以使用一个代码调试类比:

Loss          → 告诉你这次运行结果偏差有多大
反向传播      → 沿着调用链计算各层变量对偏差的影响
梯度          → 各个参数对应的局部修改信息
优化器更新    → 根据这些信息真正修改参数

因此,请记住:

反向传播负责计算梯度;梯度下降或其他优化算法负责使用梯度更新参数。


一个极简的大语言模型训练循环

忽略分布式训练、混合精度、显存管理、梯度累积等工程细节后,一个训练循环可以简化成:

for batch in training_data:
    # 清除上一个训练步骤留下的梯度
    optimizer.zero_grad()

    # 前向传播:预测各个位置的下一个 token
    logits = model(batch.input_tokens)

    # 计算预测与正确 token 之间的差距
    loss = cross_entropy(logits, batch.target_tokens)

    # 反向传播:计算 Loss 对所有参数的梯度
    loss.backward()

    # 优化器依据梯度修改参数
    optimizer.step()

其中最核心的循环是:

预测
→ 计算 Loss
→ 计算梯度
→ 更新参数

PyTorch 官方优化教程给出的标准训练流程同样包括清空梯度、反向传播和优化器更新。3

一次更新完成后,模型读取下一个 batch,再执行一遍相同过程。

真实的大规模训练系统会在许多加速设备上并行处理数据,并加入大量容错、通信和数值稳定性设计;但底层仍然是在高效、稳定地重复这个数学循环。


为什么训练时要使用 mini-batch

理论上,我们可以在每次更新参数之前,先读取整个训练集,计算一个覆盖全部数据的平均梯度。

但对于大规模训练,这样做的成本极高:

遍历全部训练数据
→ 完整计算一次平均梯度
→ 只更新一次参数

实际训练通常把数据分成许多小批次,也就是 mini-batch

训练数据
├── batch 1
├── batch 2
├── batch 3
├── batch 4
└── ……

每次只根据一个 batch 估算梯度,然后进行一次参数更新。

这有点像你想了解线上系统的平均响应时间,但不会在每次分析时都重新扫描历史上的全部请求,而是从一批请求样本中进行估计。

mini-batch 计算出来的梯度不是整个训练集的精确平均梯度,因此带有一定噪声;但它的计算成本低得多,而且可以快速进行大量更新。深度学习教材将这种从训练数据中抽取小批量样本、用其估计梯度的方法作为神经网络优化的基本实践。4

这类方法通常统称为:

随机梯度下降(Stochastic Gradient Descent,SGD)。

在严格的术语中,单样本更新和 mini-batch 更新有所区别;但在深度学习工程语境中,只要每次使用训练集的一部分来估计梯度,通常都会被归入广义的随机梯度方法。


学习率决定每一步走多远

更新公式中的 $\eta$ 就是学习率

它相当于下山时的步幅。

学习率太大

模型可能一步跨过山谷:

左侧山坡
→ 跨过最低点
→ 跳到右侧山坡
→ 再跳回来

这时 Loss 可能剧烈波动,甚至越来越大,训练可能发散。

学习率太小

每一步都很谨慎,但训练速度可能非常慢:

进行了很多次更新
→ 参数只发生极小变化
→ Loss 下降得非常缓慢

因此,真实训练通常不会从头到尾使用完全相同的学习率,而会使用学习率调度策略,例如:

  • 训练初期逐步提高学习率;
  • 中后期逐渐降低学习率;
  • 在特定阶段改变衰减速度。

可以把它理解成:

刚开始先小步试探
→ 中间加快移动
→ 接近较好区域后逐渐放慢

学习率不是一个无关紧要的配置项。它直接影响训练速度、稳定性以及最终模型质量。


为什么真实训练还需要优化器

最朴素的随机梯度下降更新方式是:

parameter -= learning_rate * gradient

但真实模型中,不同参数的梯度可能具有不同的尺度、噪声和变化历史。如果所有参数都只机械地按照当前梯度移动,训练可能效率较低或不够稳定。

因此,工程实践中通常会使用更复杂的优化器,例如:

  • SGD with Momentum;
  • Adam;
  • AdamW。

可以把普通梯度下降想象成:

只看脚下这一刻的坡度,立刻决定下一步。

而带动量的方法还会参考最近一段时间的方向,避免因为局部噪声频繁左右摇摆。

Adam 则会维护梯度的一阶矩和二阶矩估计,用于自适应地调节不同参数的更新尺度。Adam 原始论文把它描述为一种面向随机目标函数的一阶梯度优化算法,并使用自适应矩估计。6

需要注意:

优化器不是梯度下降的对立概念,而是对“如何利用梯度更新参数”这件事的具体实现与改进。

无论使用朴素 SGD、Momentum、Adam 还是 AdamW,核心仍然是:

计算梯度
→ 使用梯度决定参数更新
→ 尝试降低训练目标

不同模型、不同训练阶段和不同团队的具体优化配方可能不同,而且商业模型通常不会公开完整细节。GPT-4 技术报告也明确说明,它没有披露完整的模型规模、硬件、训练计算量、数据构建和具体训练方法。2


只预测下一个 token,为什么会产生复杂能力

这是大语言模型最令人惊讶的地方。

预训练任务看起来非常简单:

根据前面的 token
预测后面的 token

但为了在极其多样的文本中持续提高预测准确率,模型必须利用大量规律。

例如,要预测下面代码的后续内容:

for item in items:

模型需要学到:

  • Python 的语法形式;
  • 缩进和代码块结构;
  • 循环体的常见写法;
  • 变量名与上下文之间可能存在的关系。

要续写:

HTTP 状态码 404 通常表示

模型需要学到技术文档中有关 HTTP 的统计规律。

要续写:

因为前两个条件已经成立,所以根据……

模型需要追踪上下文中的条件、结论和常见推导形式。

这些能力不是通过一个个 if-else 手工写入模型的,而是在大量训练样本与大量梯度更新中,逐渐编码进参数之间的关系。

GPT-3 论文展示了:扩大自回归语言模型的规模,可以显著提高其在多种任务上的少样本表现,包括翻译、问答、完形填空以及一些推理或领域适应任务。7

不过,这里需要保持准确:

模型在语言任务中表现出复杂能力,并不等于它以与人类完全相同的方式理解世界。

“模型究竟形成了什么类型的内部表征”仍然是一个需要持续研究的问题。


预训练完成后,为什么还需要后训练

只进行下一个 token 预测,模型主要学到的是:

在类似文本中,什么内容通常会接在后面。

但用户真正需要的是:

正确理解指令,并给出有帮助、符合要求且尽可能安全的回答。

这两个目标并不完全相同。

互联网和其他文本数据中可能包含:

  • 问题后面没有答案;
  • 事实错误;
  • 争吵、攻击或不安全内容;
  • 冗长而低质量的表达;
  • 不符合“助手”角色的文本。

所以,预训练模型通常还需要进行后训练。

一个公开的经典流程来自 InstructGPT:

预训练模型
使用人工示范进行监督微调
根据人工排序训练奖励模型
利用强化学习进一步优化模型行为

InstructGPT 论文明确指出:“预测互联网网页中的下一个 token”和“有帮助且安全地遵循用户指令”是不同的目标。该工作先使用人工示范进行监督微调,再收集人工对模型输出的排序,并使用人类反馈进行进一步训练。8

虽然后训练的数据和目标会发生变化,但许多训练步骤仍然可以抽象为:

模型产生输出
→ 计算某种训练目标或损失
→ 反向传播计算梯度
→ 优化器更新参数

因此,梯度方法不仅是预训练的基础,也是许多监督微调、奖励模型训练和偏好优化方法背后的基础工具。


梯度下降不代表模型知道自己错了

这是理解模型训练时非常重要的一点。

模型不会像人一样产生这样的想法:

刚才我把法国首都答成了伦敦。
这是一个地理知识错误。
以后我应该记住正确答案是巴黎。

训练系统首先得到的是一个数值:

Loss = 4.61

反向传播再把这个总误差转换成各个参数对应的梯度:

parameter_1.grad = ...
parameter_2.grad = ...
parameter_3.grad = ...

优化器根据这些梯度完成一次数值更新。

因此:

模型不是直接学习人类抽象意义上的“正确”和“错误”,而是在优化训练者为它定义的数学目标。

这会带来一个重要结论:

训练 Loss 下降
模型在所有真实场景中都一定更好

例如:

  • 数据质量差,模型可能学到错误模式;
  • 训练数据与真实使用场景差异很大,模型可能泛化不好;
  • 损失函数没有覆盖某项需求,模型就未必会自动获得该能力;
  • 只优化“让人喜欢”,不一定等于优化“事实正确”。

所以,大模型训练不仅需要优化算法,还需要:

  • 高质量的数据;
  • 合理的训练目标;
  • 独立的评估集;
  • 安全与可靠性测试;
  • 持续的错误分析。

梯度下降也看不到整座山

梯度只能告诉模型:

在当前参数附近,哪个方向可能让 Loss 下降。

它不会一次性获得整个损失空间的完整地图,也不知道全局最低点位于哪里。

还是使用浓雾下山的比喻:

看不到整座山
→ 只能感受脚下坡度
→ 沿当前下坡方向走一步
→ 到新位置后重新判断

真实神经网络的损失空间是高维、复杂并且通常非凸的,可能包含:

  • 平坦区域;
  • 陡峭区域;
  • 狭长谷地;
  • 鞍点;
  • mini-batch 带来的梯度噪声。

因此,真实训练会结合多种工程手段提高效率和稳定性,例如:

  • 动量;
  • 自适应学习率;
  • 学习率调度;
  • 梯度裁剪;
  • 权重衰减;
  • 归一化;
  • 更合适的参数初始化。

深度学习优化教材对神经网络训练中的病态曲率、局部结构、鞍点、随机梯度和自适应优化方法进行了系统讨论。4

梯度下降并不保证:

  • 每一次 mini-batch 更新都让总体 Loss 下降;
  • 所有评估指标同时提高;
  • 一定找到数学意义上的全局最优解。

它真正提供的是:

不必在庞大的参数空间中盲目尝试所有方向,而可以利用局部导数得到一个通常有用的更新方向。


把完整训练过程串起来

现在可以把大语言模型的基础训练过程串成一条流水线:

原始文本
转换成 tokens
构造“预测下一个 token”的训练目标
模型进行前向传播
输出候选 token 的分数与概率
损失函数衡量预测误差
反向传播计算各参数的梯度
优化器依据梯度更新参数
读取下一批数据并重复

经过大量迭代后,通常会出现下面的趋势:

正确 token 获得的概率逐渐提高
→ 语言建模 Loss 逐渐下降
→ 参数逐渐形成可复用的语言与知识模式

随后,模型还可以经过监督微调、偏好训练、安全训练和其他后训练流程,使它更适合作为面向用户的助手。

从最底层看,大语言模型的训练仍然是同一个循环:

猜一次
→ 算错多少
→ 计算各参数应该如何调整
→ 修改一点
→ 再猜一次

最后只需要记住四个概念

参数(Parameters)

模型内部可以通过训练修改的大量数值。

损失(Loss)

衡量模型当前预测与训练目标相差多远的数字。

梯度(Gradient)

描述在当前位置附近,各个参数发生微小变化时,Loss 会如何变化。

学习率(Learning Rate)

决定一次参数更新迈多大步。

最后,可以用一句话定义梯度下降:

梯度下降是一类利用损失函数对模型参数的梯度,反复小幅调整参数,从而使训练误差逐渐降低的优化方法。

大语言模型之所以能够变得强大,不是因为某一次更新特别聪明,而是因为这个相对简单的循环,在庞大的模型、丰富的数据和大规模计算上,被稳定地执行了非常多次。


一张速查图

输入 token
前向传播
预测下一个 token 的概率
计算 Loss
反向传播
得到各参数的梯度
优化器更新参数
   └──────────────► 下一批训练数据

参考资料


资料核对日期:2026-07-26。