想象一下,你深夜摸黑下山,手电筒只能照到脚前一小片地面。最笨的办法是:每走一步,只看脚下哪里更陡就朝哪迈一步,完全不管刚才走过的路,也不管前方是泥坑还是碎石坡。这就是最朴素的优化方法。后来,一个聪明的向导告诉你:你要一边记录自己来时的路线,形成“惯性记忆”;一边感受脚下地势的颠簸程度,决定这一步是大步还是小步。这位向导把两件事结合起来,就成了几乎人人都在用的明星算法——Adam。再后来,他又发现,长途跋涉时背包会越来越重,需要定期清理负重,这就是 AdamW 干的事。这篇文章就用这场“下山之旅”,带你搞懂 Adam 和 AdamW 的核心原理。

岗位关联

对 AI 从业者来说,理解 Adam/AdamW 不只是会调用一行 API,而是知道学习率、动量、权重衰减为什么这样设计,这能帮你在大模型训练、微调和调参时少走大量弯路。

目录
  1. 可视化演示
  2. 优化器演进:从SGD到Adam
  3. 一阶矩动量:惯性日记
  4. 二阶矩地形:崎岖感知
  5. 偏差校正:冷启动补偿
  6. Adam完整更新流程
  7. AdamW:解耦权重衰减
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

通过一阶二阶矩估计与偏差校正流程、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 的出现,就是给这位只盯脚下的新手,装上“记忆日记”和“地形感知仪”。

θt=θt−1−η∇J(θt−1)\displaystyle \theta_t = \theta_{t-1} - \eta \nabla J(\theta_{t-1})
这个公式的意思是:当前位置减去一个按坡度缩放的小步长,就是新位置。
  1. 当前参数\(\theta_t\) 表示这一步之后模型参数的新位置,也就是你迈出这一步后站的地方。
    θt\theta_t
  2. 原始位置\(\theta_{t-1}\) 是上一步的参数位置,表示你还没迈步时站的地方。
    θt−1\theta_{t-1}
  3. 学习率\(\eta\) 是步长,控制“这一步迈多大”,可以理解为手电筒照亮的范围。
    η\eta
  4. 坡度方向\(\nabla J(\theta_{t-1})\) 是损失函数在当前点的梯度,也就是脚下最陡的下坡方向。
    ∇J(θt−1)\nabla J(\theta_{t-1})

一阶矩动量:惯性日记

去过泥地的人都知道,如果你每一步都重新判断方向,很容易陷在坑里打转。相反,如果你记住自己刚才一直在往某个方向走,即使下一步脚下坡度有点小变化,你也会顺着惯性继续往前。这种“历史方向的记忆”,就是动量。

在 Adam 中,这个记忆叫“一阶矩”,用 \(m_t\) 表示。它不是简单地把所有历史梯度求平均,而是用指数滑动平均,给最近的梯度更高的权重。想象你有一本会褪色的日记:昨天的记录墨迹很浓,三天前的记录已经模糊,十天前的几乎看不见。这样,方向变化时,新旧梯度会互相抵消,曲线就平滑了。

一阶矩的更新公式如下:

有了这本“惯性日记”,下山时你不再每一步都犹豫,而是会顺着大体方向前进。这大大缓解了在狭窄山脊上左右乱晃的问题。

mt=β1mt−1+(1−β1)gt\displaystyle m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t
可以把 \(m_t\) 理解成“最近一段时间梯度方向的加权平均”。
  1. 当前一阶矩\(m_t\) 是这一轮更新后的动量,也就是日记里最新的一行。
    mtm_t
  2. 历史动量\(m_{t-1}\) 是上一轮的动量,代表你之前走过的路线印象。
    mt−1m_{t-1}
  3. 记忆系数\(\beta_1\) 控制记住多少历史,通常取 0.9。它越大,说明历史路线在决策中占的比重越大。
    β1\beta_1
  4. 当前梯度\(g_t\) 是这一轮算出来的梯度,相当于脚下最新感受到的坡度。
    gtg_t
  5. 新信息权重\((1 - \beta_1)\) 是给当前梯度分配的新鲜度权重,保证新信息能渗透进旧记忆。
    1−β11 - \beta_1

二阶矩地形:崎岖感知

即使是同一条山路,不同地段的松软程度也不同。有些地方是平缓的草坡,你可以大步流星;有些地方是碎石坡,每一步都在打滑,就得小步挪动。在模型参数空间里,不同参数对应的“路面颠簸程度”也完全不同。二阶矩 \(v_t\) 就是用来记录这种颠簸度的。

二阶矩记录的是“梯度平方”的滑动平均。为什么要用平方?因为平方能去掉方向信息,只留下“幅度”。幅度大意味着这个方向上最近一直在剧烈变化,就像碎石坡上的每一步都在剧烈摇晃;幅度小则说明路面很稳。Adam 在更新时会用它的平方根去缩放步长,让颠簸方向的步子自动变小。

二阶矩的公式长这样:

如果把一阶矩比作“知道该往哪边走”,二阶矩就是“知道这条路有多难走”。两本账一结合,Adam 才能既方向明确,又步法稳健。

vt=β2vt−1+(1−β2)gt2\displaystyle v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2
它衡量的是“历史梯度有多剧烈”,相当于一张描述地形颠簸程度的仪表盘。
  1. 当前二阶矩\(v_t\) 是这一轮累积的地形颠簸读数。
    vtv_t
  2. 历史读数\(v_{t-1}\) 是上一轮的地形读数,相当于仪表盘上已有的刻度。
    vt−1v_{t-1}
  3. 历史系数\(\beta_2\) 通常取 0.999,比 \(\beta_1\) 更大,说明对地形的记忆比方向记忆更持久。
    β2\beta_2
  4. 当前梯度平方\(g_t^2\) 是这一轮梯度大小的平方,表示此刻脚下震动的剧烈程度。
    gt2g_t^2
  5. 新信息权重\((1 - \beta_2)\) 控制新鲜震动进入仪表的比例。
    1−β21 - \beta_2

偏差校正:冷启动补偿

刚翻开一本新日记时,上面几乎没什么记录。第一轮更新时,\(m_0\) 和 \(v_0\) 都是零,新写进去的内容会被“稀释”得特别小。比如 \(\beta_1 = 0.9\) 时,第一轮的 \(m_1\) 只有真实梯度幅度的十分之一。这就像你只走了一步,却要凭这一步判断整条山路的方向,显然会低估。

为了补偿这种“冷启动偏差”,Adam 在估计出 \(m_t\) 和 \(v_t\) 后,会各除以一个校正因子。当 \(t\) 很小时,校正因子远小于 1,把偏小的估计放大回去;当训练进行很久后,校正因子趋近于 1,修正效果越来越弱。公式如下:

有了偏差校正,Adam 就不会在训练刚开始时因为“日记太薄”而走得畏畏缩缩。冷启动阶段的每一步,都能得到相对公平的估计。

m^t=mt1−β1t,v^t=vt1−β2t\displaystyle \hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}
这相当于给前几页日记自动加一个“放大镜”,让读者看到真实内容。
  1. 校正后动量\(\hat{m}_t\) 是修正后的一阶矩,这才是真正用于更新方向的量。
    m^t\hat{m}_t
  2. 原始动量\(m_t\) 是由滑动平均得到的原始估计,前几步会偏小。
    mtm_t
  3. 衰减幂次\(\beta_1^t\) 是记忆系数反复衰减 \(t\) 次后的剩余比例,当 \(t\) 很小时它接近 \(\beta_1\)。
    β1t\beta_1^t
  4. 放大因子\(1 - \beta_1^t\) 的倒数就是放大倍数,训练越早期放大越多。
    11−β1t\frac{1}{1 - \beta_1^t}

Adam完整更新流程

现在,把惯性日记、地形仪表盘和放大镜全部装进背包,Adam 的完整“下山流程”就是:先感受坡度,再更新日记,再修正读数,最后迈步。这四步在每一轮训练中都会重复执行。

Adam 最终更新参数的公式是:

Adam 之所以成为默认优化器,正是因为它同时解决了“方向震荡”和“步长选择”两大问题。到这一步,你已经完整掌握了 Adam 的每一次迈步。

θt=θt−1−ηm^tv^t+ε\displaystyle \theta_t = \theta_{t-1} - \eta \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}
分子是方向记忆,分母是地形剧烈程度,二者相除后乘上全局步长,就得到这一步该走多远。
  1. 校正后动量\(\hat{m}_t\) 是带方向信息的校正一阶矩,决定往哪个方向走。
    m^t\hat{m}_t
  2. 校正后二阶矩\(\hat{v}_t\) 是校正后的二阶矩,描述该方向最近有多颠簸。
    v^t\hat{v}_t
  3. 开根号\(\sqrt{\hat{v}_t}\) 把平方量级还原到梯度量级,代表颠簸程度的“振幅”。
    v^t\sqrt{\hat{v}_t}
  4. 防零项\(\varepsilon\) 是一个极小的正数,防止分母为零,通常取 \(10^{-8}\),相当于在仪表盘上加一道保险丝。
    ε\varepsilon
  5. 组合更新整个分式 \(\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}\) 就是“方向除以路况”后的标准步长。
    m^tv^t+ε\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}

AdamW:解耦权重衰减

长途下山时,背包里的东西会越来越多,如果不定期扔掉一些不重要的装备,你会越走越累。机器学习里也有类似问题:当模型参数过多时,容易“背诵”训练数据,这种现象叫过拟合。对付过拟合的常用手段是权重衰减——每次更新时,把参数往零的方向轻轻拉一点,相当于清空背包。

最早的做法是在损失函数里加一个 \(L2\) 正则项,让梯度里带上“参数本身的大小”。对 SGD 来说,这确实等价于每步都按比例缩小参数。但对 Adam 而言,这个正则梯度会混进一阶矩和二阶矩,再被 \(\sqrt{\hat{v}_t}\) 缩放,导致权重衰减的力度被地形感知扭曲。AdamW 的解决方法是:把“清理背包”和“下山走路”彻底分开,更新参数时独立减掉一个与梯度无关的 \(\lambda \theta_{t-1}\)。

AdamW 的更新公式如下:

这种“解耦”看起来只是把一项挪了位置,实际效果却天差地别。大模型预训练和微调任务中,AdamW 因为正则化行为更稳定,已经成为更受偏爱的选择。

θt=θt−1−η(m^tv^t+ε+λθt−1)\displaystyle \theta_t = \theta_{t-1} - \eta \left( \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon} + \lambda \theta_{t-1} \right)
括号里第一项是自适应下山步,第二项是独立的背包清理动作,两者互不干扰。
  1. 自适应步\(\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}\) 就是 Adam 原来的更新方向,只负责“好好走路”。
    m^tv^t+ε\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon}
  2. 权重衰减项\(\lambda \theta_{t-1}\) 是独立施加的衰减,直接把上一步参数按比例缩小一点。
    λθt−1\lambda \theta_{t-1}
  3. 衰减强度\(\lambda\) 控制清理背包的力度,值越大,参数被拉向零越快。
    λ\lambda
  4. 组合更新整个括号里的内容乘以学习率 \(\eta\) 后,从原参数中减去,完成一次“走路”加“卸包”。
    η(m^tv^t+ε+λθt−1)\eta \left( \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \varepsilon} + \lambda \theta_{t-1} \right)

AI 应用场景

大模型预训练与微调

阶段:模型训练与正则化调优

解决的问题:大模型参数量巨大,容易过拟合,同时训练数据噪声大、梯度变化剧烈。传统的 Adam 配合 \(L2\) 正则时,权重衰减会被二阶矩动态缩放,导致正则强度不可控。AdamW 将权重衰减与梯度矩估计解耦,使大模型训练中的正则化行为更可控,收敛也更稳定。

具体案例:在训练 GPT 风格的语言模型或 BERT 类模型时,往往使用 AdamW 作为默认优化器,配合一组精心调好的学习率和权重衰减系数,在几十亿甚至上千亿参数上稳定完成预训练。

图像分类与迁移学习微调

阶段:模型微调阶段

解决的问题:使用预训练模型在新数据集上微调时,如果继续沿用完整重新训练的优化策略,前几步很容易震荡。Adam 的偏差校正能让冷启动阶段更平稳,自适应步长也能让不同层在微调时获得合适的学习速度。

具体案例:在图像分类任务中加载 ImageNet 预训练权重,用 Adam 微调最后几层,通常能比纯 SGD 更早达到较高准确率;如果数据集很小且担心过拟合,则把优化器切换成 AdamW,并给权重衰减设置一个合适强度。

Python 演示

python47 行
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()
目的:实现一个支持偏差校正和解耦权重衰减的 Adam/AdamW 优化器,并用最简单的二次函数 \(f(x) = x^2\) 验证它能把参数 \(x\) 从 8 收敛到接近 0。
  1. self.m = {}用字典记录每个参数的一阶矩,相当于给每个参数准备一页惯性日记。
  2. self.v = {}用字典记录每个参数的二阶矩,相当于给每个参数准备一张地形仪表盘。
  3. m = self.beta1 * m + (1 - self.beta1) * g指数滑动平均更新一阶矩,让历史方向和当前梯度按比例混合。
  4. v = self.beta2 * v + (1 - self.beta2) * g * g更新二阶矩,记录梯度平方的滑动平均。
  5. m_hat = m / (1 - self.beta1 ** self.t)对一阶矩做偏差校正,补偿冷启动阶段被稀释的估计。
  6. update = m_hat / (math.sqrt(v_hat) + self.eps)得到 Adam 的自适应更新量,颠簸方向的步子自动变小。
  7. 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中负责“记住历史方向”的是哪个量?

