第 1 章:机器学习与深度学习最小必要基础¶
本章定位¶
如果你问一个LLM从业者如何学习大模型,你得到的答案肯定是直接看 Transformer、RAG、Agent 或微调框架吧啦吧啦,最好看原汁原味的英文论文,嘿这才叫一个地道儿。
这当然没问题,前提是你有厚实的机器学习基础,但如果底层这几个概念不够稳,后面很容易出现一种状态:
- 内容完全不进脑子,不一会就被各种各样的概念搞晕,自注意力什么?自什么回归?什么注意力回归?
- 代码能跑,但不知道为什么这样写
- 知道
loss.backward()要调用,但不清楚它到底做了什么 - 会用 AdamW,但说不清它和 SGD 的区别
- 看到训练集 loss 在降很开心,却不知道模型可能已经过拟合
所以这一章不追求完整的概念,而是只做一件事:
这么多不同的机器学习任务,本质都是在做一件事儿:
数据 -> 模型 -> 预测 -> loss -> 梯度 -> 参数更新 -> 验证
如果这一章吃透,后面你会发现很多“新东西”其实只是这条链路在更大规模上的重复。
你学完后应该会什么¶
- 能用自己的话解释监督学习的基本训练流程
- 能理解为什么语言模型训练本质上也是一个分类问题
- 能看懂 Cross Entropy、梯度下降、反向传播的最小公式
- 能区分 SGD、Adam、AdamW 的核心直觉
- 能理解过拟合、泛化、验证集、Early Stopping 的作用
- 能看懂一个最小的 PyTorch 训练例子
1. 为什么学 LLM 之前要先补这些基础¶
从表面上看,大模型领域有很多新名词:
- Prompt Engineering
- RAG
- LoRA
- RLHF
- Agent
- Tool Calling
但如果往下拆,你会发现只要涉及训练、微调、评估,最后都会回到几个最基本的问题:
- 模型输入是什么
- 模型输出是什么
- 我们如何定义“预测得好不好”
- 如果预测不好,参数应该往哪个方向改
- 改完以后,模型对没见过的数据会不会更好
这就是机器学习乃至深度学习的基本问题。
可以把它理解成:后面所有高级模块,都是建立在这个最小训练闭环之上的工程放大版。
例如:
- 预训练是在海量文本上重复这个闭环
- SFT 是在更贴近指令场景的数据上重复这个闭环
- LoRA 是用更省参数的方式完成这个闭环
- LLM 评估是在闭环之外验证模型有没有真的变好
所以这一章的目标不是把你训练成机器学习研究员,而是让你拥有继续往下走所需的“最低但足够”的地基。
2. 监督学习的最小闭环¶
先看一个最朴素的监督学习流程。
假设我们手里有一批样本,每条样本都包含:
- 输入
x - 正确答案
y
训练时,我们做的事情其实非常固定:
- 把输入
x喂给模型 - 模型给出预测 \( \hat{y} \)
- 用一个损失函数比较 \( \hat{y} \) 和 \( y \)
- 根据损失对模型参数求梯度
- 用优化器更新参数
- 重复很多轮,直到模型效果变好
这就是监督学习最核心的训练闭环。
一个二分类直觉例子¶
假设我们做一个最简单的文本分类任务:
输入一句评论,判断它是正面还是负面。
例如:
- 输入:
"this movie is great" - 标签:
positive
模型输出可能是:
positive: 0.8negative: 0.2
如果真实标签是 positive,那这次预测还不错,因为正确类别概率很高。
但如果模型输出是:
positive: 0.1negative: 0.9
那就说明模型不仅错了,而且错得很自信,这种情况对应的 loss 就应该更大。
这一点非常重要,因为后面语言模型做的事,本质上也差不多:
不是判断“正面还是负面”,而是在很多 token 里判断“下一个 token 应该是哪一个”。大模型的本质就是在做这种“接龙游戏”。
3. 从分类问题到下一个 token 预测¶
很多初学者会觉得语言模型和分类任务差得很远,其实它们非常接近。
普通分类任务¶
给定输入 x,从有限个类别里选一个:
- 猫 / 狗
- 正面 / 负面
- 垃圾邮件 / 正常邮件
语言模型任务¶
给定前文 x,从词表里的所有 token 中选一个最可能的下一个 token。
例如句子:
I love deep
模型要预测下一个 token,可能输出:
learning: 0.72blue: 0.08sleeping: 0.03- ...
如果真实下一个 token 是 learning,那训练目标就是让模型给 learning 更高概率。
所以,语言模型训练可以理解为一种“超大类别数的分类问题”:
- 输入:前文 token 序列
- 输出:词表上每个 token 的概率分布
- 标签:真实下一个 token
理解了这一点,后面你会更自然地接受:
- 为什么要有 logits
- 为什么要做 softmax
- 为什么 loss 通常是 cross entropy
4. Loss:模型到底哪里做错了¶
训练的关键在于:模型做错时,我们必须把“错得有多严重”量化出来。
这个量化结果,就是损失函数。
为什么需要损失函数¶
如果只说“这次预测对了”或“这次预测错了”,信息太少。
比如下面两种情况虽然都错了,但严重程度不同:
- 正确答案概率是
0.49 - 正确答案概率是
0.001
第二种说明模型错得更离谱,训练时应该受到更大惩罚。
损失函数的作用,就是把这种差异表达出来。
语言模型里最常见的损失:Cross Entropy¶
对于分类问题,我们最常用的损失函数之一是 Cross Entropy。
如果真实类别是第 \( k \) 类,模型给这个类别分配的概率是 \( p_k \),那么单个样本的负对数似然可以写成:
这件事的直觉非常简单:
- 如果 \( p_k \) 很大,比如
0.9,那么 loss 很小 - 如果 \( p_k \) 很小,比如
0.01,那么 loss 很大
也就是说,模型越相信正确答案,损失越小。
为什么要取负对数¶
这里不用一开始把信息论背景讲得太重,先抓住两个性质就够了:
- 概率越大,loss 越小
- 对“自信但错误”的预测惩罚更重
举个例子:
- 如果正确类别概率是
0.9,那么-\log 0.9很小 - 如果正确类别概率是
0.1,那么-\log 0.1会大很多
这正好符合训练需求。
Cross Entropy 和 NLL 的关系¶
在 one-hot 标签场景下,Cross Entropy 可以看成对正确类别概率做 Negative Log Likelihood。
实际工程里你会经常看到:
softmaxlog_softmaxcross_entropynll_loss
它们在数学上是紧密相关的。
对当前阶段来说,你先记住最关键的一点:
语言模型训练时,我们希望真实 token 的概率越来越高,而 Cross Entropy 正好能把这个目标直接变成可优化的 loss。
5. 梯度下降:知道错了以后怎么改¶
光知道模型错了还不够,我们还得知道参数该怎么更新。
这就进入了优化问题。
参数更新的目标¶
假设模型有一组参数 \( \theta \),损失函数记为:
训练的目标就是找到一组更好的参数,让 loss 更小。
最基本的更新方式是梯度下降:
其中:
- \( \theta \) 是模型参数
- \( \eta \) 是学习率
- \( \nabla_\theta \mathcal{L} \) 是 loss 对参数的梯度
这个公式可以先不从数学推导入手,先从直觉理解:
梯度告诉我们,“如果想让 loss 下降,参数应该往哪个方向调”。
一个山坡类比¶
可以把 loss surface 想象成一片起伏地形。
- 你当前站在某个位置
- 海拔高度就是 loss
- 梯度告诉你当前位置最陡的上升方向
- 那么负梯度方向,就是最快下降的方向
训练时,优化器做的事,就是一小步一小步地沿着“下降方向”移动参数。
6. Forward、Backward、Gradient 分别在做什么¶
这是很多人第一次接触深度学习时最容易卡住的地方。
你可以把一次训练迭代拆成三个动作。
1. Forward¶
前向传播做的事是:
- 输入数据
- 经过模型计算
- 得到预测结果
- 计算 loss
也就是:
x -> model(x) -> prediction -> loss
2. Backward¶
反向传播做的事是:
- 从 loss 出发
- 根据链式法则
- 逐层计算每个参数对 loss 的影响
最终得到每个参数的梯度。
3. Gradient / Step¶
有了梯度以后,优化器才能更新参数:
- 哪些参数该增大
- 哪些参数该减小
- 每次改多少
这也是为什么 PyTorch 代码里经常出现下面三步:
它们分别对应:
- 计算梯度
- 更新参数
- 清空上一轮残留梯度
7. 常见优化器:SGD、Adam、AdamW¶
理解优化器时,建议不要死记公式,先抓“它想解决什么问题”。
SGD¶
随机梯度下降是最基础的优化方法。
它的思路很直接:
- 算出当前 batch 的梯度
- 沿负梯度方向更新参数
优点:
- 简单
- 直观
- 是很多优化方法的基础
缺点:
- 对学习率比较敏感
- 在复杂损失面上可能震荡明显
- 不同参数维度使用同一更新尺度,适应性较差
Adam¶
Adam 可以理解成一种“带自适应学习率和动量”的优化器。
直觉上,它会做两件事:
- 参考历史梯度方向,减少来回震荡
- 针对不同参数使用不同更新尺度
因此 Adam 通常:
- 更容易训起来
- 对初学者更友好
- 在很多深度学习任务上是默认起点
AdamW¶
AdamW 可以看成是对 Adam 的一个重要工程改进。
它把权重衰减(weight decay)和梯度更新更明确地分开处理,因此在训练 Transformer 和大模型时更常见。
一个实用结论是:
- 入门时理解 SGD 和 Adam 的差别
- 做现代 LLM 训练时,通常更常见的是 AdamW
8. 过拟合与泛化:训练 loss 降了不代表真的更好了¶
这是第一章必须建立的另一个核心观念。
什么是泛化¶
机器学习不是为了把训练集背下来,而是希望模型在没见过的新样本上也表现不错。
这种能力叫泛化能力。
什么是过拟合¶
过拟合指的是:
- 模型在训练集上表现越来越好
- 但在验证集或测试集上表现没有同步变好,甚至变差
这通常意味着模型学到的不只是规律,还有训练数据里的噪声或偶然性。
一个常见信号¶
训练过程中,如果你看到:
- train loss 持续下降
- val loss 先下降后上升
那往往就是过拟合信号。
所以训练时不能只看训练集指标,还要看验证集指标。
为什么 LLM 里也一样重要¶
大模型虽然规模更大,但并没有跳出这个逻辑。
无论是:
- 预训练
- 指令微调
- 分类微调
- 检索增强后的评测
本质上都在问同一个问题:
模型是在真正学到可迁移能力,还是只是更贴合当前训练数据?
9. 常见应对手段:正则化、Weight Decay、Early Stopping¶
当模型开始过拟合时,我们通常会使用一些控制手段。
正则化¶
正则化的核心思想是:
不要让模型为了迎合训练集而变得过于复杂或过于极端。
常见方式包括:
- L2 正则
- Dropout
- 数据增强
- 权重衰减
Weight Decay¶
Weight decay 可以粗略理解成:
在训练过程中,轻微惩罚过大的参数值,避免参数无限膨胀。
在现代深度学习训练里,它经常和 AdamW 一起出现。
Early Stopping¶
Early stopping 的思路非常朴素:
- 训练集上继续训练可能还能降 loss
- 但验证集指标已经不再改善
- 那就应该提前停止
它本质上是在防止模型继续记忆训练数据细节。
10. 用一张流程图串起训练闭环¶
到这里,我们可以把最小训练流程压缩成一张心智图:
- 准备数据:输入
x和标签y - 前向传播:模型输出预测结果
- 计算损失:比较预测和真实标签
- 反向传播:计算每个参数的梯度
- 参数更新:优化器根据梯度更新参数
- 在验证集上评估:判断是否真的变好
后面你学到的绝大多数训练代码,基本都可以映射到这六步。
11. 一个最小 PyTorch 例子¶
下面用一个最小的分类例子,把前面的概念连起来。
这个例子不追求效果多强,只追求让训练闭环清晰可见。
import torch
import torch.nn as nn
import torch.optim as optim
# 4 条样本,每条有 2 个特征
x = torch.tensor([
[0.0, 0.0],
[0.0, 1.0],
[1.0, 0.0],
[1.0, 1.0],
], dtype=torch.float32)
# 二分类标签
y = torch.tensor([0, 1, 1, 1], dtype=torch.long)
model = nn.Sequential(
nn.Linear(2, 8),
nn.ReLU(),
nn.Linear(8, 2),
)
criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(model.parameters(), lr=1e-2)
for step in range(200):
logits = model(x) # forward
loss = criterion(logits, y) # compute loss
optimizer.zero_grad()
loss.backward() # backward
optimizer.step() # update
if step % 20 == 0:
print(f"step={step}, loss={loss.item():.4f}")
with torch.no_grad():
pred = model(x).argmax(dim=1)
print("prediction:", pred.tolist())
这段代码里发生了什么¶
model(x)产生每个类别的 logitsCrossEntropyLoss会把 logits 和标签比较,计算 lossloss.backward()自动计算所有参数梯度optimizer.step()根据梯度更新参数
这就是一个完整但极小的训练闭环。
后面第二章会把这件事扩展到更标准的 PyTorch 训练流程,包括:
DatasetDataLoadertrain / eval- 验证集
- checkpoint
12. 把这个例子映射到语言模型训练¶
现在把刚才的分类例子换成语言模型,会发生什么?
普通分类¶
- 输入:一个样本特征向量
- 输出:几个类别的 logits
- 标签:类别 id
语言模型¶
- 输入:前文 token 序列
- 输出:词表大小的 logits
- 标签:下一个 token 的 id
唯一真正变复杂的地方在于:
- 输入从简单向量变成序列
- 输出类别数从 2 类变成几万词表
- 模型结构从小 MLP 变成 Transformer
但训练逻辑本身并没有变。
这也是为什么你后面看到 LLM 训练代码时,不要被体量吓住。
它们在本质上,仍然是在做:
- forward
- compute loss
- backward
- optimizer step
13. 常见误区¶
误区 1:loss 降了就说明模型一定更好了¶
不一定。
如果只是训练集 loss 降了,可能只是记住了训练数据;必须结合验证集指标一起看。
误区 2:反向传播就是“模型自己知道答案了”¶
不是。
反向传播并不是模型“理解”了,而是在数学上根据损失函数计算参数变化方向。
误区 3:优化器只是调参细节,不重要¶
不对。
优化器会直接影响训练稳定性、收敛速度和最终效果,尤其在深度模型里影响很明显。
误区 4:大模型训练和普通分类任务完全不是一回事¶
不对。
大模型训练当然更复杂,但底层训练闭环是一致的。理解这一点,能大幅降低你面对 LLM 代码时的陌生感。
14. 面试问题¶
Q1:监督学习的基本训练流程是什么?¶
输入样本进入模型得到预测,使用损失函数衡量预测和标签的差距,通过反向传播计算梯度,再由优化器更新参数,并在验证集上检查泛化效果。
Q2:为什么语言模型训练常用 Cross Entropy?¶
因为语言模型本质上是在词表中预测正确的下一个 token,属于分类问题;Cross Entropy 能直接衡量模型给真实 token 分配的概率是否足够高。
Q3:loss.backward() 做了什么?¶
它根据当前计算图,从 loss 出发按链式法则计算每个可训练参数的梯度,并把结果写入参数的 .grad 中,供优化器更新使用。
Q4:Adam 和 AdamW 的区别可以怎么解释?¶
AdamW 可以看成对 Adam 的工程改进版本,它把权重衰减和梯度更新解耦,因此在训练 Transformer 和现代大模型时更常见。
Q5:为什么不能只看训练集指标?¶
因为机器学习真正关心的是泛化能力。只看训练集可能掩盖过拟合,导致模型对新数据表现很差。
15. 本章小结¶
这一章最重要的不是记住多少公式,而是建立一个稳定认知:
机器学习训练本质上是在不断重复下面这个闭环:
输入 -> 预测 -> loss -> 梯度 -> 参数更新 -> 验证
而语言模型训练,并没有跳出这个框架。
它只是把:
- 数据变成了文本序列
- 输出空间变成了整个词表
- 模型结构变成了更强的神经网络
- 训练规模放大到了工程级别
如果你已经能把这条主线讲清楚,后面进入 PyTorch、NLP、Attention、Transformer 时就会顺很多。
下一章我们会把这些概念正式落到代码层面,进入 PyTorch 与标准训练循环。