
1. 为什么优化器值得我们单独拿出来讲从训练一个小网络到跑大模型只要是做深度学习每天都要跟优化器打交道。我刚入行那会儿不管什么任务都无脑用SGD后来发现收敛又慢还容易陷入局部震荡换成Adam之后训练体验直接上了一个台阶。但说实话当时我对Adam的理解也就停留在自适应学习率、效果挺好这个层面。直到后来调参踩了坑才真正沉下心把Adam的原理和特性抠了一遍。这篇文章想把Adam优化器讲透彻。我会从最朴素的梯度下降说起逐步拆解动量Momentum和自适应学习率这两个核心思想是怎么一步步演化成Adam的然后用通俗的方式解释Adam的完整更新公式、每一步到底在干什么再结合我在实际训练中遇到的收敛不稳、学习率敏感、泛化性争议等问题给出一些可以直接落地的使用建议。适合正在学习深度学习基础、想搞懂优化器原理的初学者也适合已经用过Adam但还想更深入理解它、想要真正调好超参数的同学。很多讲Adam的文章一上来就是公式推导看完很容易晕。我的思路是先建立直觉——为什么需要动量为什么需要自适应学习率当你明白这两个动机之后Adam的公式就不是死记硬背而是顺理成章的事。2. 从SGD到Adam优化器进化路上的两个关键痛点2.1 原始的梯度下降下山问题的朴素解法先想象一个场景你站在一座山上周围一片漆黑手里只有一个高度计怎么走到山谷最低点最朴素的策略就是走一步看一步——当前位置哪个方向下降最快就往那个方向迈一步。这个下降最快的方向就是负梯度方向这个策略就是梯度下降法。参数更新的公式长这样θ θ - η * g其中θ是参数η是学习率g是当前梯度。这个思路看起来无懈可击但实际用起来问题不少。最典型的就是学习率的选择学习率太小训练半天loss下不去像蜗牛爬学习率太大loss直接震荡甚至发散。更麻烦的是同一个学习率对网络中所有参数一视同仁但实际情况是不同参数的梯度尺度可能差好几个数量级有的参数梯度大得像过山车有的参数梯度小得像平地走路。这就引出了第一个痛点全局统一的学习率没有办法适配不同参数的更新需求。有些参数需要大步快跑有些参数需要小步慢走一刀切必然顾此失彼。2.2 第二个痛点梯度方向抖动与SGD的醉汉式下降SGD还有一个让人头疼的问题它每次只用当前batch的梯度来更新参数。由于batch是采样出来的带有一定的随机性导致梯度方向经常抖动。尤其在损失函数某个方向特别陡、另一个方向特别缓的时候梯度会在陡峭方向来回震荡像喝醉了酒走路东倒西歪明明大致方向对了但就是走不快收敛效率很低。怎么解决这个问题一个直观的想法是不要只看当前的梯度方向还要参考过去一段时间里总体上在往哪个方向走。如果过去几步都在向右走那当前这一小步即使有点向左的抖动也不该太当真。这个思想就是动量Momentum说通俗点就是给更新过程加一个惯性。2.3 自适应学习率的雏形让每个参数有自己的步长动量解决了方向抖动问题但还没有解决不同参数需要不同步长的问题。某个参数过去的梯度一直很大说明它在陡峭方向上这时候我们应该用较小的步长防止来回震荡某个参数过去的梯度一直很小说明它在平缓方向上这时候应该用较大的步长加速通过。注意这里说的步长不是指复制一份全局学习率而是指在全局学习率的基础上针对每个参数乘一个系数。这个系数怎么算一个自然的想法是看梯度历史的大小梯度大系数就小一点梯度小系数就大一点。这就是自适应学习率的基本雏形也是AdaGrad、RMSProp等一系列优化器的核心思想。Adam做的事情说白了就是把上面两个思想——动量机制和自适应学习率机制——融合在一起。它既用一阶动量梯度的指数加权移动平均来平滑梯度方向又用二阶动量梯度平方的指数加权移动平均来自适应调节每个参数的更新步长。这就是Adam名字的来历Adaptive Moment Estimation自适应矩估计。3. Adam的核心机制拆解指数移动平均与偏差修正3.1 什么是指数加权移动平均在理解Adam公式之前必须先搞懂指数加权移动平均Exponential Weighted Moving Average, EWMA。这个思想出现在很多算法里并不复杂。假设我们有一串梯度值g1、g2、g3、...、gt我们想得到一个平滑后的估计Mt让它代表最近一段时间的平均梯度方向。直接用算术平均的话需要记住所有历史梯度内存开销大且对近期变化不敏感。指数加权移动平均的做法是Mt β1 * Mt-1 (1 - β1) * gt这个式子看起来简单含义却很丰富当前时刻的平滑值由两部分组成一部分是上一时刻的平滑值乘以衰减系数β1另一部分是当前梯度乘以(1-β1)。β1越接近1历史信息的占比越大平滑效果越强但对新梯度的响应越慢。β1越接近0当前梯度占比越大反应越快但平滑效果越弱。展开来看Mt实际上是所有历史梯度的加权求和权重按照β1的指数衰减Mt (1-β1) * [gt β1*gt-1 β1²*gt-2 ...]距离当前时刻越远的梯度权重越小这就是指数加权的含义。约等于说Mt看重的是一段时间窗口内的平均梯度窗口大小大约是1/(1-β1)。当β10.9时相当于看最近10步的平均当β10.99时相当于看最近100步的平均。3.2 Adam的两路估计一阶矩与二阶矩Adam维护了两个状态变量一阶矩估计mt对梯度gt做指数加权移动平均用来估计梯度的均值方向。这对应前面说的动量机制让更新方向带上惯性减少震荡。二阶矩估计vt对梯度平方gt²做指数加权移动平均用来估计梯度的大小方差信息。这对应自适应学习率机制让每个参数根据梯度历史大小获得不同的更新步长。两个矩估计的更新公式是mt β1 * mt-1 (1 - β1) * gt vt β2 * vt-1 (1 - β2) * gt²默认参数中β1取0.9β2取0.999。这两个值的含义是一阶矩大约看最近10步的平均梯度二阶矩大约看最近1000步的梯度平方平均。β2取得非常大说明二阶矩对历史非常长情对梯度大小的估计非常平滑。3.3 偏差修正为什么必须有这一步直接用上面的mt和vt去更新参数在训练初期会有一个很大的问题mt和vt的初始值都是0训练刚开始时更新公式中的mt和vt会被初始的0拉得很小。也就是说即使第一步的梯度gt很大mt也会因为乘了(1-β1)而变得很小导致最初的几步更新得特别慢。同理vt也会被初始的0拉低但vt在分母上分母被拉低反而会导致步长偏大。初期的不稳定很影响收敛。解决办法就是偏差修正Bias Correction。在每一步更新前对mt和vt分别做修正m_hat_t mt / (1 - β1^t) v_hat_t vt / (1 - β2^t)这里的t是当前迭代步数。当t很小的时候β1^t还比较大1-β1^t比较小除以一个小数相当于放大正好补偿初期被0拉低的影响。随着t越来越大β1^t趋近于0修正系数趋近于1修正的作用就逐渐消失了。这里给不熟悉的朋友一个直觉如果β10.9、β20.999第一步时mt0.1gt除以(1-0.9)0.1后正好等于gtvt0.001gt²除以(1-0.999)0.001后正好等于gt²。所以偏差修正后的第一步矩估计就等于当前梯度的真实值这就避免了初期偏差。3.4 完整的Adam更新过程把前面的模块拼起来Adam在每一步的完整更新过程如下计算当前batch的梯度gt。更新一阶矩估计mt β1 * mt-1 (1 - β1) * gt。更新二阶矩估计vt β2 * vt-1 (1 - β2) * gt²。偏差修正m_hat_t mt / (1 - β1^t)v_hat_t vt / (1 - β2^t)。参数更新θ_t θ_t-1 - η * m_hat_t / (√v_hat_t ε)。公式里的ε是一个很小的常数默认为1e-8作用是防止除零。当某个参数的梯度非常小、v_hat_t接近0的时候除以一个非常小的数会导致更新步长爆炸ε就是用来兜底的。现在可以直观地看一下这个更新公式到底做了什么分子上的m_hat_t是有方向的、经过平滑的梯度方向比原始梯度更稳定。分母上的√v_hat_t是一个标量针对每个参数它衡量的是这个参数的梯度历史大小。梯度历史大分母就大更新步长就被压小梯度历史小分母就小更新步长就被放大。整体效果是Adam在梯度平滑的基础上为每个参数自动调节学习率相当于给整个训练过程装了一个智能油门。4. Adam为什么work直觉层面的深度解读4.1 动量机制如何解决震荡问题回到之前醉汉下坡的比喻。有了动量之后参数更新方向不再完全由当前梯度决定而是由历史累积方向当前梯度共同决定。就算当前batch的梯度突然往左偏了一点如果过去几步累积的方向是向右的最终实际更新的方向仍然大致向右。这样一来高频的、来回抖动的小波动就被过滤掉了模型走出一条更平滑的收敛路径。这在梯度方向变化不规律、噪声较大的场景下特别有效。我在训练一些带随机数据增强的视觉模型时SGD的loss曲线总带着明显的毛刺换用Adam之后曲线明显更光滑很大程度上就是动量的功劳。4.2 自适应学习率如何解决尺度问题不同参数的梯度尺度差异是深度学习里非常普遍的现象。比如在Transformer架构中Embedding层的梯度尺度和某些深层全连接层的梯度尺度可能差很多。用固定的学习率梯度大的参数容易震荡梯度小的参数又更新太慢训练效率非常低下。Adam的二阶矩相当于给每个参数维护了一把标尺记录这个参数的历史梯度尺度。更新时拿当前的梯度除以这把标尺实际上是做了一个粗略的归一化梯度大的参数被压低梯度小的参数被放大让所有参数大致都在一个相近的更新尺度上。这就省去了手动为不同参数设置不同学习率的麻烦。4.3 自适应学习率的实际作用边界需要特别强调的是Adam的自适应能力是每个参数基于自身历史梯度的而不是每个时刻全局自适应的。有很多人误以为Adam的学习率会自动变大变小不同训练阶段自动调整其实不完全对。Adam调节的是每个参数实际更新步长相对于全局学习率的比例全局学习率η本身仍然是一个需要人工设定的常数。打个比方全局学习率是油门的基础开度而自适应系数是每个气缸自己的喷油修正值。气缸会根据自身情况微调喷油量但驾驶员踩油门的幅度仍然是决定整体动力水平的关键因素。所以Adam并不是不需要调学习率而是不需要针对每个参数分别调学习率全局学习率依然需要你根据实际情况去尝试和设置。4.4 一阶矩与二阶矩的配合逻辑Adam的设计妙处在于一阶矩和二阶矩不是独立的而是相互配合的。一阶矩告诉你往哪个方向走二阶矩告诉你这一步该走多快。当某个方向上梯度一直比较稳定比如持续朝着一个方向下降二阶矩会逐渐稳定下来此时实际步长主要由全局学习率和一阶矩决定保证稳定推进。当某个方向上梯度忽大忽小、极不稳定二阶矩会变大实际步长被压缩避免在震荡方向上浪费过多能量。这种配合很像一个经验丰富的司机在开车方向盘方向一阶矩看路基的整体走向油门大小二阶矩看路面的颠簸程度。路面越颠簸油门越保守路面越平顺油门越敢踩。Adam在大量实际任务中表现稳定的关键就在于此。5. Adam的默认参数和关键超参数该怎么调5.1 默认参数配置PyTorch中Adam的默认参数是lr0.001, betas(0.9, 0.999), eps1e-08, weight_decay0TensorFlow中略有不同但核心一致。这个默认配置是经过大量实践验证的万金油组合多数任务直接用默认值就能跑出不错的效果。具体解读一下lr0.001这是Adam最推荐的全局学习率初始值。相比SGD动辄需要0.01、0.1甚至更高Adam的默认学习率低一个数量级因为自适应机制会放大小梯度参数的步长学习率太高容易导致训练初期不稳定。betas(0.9, 0.999)β1控制一阶矩的历史窗口β2控制二阶矩的历史窗口。这两个值在多数任务中不需要调整。eps1e-8防止除零的极小常数一般不调整。5.2 学习率是唯一需要重点关注的超参数虽然Adam的默认参数很省心但不代表完全不用调。在我实际训练中最需要花心思的就是学习率。Adam对学习率的敏感度比SGD低但绝对存在学习率太高训练初期loss会出现明显的震荡甚至上升学习率太低模型收敛缓慢训练半天loss下降得极其有限。通常的做法是先用默认的0.001跑一遍小规模实验比如几十个step观察loss的下降速度。如果loss下降太快且伴随震荡就调低到0.0005或0.0003如果loss几乎不动就调高到0.002或0.003。也可以配合学习率预热warmup和余弦退火等策略让学习率在训练过程中动态变化这些策略在Transformer类模型上效果尤其明显。5.3 关于weight_decayAdamW和Adam的差异Adam的默认weight_decay0意思是默认不做L2正则化。很多人在用Adam时会习惯性地加一个weight_decay参数但这里有个容易踩的坑Adam中的weight_decay实现方式与SGD不同实际效果也可能不符合预期。PyTorch中Adam的weight_decay是直接加在梯度上的L2正则化而AdamW把权重衰减和梯度更新解耦只在参数更新时直接对权重做衰减。从理论上讲AdamW的正则化效果更干净、更符合权重衰减的本意。实践上在使用预训练Transformer做微调时AdamW几乎成了默认选择。如果你只是跑一些中小规模的CNN或MLPAdam加weight_decay也能用但如果你想精细控制正则化效果建议直接用AdamW。5.4 偏差修正系数对收敛初期的影响前面讲了偏差修正的原理这里补充一个实操感受训练的前几十步修正前后的差异非常明显。不修正的话一阶矩被初始的0拉低模型在最初几步几乎原地踏步二阶矩被拉低则会让步长偏大造成早期的不稳定。有了修正之后模型从第一步起就能以一个相对正常的尺度开始更新相当于起步就进入状态。很多深度学习框架已经默认帮你处理了偏差修正但如果你自己实现优化器记得不要漏掉这一步否则可能出现训练前几步loss异常或波动剧烈的怪问题。6. 使用Adam时常见的坑和实战建议6.1 坑一loss在后期出现微小震荡是正常的不必紧张很多刚用Adam的同学会发现训练到后期loss曲线不像SGD那么平滑总带着一点点高频的小抖动。这是因为Adam的自适应学习率机制对每个参数都会根据近期梯度大小动态调整步长即使到了收敛区域某些参数的更新步长依然不会变得特别小导致loss在最优值附近小幅波动。这种情况一般不需要过度干预。如果实在介意可以配合学习率衰减策略在训练后期把学习率降下来loss曲线会稳定很多。如果震荡非常剧烈、影响到模型收敛那大概率是学习率设置过高适当调低即可。6.2 坑二训练初期loss异常大或发散先检查数据我遇到过不少用了Adam反而发散的情况最后排查下来问题往往不在优化器而在数据或模型初始化。比如输入没有做标准化、标签分布极端不均衡或者模型输出层初始化不合理都可能导致梯度中出现极大的异常值。Adam虽然用二阶矩做了归一化但偶尔一两个异常大的梯度仍然可能让参数更新跨出安全区。因此当你发现Adam训练异常时不要第一时间怀疑优化器。先检查输入数据是否已经过标准化或归一化处理。标签是否存在异常值或严重的类别不平衡。模型输出层是否配合了恰当的激活函数和初始化。是否存在NaN或Inf的梯度。6.3 坑三Adam在小批量场景下对batch size敏感Adam对batch size的敏感度比SGD更高。小batch size下梯度噪声大二阶矩的估计也受噪声影响容易出现步长抖动。大batch size下梯度更稳定Adam的收敛效果通常也更好。如果你在用小batch size训练时发现效果不好可以考虑适当增大batch size或者将β2从0.999调大到0.9999让二阶矩的估计更平滑。6.4 坑四泛化性争议——Adam收敛快但最终精度不一定最高关于Adam和SGD的泛化性之争深度学习中讨论了很多年。一个常见的现象是Adam收敛速度快但最终在验证集上的精度可能略低于调好的SGD或带动量的SGD。原因可能有两点Adam的自适应步长在训练后期可能会造成过拟合因为它在参数空间里探索得比较激进。Adam在收敛区域没有像SGD那样平滑地落入平坦极小值有研究认为Adam更容易到达尖锐的极小值导致泛化能力稍差。但这并不意味着Adam不能用。实际项目中我常用的策略是先用Adam快速跑通模型、找到合适的网络结构和超参数范围再切换到SGD或AdamW进行最终训练。先用Adam做实验探索再用SGD精调收尾这样既利用了Adam的快速收敛优势又兼顾了SGD的最终精度表现。如果项目对训练速度要求很高直接使用AdamW也是可接受的折中方案。6.5 实用技巧学习率预热Warmup的重要性在使用Adam训练Transformer类模型时学习率预热几乎是必须的。原因在于训练初期由于参数初始化不当和模型内部各层尚未协调好梯度的方差非常大一阶矩和二阶矩的估计都很不稳定。此时如果学习率直接拉满很容易让模型参数发生剧烈偏移导致训练不稳定。预热策略的做法是在训练开始的若干步例如总步数的1/10或者固定的1000步内让学习率从0线性上升到预设值。这样模型在初期不会大步更新等参数和梯度分布稳定后再逐渐放开学习率。配合上余弦退火或者分段衰减效果会更好。6.6 关于显存和计算开销的说明Adam相比SGD多维护了两个状态变量一阶矩和二阶矩因此显存占用大约多出一倍。对于大模型或者大batch size训练显存常常是一大瓶颈。如果显存不足可以考虑使用Adafactor等简化版自适应优化器或者减少batch size。另外训练时开启混合精度AMP可以显著减少显存占用和计算时间Adam在低精度下的表现也还是比较稳定的但要注意保持更新过程中的数值稳定性。7. 从Adam到AdamW一个重要的改进变体7.1 AdamW到底改了什么AdamW是在Adam的基础上把权重衰减weight decay从梯度累加中分离出来得到的。公式上Adam的weight_decay是直接加到梯度上的g_t g_t weight_decay * θ_t-1而AdamW是在参数更新后才做权重衰减θ_t θ_t-1 - η * m_hat_t / (√v_hat_t ε) - η * weight_decay * θ_t-1看起来只是计算顺序的区别但实际效果差异不小。Adam在加权重衰减时由于自适应学习率机制的存在不同参数的衰减幅度不一致正则化效果变得不可控。AdamW解耦后权重衰减是独立于自适应机制之外的实现的效果更稳定、可预测。7.2 为什么预训练模型几乎都用AdamW如果你看过近几年预训练模型的训练代码会发现AdamW几乎成了标配。原因很实在预训练模型的参数量巨大训练步数多正则化的稳定性非常重要。AdamW在保持Adam快速收敛特性的同时提供更可控的正则化效果在大型模型上表现稳定几乎不挑任务。当然这不是说所有任务都必须用AdamW。对于中小规模模型Adam加合适的weight_decay也完全可用。选择哪个更多取决于你是否需要精细控制权重衰减的效果。7.3 什么时候坚持用SGD前面夸了Adam和AdamW这么多也想说句公道话SGD并没有被淘汰。在某些任务中SGD最终的泛化表现依然很好尤其是图像分类等传统任务上调好的SGD配合动量、学习率衰减最终精度往往不输Adam甚至更好。加上SGD的实现极其简单、超参数少、显存占用低在工业界依然有很高的使用率。我的建议是做探索、做对比实验、跑baseline时无脑用Adam高效省心到了追求最终精度的时候好好调一下SGD。这个经验来自我多次比赛的实践适应大多数深度学习场景。8. 从看懂到用对我的一些个人体会Adam优化器看起来只是一个自适应梯度更新算法但真正理解它之后你对整个深度学习训练过程都会有更清晰的认识。它让我意识到训练一个模型不是简单地把数据丢进网络里而是一个需要从梯度结构、损失曲面特性、数据分布等多个角度统筹考虑的工程问题。最后分享几个我觉得特别有价值的实操体会第一先跑小规模实验判断优化器是否合适。在正式大规模训练之前用小数据集、少步数快速验证一下优化器和学习率的搭配能帮你节省大量时间。很多训练失败案例原因其实就是优化器或学习率选得不合适。第二loss曲线的形态是最好的调参指南。如果loss不下降先别急着换模型观察一下损失曲线的坡度和形态——它是在缓慢下降还是完全不动初期震荡还是后期震荡这些信息能直接告诉你该调学习率、该加预热还是该考虑优化器本身的问题。第三多尝试组合策略。优化器不是孤立存在的。Adam配上合适的学习率调度器、配合正则化手段、配合梯度裁剪效果往往远好于单独调一个优化器。比如训练RNN或Transformer时梯度裁剪几乎是标配而学习率预热带给训练的稳定性提升也非常明显。这些策略组合起来才会让Adam发挥出真正的实力。优化器只是训练工具箱里的一件工具真正重要的是你如何理解和运用它。希望这篇关于Adam优化器的分享能帮你把它用得明明白白、踏踏实实。