深度学习 loss 不下降怎么办?别急着换模型,按这个顺序查(附具体参数和代码)

🔑 关键词:深度学习,loss不下降,数据清洗,batch size与学习率,模型微调

📖 摘要:换模型只涨了 1.2 个点,清洗 3400 张标注却涨了 5.6 个点。这篇文章记录了我做工业缺陷检测时踩过的坑,给出从输入检查、单 batch 过拟合到学习率缩放的完整排查顺序,附真实参数表、GPU 利用率优化代码,以及一个不太讨喜的观点:标签数据少于 10 万时别碰大模型微调。

凌晨三点,我盯着一个 0 字节的 jpg 看了十分钟。

图片

那天分割模型的 mIoU 卡在 0.61 上不去。我们换了两版 backbone,ResNet-50 换到 ConvNeXt-T,又试了 Swin-T,涨了 0.4 个点,然后一跑长训练就掉回去。最后发现数据里有一批空图——爬虫下载失败留下的占位文件,标注却还在。删掉那 213 张图,同样的模型、同样的超参、同样的随机种子,mIoU 直接到 0.68。

我讲这个不是想说“数据很重要”。这句话谁都听过,等于没说。我想说的是一个更难听的版本:你在深度学习上遇到的瓶颈,大概率不是模型选得不够新,而是你压根不知道自己喂进去的是什么。

一、先把账算清楚:我们做过一组对照

数据是同一份 12 万张图(工业表面缺陷,7 类,长尾比 1:340),A100 40G 单卡,训练 60 epoch,跑三遍取中位数。改动项和验证集 F1 的变化:

图片

改动 F1 变化
ResNet-50 换 ConvNeXt-B +1.2
加增强(RandAugment + MixUp 0.2 + CutMix 1.0) +2.8
重标 3400 张边界样本(主要是把“疑似”改成“正常”) +5.6
CE 换 label smoothing 0.1 的 focal loss +0.9
学习率从 3e-4 调到 1e-3 + 1000 步 warmup +1.7

注意第二行和第五行加起来才 4.5,还不一定是可加的(增强和学习率会互相影响,调完增强后最优 lr 往往要往下走一点)。

这个排序很多人不爱听,因为换模型是最爽的,改一行代码,喝杯咖啡,回来就有新数字。洗数据是最不爽的,你要一张一张看,看一天可能只看了 800 张。

二、排查顺序:从第 0 步开始,别跳

图片

第 0 步:看输入,不是看 loss。 这段代码塞进训练循环,跑 20 个 step 就够:

x, y = next(iter(train_loader))
print(x.shape, x.dtype, x.min().item(), x.max().item(), x.isnan().any())
print(y.shape, y.dtype, y.unique()[:20], y.min().item(), y.max().item())

如果 x.max() 是 1.0 而你的模型第一层还带着 Normalize,或者 x 的范围是 0~255 你没除 255,这类错误在真实项目里的出现频率高得离谱。还有 y.unique() 里出现 7 但你的类别数设成了 7(0~6),越界不报错,只是某一类永远学不会。

第 1 步:用 1 个 batch 过拟合。 拿 8 张图,关掉所有增强,AdamW lr=1e-3,训练 300 步,看训练准确率能不能到 100%。到不了就是模型结构、标签映射或者 loss 有 bug,跟调参一点关系都没有。这一步花你 5 分钟,能省掉以后所有“玄学调参”的时间。

第 2 步:才是学习率和 batch size。 常用的起点:

图片

batch size 学习率 warmup 备注
64 3e-4 500 iter 单卡 3090/4090 的常见起点
256 1e-3 1000 iter 按线性缩放的常规值
1024 3e-3 2000 iter 得配 LARS 或 LAMB,否则容易发散
4096 8e-3 5 epoch 参考 Goyal et al. 2017 那篇 large minibatch SGD

线性缩放不是定律。我们的经验是在 2048 之后就该换成 sqrt 缩放(lr ∝ √bs),不然梯度噪声被平均得太小,模型往尖锐极小值跑,验证集上明显更容易崩。这个现象在小数据集上尤其明显,12 万张图我已经能稳定复现。

第 3 步:看 GPU 利用率。 nvidia-smi 里 GPU-Util 长期低于 60%,那不是模型慢,是 dataloader 在拖后腿。num_workers 从 4 提到 16,配上 pin_memory=Truepersistent_workers=Trueprefetch_factor=4,我在 4090 + 本地 NVMe 上实测吞吐涨了 2.3 倍。这一步免费,但很多人宁愿去换更大的模型。

三、一个不太讨喜的观点:小团队别急着上大模型

图片

如果你手上标签数据少于 10 万,且任务垂直到“没人愿意为它发论文”的程度(比如某种铝合金的表面裂纹分级),我的建议是别碰 ViT-L,也别碰用 LoRA 微调 7B 的视觉语言模型。

LoRA 有个很隐蔽的坑:rank 和 alpha 一改,最优学习率跟着变,而你没办法像全量微调那样靠“模型是否欠拟合”来判断状态。很多人 r=8、alpha=16、lr=2e-4 抄过来,在自己数据上就是不动,然后得出结论说数据不够。其实往往只是这组超参和你的数据分布不匹配。

我们也试过用 CLIP 做零样本缺陷分类,7 类,直接跑准确率 0.52,还不如我花一个小时标 200 张图训出来的 ResNet-18(0.79)。不是 CLIP 不行,是“裂纹”这个概念在它的预训练分布里根本没有对应物。

四、最后说点虚的

图片

我觉得深度学习里最难的不是反向传播,也不是 attention 怎么算。那些东西你花两周能看懂,网上教程多到看不完。难的是你要在信息不全的情况下做判断:这次的 loss 抖动是正常波动还是有问题?这块掉点该加数据还是加容量?今天该不该停掉这个已经跑了三天的实验?

这些问题没有论文能回答你,只有你自己踩过的坑的数量能回答你。

所以我现在带人,第一件事不是让他读 Transformer,是让他把一份脏数据从头洗一遍,洗到能说清楚每一类有多少张、哪些是边界样本。洗完了,他自然知道该看什么。

(文中的代码和数字都来自我们自己跑的项目,数据集和公司名不方便写,见谅。不同任务上结论会有出入,别照抄。)

🏷️ 标签: