1. 认识不收敛:什么是优化不收敛?
大家好,我是你们这堂课的主讲。咱们开门见山——优化不收敛,说白了就是模型训练了半天,损失函数(Loss)死活降不下去,或者降着降着突然抽风。
我刚开始做深度学习那会儿,觉得训练模型不就是调个学习率嘛。结果有一次跑一个图像分类任务,Loss 曲线跟心电图似的,上蹿下跳。我当时盯着屏幕看了半小时,心想:这模型是不是跟我有仇?
后来踩的坑多了,才明白——不收敛不是玄学,是有迹可循的工程问题。
核心定义:优化不收敛是指模型在训练过程中,损失函数无法稳定下降到可接受的范围,或者根本无法下降。
1.1 常见的三种收敛失败现象
根据我这些年的项目经验,收敛失败基本可以归为三类。咱们一个一个说。
现象一:Loss 震荡
Loss 曲线像锯齿一样,上下波动剧烈,就是不往低处走。
- 典型表现:训练 Loss 忽高忽低,验证 Loss 也跟着抖。
- 常见原因:学习率太大、Batch Size 太小、数据噪声过多。
- 我的经验:有一次做 NLP 情感分析,我图省事把学习率设成 0.1,结果 Loss 直接跳起了探戈。后来降到 0.001,曲线立马老实了。
小技巧:如果你看到 Loss 震荡,先试试把学习率除以 10。八成能缓解。
现象二:Loss 爆炸
Loss 突然飙升到无穷大(NaN),训练直接崩掉。
- 典型表现:前几步 Loss 正常,突然一步跳到 1e+10,然后全是 NaN。
- 常见原因:梯度爆炸、权重初始化不当、数据中有异常值。
- 我的经验:我曾经在训练一个 50 层的 ResNet 时,忘了做梯度裁剪。跑了 200 步,Loss 直接炸穿天花板。嗯,从那以后我每次训练都会加上梯度裁剪。
注意:Loss 爆炸通常意味着梯度已经溢出。赶紧检查梯度范数,或者用 torch.nn.utils.clip_grad_norm_ 做裁剪。
现象三:Loss 停滞
Loss 降到一个值后就不动了,像卡住了一样。
- 典型表现:Loss 曲线变成一条水平线,无论怎么训练都不变。
- 常见原因:陷入局部最优、梯度消失、学习率太小、模型容量不足。
- 我的经验:有一次做推荐系统,模型 Loss 卡在 0.5 死活下不去。我试了各种优化器都不行,最后发现是 Embedding 维度设得太小,模型根本学不到足够的信息。
一句话总结:震荡调学习率,爆炸做裁剪,停滞查模型。
1.2 三种现象的对比
为了方便你快速诊断,我整理了一个表格。你训练的时候可以对照着看。
| 现象 | Loss 曲线特征 | 最可能的原因 | 优先尝试的解法 |
|---|---|---|---|
| Loss 震荡 | 锯齿状,上下波动 | 学习率过大 / Batch Size 过小 | 降低学习率,增大 Batch Size |
| Loss 爆炸 | 突然飙升到 NaN | 梯度爆炸 / 权重初始化不当 | 梯度裁剪,检查初始化 |
| Loss 停滞 | 水平线,不再下降 | 局部最优 / 梯度消失 / 模型容量不足 | 调整学习率策略,增加模型复杂度 |
1.3 知识结构图:不收敛的完整画像
下面这张图帮你把整个知识体系串起来。你可以把它当作本章的“地图”。
1.4 如何快速诊断?
你可能会问:我训练的时候怎么知道是哪种情况?
我的建议是:看曲线,别猜。把训练过程中的 Loss 曲线实时打印出来,一眼就能看出来。
# 一个简单的训练循环,打印 Loss
for epoch in range(num_epochs):
for batch in dataloader:
loss = train_step(batch)
# 每 10 步打印一次
if step % 10 == 0:
print(f"Step {step}: Loss = {loss:.4f}")
# 如果 Loss 突然变大 10 倍以上,立刻报警
if loss > prev_loss * 10:
print("⚠️ Loss 可能爆炸,检查梯度!")
prev_loss = loss
实用建议:我个人习惯在训练脚本里加一个 Loss 监控回调。一旦 Loss 连续 100 步不下降,自动降低学习率。这能省下不少手动调参的时间。
1.5 本章小结
咱们这一章就讲这些。核心就三句话:
- 震荡:曲线像锯齿,先降学习率。
- 爆炸:Loss 突然飞升,赶紧做梯度裁剪。
- 停滞:Loss 不动了,检查模型结构或优化策略。