ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

优化器选型与调参实战:从SGD到AdamW,让模型训练稳定收敛

优化器选型与调参实战:从SGD到AdamW,让模型训练稳定收敛 聊到 Model-Optimizer很多人第一反应是“选哪个优化器接口”但真正跑过模型训练的人都知道优化器不是训练脚本里顺手填的一个参数而是整个训练过程能否收敛、收敛多快、最终精度高低的控制中枢。我见过太多项目模型结构没问题、数据也没问题就因为优化器选错或者参数配错loss 曲线要么原地打转要么直接炸成一片噪声。这篇文章不打算写教科书式的公式堆砌而是从我实际的调参经验出发把优化器的原理、选型、调参和排障讲透让正准备训练模型的朋友能少走弯路。这篇文章适合两类人一类是刚入门深度学习、想搞清楚“SGD、Adam、AdamW 到底该用谁”的新手另一类是已经跑过不少实验但经常被 loss 震荡、不收敛、精度上不去等问题折磨的工程师。我会尽量用生活化的类比解释底层原理再给出可以直接抄作业的配置思路最后分享我踩过的坑和排查经验。1. 优化器为什么是训练成败的分水岭1.1 先搞懂优化器在整个训练流程里的位置训练一个模型本质上是在做一件事找到一组参数让损失函数的值尽可能小。这个“找”的过程就是优化。而优化器就是负责根据损失函数的梯度信息决定参数往哪个方向走、走多远的那个执行者。你可以把训练过程想象成在一个漆黑的山谷里下山。模型参数是你在山谷里的坐标损失函数的值是当前海拔梯度告诉你哪个方向是下坡。优化器的作用就是根据这个下坡方向迈出下一步。如果你每一步都走得很稳很快就能到谷底如果步子太大可能直接跳过谷底冲到对面山坡上如果方向判断失误那就可能在山谷里来回震荡永远下不去。这个类比很粗糙但能帮你建立第一层直觉优化器的核心问题是如何根据历史梯度信息和当前梯度信息稳健地逼近损失曲面的最低点。这里的关键词是“稳健”。因为实际训练中损失曲面远比“山谷”复杂得多它可能是崎岖的、有大量局部坑洼的高维地形所以优化器还需要解决“别被困在局部低点”“别在鞍点附近停滞”“别因噪声太大而震荡”等一系列问题。1.2 损失曲面比想象中麻烦得多很多人第一次训练模型时都会有一个朴素想法只要梯度下降loss 就应该一直下降。但实际跑起来往往会发现loss 曲线会呈现各种奇怪的形态——先降后升、平台期不动、剧烈震荡、甚至直接变成 NaN。这些现象背后的原因都指向损失曲面的几何性质。高维参数空间里损失曲面最常见的“陷阱”不是局部极小值而是鞍点。鞍点附近梯度在某些方向上接近零但并不是真正的谷底。普通 SGD 在这种地方步伐会变得极慢看起来就像训练卡住了。另外损失曲面在不同方向上的曲率差异可能非常大有的方向坡很陡有的方向坡很缓。如果所有方向都用同一个学习率就会出现“陡坡方向震荡、缓坡方向龟速”的局面。优化器的发展史本质上就是一部对抗这些损失曲面困境的历史。从 SGD 到动量优化从自适应学习率到当前的 AdamW每一代优化器都在解决某一类特定的训练难题。理解了这些困境你才能理解为什么需要不同的优化器也才能在面对具体问题时做出正确的选择。2. 主流优化器横向对比与选型思路2.1 从 SGD 到 Momentum动量的价值先说最基础的 SGD随机梯度下降。它的更新规则极其简单参数朝着当前梯度方向前进前进的距离等于学习率乘以梯度。这个方案在凸优化理论上很漂亮但在深度学习实践中问题很大。最大的问题是它对梯度噪声非常敏感。实际训练中我们计算的是一个小批量样本的梯度而不是全量数据的真实梯度所以梯度本身带有随机噪声。如果直接用带噪声的梯度去更新参数路径就会非常曲折。你可以想象在滑雪时每一秒脚下都有人从侧面推你一把你很难走出直线。Momentum动量的出现解决了这个问题。它的思路特别接地气维护一个速度变量每次更新时把当前梯度和历史速度做一个加权融合。如果历史速度方向与当前梯度方向一致速度就会越来越大相当于加速通过平坦区域如果方向不一致速度就会被抑制相当于缓冲掉噪声带来的反复。我个人的经验是在 CV 任务中SGD Momentum 的组合至今仍是很多优秀模型的标配。它不会像自适应学习率优化器那样引入过多的隐式调节行为更可预测配合精心调好的学习率 schedule往往能得到更好的泛化性能。很多人以为“新人就该用 Adam”但我反而建议新人在简单的分类任务上先用 SGD Momentum 把训练流程跑通因为它的行为更直接更容易帮助你建立调参直觉。2.2 RMSProp 和 Adam自适应学习率的崛起SGD 系优化器有一个共同痛点所有参数共享同一个学习率。但正如前面提到的损失曲面在不同方向上的曲率差异很大这会导致某些维度更新太慢、某些维度更新太快。RMSProp 的想法是为每个参数单独维护一个梯度平方的滑动平均然后用它来归一化当前梯度的量级。直观解释是如果某个参数的历史梯度一直很大说明这个方向很陡峭那就把有效学习率调小一点反之如果某个参数的历史梯度很小说明这个方向很平缓那就把步子迈大一点。这相当于给每个参数装了一个自动的“地形感知器”。Adam 则在 RMSProp 的基础上又加上了动量项同时维护一阶动量梯度的均值和二阶动量梯度平方的均值因此它既对每个参数自适应调节学习率又能在方向上有一定的平滑能力。理论上说Adam 是一种非常强大的组合加上它对初始学习率的敏感度比 SGD 低得多所以它成了很多深度学习项目的默认选择。但 Adam 并不总是最好的答案。它的二阶动量归一化会把梯度尺度归一化到接近单位级别这在训练早期可能造成参数更新过大影响泛化而它对学习率的自适应也使得它的最终收敛行为在接近最优解时不够精细。这就是为什么很多从论文里复现的项目最终会在训练后期把优化器切回 SGD 做微调以获得更低的最终损失。2.3 AdamW 与解耦权重衰减一个容易被忽略的修正说到 Adam 的改进就不得不提 AdamW。它最初是苏黎世联邦理工的研究者提出的一个小改动但影响非常深远。传统做法是用 L2 正则化来惩罚过大的权重而在 Adam 里如果直接把权重衰减项加进梯度再参与二阶动量归一化那么权重衰减的实际效果会被归一化操作削弱变成一个随梯度尺度变化的不稳定量。AdamW 的做法很直接把权重衰减从梯度计算中拿出来在参数更新时直接对权重做一个衰减缩放。这样权重衰减就不再受梯度归一化的干扰表现得更像一个独立的“参数缩水”操作。实践中的差异是切实存在的用 Adam 配 L2 权重衰减和用 AdamW 配解耦权重衰减训练出来的模型权重分布、泛化能力都有可感知的区别。现在很多主流框架已经默认把 AdamW 作为推荐优化器。比如 Hugging Face 的 Transformers 库默认配置就是 AdamW。如果你正在训练预训练语言模型、视觉 Transformer 或者多模态模型直接选 AdamW 基本不会出错。但如果你是在训练一个小型的 CNN 分类模型数据集规模也不大那 SGD Momentum 反而可能更稳要针对具体任务去权衡。2.4 一张表看清主流优化器的适用场景为了让大家在选型时不纠结我把常见优化器的特点和适用场景整理成一个对照表优化器核心机制优点典型适用场景我的建议SGD直接沿梯度方向更新简单、行为可预测、泛化性好CNN 图像分类、目标检测有调参时间时优先考虑后期收敛更干净SGD Momentum引入历史速度平滑梯度噪声加速训练、抑制震荡大规模 ImageNet 训练、超参数已对齐的经典任务CV 复现论文时的默认选择之一RMSProp按梯度平方滑动平均归一化学习率适应不同方向的曲率差异RNN、强化学习、非平稳任务对序列模型比 Adam 有时更稳Adam一阶动量 二阶动量自适应参数自调节、初始学习率敏感度低Transformer、生成模型、多模态跑通流程的首选注意后期收敛问题AdamWAdam 解耦权重衰减权重衰减更可控、泛化更稳BERT/GPT 等预训练模型预训练与微调场景下的长期首选这个表不是绝对的但它能帮你建立一个初步的选型框架。核心逻辑就一句话自适应优化器帮你省心省力快速出结果SGD 系优化器帮你精雕细琢逼近最优。3. 优化器关键参数与实操调参指南3.1 学习率最容易被低估的超参数现在很多框架都有自动学习率调节工具比如 PyTorch 里的 CosineAnnealingLR、OneCycleLR让我感觉“手动选学习率”这项基本功正被慢慢淡化。但说到底学习率才是所有超参数里和优化器交互最密切的一个必须理解它。学习率决定的是参数沿着梯度方向前进的步长。步长太小训练速度慢得像蜗牛爬还容易陷入局部坑洼步长太大loss 会在最小值附近反复横跳甚至发散。给一个参考对于 Adam/AdamW常用初始学习率在 1e-4 到 3e-4 之间对于 SGD Momentum常用初始学习率在 0.01 到 0.1 之间搭配 step decay 或 cosine schedule 使用。我踩过的一个真实教训是有一次训练一个视觉 Transformer 模型参考别人配置直接用了 3e-4 的初始学习率结果训练前 2000 步 loss 完全没有下降。后来我把学习率调到 1e-3loss 立刻开始稳步下降。所以很多“不收敛”问题根源不是优化器选错了而是学习率和优化器、模型结构不匹配。我的建议是遇到不收敛时先用对数刻度去扫学习率比如从 1e-5 到 1e-2每隔 10 倍试一组。这个成本不高但能帮你快速定位问题。3.2 batch size 与优化器行为的联动batch size 直接影响梯度噪声的大小进而影响优化器的行为。小 batch 的梯度噪声大模型在优化过程中会不自觉地“探索”更多区域这在某些场景下反而能帮助逃离局部极小值大 batch 的梯度更接近真实梯度训练更平滑但需要更高的学习率才能充分利用大步长优势。实际工程中batch size 往往受到显存限制能选的空间不大。但你需要知道的是如果你把 batch size 从 128 提高到 1024那么学习率也应该相应调大。一种常用的经验规则是线性缩放法则学习率按 batch size 的倍数同比放大比如 batch size 扩大 8 倍学习率也乘 8。但注意这只是起点实际训练过程中还要观察 loss 曲线再做微调。另外大 batch 配合 AdamW 时权重衰减的强度可能需要重新调整。因为 batch size 变大后梯度噪声减小模型的“隐式正则化”效果变弱这时适当增强权重衰减、或者加入一些数据增强才能保持相同的泛化水平。这个细节很容易被忽略等你发现大 batch 训练出来的模型精度明显低于小 batch 时往往就是这个原因。3.3 warmup、梯度裁剪与 schedule 的组合很多优化器在训练初期是不稳定的。尤其是 AdamW 这类自适应优化器它的一阶动量和二阶动量在初始阶段都是零如果一开始就用较大学习率就会导致参数更新幅度异常大。所以现在主流做法是加一个 warm-up 阶段让学习率从很小的值线性增长到目标学习率。这个做法在 Transformer 类模型的训练中是标配它能有效避免初期训练崩溃。梯度裁剪则是另一个常和优化器搭配使用的策略。当你发现 loss 曲线偶尔会出现尖峰、但随后又能恢复时多半是梯度在某些 batch 上异常偏大这时用梯度裁剪会让训练稳定不少。PyTorch 里直接clip_grad_norm_(model.parameters(), max_norm1.0)就能实现。对于语言模型、多模态模型这类容易积攒超大梯度的任务我建议默认开启梯度裁剪。把这三个东西组合起来一个典型的高质量训练配置大概是这样的AdamW CosineAnnealingLR 前 5% 步数做线性 warmup 梯度裁剪阈值 1.0。这套逻辑我用了很多年在多个项目上都非常稳定。当然具体任务不同这些数字都需要根据实际曲线微调但比“什么都默认”要可靠得多。4. 常见问题与排查技巧实录4.1 训练震荡不收敛怎么排查训练中最常见的问题就是 loss 曲线震荡幅度很大或者一直在高位徘徊不下来。面对这个问题不要急着换优化器先按顺序排查。第一步看数据。我遇到过好几次loss 一直降不下来最后发现是数据标注有错、或者数据加载顺序存在批次不均衡。先跑一个小的基准实验在少量数据上看看模型能不能过拟合如果连小数据都学不进去那跟优化器关系不大要回到数据和模型结构上找问题。第二步看学习率。把学习率降低几个数量级试一试。如果降低后 loss 变得平缓但能稳定下降那就说明之前学习率太大。注意观察 loss 曲线的形态如果 loss 在某个值附近反复震荡但不发散通常是学习率偏大如果 loss 快速反弹到 NaN那就不仅是学习率问题还要看数值稳定性。第三步检查输入的数据尺度。有些场景下输入特征没有做归一化导致梯度在不同维度上量级差异巨大Adam 的归一化机制在极端情况下也无法处理。把输入标准化到均值为 0、方差为 1 附近问题往往迎刃而解。4.2 梯度消失与梯度爆炸的解决经验梯度爆炸的典型表现是 loss 突然变成 NaN 或者 inf。这时除了检查学习率还要检查网络结构里有没有不稳定的操作比如深层网络的连乘、注意力机制里的数值过大等。梯度裁剪可以兜底但根本解决方案是在模型设计上避免不稳定的数值流动比如使用残差连接、LayerNorm、合适的激活函数。梯度消失则表现得更加隐蔽损失曲线下降得极其缓慢网络似乎在学但效率低下。碰到这种问题我通常先查看网络各层的梯度范数。PyTorch 里可以通过注册钩子记录每层梯度的 L2 范数看看从输出层到输入层梯度是不是指数级地衰减。如果是说明网络深度过深或者激活函数选择不当比如在不合适的场景用了 Sigmoid。另外初始化方法也很重要。以前我习惯用 PyTorch 的默认初始化但后来发现在深层网络上Kaiming 初始化配合合理的残差结构对缓解梯度消失有明显的帮助。这里必须提醒一句优化器解决不了所有优化问题。有些模型结构本身就已经让损失曲面变得极度恶劣再好的优化器也只能勉强维持训练不崩溃。当你在优化器层面做了各种尝试仍然无解时回头审查网络结构往往才是真正的出路。4.3 从 Adam 切到 SGD 微调稳定精调的小技巧在实际项目中我们经常遇到这样的情况用 Adam 训练出来的模型验证集精度到了一个瓶颈怎么调学习率都上不去。这个时候可以试试“学习率衰减后切换优化器”的技巧。具体做法是先用 AdamW 以正常的 schedule 训练大部分步数让模型接近收敛然后在最后几百步把 optimizer 换成 SGD Momentum并把学习率调低一个数量级比如从 1e-4 降到 1e-5再做一段短训练。这个方法在一些论文里被称为 “Adam-to-SGD 切换”。背后的原理是Adam 在接近最优解时由于自适应的归一化机制参数的更新实际上不够“干净”而 SGD 在低学习率下能把参数精确地推向更优的位置。我用这个方法在几个视觉模型上做过实验验证集精度通常能提升 0.3% 到 0.5%虽然不多但在打比赛或者刷 benchmark 时可能就是决定性的差距。需要注意的坑是切换优化器后动量 buffer 会被清空SGD 初期会有一段自适应过程所以切换时的学习率一定不能太高否则会出现 loss 先反弹再下降甚至扩散的现象。4.4 Model-Optimizer 常见问题速查表现象首要怀疑因素快速尝试方案loss 完全不下学习率过小或数据/模型错误对数区间扫学习率试跑小数据过拟合实验loss 震荡剧烈学习率过大、batch size 过小降低学习率增大 batch size、调大权重衰减loss 突然变 NaN梯度爆炸、数值不稳定开启梯度裁剪降低学习率检查输入数据是否有 inf/nanloss 降到一半停住陷于鞍点或局部极小值调大初始学习率、增加 warmup换 AdamW 试一下训练正常但加速瓶颈学习率 schedule 不合理尝试 cosine schedule 代替 step decay之后精度上不去泛化欠佳切 SGD 微调、增强数据增强、调整权重衰减这张表不算全面但基本能覆盖我这两年遇到过的大部分训练异常。遇到问题时别慌按表里的逻辑一步步排查多数情况下 30 分钟内能定位到根因。5. 实战案例CLIP 风格多模态模型的优化器配置5.1 案例背景与显存约束说一个我自己完整跑过的实战案例帮助你把前面讲的原理串起来。去年我在有限显存条件下训练一个类 CLIP 的图文对比模型约束很明显batch size 不可能开大单卡只有 24G 显存但同时又要保证模型能用。在这种约束下我直接排除了 SGD Momentum因为大 batch 才有足够稳定的梯度支撑 SGD。最终选定 AdamW并把 batch size 定在 128 左右配合梯度累积实现等效 batch size 512。学习率则按照线性缩放法则先以 batch size 128 为基准定一个 2e-4 的初始值再随梯度累积步数适当调整到 4e-4 附近。5.2 实际配置与训练曲线分析最终采用的训练配置如下优化器AdamW初始学习率 4e-4betas(0.9, 0.98)eps1e-6学习率 scheduleCosineAnnealingLR最小学习率设为 1e-5Warmup前 500 步线性从 0 增加到 4e-4梯度裁剪max_norm1.0权重衰减0.2这个值偏大是为了在大 batch 隐式正则化变弱时弥补泛化损失等效 batch size512物理 batch 128 梯度累积 4 步这个配置跑下来loss 曲线非常健康前期 warmup 阶段缓慢下降中期快速收敛后期余弦衰减阶段平稳逼近最低点。在验证集上的 zero-shot 指标比同参数规模的 baseline 高出了一截。事后复盘几个关键选择起了决定性作用AdamW 保证了训练稳定性解耦权重衰减让模型的泛化表现更可预期梯度裁剪则兜底保护了训练过程让我可以放心地看着曲线而不必时刻提心吊胆。这次训练也让我意识到优化器从来不是孤立存在的。它和学习率 schedule、batch size、权重衰减、梯度裁剪是一整套协同系统。只看单点、盲目照抄别人的配置是最容易踩坑的路径。写在最后的个人体会在我做过的大大小小几十个模型训练项目里真正让我意识到优化器重要性的不是某一次理论推演而是一次次亲眼看到“换一个优化器、调一组参数之后同样的模型和数据最终结果天差地别”。SGD、Adam、AdamW 没有绝对的优劣它们是在不同约束条件下找到的平衡点。如果你只记得住一条经验我会说先花时间理解你的数据和任务再用系统性方式去扫超参数而不是盲目迷信某一个优化器。模型能不能训练起来、能训练到多好优化器是那个关键开关但操控开关的那个人才是决定最终结果的核心。最后再分享一个小技巧每一个新项目里我都会把优化器参数单独抽成一个配置类记录下每次实验的优化器类型、学习率、schedule、batch size 和最终指标。当你积累了十几条实验记录以后再看这些记录你对自己的任务域会有一个远超常人的判断力。这比网上任何“推荐配置”都更靠谱。
返回列表