单选题 · easy

  • A.一阶矩 m_t← 正确答案
  • B.二阶矩 v_t
  • C.学习率 η
  • D.权重衰减 λ

答案:A。一阶矩 \(m_t\) 是对历史梯度的滑动平均,相当于下山时记录来路的惯性日记;二阶矩负责记录梯度剧烈程度,学习率是全局步长,权重衰减负责清理背包。

Q2. Adam 默认参数中,β2 通常取多少?

单选题 · easy

  • A.0.9
  • B.0.99
  • C.0.999← 正确答案
  • D.0.5

答案:C。论文默认 \(\beta_2=0.999\),意味着二阶矩对历史地形的记忆远远长于一阶矩;\(\beta_1=0.9\) 才是动量记忆系数。

Q3. 偏差校正主要解决什么问题?

单选题 · mid

  • A.导致梯度消失问题
  • B.初始矩估计偏差← 正确答案
  • C.学习率不断增大
  • D.参数直接变负数

答案:B。训练刚开始时,\(m_t\) 和 \(v_t\) 从 0 起步,会被严重低估。偏差校正像放大镜一样补偿冷启动偏差,让前几步的估计更真实,而不是解决梯度消失或符号问题。

Q4. AdamW 与 Adam 的最大区别是什么?

单选题 · mid

  • A.学习率设置得更大
  • B.权重衰减与更新解耦← 正确答案
  • C.同时使用一阶二阶矩
  • D.完全不需要动量

答案:B。AdamW 把权重衰减从梯度计算中拿出来,直接作用于参数更新,避免它被二阶矩缩放干扰;Adam 仍然使用动量,也仍然依赖一阶二阶矩。

Q5. 在 Adam 中,若某参数对应的 v_t 很小,会导致什么影响?

单选题 · hard

  • A.该参数更新步长变小
  • B.该参数更新步长变大← 正确答案
  • C.该参数动量立即清零
  • D.该参数学习率自动失效

答案:B。分母是 \(\sqrt{\hat{v}_t}+\varepsilon\),\(v_t\) 越小意味着这个方向地形越平坦,反而会让更新步长变大;就像在平缓路面上可以放心大步前进。

Q6. 指数滑动平均中 β1 越大表示什么?

单选题 · mid

  • A.对近期梯度越敏感
  • B.对历史梯度记忆越长← 正确答案
  • C.对学习率影响越小
  • D.对权重衰减作用越强

答案:B。\(\beta_1\) 越大,上一轮动量继承得越多,历史梯度占的比重越大,对新梯度的响应越慢,相当于日记中老内容褪色得更慢。

Q7. 在标准Adam中如果把权重衰减写进损失并参与矩估计,权重衰减强度会发生什么?

单选题 · extreme

  • A.被二阶矩动态缩放← 正确答案
  • B.被一阶矩完全抵消
  • C.被学习率永远固定
  • D.被偏差校正消除

答案:A。L2 正则梯度混入 \(v_t\) 后,最终更新还要除以 \(\sqrt{\hat{v}_t}\),所以衰减强度会被动态缩放,不同参数上的正则力度不一致。AdamW 把衰减放到分母之外,正是为了消除这种扭曲。

Q8. Adam 的完整更新包含哪些步骤?

多选题 · mid

  • A.计算目标函数的梯度← 正确答案
  • B.更新一阶矩和二阶矩← 正确答案
  • C.对矩估计进行偏差校正← 正确答案
  • D.自动选择训练批次大小

答案:ABC。Adam 每轮先算梯度,再更新 \(m_t\) 和 \(v_t\),然后做偏差校正并更新参数;批次大小是训练流程中的外部选择,不由 Adam 自动决定。

Q9. 下列哪些情况更适合优先考虑 AdamW?

多选题 · hard

  • A.大模型预训练与微调← 正确答案
  • B.对权重衰减敏感的任务← 正确答案
  • C.只训练十步的调试实验
  • D.参数必须保持整数的情况

答案:AB。AdamW 的解耦权重衰减在参数规模大、正则敏感的任务上表现更稳定;只训练十步和参数整数约束都不是优化器能解决的核心问题。

Q10. 二阶矩 v_t 估计的是什么?

多选题 · easy

  • A.梯度平方的滑动平均← 正确答案
  • B.历史梯度波动的大小← 正确答案
  • C.样本标签的方差
  • D.参数本身的绝对值

答案:AB。二阶矩记录 \(g_t^2\) 的滑动平均,所以它既能理解成梯度平方的平均,也能理解成历史波动幅度;它不关心标签分布,也不关心参数大小。