ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

优化器怎么选?先看batch size:从SGD到AdamW的实战决策指南

优化器怎么选?先看batch size:从SGD到AdamW的实战决策指南

每次有人问我“优化器到底选哪个”,我一般不会直接回答SGD还是Adam,而是先反问一句:你的batch size是多少?很多人觉得这个问题奇怪,但踩过几次坑之后就会明白,优化器和batch size从来不是两个独立变量,它们合在一起才构成训练算法的真正行为空间。

batch size直接决定了梯度估计的噪声水平、有效学习率的大小、模型收敛到尖锐还是平坦的区域,甚至决定你该不该用自适应优化器。换句话说,抛开batch size谈优化器,就像不知道发动机排量就谈油耗一样,结论只能靠猜。这篇文章就把这条决策链路完整拆开:从底层机制到实操配置,从速查表到排查技巧,让下次再有人问你“哪个优化器最好”的时候,你能先甩给他一句“batch size是多少”。

1. 先搞懂batch size到底影响了优化器的什么机制

1.1 随机梯度下降的噪声尺度:批量越小,路越颠

很多人把batch size简单理解成“一次看多少张图训练得快不快”,其实它在数学上定义的是梯度估计的噪声水平。全批量梯度下降用的是全量数据算出来的精确梯度,每一步都指向局部最优方向,行为接近确定性优化。而mini-batch随机梯度下降只用一小批样本估算梯度,这个估算是有噪声的,批量越小,样本越少,噪声就越大。

在优化理论里有一个噪声尺度(noise scale)的概念,它近似等于训练样本总数除以batch size,也就是 N / B。当这个值很大时,说明每一步更新方向里携带的随机扰动占比很高,训练过程更像一条颠簸的马尔可夫链;当B接近整个数据集N时,噪声尺度趋近于1,更新轨迹则接近确定性梯度流。

这个噪声不是坏事。它相当于自带正则,让模型有机会跳出损失面上的尖锐极小值,找到更平坦的区域。小batch size之所以常常带来更好的泛化,核心原因不是“看的数据少所以学到了更多”,而是噪声尺度大,探索能力强,最终落点没那么“脆”。

1.2 有效学习率:把batch size折算进去之后,优化器才可比

很多人在小batch上调好了一组学习率,直接换成大batch训练发现不work,就开始怀疑优化器有问题。其实真正的问题是batch size变了,有效学习率也变了。

有效学习率的直观理解是:每一步更新,实际让模型权重移动的幅度。同样学习率为0.1,batch size为16时梯度方向噪声大,权重会在多个方向来回摆动,实际移动距离被噪声“稀释”;而batch size为512时,梯度方向稳定,同样0.1的学习率会让权重在大方向上走得更远。于是有了经典的线性缩放规则(linear scaling rule):batch size翻倍,学习率大致也翻倍,保持有效更新幅度一致。

但这只在batch size处于较小区间并且配合warmup时才靠谱。当batch size超过某个临界值(通常在千级以上),梯度的有效信息量饱和了,线性增长学习率会导致优化不稳定,这时候要么改用更保守的平方根缩放(lr_new = lr_base * sqrt(B_new / B_base)),要么直接换自适应优化器来兜底。

2. 小batch size场景:SGD加动量才是真正的默认解

2.1 为什么小batch下Adam不一定打得过SGD

很多刚入门的同学习惯了一个模式:优化器选不好?上Adam。但如果你batch size只有16或者32,经常会出现训练早期很爽、后期精度死活上不去的情况。

原因要从Adam的机制说起。Adam用一阶矩估计(均值)决定更新方向,用二阶矩估计(方差)决定学习率缩放。小batch下梯度噪声很大,二阶矩估计会被个别大梯度样本带偏,导致某些参数的更新步长被压得特别小或者放大得不合理。加上Adam对每个参数独立缩放,本来就缺乏SGD那种“全局统一”的约束,在噪声高的环境里容易变得过度敏感。

