跳转至

第 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

训练时,我们做的事情其实非常固定:

  1. 把输入 x 喂给模型
  2. 模型给出预测 \( \hat{y} \)
  3. 用一个损失函数比较 \( \hat{y} \)\( y \)
  4. 根据损失对模型参数求梯度
  5. 用优化器更新参数
  6. 重复很多轮,直到模型效果变好

这就是监督学习最核心的训练闭环。

一个二分类直觉例子

假设我们做一个最简单的文本分类任务:

输入一句评论,判断它是正面还是负面。

例如:

  • 输入:"this movie is great"
  • 标签:positive

模型输出可能是:

  • positive: 0.8
  • negative: 0.2

如果真实标签是 positive,那这次预测还不错,因为正确类别概率很高。

但如果模型输出是:

  • positive: 0.1
  • negative: 0.9

那就说明模型不仅错了,而且错得很自信,这种情况对应的 loss 就应该更大。

这一点非常重要,因为后面语言模型做的事,本质上也差不多:

不是判断“正面还是负面”,而是在很多 token 里判断“下一个 token 应该是哪一个”。大模型的本质就是在做这种“接龙游戏”。

3. 从分类问题到下一个 token 预测

很多初学者会觉得语言模型和分类任务差得很远,其实它们非常接近。

普通分类任务

给定输入 x,从有限个类别里选一个:

  • 猫 / 狗
  • 正面 / 负面
  • 垃圾邮件 / 正常邮件

语言模型任务

给定前文 x,从词表里的所有 token 中选一个最可能的下一个 token。

例如句子:

I love deep

模型要预测下一个 token,可能输出:

  • learning: 0.72
  • blue: 0.08
  • sleeping: 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 \),那么单个样本的负对数似然可以写成:

\[ \mathcal{L} = -\log p_k \]

这件事的直觉非常简单:

  • 如果 \( p_k \) 很大,比如 0.9,那么 loss 很小
  • 如果 \( p_k \) 很小,比如 0.01,那么 loss 很大

也就是说,模型越相信正确答案,损失越小。

为什么要取负对数

这里不用一开始把信息论背景讲得太重,先抓住两个性质就够了:

  1. 概率越大,loss 越小
  2. 对“自信但错误”的预测惩罚更重

举个例子:

  • 如果正确类别概率是 0.9,那么 -\log 0.9 很小
  • 如果正确类别概率是 0.1,那么 -\log 0.1 会大很多

这正好符合训练需求。

Cross Entropy 和 NLL 的关系

在 one-hot 标签场景下,Cross Entropy 可以看成对正确类别概率做 Negative Log Likelihood。

实际工程里你会经常看到:

  • softmax
  • log_softmax
  • cross_entropy
  • nll_loss

它们在数学上是紧密相关的。

对当前阶段来说,你先记住最关键的一点:

语言模型训练时,我们希望真实 token 的概率越来越高,而 Cross Entropy 正好能把这个目标直接变成可优化的 loss。

5. 梯度下降:知道错了以后怎么改

光知道模型错了还不够,我们还得知道参数该怎么更新。

这就进入了优化问题。

参数更新的目标

假设模型有一组参数 \( \theta \),损失函数记为:

\[ \mathcal{L}(\theta) \]

训练的目标就是找到一组更好的参数,让 loss 更小。

最基本的更新方式是梯度下降:

\[ \theta \leftarrow \theta - \eta \nabla_\theta \mathcal{L} \]

其中:

  • \( \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 代码里经常出现下面三步:

loss.backward()
optimizer.step()
optimizer.zero_grad()

它们分别对应:

  • 计算梯度
  • 更新参数
  • 清空上一轮残留梯度

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. 用一张流程图串起训练闭环

到这里,我们可以把最小训练流程压缩成一张心智图:

  1. 准备数据:输入 x 和标签 y
  2. 前向传播:模型输出预测结果
  3. 计算损失:比较预测和真实标签
  4. 反向传播:计算每个参数的梯度
  5. 参数更新:优化器根据梯度更新参数
  6. 在验证集上评估:判断是否真的变好

后面你学到的绝大多数训练代码,基本都可以映射到这六步。

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) 产生每个类别的 logits
  • CrossEntropyLoss 会把 logits 和标签比较,计算 loss
  • loss.backward() 自动计算所有参数梯度
  • optimizer.step() 根据梯度更新参数

这就是一个完整但极小的训练闭环。

后面第二章会把这件事扩展到更标准的 PyTorch 训练流程,包括:

  • Dataset
  • DataLoader
  • train / 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 与标准训练循环。