外观
训练配方与调参
一句话定义:训练配方(training recipe)是把一组经过验证的默认选择——数据、初始化、损失、优化器、学习率、正则化——组合成一套能稳定收敛的实验起点;调参则是在这个起点上,用可测量的信号(损失、验证指标)做单变量搜索。
调参不是玄学,而是有默认值、有诊断信号、有搜索方法的工程。理论根基(梯度下降、损失函数、正则化)见优化与梯度下降与过拟合与正则化;训练中遇到"loss 不降"这类症状时的排查步骤见调试与诊断。
一、默认配方表
不知道用什么时,先照抄这张表,它覆盖了大多数视觉/文本任务的合理起点:
| 环节 | 默认选择 | 说明 |
|---|---|---|
| 数据归一化 | 均值/标准差标准化(用训练集统计量) | 输入接近 0 均值、单位方差 |
| 数据增强 | 视觉:随机裁剪+翻转;文本:随机 mask/裁剪 | 增强强度随数据量增大而提高 |
| 权重初始化 | PyTorch 默认(Kaiming 初始化 + 正确 fan 模式) | 手工初始化是大多数新手的错误来源 |
| 损失函数 | 分类 CrossEntropyLoss;回归 MSELoss/HuberLoss | 输出层要匹配,见损失函数与输出层 |
| 优化器 | 首选 Adam(lr=1e-3);CNN 可换 SGD+momentum(lr=1e-2 配 weight_decay) | Adam 对 lr 不敏感、上手稳 |
| 学习率 | 3e-4(Adam)~1e-2(SGD),再用范围测试精调 | 见下一节 |
| 批大小 | 32–256,尽量取大(2 的幂) | 与学习率联动,见"batch size 与学习率" |
| 归一化层 | BatchNorm(CV);LayerNorm(NLP/Transformer) | 见初始化与归一化 |
| 正则化 | weight_decay(Adam 配 1e-4~5e-4);Dropout 按需 | 数据量小/模型大时加重 |
| 训练轮数 | 小数据 10–50;大数据 50–200,配早停 | 以验证集早停为最终判据 |
| 学习率调度 | warmup + 余弦退火(或按验证集折半) | 见"训练周期规划" |
| 随机种子 | torch.manual_seed + np.random.seed + 环境变量 | 见"复现实验纪律" |
一句话原则
先让训练"正常过拟合",再谈正则化。如果一个模型连训练集都拟合不了(训练 loss 不下降),那问题在模型或数据,不在正则化;此时加 weight_decay 只会雪上加霜。
二、学习率怎么定:范围测试法
学习率是最重要的单超参数。过小收敛慢,过大直接发散。与其靠猜,不如做学习率范围测试(learning rate range test):让学习率在一个 epoch 内从极小值线性(或指数)升到较大值,画出"学习率 → 损失"曲线:
python
import torch, torch.nn as nn
from torch.utils.data import DataLoader
def lr_find(model, loader, criterion, optimizer_cls,
lr_min=1e-6, lr_max=1e0, steps=200):
"""在 steps 个 batch 内线性扫描学习率,返回 (lrs, losses)"""
optimizer = optimizer_cls(model.parameters(), lr=lr_min)
scheduler = torch.optim.lr_scheduler.LambdaLR(
optimizer, lambda t: lr_min * (lr_max / lr_min) ** (t / steps))
lrs, losses = [], []
for i, (x, y) in enumerate(loader):
if i >= steps:
break
optimizer.zero_grad()
loss = criterion(model(x), y)
loss.backward()
optimizer.step()
scheduler.step()
lrs.append(optimizer.param_groups[0]["lr"])
losses.append(loss.item())
return lrs, losses画出来后看曲线:损失刚开始下降的区域,取学习率为曲线最陡下降段的 1/10 左右,通常是最佳起点。比如损失从 1e-4 开始明显下降、到 1e-2 时开始反弹,那就取 3e-3 附近。
这条曲线的形态本身也是诊断信息:如果没有任何学习率能让 loss 下降,问题大概率不在学习率,而在数据或模型(排查步骤见调试与诊断)。PyTorch 也提供 torch.optim.lr_scheduler 内置方案,另可参考 Leslie Smith 的原始论文(文末资料)。
三、batch size 与学习率联动
批大小不是孤立的超参数,它与学习率、梯度质量耦合:
- 大 batch 的梯度更准(方差小),可以、也应该用更大的学习率。经验法则:batch size 翻倍,学习率大致乘 √2(线性缩放法则的平方根版本)。
- 小 batch 的梯度噪声大,起隐式正则化作用,但收敛不稳,通常配小学习率。
- 大 batch 的最终泛化有时略差于小 batch("large-batch generalization gap"),但前提是你把学习率和调度也按比例调好——很多时候差距来自学习率没调,而不是 batch 本身。
python
# 从 32 提到 64 的配套改动示例
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3 * (64 / 32) ** 0.5)显存不够不是小 batch 的借口
batch size 受显存限制时,优先用梯度累积(见"显存管理")模拟大 batch,而不是直接改小 batch 并保持原学习率——后者会悄悄改变训练动力学。
四、训练周期规划:warmup + 余弦退火
现代训练通常不是固定学习率跑到底,而是三段式:
- warmup(几百到几千步):学习率从很小的值线性升到目标值。原因:训练初期梯度统计量还没稳定(尤其有 BatchNorm 时),直接上大学习率容易让优化器(Adam 的动量/方差估计)被早期样本带偏。
- 主体阶段:保持或缓慢下降。
- 余弦退火/线性衰减:把学习率平滑降到接近 0,让参数在损失面最小值附近"精修"。
python
import math
from torch.optim.lr_scheduler import LambdaLR
def cosine_with_warmup(optimizer, total_steps, warmup_steps=1000):
def lr_lambda(step):
if step < warmup_steps:
return step / warmup_steps # 线性升温
progress = (step - warmup_steps) / max(1, total_steps - warmup_steps)
return 0.5 * (1 + math.cos(math.pi * progress)) # 余弦退火到 0
return LambdaLR(optimizer, lr_lambda)
scheduler = cosine_with_warmup(optimizer, total_steps=len(train_loader) * 30)torch.optim.lr_scheduler 还有更省心的 CosineAnnealingWarmRestarts 与直接看验证集的 ReduceLROnPlateau。学习率调度的选择依赖损失面形状,属于经验领域,但**"warmup + 衰减"几乎总是优于恒定的学习率**。
五、正则化组合强度
正则化是"按需加权",不是"越多越好"。组合拳的一般逻辑:
| 信号 | 正则化手段 | 强度建议 |
|---|---|---|
| 训练 loss 低、验证 loss 高(过拟合) | weight_decay、Dropout、增强、早停 | 从最便宜的 weight_decay 开始逐个加 |
| 训练 loss 也下不去(欠拟合) | 关掉正则化,先检查模型容量/学习率 | 别加正则化 |
| 数据很小(<10k) | 全部常规正则化 + 早停 + 迁移学习 | 强烈建议迁移学习 |
| 训练集很大(>1M) | 正则化作用有限,重点放在调度与数据质量 | 轻量 weight_decay 即可 |
"最便宜的"顺序:weight_decay 基本零成本;Dropout 只在训练时激活;增强有读数据开销;早停是免费但只防过拟合后半程。每加一项都要量化它的净收益——比如用"有/无"对照看验证集,而不是凭感觉叠加(实验纪律见DL 设计原则)。
六、迁移学习配方
把预训练模型搬到新任务上,有一个成熟的两阶段配方(理论见表征学习与预训练,实例见渐进式教程:三版跑起来):
- 阶段一:冻结主干,只训练新分类头。学习率取
1e-3(Adam),跑几个 epoch 让分类头先对齐特征。 - 阶段二:解冻全部(或后半部分)参数,小学习率微调。学习率取
1e-4~1e-5,比阶段一小一个量级——预训练权重已很优秀,大学习率会把学好的表征"冲毁"。 - 归一化统计量必须与预训练一致(ImageNet 的
(0.485, 0.456, 0.406)),否则特征分布错位。 - 分辨率:把输入调整到预训练模型的期望分辨率(如 224×224),或选择专为小图设计的预训练模型。
- 监控微调幅度:计算每层权重的相对变化,如果某层变化超过 10% 量级,说明学习率太大。
一个常见的失误是"全量微调 + 从头训练的学习率"——这是常见陷阱与反模式里微调翻车的第一名。
七、显存管理三件套
模型放不下显存时,按性价比依次使用:
1. 梯度累积(gradient accumulation)
模拟更大的 batch,不改变梯度统计性质:
python
accumulation_steps = 4 # 相当于把 batch_size 放大 4 倍
optimizer.zero_grad()
for i, (x, y) in enumerate(loader):
loss = criterion(model(x), y) / accumulation_steps # 平均而不是求和
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()注意:loss 要除以 accumulation_steps 做平均,否则等效学习率被放大了 accumulation_steps 倍;BatchNorm 的统计量仍按单 batch 计算,所以大 batch 下的 BN 行为无法完全等价。
2. 混合精度(AMP)
用 PyTorch 内置 torch.amp,一张卡通常能省 30–50% 显存并提速,且多数任务精度几乎不掉:
python
from torch.amp import GradScaler, autocast
scaler = GradScaler("cuda")
for x, y in loader:
x, y = x.to(device), y.to(device)
optimizer.zero_grad()
with autocast("cuda"):
loss = criterion(model(x), y)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()混合精度的坑(精度溢出、梯度变成 NaN、某些算子不支持半精度)见常见陷阱与反模式。
3. 梯度检查点(gradient checkpointing)
torch.utils.checkpoint 以"反向传播时重算前向"换显存,空间换时间:
python
from torch.utils.checkpoint import checkpoint
# 对每个子模块的 forward 启用
def forward(self, x):
x = checkpoint(self.layer1, x) # 前向不保存中间激活,反向时重算
x = checkpoint(self.layer2, x)
return x典型用途是 7B 级大模型微调,配合量化/LoRA 使用;10 层以下的小模型收益不大。
优先级顺序:混合精度(几乎零成本)→ 梯度累积(不改模型)→ 梯度检查点(改模型、变慢)→ 模型并行/量化(重构)。显存问题的系统性解法还涉及 torch.utils.benchmark 与内存分析器。
八、复现实验纪律
"昨天还好好的,今天跑不出来了"几乎都是环境或随机性在作怪。复现纪律是实验的底线:
- 固定种子:在导入后立刻设置,覆盖 torch / numpy / random:
python
import random, numpy as np, torch
def set_seed(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False # 确定性优先,略牺牲性能- 固定依赖:
pip freeze > requirements.txt(或pyproject.toml锁版本),注明 Python/CUDA/驱动版本。 - 固定环境:使用 Docker 或 conda 环境,写明镜像 tag。
- 记录一切:每个实验记录 超参、数据版本、代码 commit、随机种子、单次运行结果。建议用配置化实验(见 DL 设计原则),把超参写进
config.yaml而不是散在代码里。 - 两次"黄金复现":代码上线/论文提交前,用同一种子从零跑两次,误差应小于随机种子本身的波动;波动大小本身就是评估指标的误差上界,应写进评估实践的报告里。
延伸阅读
- 调试与诊断——配方跑不通时的排查流程
- 渐进式教程:三版跑起来——配方的渐进式落地
- 从零构建一个深度学习项目——最小编管道
- 常见陷阱与反模式——配方最常见的翻车点
- DL 设计原则——实验设计的元规则
- 初始化与归一化——配方表背后的理论
参考资料
- Smith. Cyclical Learning Rates for Training Neural Networks (2017)——学习率范围测试的原始论文
- Goyal et al. Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour (2017)——线性缩放法则(batch size 与学习率联动)
- Loshchilov, Hutter. SGDR: Stochastic Gradient Descent with Warm Restarts (ICLR 2017)——余弦退火学习率
- PyTorch. Automatic Mixed Precision——AMP 官方文档