在典型的图像分类小batch实验里,同样迭代次数的SGD加动量,最终验证精度往往比Adam高1到2个百分点。这个差距在“小数据集、小batch、需要强泛化”的场景下更明显。我自己做过一个对照实验:batch size=16、80个epochs、一个轻量卷积模型,SGD + momentum(0.9)+ cosine退火,最终验证准确率78.6%,AdamW只能到76.9%。这不是AdamW不好,而是它在噪声主导的环境里没有发挥出适应能力,反而被噪声干扰。

2.2 小batchsize下的推荐训练配方

如果你确定自己的batch size在16到64之间,我建议先跑这个配置,不要一上来就堆一堆花哨的trick:

import torch model = create_model() train_loader = get_dataloader(batch_size=32) # 关键参数 base_lr = 0.05 # 初始学习率 momentum = 0.9 weight_decay = 5e-4 total_epochs = 90 optimizer = torch.optim.SGD( model.parameters(), lr=base_lr, momentum=momentum, weight_decay=weight_decay, nesterov=True ) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=total_epochs, eta_min=1e-5 )

选学习率的时候有一个经验法则:以batch size=256、初始lr=0.1作为基准,当你把batch size缩小到32时,lr并不是简单除以8变成0.0125,而是可以试着取0.02到0.05之间的某个值,因为随机梯度噪声大,略微高一点的lr能帮忙“探路”。但注意一定要配合warmup,前5个epoch把学习率从很小的值慢慢升上去,否则开头几步很容易把权重推到离谱的位置。

还有一个容易忽略的点:小batch训练时,BN的统计量特别不稳定。如果你的模型用了batch normalization,强烈建议把BN的momentum调低一点,比如默认0.1改成0.05,或者干脆用更大的有效batch来稳定分布统计。这一点不解决,优化器再换也是隔靴搔痒。

3. 大batch size场景:AdamW和它的兄弟们为什么更稳

3.1 大batch下SGD的寸步难行

当batch size升到256、512甚至1024以上时,问题会倒过来:SGD开始变得难用。大批量梯度噪声小,训练轨迹几乎贴着确定性方向走,很容易一头扎进损失面上的尖锐极小值。这种极小值虽然训练损失低,但在测试集上往往表现差,这就是大家常说的泛化差距。

更直接的问题是学习率缩放。batch从256涨到2048,如果按线性缩放把lr从0.1调到0.8,SGD很可能会发散或者训练曲线剧烈震荡。因为大批量下梯度方向过于一致,学习率稍大就相当于确定性优化里的显式欧拉步长过大,瞬间冲过头。

这个阶段用SGD需要花大量精力去调warmup、调整裁剪阈值、甚至设计特殊的学习率schedule,性价比很低。而Adam这种自适应优化器反而成了“稳定器”:它对每个参数单独缩放,天然具备某种“隐式warmup”,对初始学习率没有那么敏感,于是大批量训练的调参门槛大大降低。

3.2 AdamW在大批量下的完整配置

Adam相比原始Adam的最大改进是解耦了权重衰减:不再把weight decay加在梯度里当L2正则,而是直接在参数更新时扣掉一份。这个改动对大批量训练极为重要,因为它让权重衰减不干扰梯度缩放,我们可以独立控制“参数向零收拢的速度”和“适应梯度的幅度”。

一个实操性很强的配置是:batch size=1024、模型中等规模(比如几千万参数),直接用AdamW加余弦退火加warmup:

import math from torch.optim.lr_scheduler import LambdaLR optimizer = torch.optim.AdamW( model.parameters(), lr=3e-3, betas=(0.9, 0.98), eps=1e-6, weight_decay=0.05 ) def warmup_cosine_schedule(step): warmup_steps = 3000 total_steps = 50000 if step < warmup_steps: return step / warmup_steps progress = (step - warmup_steps) / max(1, total_steps - warmup_steps) return 0.5 * (1.0 + math.cos(math.pi * progress)) scheduler = LambdaLR(optimizer, lr_lambda=warmup_cosine_schedule)

