深度学习模型训练不收敛?我踩了3个月的坑,总结出这5个反直觉的调参技巧
去年秋天,我接了个小项目,用ResNet-18做工业缺陷检测。数据集不大,8000张图,两类。我心想这还不简单,直接上Adam,lr=1e-3,batch_size=32,跑50个epoch。结果训练loss从0.69降到0.68就不动了,验证集准确率一直在50%晃悠。我换了模型、加了数据增强、甚至换成EfficientNet,还是不行。折腾了快一个月,差点以为深度学习是玄学。
后来我静下心,把PyTorch官方ImageNet训练脚本翻出来,一行行对比。发现一个反直觉的事:在小数据集上,Adam默认参数可能还不如SGD。我做了个对比实验,固定ResNet-18,CIFAR-10,batch_size=128。SGD with momentum=0.9, lr=0.1, weight_decay=5e-4, 用CosineAnnealingLR,100个epoch跑到93.2%。而Adam默认lr=1e-3,同样100个epoch只有89.1%。更奇怪的是,Adam的lr=1e-4时反而到了91.5%。这说明Adam对学习率极度敏感,而SGD在0.1附近很稳。
所以第一个反直觉技巧:先别急着用Adam。如果你的数据集小于10万张,试试SGD+Momentum,lr从0.1开始,如果loss爆炸就降到0.01。我现在的习惯是,先跑5个epoch看loss曲线。如果loss下降但震荡大,就把lr乘0.3。如果loss几乎不变,先把weight_decay从5e-4降到1e-5试试——很多时候是正则化太强把模型压死了。
第二个坑是批归一化(BatchNorm)的位置。我一开始把BN放在ReLU后面,因为很多教程这么写。后来看了原论文,BN应该放在卷积和ReLU之间。我改了之后,训练稳定性明显提升。具体来说,我的卷积块顺序是:Conv2d -> BatchNorm2d -> ReLU -> MaxPool。别小看这个顺序,在CIFAR-10上,错误率能差1.5个百分点。而且BN的momentum参数默认0.1,如果batch_size很小(比如8),可以调到0.01,不然running_mean和running_var更新太快,验证集效果差。
第三个是学习率预热。很多人忽略这个。我一开始直接上CosineAnnealing,前几个epoch loss直接NaN。后来加了5个epoch的线性预热,从1e-6升到0.1,就稳了。代码很简单:
warmup_epochs = 5
scheduler = torch.optim.lr_scheduler.SequentialLR(
optimizer,
schedulers=[
torch.optim.lr_scheduler.LinearLR(optimizer, start_factor=1e-6, total_iters=warmup_epochs),
torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs - warmup_epochs)
],
milestones=[warmup_epochs]
)
这个在PyTorch 1.10以上都有。别用旧版的LambdaLR手写,容易出错。
第四个是数据增强的强度。我一开始用了RandAugment,结果模型欠拟合。后来发现小数据集上,强增强反而有害。我换成了简单的RandomHorizontalFlip + RandomCrop(32, padding=4),准确率反而高了2%。所以别迷信最新论文里的增强策略,先试试最朴素的。另外,如果你用了MixUp或CutMix,把epoch数增加50%,不然模型没学完。
第五个是梯度裁剪。很多人一遇到梯度爆炸就加clip_gradnorm(max_norm=1.0)。但我发现,如果梯度经常被裁剪,说明学习率太大了。我试过把lr从0.1降到0.03,梯度自然就小了,不用裁剪。而且裁剪会掩盖问题,让你以为模型稳定了,其实是在原地踏步。我的建议是:先监控梯度范数,如果超过10再考虑裁剪,而且max_norm设成5或10,别设1。
最后说个感受:深度学习调参没有银弹。我现在的流程是,先跑一个baseline,记录loss曲线、梯度范数、学习率。然后每次只改一个参数,观察变化。别一次改一堆,不然你都不知道是哪个起了作用。还有,多看看PyTorch官方示例,比很多博客靠谱。我那个项目最后用了SGD lr=0.05, momentum=0.9, weight_decay=1e-4, batch_size=64, 200个epoch,准确率到了96.3%。虽然不算高,但至少能用了。
如果你也卡在某个准确率上不去,不妨试试把Adam换成SGD,把学习率调大10倍,再慢慢降。有时候反直觉的方向才是对的。