训练技巧与策略
问题
深度学习训练中有哪些关键技巧?Batch Normalization 和 Layer Normalization 有什么区别?Dropout 的原理是什么?混合精度训练如何加速?
答案
深度学习模型能否训好,很大程度取决于训练策略。本文汇总面试中常问的核心训练技巧。
一、归一化(Normalization)
归一化的目的:稳定训练、加速收敛、减少对初始化的敏感度。
Batch Normalization(BN)
对同一 batch 中所有样本的同一特征维度归一化:
- :当前 mini-batch 的均值和方差
- :可学习的缩放和偏移参数
Layer Normalization(LN)
对单个样本的所有特征归一化(与 batch 大小无关):
| 归一化 | 归一化维度 | 依赖 batch | 适用场景 |
|---|---|---|---|
| Batch Norm | 跨样本,同一特征 | ✅ | CNN(图像) |
| Layer Norm | 跨特征,同一样本 | ❌ | Transformer / NLP |
| Group Norm | 跨通道分组 | ❌ | 小 batch 的 CNN |
| RMS Norm | LN 的简化版(去掉均值) | ❌ | LLaMA、现代 LLM |
为什么 Transformer 用 LN 而不是 BN?
- NLP 中序列长度不固定,BN 难以在不同长度上计算统计量
- 推理时 batch size 可能为 1,BN 需要维护 running mean/var
- LN 对每个样本独立归一化,更适合变长序列
RMSNorm(Root Mean Square Normalization)
LLaMA 等现代 LLM 使用 RMSNorm——去掉了均值中心化,只做方差归一化:
比 LN 少一次均值计算,速度更快,效果相当。
二、正则化
Dropout
训练时随机丢弃一定比例的神经元(输出置零),迫使网络不依赖任何单个神经元:
# PyTorch 中的 Dropout
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(768, 256)
self.dropout = nn.Dropout(p=0.1) # 10% 的神经元被丢弃
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = self.dropout(x) # 训练时随机丢弃,推理时自动关闭
return self.fc2(x)
关键细节
- 训练时:以概率 将激活设为 0,并将剩余激活除以 保持期望不变(Inverted Dropout)
- 推理时:不做任何 Dropout,使用完整网络
其他正则化技巧
| 方法 | 原理 | 典型场景 |
|---|---|---|
| L2 正则化(Weight Decay) | 惩罚大权重 | 所有模型 |
| Label Smoothing | 软标签替代 hard 0/1 | 分类任务 |
| 数据增强 | 增加训练数据多样性 | CV(翻转、裁剪)、NLP(回译) |
| Early Stopping | 验证集指标不提升时停止 | 防过拟合 |
三、混合精度训练
使用 FP16(半精度)代替 FP32(全精度)进行前向和反向传播,速度翻倍、显存减半。
为什么不全用 FP16?
FP16 的表示范围小(最大 65504),容易出现:
- 溢出:梯度/损失值太大
- 下溢:梯度太小变成 0
Loss Scaling 技巧
将 Loss 乘以一个大系数(如 1024),使梯度放大到 FP16 可表示的范围,更新权重时再除回来:
# PyTorch AMP(Automatic Mixed Precision)
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for data, target in dataloader:
optimizer.zero_grad()
# 前向传播用 FP16
with autocast():
output = model(data)
loss = criterion(output, target)
# 反向传播:自动 Loss Scaling
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
精度类型对比
| 类型 | 位数 | 范围 | 用途 |
|---|---|---|---|
| FP32 | 32 bit | 权重存储 | |
| FP16 | 16 bit | 前向/反向计算 | |
| BF16 | 16 bit | 现代 LLM 训练首选 | |
| FP8 | 8 bit | -- | 推理、部分训练 |
BF16 vs FP16
- BF16 范围和 FP32 一样大(8 bit 指数位),但精度低(7 bit 尾数)
- FP16 精度更高(10 bit 尾数),但范围小,容易溢出
- 现代 GPU(A100、H100)原生支持 BF16,LLM 训练首选 BF16
四、梯度累积
当 batch size 受显存限制时,可以用梯度累积模拟大 batch:
accumulation_steps = 4 # 相当于 batch size × 4
for i, (data, target) in enumerate(dataloader):
with autocast():
output = model(data)
loss = criterion(output, target) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
五、Warmup 与学习率调度
现代训练通常使用 Warmup + Cosine Decay:
- Warmup(前 1-5% 步):学习率从 0 线性增长到目标值
- Cosine Decay:余弦曲线从目标值衰减到最小值
from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR, SequentialLR
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)
# 前 1000 步 warmup,之后余弦衰减
warmup = LinearLR(optimizer, start_factor=0.01, total_iters=1000)
cosine = CosineAnnealingLR(optimizer, T_max=50000, eta_min=1e-5)
scheduler = SequentialLR(optimizer, [warmup, cosine], milestones=[1000])
六、梯度裁剪
防止梯度爆炸——限制梯度的最大范数:
# 全局梯度裁剪(按范数)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
LLM 训练中通常将 max_norm 设为 1.0,这是标准做法。
常见面试问题
Q1: Batch Normalization 在训练和推理时的行为有什么不同?
答案:
- 训练时:使用当前 mini-batch 的均值和方差归一化,同时更新 running mean 和 running var(指数移动平均)
- 推理时:使用训练过程中积累的 running mean 和 running var(固定值),不依赖当前 batch
Q2: 为什么 Dropout 能防止过拟合?
答案: 两个角度解释:
- 集成学习角度:每次随机丢弃不同神经元,相当于训练了指数级数量的子网络,推理时是所有子网络的集成
- 特征冗余角度:强迫每个神经元独立有用,不能依赖特定的"协作伙伴",学到更鲁棒的特征
Q3: 什么是梯度消失和梯度爆炸?如何解决?
答案:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 梯度消失 | sigmoid/tanh 饱和区梯度趋零;网络太深 | ReLU 激活、残差连接、LN、合适的初始化 |
| 梯度爆炸 | 权重/梯度连乘导致指数增长 | 梯度裁剪、权重正则化、BN/LN |
Q4: 混合精度训练的原理是什么?
答案:
核心思路:前向计算和梯度用 FP16/BF16(速度快、显存省),权重拷贝用 FP32(保持精度)。Loss Scaling 防止梯度下溢——先放大 Loss 使梯度变大,更新权重时再缩回。PyTorch 的 AMP(autocast + GradScaler)会自动处理这些细节。