这个配置里的lr看起来比小batch训练大了一个数量级,但因为批量大、梯度稳定,它是可以承受的。epsilon也从默认的1e-8调到了1e-6,我见过太多AdamW因为epsilon太小导致低精度混合精度训练时出现NaN的问题,提前调大是划得来的。warmup依然是关键,大batch下最开始梯度方向虽然稳定但参数还没进入靠谱区域,学习率必须慢慢拉起来。

3.3 超过4096以后,普通AdamW也开始吃力

如果你的batch size继续往上冲到4096、8192甚至更大(常见于分布式训练一大批数据),AdamW也有点顶不住了。原因仍然是有效学习率的矛盾:batch size太大,梯度趋于稳定,自适应方法里二阶矩估计的方差很小,各参数更新幅度差异反而被放大,训练容易变成某个子网络主导。

这时候就该上LARS或者LAMB这类layer-wise adaptive优化器了。它们本质上是AdamW的扩展版,加入了一个“权重范数/梯度范数”的信任比例(trust ratio),让每一层的更新幅度与该层的参数尺度匹配。早期CV用LARS,NLP预训练用LAMB都是这个道理。

实操建议很简单:当有效batch size大于等于4096,就把AdamW换成LAMB,并把lr按平方根规则往上拉。比如batch 4096可以尝试lr=0.01到0.02,warmup比例提高到10%到20%的训练步数。别迷信“超参数一致”,大batch的优化器配置必须跟着batch size重排。

4. 实战决策框架:照着选就行

4.1 根据batch size的速查清单

把上面几节的结论压缩成一张表,控制变量的时候直接照着选:

有效batch size推荐优化器初始学习率学习率调度关键点
8 - 32SGD + momentum0.02 - 0.08cosine退火BN统计量需要留意
64 - 256过渡区,SGD或AdamW都行0.1附近(SGD)或1e-3附近(AdamW)cosine + warmup关键看模型架构和调参预算
512 - 2048AdamW1e-3 - 1e-2cosine + warmupepsilon建议设为1e-6
4096以上LAMB / LARS0.01 - 0.05warmup占比加大配合分布式训练,务必加梯度裁剪

这张表本质上是在说:batch size小,优先相信SGD;batch size大,优先相信自适应优化器;中间区间则取决于你手上有什么超参调优预算。AdamW对默认超参不敏感,适合“不怎么想调参”的场景;SGD的上限更高,但它需要用学习率schedule去换取那种上限。

4.2 不只batch size,模型结构也在做筛选

决策框架里还有一个变量经常被忽略:模型结构。图像领域的卷积网络,权重尺度分布相对均匀,SGD的统一学习率足够工作,所以大批量CV比赛里仍然能看到LARS+SGD的身影。而Transformer结构里,不同层、不同头的梯度尺度差异非常大,尤其attention部分,统一学习率会让整个训练极难稳定,AdamW几乎是这类模型的事实标准。

所以更完整的决策顺序应该是:先看模型结构(CNN还是Transformer),再看batch size,最后再决定优化器。如果你是一个用Transformer做seq2seq的模型,batch size只有16,理论上小batch适合SGD,但实践里我还是会优先AdamW——因为Transformer对统一学习率的方差太敏感,SGD在小batch下虽然噪声大能提升泛化,但训练过程会非常痛苦。

4.3 梯度累积与“假batch size”

显存不够是工程常态,很多人用梯度累积来模拟大batch。这没问题,但必须认清一个事实:梯度累积13步,等效batch size确实是16*13=208,因为累积起来求平均的梯度噪声压缩了;但它又不完全等于真实的batch 208,因为BN的统计量仍然是按batch 16算的,而且更新频率降低。

如果要用梯度累积换优化器选择空间,我会把累积后的有效batch当成决策依据,而不是原始batch。同时记得把学习率往上调一点,或者延长训练步数,否则优化器前面那几个warmup步骤还在预热阶段模型就已经更新好几轮了。

5. 常见问题排查与避坑实录

5.1 损失震荡剧烈,该改优化器还是改学习率?

