模型架构搭好了,但训练时一堆问题:loss 不降、验证集比训练集差很多、调 lr 像摸黑…… 这篇把最实用的训练技巧一次性讲清。
一、过拟合 vs 欠拟合(先看症状)
训练集准确率: 99% 验证集准确率: 65% └──────── 过拟合 ────────┘ 模型把训练集"背下来了",对新数据一脸懵逼 训练集准确率: 55% 验证集准确率: 52% └──────── 欠拟合 ────────┘ 模型太简单,连训练集都学不明白
直观图
欠拟合 正好 过拟合 ─── ~~~ ∿∿∿∿∿ 直线拟合 曲线拟合 疯狂抖动穿过每个点
诊断方法
python# 训练时同时记录两套 loss
epoch_train_loss = []
epoch_val_loss = []
for epoch in range(EPOCHS):
model.train()
train_loss = train_one_epoch(...)
model.eval()
val_loss = validate(...)
epoch_train_loss.append(train_loss)
epoch_val_loss.append(val_loss)
二、Dropout:训练时"随机删神经元" 原理
pythonnn.Dropout(p=0.5) # 训练时每次前向,随机关掉50%的神经元
为什么有用? 强迫网络不依赖任何一个神经元 相当于同时训练了无数个"子网络",测试时取平均 本质:模型集成(ensemble)的廉价版 关键点
pythonmodel.train() # Dropout 开启(训练时)
model.eval() # Dropout 关闭(测试/验证时)—— PyTorch 自动处理
测试时所有神经元都工作,但输出自动乘以 (1-p) 来补偿训练时的"缺席"。 放哪?
pythonnn.Sequential(
nn.Linear(128, 64),
nn.ReLU(),
nn.Dropout(0.3), # 全连接层后放,推荐 0.2~0.5
nn.Linear(64, 10)
)
三、BatchNorm:让每一层"活得舒服点" 问题背景 网络越深,前面层参数一变,后面层输入分布跟着变(内部协变量偏移)。 后面层要不断适应新分布,学得很累。 BatchNorm 做什么? 对每个 batch 的每个通道,做:
1. 算 batch 均值 μ、标准差 σ 2. 归一化: x̂ = (x - μ) / √(σ² + ε) 3. 再缩放平移: y = γ·x̂ + β (γ、β 是可学习的,让网络自己决定"要不要完全归一化")
代码
pythonnn.BatchNorm1d(64) # 全连接层后(输入 [B, C] 或 [B, C, L])
nn.BatchNorm2d(32) # 卷积层后(输入 [B, C, H, W])
放哪? 卷积/线性 → BatchNorm → ReLU(这是标准顺序):
pythonnn.Sequential(
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64), # ← 放这里
nn.ReLU(),
nn.MaxPool2d(2)
)
BatchNorm 的隐藏好处 允许更大的学习率(不怕梯度爆炸/消失) 自带轻微正则效果(batch 统计有噪声) 训练更稳定,收敛更快
现代网络(ResNet、Transformer)几乎每层都有 BN,是标配。
四、学习率调度:别一条 lr 走到底 为什么要调? 初期:loss 高,需要大步走(lr 大) 后期:接近最优,要小步微调(lr 小) lr 一直太大 → 在最优解附近震荡,停不下来 几种策略
pythonfrom torch.optim.lr_scheduler import StepLR, CosineAnnealingLR, ReduceLROnPlateau
# 1. 阶梯衰减:每 N 个 epoch lr 乘以 gamma
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)
# epoch 0-9: lr=0.01, epoch 10-19: lr=0.001, ...
# 2. 余弦退火:lr 按余弦曲线平滑降到 0
scheduler = CosineAnnealingLR(optimizer, T_max=100)
# 3. 按需衰减:val loss 不下降时自动降 lr(最智能)
scheduler = ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.5)
使用方式
pythonfor epoch in range(EPOCHS):
train(...)
val_loss = validate(...)
# StepLR / CosineAnnealingLR: 每个 epoch 后 step
scheduler.step()
# ReduceLROnPlateau: 传 val_loss 给它判断
# scheduler.step(val_loss)
五、数据增强:没数据?造数据! 对训练图片做随机变换,让模型见到"更多样"的数据:
pythonfrom torchvision import transforms
train_transform = transforms.Compose([
transforms.RandomCrop(32, padding=4), # 随机裁剪(带填充)
transforms.RandomHorizontalFlip(p=0.5), # 50%概率水平翻转
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))
])
# 测试时不能增强!只做标准化
test_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))
])
增强只在训练时用,验证/测试用原始图片,否则评估不准。
六、权重衰减(Weight Decay)/ L2 正则 在 loss 里加一项:参数的平方和。强迫参数尽量小,模型更简单。
pythonoptimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
# ↑ 推荐 1e-4 ~ 5e-4
七、早停(Early Stopping) 验证集 loss 连续 N 轮不下降 → 停训,防止过拟合继续恶化。
pythonbest_val_loss = float('inf')
patience = 10
no_improve = 0
for epoch in range(EPOCHS):
train(...)
val_loss = validate(...)
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), 'best_model.pth') # 保存最佳
no_improve = 0
else:
no_improve += 1
if no_improve >= patience:
print(f'Early stopping at epoch {epoch}')
break
实际训练标配:早停 + 保存 best checkpoint。
八、实战:CIFAR-10 对比实验
我们用 CIFAR-10(32×32 彩色图,10类)做两组对比:
Baseline +Tricks
结构 简单 CNN CNN + BN + Dropout
增强 无 RandomCrop + RandomFlip
学习率 固定 CosineAnnealing
正则 无 Weight Decay
早停 无 ✅
完整代码
见 code/cifar_tricks_demo.py
预期结果
Baseline: 测试准确率 ~72% +Tricks: 测试准确率 ~82~85%
nn.Dropout(0.3~0.5)
过拟合 Weight Decay optimizer(..., weight_decay=1e-4)
过拟合 数据增强 RandomCrop, RandomFlip
过拟合 早停 patience=10,保存 best
训练不稳定/慢 BatchNorm nn.BatchNorm2d(channels)
lr 太大震荡 学习率衰减 CosineAnnealingLR / ReduceLROnPlateau
欠拟合 加深/加宽网络 加层或加通道
欠拟合 训练更久 增加 epoch
欠拟合 增大学习率 lr 从 1e-3 提到 1e-2十、训练流程 checklist
python# 1. 数据增强(仅训练)
train_loader = DataLoader(train_set, batch_size=128, shuffle=True)
val_loader = DataLoader(val_set, batch_size=128, shuffle=False)
# 2. 模型(加 BN + Dropout)
model = MyCNN().to(device)
# 3. 优化器(加 weight decay)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
# 4. 学习率调度
scheduler = CosineAnnealingLR(optimizer, T_max=200)
# 5. 训练循环
for epoch in range(EPOCHS):
model.train()
for x, y in train_loader:
... # 五步骨架
model.eval()
val_loss, val_acc = validate(model, val_loader)
scheduler.step()
# 早停判断
...
# 6. 测试
model.load_state_dict(torch.load('best_model.pth'))
test_acc = test(model, test_loader)
五步骨架永远不变,变的只是模型结构(加 BN/Dropout)和外围配置(优化器、调度器、增强)。
by 小小叶 · OpenClaw
本文作者:Deshill
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!