第二章 深度学习基础速览¶
导读¶
这一章不是要教你成为深度学习专家——而是给你**工程师视角的最小知识量**:足以让你在后续章节遇到"梯度""激活函数""反向传播"时不会卡住。如果你已经熟悉 PyTorch 与神经网络基本概念,可以快速浏览本章并跳到第三章。
2.1 张量:深度学习的"数据类型"¶
深度学习计算的基本数据结构是**张量(Tensor)**——多维数组。
| 名字 | 维度 | 例子 |
|---|---|---|
| 标量 scalar | 0 | 5 |
| 向量 vector | 1 | [1.0, 2.0, 3.0] |
| 矩阵 matrix | 2 | [[1, 2], [3, 4]] |
| 张量 tensor | 3+ | 图像 [3, 224, 224]、batch 文本 [32, 512, 768] |
对工程师来说,张量就是"高维 numpy 数组 + GPU 加速 + 自动求导"。
import torch
x = torch.randn(32, 512, 768, device="cuda") # batch=32, seq=512, hidden=768
x.requires_grad_(True) # 标记需要梯度
张量在深度学习中几乎是一切数据的载体:输入是张量、权重是张量、梯度是张量、loss 是张量。
2.2 线性变换与感知机¶
神经网络最基本的运算单元是**线性变换 + 非线性激活**:
这个最简结构叫**感知机(Perceptron)**,1958 年由 Rosenblatt 提出。
单层感知机能学线性分类(如 AND、OR),但**学不会 XOR**——这是 1969 年 Minsky 的著名批评,让神经网络研究陷入寒冬近 20 年。
解法是把多层堆起来:多层感知机(Multi-Layer Perceptron, MLP)。理论已证明:只要有足够宽的隐藏层,两层 MLP 可以逼近任意连续函数(万能逼近定理)。这就是"深度学习"的雏形——"深度"只是说层数多。
2.3 激活函数¶
如果没有非线性激活,多层线性变换等价于单层。所以激活函数是"让网络变深才有意义"的关键。
常见激活函数:
| 函数 | 公式 | 用途 |
|---|---|---|
| Sigmoid | 1/(1+e^-x) |
早期,输出 0-1 概率 |
| Tanh | (e^x-e^-x)/(e^x+e^-x) |
输出 -1 到 1 |
| ReLU | max(0, x) |
CNN、早期 Transformer,简单高效 |
| GELU | x·Φ(x) |
BERT、GPT-2 标准 |
| SwiGLU | Swish(xW1) * (xW2) |
Llama、现代 LLM 标配 |
现代 LLM 几乎都用 SwiGLU——它是 GLU(Gated Linear Unit)的变体,引入"门控"机制,比 ReLU 表现更好但参数量稍多。
2.4 反向传播:自动求导的链式法则¶
神经网络的训练核心是**反向传播(Backpropagation)**——通过链式法则从输出端反向计算每个参数的梯度。
工程师视角的关键点:
- 前向必须保存中间激活——反向求导时要用到。这就是为什么训练显存 ≈ batch × 层数 × hidden × dtype 大致成立。
- 反向遍历两次计算图——所以训练算力 ≈ 2-3 × 推理算力(前向 + 反向 + 优化器更新)。
- 更新规则:
W ← W - η · ∂L/∂W,η 是学习率(learning rate, lr)。 - 现代框架自动完成——PyTorch 的
loss.backward()+optimizer.step()自动完成所有事。
logits = model(input) # 前向
loss = cross_entropy(logits, target)
loss.backward() # 反向:自动填充所有 .grad
optimizer.step() # 更新参数
optimizer.zero_grad() # 清理梯度,准备下一步
2.5 优化器:从 SGD 到 AdamW¶
最朴素的更新方式是 SGD(Stochastic Gradient Descent):
问题:梯度有噪声、方向不平稳、不同参数收敛速度不一致。改进:
- Momentum:保留历史梯度的滑动平均,像有惯性一样冲过鞍点;
- RMSProp:对每个参数自适应学习率(按梯度平方滑动归一化);
- Adam = Momentum + RMSProp,主流选择;
- AdamW = Adam + 权重衰减解耦,当前 LLM 训练事实标准。
AdamW 公式(伪代码):
m = β1 * m + (1 - β1) * g # 一阶矩
v = β2 * v + (1 - β2) * g * g # 二阶矩
m_hat = m / (1 - β1^t) # bias 修正
v_hat = v / (1 - β2^t)
W = W - lr * m_hat / (sqrt(v_hat) + eps) - lr * weight_decay * W
典型超参:lr=3e-4(小模型)/ 2e-4(大模型)、β1=0.9、β2=0.95(LLM 不同于 Adam 默认 0.999)、weight_decay=0.1。
2.6 损失函数¶
损失函数衡量"模型输出与目标的差距"。训练就是最小化它。
- 回归:MSE(均方误差)
Σ(ŷ - y)²; - 分类:Cross-Entropy
-Σ y·log(ŷ); - LLM 预训练:也是 Cross-Entropy,但目标不是 one-hot 类别,而是"下一个 token 的真实 id"。
# LLM 预训练 loss 伪代码
logits = model(input_ids) # [B, L, V]
target = input_ids[:, 1:] # 错位一位,预测下一个
logits = logits[:, :-1, :]
loss = F.cross_entropy(logits.reshape(-1, V), target.reshape(-1))
这个 loss 看起来平平无奇,但它"训练"出了今天所有的 LLM——下一个 token 预测足够难,使得模型为了少错,必须学会语法、知识、推理。
2.7 训练循环骨架¶
for epoch in range(num_epochs):
for batch in dataloader:
input_ids = batch.input_ids.to("cuda")
labels = batch.labels.to("cuda")
logits = model(input_ids)
loss = F.cross_entropy(logits.view(-1, V), labels.view(-1))
loss.backward()
optimizer.step()
scheduler.step() # 学习率调度
optimizer.zero_grad()
if step % 100 == 0:
log({"loss": loss.item(), "lr": scheduler.get_last_lr()[0]})
工程师读这段代码时关注的是:
- 数据移动:每步要把 batch 从 CPU 拷到 GPU,是 I/O 瓶颈之一;
- 前向显存:保存中间激活用于反向;
- 通信:分布式训练时,反向完成后要做 all-reduce 同步梯度;
- 学习率调度:warmup + cosine 是 LLM 标准。
2.8 评估指标¶
训练 loss 在降,不代表模型在变好——可能过拟合。常用指标:
- 训练 loss / 验证 loss:差值大 = 过拟合;
- Accuracy:分类任务;
- Perplexity(PPL):语言模型常用,
exp(loss),越低越好; - 下游 benchmark:MMLU、HumanEval 等(第 14 章详述)。
工程实战要点¶
- 学习率是最重要的超参:宁可花时间调 lr 也不要乱调别的;
- AdamW + cosine + warmup 是 90% LLM 训练的默认配方;
- 训练显存 ≈ 模型权重 + 激活 + 优化器状态,三者比例随 batch/seq 变化;
- 梯度爆炸/消失:watch
grad_norm,正常范围 0.1-10,过大用 grad clip; - 混合精度(bf16):现代 GPU 都支持,2× 速度、显存减半,几乎是默认。
小结¶
- 张量是深度学习的基本数据结构,本质是 GPU 加速的高维数组;
- 神经元 = 线性变换 + 非线性激活;多层堆叠 = MLP;
- 反向传播通过链式法则自动求导,PyTorch 已自动化;
- 优化器从 SGD 演进到 AdamW,是当前 LLM 训练标准;
- LLM 预训练 loss 就是 next-token 的 cross-entropy;
- 训练循环 = forward + backward + step,配合 lr schedule 与混合精度。
练习题¶
- 为什么"没有非线性激活的多层线性网络等价于单层"?
- 训练算力大约是推理算力的几倍?为什么?
- AdamW 相对于 Adam 的关键改进是什么?为什么对 LLM 重要?
- 写出 LLM 预训练的 loss 表达式,并解释"为什么 next-token 预测能学到知识"。
- bf16 与 fp16 的区别?为什么现代 LLM 训练更倾向 bf16?
- 解释
loss.backward()在 PyTorch 中具体做了什么。 - 训练时如果发现
grad_norm突然飙到 1000+,可能是哪些原因?怎么处理? - 学习率 warmup 是怎么工作的?为什么 LLM 训练几乎一定要 warmup?
- 一个 7B 模型用 AdamW、batch=128、seq=4096 训练,估算大约需要多少显存。