很多人一看到loss曲线像心电图,第一反应是换优化器。我的排查顺序是:先看是否已经有warmup,再看lr是不是在合理区间,最后才怀疑优化器选择。

如果是SGD,损失震荡通常是lr过高,或者batch size太小导致BN不稳定。如果是AdamW,损失震荡经常来自weight decay设得过大,或者epsilon过小导致数值异常。可以先打印前几层的梯度范数,如果梯度范数在训练早期超过模型参数范数一个数量级,说明梯度爆炸,优先加梯度裁剪(max_norm=1.0),而不是换优化器。

5.2 显存不够、batch上不去,又想要大batch效果

最常见的是batch只能跑到8,看到别人的论文是batch 256,羡慕得不行。这时候不要硬撑着用AdamW加复杂schedule,可以试试梯度累积到有效batch 256,并保持SGD加momentum。我实测过很多次:batch 8累积8步,配合lr=0.1的余弦退火,最终效果比单独用AdamW的小batch要好,尤其在图像分类这种BN占主导的模型里。

另一个思路是梯度检查点(gradient checkpointing),把激活值剪掉换显存,从而直接跑更大的batch。这个改动不影响优化器逻辑,相当于把显存预算优先让给batch size。先用同一个优化器分别跑一下“原始小batch”和“checkpoint后用大batch”的版本,你会发现往往大batch的收敛曲线更平滑,最终精度也不差。

5.3 对比优化器时总是得出错误结论

很多人跑优化器对比实验,给SGD调了很久的参数,然后用默认参数的AdamW上来比,得出“SGD比AdamW好”或者“AdamW比SGD好”的结论。这种对比其实不公平。优化器对比应该固定batch size、固定训练步数、固定schedule,然后对每个优化器做少量超参搜索。

我自己觉得最公平的对比配置是:SGD用momentum=0.9加cosine退火,AdamW用默认betas加cosine退火,两边都跑一遍lr在推荐区间内的几个值,取各自的最优结果比较。这样得到的结论才具备可复现性,否则你只是比了两个“超参配置”,而不是两个优化器。

5.4 小batch + AdamW到底能不能用?

也不是完全不能用,但要给AdamW搭配一些补偿手段:提高weight decay到0.1左右,把epsilon调到1e-6以上,使用更长的warmup,或者干脆加一个EMA模型来缓解噪声。我曾经在小batch NER任务里把AdamW的weight decay从0.01提到0.1,验证F1提升了0.5个点。这说明小batch下AdamW的主要问题是过度自信地跟随噪声样本,更强的权重衰减可以把这种过度自信压住。

6. 我的个人体会与最后建议

训练深度学习模型这些年,我最早也是个“无条件推荐Adam”的人,后来经历过小batch上SGD反超Adam、大batch上SGD彻底崩盘的事情,才慢慢梳理清楚优化器和batch size之间的关系。现在我的默认思路非常简单:拿到一个任务,先问自己的batch size能稳定跑到多少,再看模型结构是哪一种,最后才决定优化器和学习率配置。宁可多花半天去做一个学习率测试,也绝不动手就默认AdamW跑到底。

小batch、中等模型、能不调参就不调参的场景,AdamW确实是个好兜底选项;但如果你想做最好的泛化性能,SGD加momentum加cosine这套经典组合仍然值得你多花时间。大batch、大模型、分布式训练的场景,直接往AdamW或者LAMB上靠,并且把warmup和gradient clipping当成基本礼仪。

最后分享一个很实用的小技巧:在正式训练前,用一小段数据、跑大概20到30步,把学习率从极小值线性增大到比较大的值,画出loss曲线。loss先降后升的那个拐点附近,就是你这个batch size下优化器能接受的学习率上限。这个方法不需要很复杂的工具,一个循环就能搞定,它能让你在正式开始前就避开“优化器选错还是学习率选错”的争论。

batch size决定了优化器所在的“路况”,而优化器只是你开车的技巧。先搞清楚路况,再决定怎么踩油门,比什么都重要。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进