想象一下,你深夜摸黑下山,手电筒只能照到脚前一小片地面。最笨的办法是:每走一步,只看脚下哪里更陡就朝哪迈一步,完全不管刚才走过的路,也不管前方是泥坑还是碎石坡。这就是最朴素的优化方法。后来,一个聪明的向导告诉你:你要一边记录自己来时的路线,形成“惯性记忆”;一边感受脚下地势的颠簸程度,决定这一步是大步还是小步。这位向导把两件事结合起来,就成了几乎人人都在用的明星算法——Adam。再后来,他又发现,长途跋涉时背包会越来越重,需要定期清理负重,这就是 AdamW 干的事。这篇文章就用这场“下山之旅”,带你搞懂 Adam 和 AdamW 的核心原理。
对 AI 从业者来说,理解 Adam/AdamW 不只是会调用一行 API,而是知道学习率、动量、权重衰减为什么这样设计,这能帮你在大模型训练、微调和调参时少走大量弯路。
可视化演示
通过一阶二阶矩估计与偏差校正流程、Adam与AdamW权重衰减机制对比、以及训练损失曲线对比三个模式,展示Adam优化器核心机制及AdamW的解耦权重衰减改进。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Adam一阶矩与二阶矩估计机制 一阶矩m为梯度指数移动平均反映方向二阶矩v为梯度平方的EMA衡量方差 偏差校正消除初始零值偏差使早期训练更稳定hat_m=m/(1-β1^t) m_t=β1·m_{t-1}+(1-β1)g_t, v_t=β2·v_{t-1}+(1-β2)g_t² 自适应学习率由hat_m/√hat_v决定
优化器演进:从SGD到Adam
深夜下山时,最原始的策略是:脚踩在哪,就低头看哪块地面更陡,然后朝更陡的反方向迈一步。你完全不记得刚才的路,也感知不到前方路况。这种“只看当下”的走法,在机器学习里对应最朴素的梯度下降法,也就是常说的 SGD(随机梯度下降)。它能走,但在峡谷形状的损失曲面上会来回震荡,速度很慢。
山区路况复杂,真正的向导不会单纯看脚下。他一来会记住自己刚才来的方向,形成一股“惯性”;二来会根据不同方向的路面松软程度,调整每一步的大小。把这两点组合起来,便有了 Adam。可以说,Adam 是“惯性记忆”和“逐参数自适应步长”的合体。
我们先用公式看一眼 SGD 是怎么迈步的:
SGD 的问题在于:它每一步都从零开始,没有记忆,也没有地形感知。Adam 的出现,就是给这位只盯脚下的新手,装上“记忆日记”和“地形感知仪”。
- 当前参数\(\theta_t\) 表示这一步之后模型参数的新位置,也就是你迈出这一步后站的地方。
- 原始位置\(\theta_{t-1}\) 是上一步的参数位置,表示你还没迈步时站的地方。
- 学习率\(\eta\) 是步长,控制“这一步迈多大”,可以理解为手电筒照亮的范围。
- 坡度方向\(\nabla J(\theta_{t-1})\) 是损失函数在当前点的梯度,也就是脚下最陡的下坡方向。
一阶矩动量:惯性日记
去过泥地的人都知道,如果你每一步都重新判断方向,很容易陷在坑里打转。相反,如果你记住自己刚才一直在往某个方向走,即使下一步脚下坡度有点小变化,你也会顺着惯性继续往前。这种“历史方向的记忆”,就是动量。
在 Adam 中,这个记忆叫“一阶矩”,用 \(m_t\) 表示。它不是简单地把所有历史梯度求平均,而是用指数滑动平均,给最近的梯度更高的权重。想象你有一本会褪色的日记:昨天的记录墨迹很浓,三天前的记录已经模糊,十天前的几乎看不见。这样,方向变化时,新旧梯度会互相抵消,曲线就平滑了。
一阶矩的更新公式如下:
有了这本“惯性日记”,下山时你不再每一步都犹豫,而是会顺着大体方向前进。这大大缓解了在狭窄山脊上左右乱晃的问题。
- 当前一阶矩\(m_t\) 是这一轮更新后的动量,也就是日记里最新的一行。
- 历史动量\(m_{t-1}\) 是上一轮的动量,代表你之前走过的路线印象。
- 记忆系数\(\beta_1\) 控制记住多少历史,通常取 0.9。它越大,说明历史路线在决策中占的比重越大。
- 当前梯度\(g_t\) 是这一轮算出来的梯度,相当于脚下最新感受到的坡度。
- 新信息权重\((1 - \beta_1)\) 是给当前梯度分配的新鲜度权重,保证新信息能渗透进旧记忆。
二阶矩地形:崎岖感知
即使是同一条山路,不同地段的松软程度也不同。有些地方是平缓的草坡,你可以大步流星;有些地方是碎石坡,每一步都在打滑,就得小步挪动。在模型参数空间里,不同参数对应的“路面颠簸程度”也完全不同。二阶矩 \(v_t\) 就是用来记录这种颠簸度的。
二阶矩记录的是“梯度平方”的滑动平均。为什么要用平方?因为平方能去掉方向信息,只留下“幅度”。幅度大意味着这个方向上最近一直在剧烈变化,就像碎石坡上的每一步都在剧烈摇晃;幅度小则说明路面很稳。Adam 在更新时会用它的平方根去缩放步长,让颠簸方向的步子自动变小。
二阶矩的公式长这样:
如果把一阶矩比作“知道该往哪边走”,二阶矩就是“知道这条路有多难走”。两本账一结合,Adam 才能既方向明确,又步法稳健。
- 当前二阶矩\(v_t\) 是这一轮累积的地形颠簸读数。
- 历史读数\(v_{t-1}\) 是上一轮的地形读数,相当于仪表盘上已有的刻度。
- 历史系数\(\beta_2\) 通常取 0.999,比 \(\beta_1\) 更大,说明对地形的记忆比方向记忆更持久。
- 当前梯度平方\(g_t^2\) 是这一轮梯度大小的平方,表示此刻脚下震动的剧烈程度。
- 新信息权重\((1 - \beta_2)\) 控制新鲜震动进入仪表的比例。
偏差校正:冷启动补偿
刚翻开一本新日记时,上面几乎没什么记录。第一轮更新时,\(m_0\) 和 \(v_0\) 都是零,新写进去的内容会被“稀释”得特别小。比如 \(\beta_1 = 0.9\) 时,第一轮的 \(m_1\) 只有真实梯度幅度的十分之一。这就像你只走了一步,却要凭这一步判断整条山路的方向,显然会低估。
为了补偿这种“冷启动偏差”,Adam 在估计出 \(m_t\) 和 \(v_t\) 后,会各除以一个校正因子。当 \(t\) 很小时,校正因子远小于 1,把偏小的估计放大回去;当训练进行很久后,校正因子趋近于 1,修正效果越来越弱。公式如下:
有了偏差校正,Adam 就不会在训练刚开始时因为“日记太薄”而走得畏畏缩缩。冷启动阶段的每一步,都能得到相对公平的估计。
- 校正后动量\(\hat{m}_t\) 是修正后的一阶矩,这才是真正用于更新方向的量。
- 原始动量\(m_t\) 是由滑动平均得到的原始估计,前几步会偏小。
- 衰减幂次\(\beta_1^t\) 是记忆系数反复衰减 \(t\) 次后的剩余比例,当 \(t\) 很小时它接近 \(\beta_1\)。
- 放大因子\(1 - \beta_1^t\) 的倒数就是放大倍数,训练越早期放大越多。
Adam完整更新流程
现在,把惯性日记、地形仪表盘和放大镜全部装进背包,Adam 的完整“下山流程”就是:先感受坡度,再更新日记,再修正读数,最后迈步。这四步在每一轮训练中都会重复执行。
Adam 最终更新参数的公式是:
Adam 之所以成为默认优化器,正是因为它同时解决了“方向震荡”和“步长选择”两大问题。到这一步,你已经完整掌握了 Adam 的每一次迈步。
- 校正后动量\(\hat{m}_t\) 是带方向信息的校正一阶矩,决定往哪个方向走。
- 校正后二阶矩\(\hat{v}_t\) 是校正后的二阶矩,描述该方向最近有多颠簸。
- 开根号\(\sqrt{\hat{v}_t}\) 把平方量级还原到梯度量级,代表颠簸程度的“振幅”。
- 防零项\(\varepsilon\) 是一个极小的正数,防止分母为零,通常取 \(10^{-8}\),相当于在仪表盘上加一道保险丝。
- 组合更新整个分式 \(\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}\) 就是“方向除以路况”后的标准步长。
AdamW:解耦权重衰减
长途下山时,背包里的东西会越来越多,如果不定期扔掉一些不重要的装备,你会越走越累。机器学习里也有类似问题:当模型参数过多时,容易“背诵”训练数据,这种现象叫过拟合。对付过拟合的常用手段是权重衰减——每次更新时,把参数往零的方向轻轻拉一点,相当于清空背包。
最早的做法是在损失函数里加一个 \(L2\) 正则项,让梯度里带上“参数本身的大小”。对 SGD 来说,这确实等价于每步都按比例缩小参数。但对 Adam 而言,这个正则梯度会混进一阶矩和二阶矩,再被 \(\sqrt{\hat{v}_t}\) 缩放,导致权重衰减的力度被地形感知扭曲。AdamW 的解决方法是:把“清理背包”和“下山走路”彻底分开,更新参数时独立减掉一个与梯度无关的 \(\lambda \theta_{t-1}\)。
AdamW 的更新公式如下:
这种“解耦”看起来只是把一项挪了位置,实际效果却天差地别。大模型预训练和微调任务中,AdamW 因为正则化行为更稳定,已经成为更受偏爱的选择。
- 自适应步\(\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}\) 就是 Adam 原来的更新方向,只负责“好好走路”。
- 权重衰减项\(\lambda \theta_{t-1}\) 是独立施加的衰减,直接把上一步参数按比例缩小一点。
- 衰减强度\(\lambda\) 控制清理背包的力度,值越大,参数被拉向零越快。
- 组合更新整个括号里的内容乘以学习率 \(\eta\) 后,从原参数中减去,完成一次“走路”加“卸包”。
AI 应用场景
大模型预训练与微调
阶段:模型训练与正则化调优
解决的问题:大模型参数量巨大,容易过拟合,同时训练数据噪声大、梯度变化剧烈。传统的 Adam 配合 \(L2\) 正则时,权重衰减会被二阶矩动态缩放,导致正则强度不可控。AdamW 将权重衰减与梯度矩估计解耦,使大模型训练中的正则化行为更可控,收敛也更稳定。
具体案例:在训练 GPT 风格的语言模型或 BERT 类模型时,往往使用 AdamW 作为默认优化器,配合一组精心调好的学习率和权重衰减系数,在几十亿甚至上千亿参数上稳定完成预训练。
图像分类与迁移学习微调
阶段:模型微调阶段
解决的问题:使用预训练模型在新数据集上微调时,如果继续沿用完整重新训练的优化策略,前几步很容易震荡。Adam 的偏差校正能让冷启动阶段更平稳,自适应步长也能让不同层在微调时获得合适的学习速度。
具体案例:在图像分类任务中加载 ImageNet 预训练权重,用 Adam 微调最后几层,通常能比纯 SGD 更早达到较高准确率;如果数据集很小且担心过拟合,则把优化器切换成 AdamW,并给权重衰减设置一个合适强度。
Python 演示
import math class AdamOptimizer: def __init__(self, lr=0.5, beta1=0.9, beta2=0.999, eps=1e-8, weight_decay=0.0, decoupled=True): self.lr = lr self.beta1 = beta1 self.beta2 = beta2 self.eps = eps self.weight_decay = weight_decay self.decoupled = decoupled self.m = {} self.v = {} self.t = 0 def step(self, params, grads): self.t += 1 for name, p in params.items(): g = grads[name] if (not self.decoupled) and self.weight_decay > 0: g = g + self.weight_decay * p m = self.m.get(name, 0.0) v = self.v.get(name, 0.0) m = self.beta1 * m + (1 - self.beta1) * g v = self.beta2 * v + (1 - self.beta2) * g * g self.m[name] = m self.v[name] = v m_hat = m / (1 - self.beta1 ** self.t) v_hat = v / (1 - self.beta2 ** self.t) update = m_hat / (math.sqrt(v_hat) + self.eps) if self.decoupled and self.weight_decay > 0: update = update + self.weight_decay * p params[name] = p - self.lr * update return params def train(): opt = AdamOptimizer(lr=0.5, weight_decay=0.01) params = {"x": 8.0} for i in range(20): x = params["x"] grads = {"x": 2.0 * x} params = opt.step(params, grads) if i % 5 == 0: print("step {}: x = {:.6f}".format(i, params["x"])) if __name__ == "__main__": train()
self.m = {}用字典记录每个参数的一阶矩,相当于给每个参数准备一页惯性日记。self.v = {}用字典记录每个参数的二阶矩,相当于给每个参数准备一张地形仪表盘。m = self.beta1 * m + (1 - self.beta1) * g指数滑动平均更新一阶矩,让历史方向和当前梯度按比例混合。v = self.beta2 * v + (1 - self.beta2) * g * g更新二阶矩,记录梯度平方的滑动平均。m_hat = m / (1 - self.beta1 ** self.t)对一阶矩做偏差校正,补偿冷启动阶段被稀释的估计。update = m_hat / (math.sqrt(v_hat) + self.eps)得到 Adam 的自适应更新量,颠簸方向的步子自动变小。update = update + self.weight_decay * p如果是 AdamW,把权重衰减独立加到更新量中,实现“走路”和“卸包”解耦。
常见误区
误区既然 Adam 能自适应调整步长,那把学习率设成 1 也应该没问题吧?反正它会自己收敛。
正解Adam 的自适应是“每个参数方向上的缩放”,但全局学习率 \(\eta\) 仍然是一个总开关。学习率太大,还没等动量稳定就已经发散;学习率太小,训练会慢得让人失去耐心。实际使用中,学习率通常设在 \(10^{-4}\) 到 \(10^{-2}\) 之间,必要时还要配合预热或衰减策略。
为何常见:因为 Adam 在默认参数下表现确实远好于盲调 SGD,很多人就误以为它不需要任何外部步长控制。
误区AdamW 与 Adam 的差别不就是权重衰减更强吗?我把 Adam 的 weight_decay 从 0.01 改成 0.1 不就行了?
正解AdamW 的核心不是数值大小,而是“位置”。标准 Adam 把正则梯度混入一阶矩和二阶矩,再被 \(\sqrt{\hat{v}_t}\) 缩放,导致衰减强度被地形感知扭曲。AdamW 把衰减项从梯度估计中拿出来,直接作用在参数上。单纯调大 Adam 的权重衰减,无法解决这种耦合问题。
为何常见:因为从论文公式看,两者都多了一项 \(\lambda\theta\),很多人只看到形式相似,没看到它在更新流程中的位置差异。
误区偏差校正只是一个小修正,训练步数多了早晚会趋近 1,省掉它应该无所谓。
正解训练长度足够时,校正因子确实接近 1,但训练刚开始的几十步同样会被省略后的偏差影响。此时模型对参数变化极其敏感,一步走偏可能影响后续整个学习轨迹。对于训练轮数极少的微调任务,忽略偏差校正的后果会更明显。
为何常见:人们习惯性认为“训练时间长,初始误差会被淹没”,忽视了冷启动阶段的轨迹对后续动力学的影响。
误区Adam 论文里写了 \(\beta_1=0.9\)、\(\beta_2=0.999\),那我在任何任务里都用这两个值就一定最好。
正解默认值只是通用起点。梯度噪声特别大的任务,比如强化学习,可能更适合更大的 \(\beta_2\) 或更小的 \(\beta_1\),让记忆更持久;训练样本变化快时,也需要重新平衡两个系数。默认参数很稳,但不代表最优。
为何常见:因为默认参数在大量任务上表现得足够好,初学者很难建立起“超参数也应该调试”的意识。
面试题
Q1. Adam中负责“记住历史方向”的是哪个量?
- A.一阶矩 m_t← 正确答案
- B.二阶矩 v_t
- C.学习率 η
- D.权重衰减 λ
答案:A。一阶矩 \(m_t\) 是对历史梯度的滑动平均,相当于下山时记录来路的惯性日记;二阶矩负责记录梯度剧烈程度,学习率是全局步长,权重衰减负责清理背包。
Q2. Adam 默认参数中,β2 通常取多少?
- A.0.9
- B.0.99
- C.0.999← 正确答案
- D.0.5
答案:C。论文默认 \(\beta_2=0.999\),意味着二阶矩对历史地形的记忆远远长于一阶矩;\(\beta_1=0.9\) 才是动量记忆系数。
Q3. 偏差校正主要解决什么问题?
- A.导致梯度消失问题
- B.初始矩估计偏差← 正确答案
- C.学习率不断增大
- D.参数直接变负数
答案:B。训练刚开始时,\(m_t\) 和 \(v_t\) 从 0 起步,会被严重低估。偏差校正像放大镜一样补偿冷启动偏差,让前几步的估计更真实,而不是解决梯度消失或符号问题。
Q4. AdamW 与 Adam 的最大区别是什么?
- A.学习率设置得更大
- B.权重衰减与更新解耦← 正确答案
- C.同时使用一阶二阶矩
- D.完全不需要动量
答案:B。AdamW 把权重衰减从梯度计算中拿出来,直接作用于参数更新,避免它被二阶矩缩放干扰;Adam 仍然使用动量,也仍然依赖一阶二阶矩。
Q5. 在 Adam 中,若某参数对应的 v_t 很小,会导致什么影响?
- A.该参数更新步长变小
- B.该参数更新步长变大← 正确答案
- C.该参数动量立即清零
- D.该参数学习率自动失效
答案:B。分母是 \(\sqrt{\hat{v}_t}+\varepsilon\),\(v_t\) 越小意味着这个方向地形越平坦,反而会让更新步长变大;就像在平缓路面上可以放心大步前进。
Q6. 指数滑动平均中 β1 越大表示什么?
- A.对近期梯度越敏感
- B.对历史梯度记忆越长← 正确答案
- C.对学习率影响越小
- D.对权重衰减作用越强
答案:B。\(\beta_1\) 越大,上一轮动量继承得越多,历史梯度占的比重越大,对新梯度的响应越慢,相当于日记中老内容褪色得更慢。
Q7. 在标准Adam中如果把权重衰减写进损失并参与矩估计,权重衰减强度会发生什么?
- A.被二阶矩动态缩放← 正确答案
- B.被一阶矩完全抵消
- C.被学习率永远固定
- D.被偏差校正消除
答案:A。L2 正则梯度混入 \(v_t\) 后,最终更新还要除以 \(\sqrt{\hat{v}_t}\),所以衰减强度会被动态缩放,不同参数上的正则力度不一致。AdamW 把衰减放到分母之外,正是为了消除这种扭曲。
Q8. Adam 的完整更新包含哪些步骤?
- A.计算目标函数的梯度← 正确答案
- B.更新一阶矩和二阶矩← 正确答案
- C.对矩估计进行偏差校正← 正确答案
- D.自动选择训练批次大小
答案:ABC。Adam 每轮先算梯度,再更新 \(m_t\) 和 \(v_t\),然后做偏差校正并更新参数;批次大小是训练流程中的外部选择,不由 Adam 自动决定。
Q9. 下列哪些情况更适合优先考虑 AdamW?
- A.大模型预训练与微调← 正确答案
- B.对权重衰减敏感的任务← 正确答案
- C.只训练十步的调试实验
- D.参数必须保持整数的情况
答案:AB。AdamW 的解耦权重衰减在参数规模大、正则敏感的任务上表现更稳定;只训练十步和参数整数约束都不是优化器能解决的核心问题。
Q10. 二阶矩 v_t 估计的是什么?
- A.梯度平方的滑动平均← 正确答案
- B.历史梯度波动的大小← 正确答案
- C.样本标签的方差
- D.参数本身的绝对值
答案:AB。二阶矩记录 \(g_t^2\) 的滑动平均,所以它既能理解成梯度平方的平均,也能理解成历史波动幅度;它不关心标签分布,也不关心参数大小。