自适应优化器
自适应学习率的思想源于一个朴素的观察,不同参数在训练过程中扮演着不同的角色,有些参数频繁更新,梯度大而稳定。有些参数更新稀疏,梯度小甚至偶尔为零。如果给所有参数配发同样的步长,频繁更新的参数可能步子太大、震荡难收敛,而稀疏更新的参数可能步子太小、前进缓慢。这就像校长给全校学生布置同样的作业量,高年级的学生觉得无聊,低年级的学生觉得吃力。
自适应优化器(Adaptive Optimizers)正是为解决以上"一刀切"问题而生,它们根据参数的历史梯度自动调整学习率,为每个参数分配独立的更新步长。这种因材施教的策略最早由约翰·杜奇(John Duchi)于 2011 年提出,他的 AdaGrad 算法开创了自适应学习率的先河。随后,辛顿在 2012 年的 Coursera 课程中提出了 RMSprop,解决了 AdaGrad 学习率过早衰减的问题。2015 年,迪德里克·金玛(Diederik Kingma)和吉米·巴(Jimmy Ba)发表论文《Adam: A Method for Stochastic Optimization》,将动量法与自适应学习率有效结合,Adam 由此成为深度学习领域最流行的优化器。2019 年,伊利亚·洛希洛夫(Ilya Loshchilov)和弗兰克·赫特(Frank Hutter)发现 Adam 的权重衰减实现存在理论缺陷,提出了 AdamW,进一步提升了泛化能力。2024 年,凯勒·乔丹(Keller Jordan)提出了 Muon,不再沿袭逐参数自适应学习率的路线,而是利用参数矩阵的几何结构,通过牛顿-舒尔茨(Newton-Schulz)迭代将动量更新正交化,在 NanoGPT 和 CIFAR-10 速度竞赛中创下纪录。本章将会介绍以上四种经典的自适应优化器以及代表新方向的 Muon,分析它们的设计原理、优缺点和适用场景。
AdaGrad
上一章介绍的动量法和 NAG 解决了梯度下降的震荡问题,但它们仍然给所有参数分配同样的学习率,这在许多场景中会受限,譬如在自然语言处理等任务中,词汇嵌入层的参数更新极不均匀,常用词频繁出现、梯度大,罕见词偶尔出现、梯度小甚至为零。如果用统一学习率,常用词的嵌入向量更新太快容易震荡,罕见词的嵌入向量更新太慢难以学到有效表示。这促使神经网络的研究者们开始寻找让每个参数拥有自己的学习率的方法。
AdaGrad(Adaptive Gradient Algorithm)由约翰·杜奇在 2011 年提出,是最早的自适应优化器之一。AdaGrad 的设计思想直白明了,既然频繁更新的参数已经学到了很多信息,就应该放慢脚步,反之,稀疏更新的参数还有大量信息待学,就应该大步前进。这种因材施教的策略让每个参数都能在最适合自己的节奏下学习,避免统一学习率带来的效率浪费。
实现这一想法的关键是要度量出参数更新幅度的大小。AdaGrad 选择的度量项是累积梯度平方,选择梯度平方而非梯度本身有两个原因,一是平方运算将负梯度转为正数,避免正负抵消导致累积量失真(参数可能频繁在正负方向来回更新,但累积量应该如实反映更新活跃程度)。二是平方放大大的梯度、缩小小的梯度,使活跃参数的累积量更快增长,学习率衰减更显著,实现越活跃越谨慎的调节效果。选择累积而非滑动平均,是因为 AdaGrad 的设计初衷是为了处理稀疏梯度问题,稀疏参数偶尔才有梯度,累积量增长缓慢,能长期保持较大学习率,而频繁参数累积量快速增长,学习率快速衰减,防止震荡。这种累积增长、学习率递减的设计,本质上是用历史梯度作为信用额度去给未来做背书,梯度越活跃,累积量越大,学习率越小,表示该参数已经学得够多,后续更新应该更谨慎。
设 是历史梯度平方的累积量,记录了参数更新了多少次、每次更新幅度多大, 是当前梯度的平方,将梯度值转为非负数(负梯度同样要学习),以下公式表达了梯度累计的过程:
AdaGrad 使用学习率超参数除以累积梯度的平方根来达到累积量越大、实际学习率越小的效果。为了防止开始时还没有累积梯度,导致出现除数为零,操作上会将累积梯度附加一个很小的基础值 (通常为 )。AdaGrad 的有效学习率表示为 ,这里表示的是逐元素运算,即梯度中每个分量的学习率为 ,这意味着同一批参数中,有的参数学习率可能很大,有的可能很小,完全取决于各自的梯度历史。综上,得到权重参数的更新过程:
AdaGrad 这种自适应调整特别适合处理稀疏梯度问题(如自然语言处理中的词嵌入),稀疏更新的参数获得更大的学习率,加速学习。不过,AdaGrad 看似完美的累积历史梯度策略,其实隐藏着一个严重缺陷。既然 是历史梯度平方的累积,训练过程中它只会不断增大(梯度平方非负), 也只增不减。这意味着有效学习率 只会不断下降,AdaGrad 调节的只是学习率变小的快慢而已。这样的话,参数每更新一步,学习率就变小一点。训练后期,学习率可能变得极小(如 ),参数几乎不再更新,训练停滞。这个缺陷使 AdaGrad 陷入两难,它适合短期训练或稀疏梯度问题(频繁更新参数获得小学习率,稀疏参数获得大学习率),但长期训练会导致学习率过早衰减,后期训练停滞。为了解决这个缺陷,就引出了 AdaGrad 的改进版本 RMSprop。
RMSprop
RMSprop(Root Mean Square Propagation)由杰弗里·辛顿在 2012 年的 Coursera 课程中提出。有趣的是,辛顿从未正式发表过关于 RMSprop 的论文,它仅作为课程笔记被流传,但因其简单有效迅速被社区采纳。RMSprop 对 AdaGrad 的改进是用指数滑动平均代替 AdaGrad 的累积。滑动平均是一种加权平均,新数据权重大、旧数据权重逐渐衰减,就像一个窗口在数据链中滑动,让历史信息有进有出,因此 RMSprop 能做到只保留最近若干步(约 步)的梯度信息。
设 是梯度平方的指数滑动平均, 是衰减系数(通常取 ),用作历史累积梯度平方的权重,控制历史信息的保留程度,剩下的 用作新梯度平方的权重,这样保证了权重之和为 1。RMSprop 的梯度累计公式为(可与公式 (1) 对比):
如果 取 的话,就是保留 90% 的历史梯度信息,加入 10% 的当前梯度信息,旧历史逐渐淡出,避免数值膨胀。有效窗口约为 步,大约只保留最近 10 步的梯度信息。除此以外,RMSprop 的权重更新与 AdaGrad 完全一致了(见 (2) ):
RMSprop 使用指数滑动平均的特性带来了两点收益,首先是窗口效应,只保留最近约 步的梯度信息,避免了梯度累计只进不出,让学习率不会单调递减,而是呈现出平缓起伏的状态。此外,RMSprop 能够更快适应梯度变化,当梯度变化剧烈时, 也随之快速调整,学习率响应更加灵敏。但 RMSprop 也有一个缺陷(这个缺陷 AdaGrad 也有,并不是 RMSprop 的改进带来的),它只使用梯度平方调整学习率,没有累积历史梯度方向,相当于放弃了动量法的平滑效果。能否同时拥有动量的平稳性和自适应学习率的灵活性?这引出了当前正在被广泛使用的自适应优化器 Adam。
Adam
Adam(Adaptive Moment Estimation)由迪德里克·金玛(Diederik Kingma)和吉米·巴(Jimmy Ba) 2015 年在国际学习表征会议(ICLR)上提出。Adam 的名称源于其核心机制:同时估计梯度的一阶矩(First Moment,即均值)和二阶矩(Second Moment,即未中心化的方差),将动量法与自适应学习率融于一身。这种双管齐下的策略让参数更新既有方向上的平滑稳定,又有步长上的因材施教,成为当前深度学习领域最流行的优化器。
实现这一融合的关键是 Adam 同时维护两个状态变量。一阶矩 累积历史梯度的方向信息,相当于动量法中的速度变量,用于平滑更新路径、抑制震荡。二阶矩 累积历史梯度的平方,相当于 RMSprop 中的滑动平均,用于调整各参数的学习率。两者独立运作、互不干扰,一阶矩负责往哪走,二阶矩负责走多远。这种分工让 Adam 在各类任务上都表现稳健,对超参数选择不敏感,默认参数()就能适配绝大多数场景,这也是它能广泛流行的重要原因之一。
设 是梯度的一阶矩估计(动量), 是一阶矩的衰减系数(默认取 ), 是当前梯度方向,以下公式表达了 Adam 的动量累积的过程(与动量法的原理一致):
设 是梯度的二阶矩估计(累积梯度), 是二阶矩的衰减系数(默认取 ), 是当前梯度的平方,以下公式表达了 Adam 的梯度平方的滑动平均过程(与 RMSprop 梯度累计公式 (3) 完全一致):
Adam 最开始初始化 ,这个看似自然的设定却带来了冷启动问题。展开一阶矩公式 (4), 实际上是历史梯度的加权求和,即 。权重之和本应为1,这样才能正确反映梯度的加权平均,但由于初始化 ,实际权重之和是等比数列求和的结果 。训练初期 的值小, 接近1(譬如 时 则 ),权重之和 远小于1,缺失的权重被零初始化值占据,导致估计值偏向零,同理,二阶矩公式也存在相同的缺陷,这就是 Adam 的冷启动问题。因此,需要定义中间变量 和 ,让 和 除以权重之和来修正偏差,补足缺失部分:
观察 Adam 的迭代过程,训练初期 小, 接近 1,修正因子 取得最大值,估计值被放大以抵消零初始化偏差;训练后期 大, 接近 0,修正因子接近 1,修正影响消失。用具体数值来直观说明,设 ,,梯度 ,未修正的 ,修正后的 ,恰好等于实际梯度。综合以上四个变量,Adam 的权重更新公式为:
这个公式可以理解为用偏差修正后的动量 指明前进方向(平滑后的梯度),用自适应学习率 控制步长(各参数独立调整)。用 (通常 )防止除零, 是全局学习率(通常 ,比 SGD 的默认值小,因为自适应机制会使部分参数的有效步长过大)。Adam 结合了动量法和自适应学习率的优点,在计算机视觉、自然语言处理、推荐系统等领域广泛使用,成为深度学习研究的默认优化器。
Adam 提供了四个超参数的设置,但除了全局学习率外,其中三个通常使用默认值即可,实际训练时,损失震荡则降低学习率,收敛慢则增大学习率:
| 超参数 | 默认值 | 作用 |
|---|---|---|
| 全局学习率,范围通常 | ||
| 一阶矩衰减系数,控制动量平滑程度 | ||
| 二阶矩衰减系数,控制学习率自适应程度 | ||
| 数值稳定常数,防止除零 |
AdamW
Adam 似乎已经集齐了动量法和自适应学习率的所有优点,成为深度学习的默认优化器。然而,2019 年一篇论文揭示了一个隐藏的理论缺陷,Adam 中权重衰减(L2 正则化)的实现方式与自适应学习率冲突,正则化效果不稳定。这个问题引出了 Adam 的最新修正版本 AdamW。
权重衰减(Weight Decay)是防止过拟合的常用技术,在 SGD 中实质上等同于 L2 正则化。在损失函数中添加参数惩罚项,迫使参数保持较小值 ,相应地,梯度变为 ,在 SGD 中,权重衰减的实现很简单直观:
每步权重乘以 ,逐渐衰减,所有参数同等对待。但 Adam 的实现方式不同。梯度 被累积到一阶矩 和二阶矩 中,权重衰减项 也被自适应学习率缩放:
这导致一个问题,当 很大(参数梯度历史活跃),自适应学习率 很小,权重衰减项被缩小,正则化效果减弱。当 很小(参数梯度历史稀疏),自适应学习率很大,权重衰减项被放大,这与 L2 正则化均匀衰减所有权重的设计初衷明显不符。回顾 Adam 的机制,梯度更新被自适应学习率缩放,这是为了让频繁更新的参数放慢脚步、稀疏更新的参数加速前进。但权重衰减的目的是均匀约束所有参数,防止任何一个参数过大导致过拟合,与自适应调整的方向完全不同。将两者混在一起,相当于让本应"一视同仁"的正则化也被"因材施教"了,逻辑上自相矛盾。
AdamW(Adam with Decoupled Weight Decay)由伊利亚·洛希洛夫(Ilya Loshchilov)在 2019 年的论文《Decoupled Weight Decay Regularization》中提出,论文揭示了 Adam 的权重衰减问题,并给出简洁的解决方案,将权重衰减从梯度更新中分离,让梯度更新负责学习数据规律,权重衰减负责控制模型复杂度,两者应该独立运作、互不干扰。AdamW 的更新规则与 Adam 相比(见 (5)),区别在于多了权重衰减项:
其中加入的 就是权重衰减项,直接作用于参数,不被自适应学习率缩放, 是梯度更新项,被自适应学习率缩放。整体公式可以理解为权重衰减独立执行,梯度更新自适应执行,两者解耦不干扰。实践中,现在一般在所有场景中都可以使用 AdamW 替代 Adam,尤其在需要强正则化的任务上,譬如大规模语言模型训练,AdamW 如今已成为许多 Transformer 架构模型,如 BERT、GPT 的默认优化器。
Muon
前面介绍的优化器的共同特征是为每个参数分配独立的学习率,通过梯度历史来调整更新步长。这种逐参数自适应的策略取得了巨大成功,但是神经网络参数其实并非独立标量,参数之间是存在关联关系的。神经网络隐藏层的权重天然是二维矩阵,行对应对输入特征的变换,列对应对输出神经元的组合。将矩阵展平为向量再逐元素调整学习率,等于主动放弃了矩阵的几何信息。2024 年 12 月,凯勒·乔丹(Keller Jordan)在博客文章中提出了 Muon 优化器,不走逐参数自适应学习率的传统路径,通过正交化动量更新来利用矩阵的几何结构。
Muon 的名称源自"动量经牛顿-舒尔茨正交化"这个拗口复杂的名字(Momentum Orthogonalized by Newton-Schulz),而它的具体步骤却出人意料地简单,只有以下两步:
第一步,动量累积。Muon 像动量法一样,维护一个历史梯度的加权平均(动量缓冲区),新梯度加入时保留大部分旧方向、融入小部分新方向,以此平滑更新路径、抑制震荡。
第二步,正交化。这是 Muon 与所有前述优化器不同的地方。可以把一个矩阵想象成多个独立方向及其强度的组合。譬如,一个权重矩阵可能在某些方向上推动得很猛(强度大),在另一些方向上只是轻轻触碰(强度小)。正交化做的事情就是只保留这些方向本身,而把它们的强度全部拉平到相同的水平。就像一个团队里有嗓门大的成员和沉默寡言的成员,正交化相当于给每个人装上同样音量的麦克风,让所有声音都能被平等地听到。
之所以要进行正交化,是因为乔丹在实验中发现 SGD 动量和 Adam 为模型隐藏层产生的更新矩阵,往往被少数几个强势方向所主导,其余大量"稀有方向"的幅度微乎其微。这些稀有方向虽然不显眼,却可能携带着对学习至关重要的信息。正交化将这些弱小方向放大到与主导方向同等的强度,让它们也能平等地参与参数更新,避免网络在学习中产生盲区。正交化的标准做法是先对矩阵做奇异值分解,分离出方向和强度,再丢弃强度只保留方向。但奇异值分解的计算代价太高,无法承受每一步训练都跑一次。Muon 使用牛顿-舒尔茨迭代来绕开奇异值分解,这也是它名称中 "N" 的来源。
牛顿-舒尔茨迭代利用了一个数学事实:如果把一个精心构造的多项式函数反复作用在矩阵上,这个函数会作用在矩阵的强度部分而非方向部分,逐步把所有强度推向 1,最终逼近正交化的结果。整个过程只需要矩阵乘法,不需要显式地分解矩阵。乔丹团队选了一个五次多项式,并在大量实验中调优了多项式的三个系数,使得仅需五轮迭代就能在 BF16 精度下稳定逼近正交化的效果。迭代开始前,先把矩阵整体缩放到单位长度,确保所有强度落在 0 到 1 之间。然后每轮迭代对矩阵应用一次该多项式,那些初始就很小的强度会被快速拉升,逐渐逼近 1。五轮之后,矩阵已经非常接近正交化的结果。与直接做奇异值分解(太慢)和另一种称为耦合牛顿迭代的方法(需要 FP32 精度,在 GPU 上效率低)相比,牛顿-舒尔茨迭代可以在现代 GPU 原生的 BF16 精度下运行,速度快、数值稳定,这是它能嵌入训练循环的关键。
走完这两步后,Muon 将正交化后的动量作为最终更新,乘以全局学习率作用到权重上。整个过程可以概括为动量累积提供平滑的方向,正交化拉平所有方向的强度,学习率控制整体步长。不过既然 Muon 利用了矩阵的二维几何关系,它就仅能适用于神经网络隐藏层的二维权重矩阵。对于标量参数(如偏置)、向量参数(如层归一化的缩放因子)以及输入/输出嵌入层,仍需使用 AdamW 等标准优化器。这种混合优化器策略(隐藏层用 Muon、其余参数用 AdamW)是 2026 年语言模型使用的主流方式。
Muon 在资源与性能上都有巨大优势。性能方面,Muon 在多项基准测试中表现出色。它将 CIFAR-10 达到 94% 准确率的训练时间压缩了约 21%。在 NanoGPT 速度竞赛(FineWeb 数据集)中,Muon 将训练速度提升了 1.35 倍。使用 8 张 H100 训练 1.5B 参数的 Transformer 达到 GPT-2 XL 级别的性能,Muon 仅需 10 小时,而 AdamW 则需要 13.3 小时。此后,Moonlight 16B 混合专家模型(5.7T tokens 训练数据)等大规模项目进一步验证了 Muon 在工业级场景下的可行性。资源方面,Muon 的计算开销和内存开销都很低。在典型的语言模型训练中(如 Llama 405B,模型维度 16384,批大小 16M tokens),五步牛顿-舒尔茨迭代的额外浮点运算占比不到 1%。在内存消耗上,Muon 仅维护一个动量缓冲区,相比 AdamW 直接节省了约一半的优化器状态内存,大幅降低了训练环境的资源门槛。
Muon 代表了优化器设计的全新方向,从为每个标量参数独立调整学习率,到直接利用参数的矩阵结构,通过几何变换改进更新方向。当然,这种矩阵原生优化目前还处于早期阶段,仅覆盖线性层,尚未扩展到注意力机制等更复杂的组件。但它已经在速度竞赛中证明了自己的价值,并启发了后续一系列基于矩阵结构的优化器研究。
优化器选择指南
至此,我们已经学习了 SGD、动量法、NAG、AdaGrad、RMSprop、Adam、AdamW 和 Muon 八种优化器。每种优化器各有特点,本节从优化器和任务类型两个角度出发,提供一份"两步走"的简单明了的选择指南。
步骤一,首先了解各个优化器的特点:
优化器 核心机制 优势 缺陷 适用场景 SGD 基础梯度下降 简单、稳定 震荡、慢 简单任务、精调 Momentum 动量平滑 加速、抑制震荡 需调学习率 通用 NAG 预测位置梯度 提前响应拐点 稍复杂 追求精度 AdaGrad 累积梯度平方 稀疏梯度友好 学习率衰减 稀疏数据、短期训练 RMSprop 滑动平均梯度平方 学习率稳定 无动量 长期训练、RNN Adam 动量 + 自适应 鲁棒、快速 权重衰减问题 通用、默认选择 AdamW Adam+解耦权重衰减 正则化稳定 SGD 相比有额外的二阶矩内存开销 默认选择 Muon 动量 + Newton-Schulz 正交化 利用矩阵结构、内存省 仅适用于二维矩阵参数 Transformer 隐藏层、大规模训练 步骤二,根据任务特点选择优化器:
任务类型 推荐优化器 理由 通用深度学习 AdamW 鲁棒性强,默认选择 计算机视觉 SGD + Momentum 实验表明泛化能力更好 自然语言处理 AdamW 稀疏梯度,自适应优势明显 RNN/LSTM RMSprop / AdamW 适应梯度尺度差异 精调预训练模型 SGD + Momentum 小学习率精调,防止破坏预训练特征 稀疏数据(推荐系统) AdamW 稀疏参数获得大学习率 大规模语言模型训练 Muon + AdamW 隐藏层用 Muon 提速,其余参数用 AdamW
自适应优化器实践
理论分析揭示了各优化器的设计原理,接下来通过代码实验对比 SGD、动量法、NAG、AdaGrad、RMSprop、Adam、AdamW 和 Muon 八种优化器在二次损失函数上的收敛效果。实验使用长椭圆形损失函数(不同方向梯度差异大),从同一起点出发,观察各优化器的参数路径、损失曲线和有效学习率变化。代码实现了八种优化器的完整更新逻辑,包括动量累积、梯度平方累积、滑动平均、偏差修正、预测位置梯度计算、权重衰减解耦和牛顿-舒尔茨正交化。注意 Muon 专为二维矩阵参数设计,本实验将其用于二维向量(视为 矩阵),此时正交化退化为方向归一化,其矩阵结构优势需在实际神经网络训练中才能完全体现。
import numpy as np
import matplotlib.pyplot as plt
# 定义损失函数和梯度 - 使用10倍梯度差异以展示SGD震荡特征
def loss_function(W):
"""二次损失函数 L = 0.5 * W^T A W"""
A = np.array([[1, 0], [0, 10]]) # 梯度差异10倍
return 0.5 * np.dot(W, A @ W)
def gradient(W):
"""梯度 ∇L = A W"""
A = np.array([[1, 0], [0, 10]])
return A @ W
# 各优化器实现
class SGD:
def __init__(self, lr=0.15): # 学习率 > 0.1 产生震荡(lr > 1/梯度差异)
self.lr = lr
self.path = []
def step(self, W, grad):
W_new = W - self.lr * grad
self.path.append(W_new.copy())
return W_new
class Momentum:
def __init__(self, lr=0.05, momentum=0.9): # 学习率调小,避免过度震荡
self.lr = lr
self.momentum = momentum
self.v = np.zeros(2)
self.path = []
def step(self, W, grad):
self.v = self.momentum * self.v + self.lr * grad
W_new = W - self.v
self.path.append(W_new.copy())
return W_new
class NAG:
"""Nesterov Accelerated Gradient - 在预测位置计算梯度"""
def __init__(self, lr=0.05, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = np.zeros(2)
self.path = []
def step(self, W, grad_func):
# NAG核心:在预测位置计算梯度,提前响应拐点
W_lookahead = W - self.momentum * self.v
grad_lookahead = grad_func(W_lookahead)
self.v = self.momentum * self.v + self.lr * grad_lookahead
W_new = W - self.v
self.path.append(W_new.copy())
return W_new
class AdaGrad:
def __init__(self, lr=1.0, eps=1e-8): # 学习率大,初期收敛快
self.lr = lr
self.eps = eps
self.G = np.zeros(2)
self.path = []
def step(self, W, grad):
self.G += grad ** 2 # 累积梯度平方
lr_adaptive = self.lr / np.sqrt(self.G + self.eps) # 学习率递减
W_new = W - lr_adaptive * grad
self.path.append(W_new.copy())
return W_new
class RMSprop:
def __init__(self, lr=0.3, gamma=0.9, eps=1e-8): # 学习率适中
self.lr = lr
self.gamma = gamma
self.eps = eps
self.E = np.zeros(2)
self.path = []
def step(self, W, grad):
self.E = self.gamma * self.E + (1 - self.gamma) * (grad ** 2) # 滑动平均
lr_adaptive = self.lr / np.sqrt(self.E + self.eps) # 学习率稳定
W_new = W - lr_adaptive * grad
self.path.append(W_new.copy())
return W_new
class Adam:
def __init__(self, lr=0.3, beta1=0.9, beta2=0.999, eps=1e-8):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.eps = eps
self.m = np.zeros(2) # 一阶矩(动量)
self.v = np.zeros(2) # 二阶矩(梯度平方)
self.t = 0
self.path = []
def step(self, W, grad):
self.t += 1
self.m = self.beta1 * self.m + (1 - self.beta1) * grad # 动量累积
self.v = self.beta2 * self.v + (1 - self.beta2) * (grad ** 2) # 梯度平方累积
# 偏差修正
m_hat = self.m / (1 - self.beta1 ** self.t)
v_hat = self.v / (1 - self.beta2 ** self.t)
W_new = W - self.lr / (np.sqrt(v_hat) + self.eps) * m_hat
self.path.append(W_new.copy())
return W_new
class AdamW:
def __init__(self, lr=0.3, beta1=0.9, beta2=0.999, eps=1e-8, weight_decay=0.01):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.eps = eps
self.weight_decay = weight_decay
self.m = np.zeros(2)
self.v = np.zeros(2)
self.t = 0
self.path = []
def step(self, W, grad):
self.t += 1
self.m = self.beta1 * self.m + (1 - self.beta1) * grad
self.v = self.beta2 * self.v + (1 - self.beta2) * (grad ** 2)
m_hat = self.m / (1 - self.beta1 ** self.t)
v_hat = self.v / (1 - self.beta2 ** self.t)
# 解耦权重衰减:直接作用于参数,不被自适应学习率缩放
W_new = W - self.lr * self.weight_decay * W
W_new = W_new - self.lr / (np.sqrt(v_hat) + self.eps) * m_hat
self.path.append(W_new.copy())
return W_new
class Muon:
"""Muon优化器 - 动量累积后通过Newton-Schulz迭代正交化更新矩阵"""
def __init__(self, lr=0.3, momentum=0.9, ns_steps=5):
self.lr = lr
self.momentum = momentum
self.ns_steps = ns_steps
self.B = np.zeros((2, 1)) # 动量缓冲区(矩阵形式)
self.path = []
def _newtonschulz(self, G):
"""Newton-Schulz迭代将矩阵近似正交化(G ≈ U S V^T → U V^T)"""
a, b, c = (3.4445, -4.7750, 2.0315) # 五次多项式的调优系数
X = G / (np.linalg.norm(G) + 1e-7)
if G.shape[0] > G.shape[1]:
X = X.T
for _ in range(self.ns_steps):
A = X @ X.T
B_mat = b * A + c * A @ A
X = a * X + B_mat @ X
if G.shape[0] > G.shape[1]:
X = X.T
return X
def step(self, W, grad):
grad_mat = grad.reshape(-1, 1)
self.B = self.momentum * self.B + grad_mat # 动量累积
O = self._newtonschulz(self.B) # Newton-Schulz正交化
W_new = W - self.lr * O.flatten()
self.path.append(W_new.copy())
return W_new
# 运行实验
W_init = np.array([5.0, 5.0]) # 起点
n_iterations = 50
optimizers = {
'SGD': SGD(lr=0.15),
'Momentum': Momentum(lr=0.05, momentum=0.9),
'NAG': NAG(lr=0.05, momentum=0.9),
'AdaGrad': AdaGrad(lr=1.0),
'RMSprop': RMSprop(lr=0.3, gamma=0.9),
'Adam': Adam(lr=0.3),
'AdamW': AdamW(lr=0.3, weight_decay=0.01),
'Muon': Muon(lr=0.3, momentum=0.9)
}
results = {}
for name, opt in optimizers.items():
W = W_init.copy()
losses = []
for t in range(n_iterations):
loss = loss_function(W)
losses.append(loss)
grad = gradient(W)
# NAG需要传入梯度函数,其他优化器传入梯度值
if name == 'NAG':
W = opt.step(W, gradient)
else:
W = opt.step(W, grad)
results[name] = {
'path': np.array(opt.path),
'losses': losses,
'final_W': W,
'final_loss': loss_function(W)
}
print(f"{name:10s}: 最终位置 ({W[0]:.4f}, {W[1]:.4f}), 最终损失 {loss_function(W):.6f}")
print()
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
colors = {'SGD': '#e74c3c', 'Momentum': '#3498db', 'NAG': '#e67e22',
'AdaGrad': '#f39c12', 'RMSprop': '#9b59b6', 'Adam': '#2ecc71', 'AdamW': '#1abc9c',
'Muon': '#e91e63'}
# 图1:参数路径
ax1 = axes[0, 0]
W1_range = np.linspace(-6, 6, 100)
W2_range = np.linspace(-6, 6, 100)
W1_grid, W2_grid = np.meshgrid(W1_range, W2_range)
L_grid = 0.5 * (W1_grid**2 + 10 * W2_grid**2)
ax1.contour(W1_grid, W2_grid, L_grid, levels=[1, 5, 10, 25, 50, 100],
colors='gray', alpha=0.5, linewidths=0.5)
ax1.contourf(W1_grid, W2_grid, L_grid, levels=[0, 1, 5, 10, 25, 50, 100, 200],
cmap='Blues', alpha=0.3)
for name, result in results.items():
path = result['path']
ax1.plot(path[:, 0], path[:, 1], 'o-', color=colors[name],
linewidth=2, markersize=3, alpha=0.7, label=name)
ax1.plot(W_init[0], W_init[1], 'ko', markersize=10, label='起点')
ax1.plot(0, 0, 'k*', markersize=15, label='最小值')
ax1.set_xlabel('W1', fontsize=11)
ax1.set_ylabel('W2', fontsize=11)
ax1.set_title('参数路径对比', fontsize=12)
ax1.legend(loc='upper right')
ax1.grid(True, alpha=0.3)
ax1.set_xlim(-6, 6)
ax1.set_ylim(-6, 6)
# 图2:损失曲线
ax2 = axes[0, 1]
for name, result in results.items():
ax2.plot(result['losses'], color=colors[name], linewidth=2, label=name)
ax2.set_xlabel('迭代次数', fontsize=11)
ax2.set_ylabel('损失值', fontsize=11)
ax2.set_title('损失变化曲线', fontsize=12)
ax2.legend()
ax2.grid(True, alpha=0.3)
ax2.set_yscale('log')
# 图3:有效学习率变化(W1方向)- 仅自适应优化器
ax3 = axes[1, 0]
adaptive_optimizers = ['AdaGrad', 'RMSprop', 'Adam', 'AdamW']
for name, result in results.items():
if name in adaptive_optimizers:
path = result['path']
lr_eff = []
for i in range(len(path) - 1):
W1_change = path[i+1, 0] - path[i, 0]
W1_grad = path[i, 0] # grad_W1 ≈ W1
lr_eff.append(np.abs(W1_change) / np.abs(W1_grad + 1e-8))
ax3.plot(lr_eff[:min(30, len(lr_eff))], color=colors[name], linewidth=2, label=name, alpha=0.7)
ax3.set_xlabel('迭代次数', fontsize=11)
ax3.set_ylabel('有效学习率(W1方向)', fontsize=11)
ax3.set_title('自适应学习率变化', fontsize=12)
ax3.legend()
ax3.grid(True, alpha=0.3)
# 图4:收敛速度对比(损失下降速度)
ax4 = axes[1, 1]
for name, result in results.items():
losses = result['losses']
loss_decrease = [losses[i] - losses[i+1] for i in range(len(losses)-1)]
ax4.plot(loss_decrease[:min(30, len(loss_decrease))], color=colors[name], linewidth=2, label=name, alpha=0.7)
ax4.set_xlabel('迭代次数', fontsize=11)
ax4.set_ylabel('每步损失下降量', fontsize=11)
ax4.set_title('损失下降速度对比', fontsize=12)
ax4.legend()
ax4.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
plt.close()
本章小结
本章介绍了自适应优化器的原理与应用,展示了为每个参数分配不同学习率的思想如何提升优化效率,并介绍了 Muon 这一利用矩阵几何结构的新型优化器。至此,神经网络优化的内容已全部完成。我们掌握了梯度下降、动量法、NAG、自适应优化器和 Muon 的原理与应用,理解了如何选择和调优优化算法。下一章将进入神经网络稳定性,介绍权重初始化、Dropout、批归一化和梯度问题诊断,解决训练稳定性问题。
练习题
分析 AdaGrad 学习率单调递减的原因。设梯度恒为 ,推导经过 步后有效学习率 。这个性质有什么影响?
参考答案
AdaGrad 学习率递减推导:
AdaGrad 的累积梯度平方:
设梯度恒为 (),则:
有效学习率:
有效学习率与 成反比,随迭代次数增加单调递减。
影响分析:
初期收敛快:训练初期 小, 大,参数快速更新
后期收敛慢:训练后期 大, 小,参数更新极慢
长期训练停滞:当 很大(如 ),,学习率极小,训练几乎停滞
数值示例:设 , :
参数更新幅度 1 0.1 大 100 0.01 中 10000 0.001 小 0.0001 极小 结论:
AdaGrad 的学习率单调递减导致:
- 适合短期训练或稀疏梯度问题(频繁更新参数获得小学习率,稀疏参数获得大学习率)
- 不适合长期训练:学习率过早衰减,后期训练停滞
- RMSprop 通过滑动平均解决了这个问题
改进方向:
RMSprop 使用指数滑动平均:
当梯度恒为 :
收敛到 (稳定值),而非累积增长。有效学习率 保持稳定。
总结:AdaGrad 学习率单调递减源于累积梯度平方的累积增长。这个特性使 AdaGrad 适合短期训练和稀疏梯度,但长期训练会停滞。RMSprop 使用滑动平均避免累积,学习率稳定,适合长期训练。
解释 Adam 偏差修正的必要性。设初始化 , ,梯度 。计算未修正的 和修正后的 ,分析差异。
参考答案
偏差修正计算:
Adam 一阶矩估计:
设 , , :
未修正:
修正后:
差异分析:
- 未修正 (偏向零)
- 修正后 (等于实际梯度)
- 修正因子 放大了
偏差原因:
Adam 初始化 ,一阶矩估计是历史梯度的加权平均:
权重之和:
当 小(训练初期),权重之和 :
- : 权重之和
- : 权重之和
- : 权重之和
权重之和小于 1,估计偏向零(因为初始化 占据了缺失的权重)。
修正原理:
偏差修正抵消初始化偏差:
将估计乘以 ,补足缺失的权重。
当 大(训练后期),:
- :
- 修正因子
修正影响消失。
数值示例:设梯度恒为 10:
(未修正) (修正) 比值 1 1 10 10x 5 4.1 6.9 1.7x 10 6.5 10 1.5x 100 9.99 10 1x 结论:
Adam 偏差修正的必要性:
- 训练初期( 小):初始化 导致估计偏向零,修正放大估计,抵消偏差
- 训练后期( 大):权重之和接近 1,偏差消失,修正影响减弱
- 偏差修正使训练初期梯度估计准确,避免冷启动问题
实际意义:
无偏差修正的 Adam 在训练初期学习率可能过小(因为 偏向零),参数更新慢。偏差修正使初期学习率正常,加速收敛。
这就是为什么 Adam 的偏差修正是关键设计,因为它解决了初始化偏差导致的冷启动问题。
解释 AdamW 为何比 Adam 的权重衰减效果更稳定。分析 Adam 中 L2 正则化梯度被自适应学习率缩放的问题。
参考答案
Adam 中权重衰减的实现问题:
L2 正则化在损失函数中添加惩罚项:
梯度变为:
Adam 累积梯度到一阶矩 和二阶矩 :
参数更新:
权重衰减项 被包含在 中,并被自适应学习率 缩放。
问题分析:
当某参数梯度历史活跃( 大):
- 自适应学习率 小
- 权重衰减项 被缩小,正则化效果减弱
当某参数梯度历史稀疏( 小):
- 自适应学习率 大
- 权重衰减项 被放大,正则化效果增强
这与 L2 正则化"均匀衰减所有权重"的设计初衷不符。L2 正则化应使所有权重同等衰减(每步乘以 ),但 Adam 使权重衰减效果与参数的梯度历史相关。
数值示例:
设 , , :
参数 自适应学习率 权重衰减幅度 活跃参数 100 稀疏参数 1 活跃参数权重衰减几乎为 0,稀疏参数权重衰减较大。正则化效果不均匀。
AdamW 的解耦设计:
AdamW 将权重衰减从梯度更新中分离:
权重衰减项 直接作用于参数,不被自适应学习率缩放。
每步权重乘以 :
所有权重同等衰减,与 SGD 的行为一致。
AdamW vs Adam 权重衰减对比:
特性 Adam AdamW 权重衰减实现 梯度中添加 L2 项 直接衰减权重 衰减效果 受 影响(不均匀) 稳定均匀 超参数耦合 和 耦合 解耦独立 结论:
Adam 的权重衰减问题源于 L2 正则化梯度被自适应学习率缩放。活跃参数衰减效果减弱,稀疏参数衰减效果增强,正则化不均匀。
AdamW 将权重衰减解耦,直接作用于参数,衰减效果稳定均匀。实验表明 AdamW 泛化能力优于 Adam,尤其在需要强正则化的任务上。
建议:优先使用 AdamW 替代 Adam,权重衰减效果更稳定可靠。
