4. 训练过程错误:显存不足(OOM)、学习率设置不当、梯度爆炸/消失、训练集与验证集划分错误
训练模型的时候,我敢说你一定遇到过这几个“老朋友”。显存爆了、Loss飞了、模型不收敛…… 说实话,我刚开始用Reality AI那会儿,几乎每个项目都要跟它们打一架。今天咱们就把这几个坑一个一个填平。
核心要点:训练过程的四个常见错误,本质上是资源管理、超参数调优、梯度稳定性和数据划分的问题。每个问题都有对应的诊断方法和解决方案。
4.1 显存不足(OOM)
OOM,全称Out of Memory。说白了就是显存放不下了。我在项目中遇到过最夸张的一次,一个3D点云模型直接把24G显存吃满,然后程序直接崩了。嗯,当时我盯着黑屏愣了三秒。
为什么会OOM?
- Batch Size太大——这是最常见的原因。你想想看,一个batch里塞太多样本,显存自然扛不住。
- 输入尺寸过大——比如图像分辨率太高,或者序列长度太长。
- 模型本身太大——参数数量多,中间激活值也大。
- 梯度累积——有些框架默认会保留所有中间梯度。
怎么解决?
我个人习惯按这个顺序排查:
- 减小Batch Size——从32降到16,甚至8。别怕,小batch也能训练,就是慢点。
- 降低输入尺寸——比如图像从512x512降到256x256。
- 使用梯度累积——模拟大batch的效果,但显存占用小。
- 开启混合精度训练——FP16比FP32省一半显存。
# Reality AI 中开启混合精度训练
from reality_ai import Trainer, MixedPrecision
trainer = Trainer(
model=my_model,
mixed_precision=MixedPrecision.FP16, # 开启FP16
batch_size=16,
gradient_accumulation_steps=4 # 梯度累积
)
小技巧:如果你不确定显存够不够,可以先跑一个batch试试。我经常用这个办法快速验证,比等半天再崩要好得多。
4.2 学习率设置不当
学习率,说白了就是模型每次更新的步长。步子迈大了容易扯着蛋(Loss震荡),步子迈小了又走不动(收敛太慢)。
学习率太高:
- Loss曲线像过山车,上下乱跳
- 模型可能直接发散,Loss变成NaN
- 我见过有人设了0.1,结果Loss直接飞到1e10
学习率太低:
- Loss下降缓慢,训练半天没动静
- 容易陷入局部最优
怎么选学习率?
我建议用学习率范围测试(LR Range Test)。简单说就是让学习率从小变大,看Loss什么时候开始上升,那个拐点附近就是最佳学习率。
# Reality AI 中的学习率范围测试
from reality_ai import LRFinder
lr_finder = LRFinder(model, optimizer)
lr_finder.range_test(train_loader, start_lr=1e-7, end_lr=1, num_iter=100)
lr_finder.plot() # 画出Loss vs 学习率曲线
# 根据曲线选择最佳学习率,比如 1e-3
注意:学习率不是一成不变的。我习惯用余弦退火或者阶梯式衰减,让学习率在训练过程中逐渐降低。这样前期学得快,后期学得稳。
4.3 梯度爆炸/消失
梯度爆炸和消失,是深度学习的“老毛病”了。尤其是深层网络,比如ResNet-50、Transformer这些。
梯度爆炸:
- Loss突然变成NaN
- 模型参数变得非常大
- 训练直接崩掉
梯度消失:
- Loss下降非常慢,甚至不动
- 浅层网络的参数几乎不更新
- 模型学不到东西
怎么诊断?
我一般会打印梯度的范数(gradient norm)。如果范数突然变得很大(比如超过100),那就是梯度爆炸。如果范数一直很小(比如小于1e-6),那就是梯度消失。
# 监控梯度范数
for name, param in model.named_parameters():
if param.grad is not None:
grad_norm = param.grad.norm().item()
print(f"{name}: grad_norm = {grad_norm:.6f}")
解决方案:
| 问题 | 解决方案 | 说明 |
|---|---|---|
| 梯度爆炸 | 梯度裁剪(Gradient Clipping) | 限制梯度的最大值,比如设为1.0 |
| 梯度消失 | 使用ReLU、残差连接、BatchNorm | 这些技巧能缓解梯度消失 |
| 两者都有 | 调整初始化方式 | 比如用Xavier或He初始化 |
# Reality AI 中启用梯度裁剪
from reality_ai import Trainer
trainer = Trainer(
model=my_model,
gradient_clip_val=1.0, # 梯度裁剪阈值
gradient_clip_algorithm='norm' # 按范数裁剪
)
避坑指南:我曾经在一个LSTM项目里遇到梯度爆炸,折腾了两天才发现是初始化的问题。后来改用正交初始化,问题就解决了。所以,遇到梯度问题先检查初始化。
4.4 训练集与验证集划分错误
这个错误看起来简单,但后果很严重。我见过有人把验证集和训练集混在一起,结果模型在验证集上表现“完美”,一到真实场景就拉胯。
常见错误:
- 数据泄露——验证集里混入了训练集的数据
- 划分比例不当——比如验证集太小,评估结果不可靠
- 时间序列乱序——用未来的数据预测过去
- 类别分布不均——训练集和验证集的类别比例差异大
正确的做法:
- 随机划分——用sklearn的train_test_split,设置random_state保证可复现
- 分层采样——对于分类任务,保持训练集和验证集的类别比例一致
- 时间序列划分——按时间顺序切分,不能用未来的数据
- 交叉验证——数据量小的时候,用K折交叉验证更可靠
# 正确的数据划分方式
from sklearn.model_selection import train_test_split
# 随机划分,保持类别分布
X_train, X_val, y_train, y_val = train_test_split(
X, y,
test_size=0.2,
random_state=42,
stratify=y # 分层采样
)
# 时间序列划分
train_size = int(len(data) * 0.8)
train_data = data[:train_size]
val_data = data[train_size:]
注意:划分完数据后,一定要检查一下验证集的分布。我习惯画个直方图对比一下训练集和验证集的标签分布,确保它们差不多。如果差异太大,说明划分有问题。
知识体系总览
下面这张图把本章的四个错误和对应的解决方案串起来了。你可以把它当作一个快速排查指南。
好了,这四个错误基本上覆盖了训练过程中80%的坑。下次遇到问题,别慌,按这个流程排查一遍,大概率能解决。如果还不行……嗯,那就看看是不是代码写错了(笑)。