从 SGD 到 Adam:用一个例子讲透深度神经网络五大改进器 深度学习里的优化器种类繁多,公式看着一个比一个复杂。但如果把它们放在同一个例子里逐步计算,你会发现它们其实是一条清晰的演进线:每一个新优化器,都是为了修复前一个的某个具体缺陷。 本文用 同一个最简单的例子 ,把 SGD、Momentum、AdaGrad、RMSProp、Adam 五个优化器从头到尾走一遍,让你看清它们到底在解决什么问题、彼此有什么区别。 目录 统一的例子设定 梯度是怎么算出来的 1. SGD:最朴素的梯度下降 2. Momentum:给梯度下降加惯性 3. AdaGrad:给每个参数配专属学习率 4. RMSProp:修好 AdaGrad 的学习率衰竭 5. Adam:Momentum + RMSProp 合体 五大优化器全景对比 附:多层网络的梯度是怎么算的 统一的例子设定 为了让五个优化器可以直接对照,全文使用同一个极简任务。 任务 :学习一条过原点的直线 y = w·x ,只有一个参数 w 。 训练集 (真实规律是 y = 2x ,我们希望模型学出 w ≈ 2 ): 样本 x 真实 y 1 1 2 2 2 4 3 3 6 损失函数 :均方误差 MSE L = 1 N ∑ i = 1 N ( w x i − y i ) 2 L = \frac{1}{N}\sum_{i=1}^{N}(w x_i - y_i)^2 L = N 1 ∑ i = 1 N ( w x i − y i ) 2 初始值 : w = 0 。学习率等超参数在每个优化器里单独给出。 梯度是怎么算出来的 在进入优化器之前,先把梯度公式推清楚,后面所有计算都要用到它。 对单个样本,损失是 L i = ( w x i − y i ) 2 L_i = (w x_i - y_i)^2 L i = ( w x i − y i ) 2 。用 链式法则 对 w 求导,设中间变量 u = w x i − y i u = w x_i - y_i u = w x i − y i : ∂ L i ∂ w = ∂ L i ∂ u ⏟ 2 u ⋅ ∂ u ∂ w ⏟ x i = 2 ( w x i − y i ) x i \frac{\partial L_i}{\partial w} = \underbrace{\frac{\partial L_i}{\partial u}}_{2u} \cdot \underbrace{\frac{\partial u}{\partial w}}_{x_i} = 2(w x_i - y_i)\,x_i ∂ w ∂ L i = 2 u ∂ u ∂ L i ⋅ x i ∂ w ∂ u = 2 ( w x i − y i ) x i 直觉上这个式子拆成三部分: ∂ L i ∂ w = 2 ⋅ ( w x i − y i ) ⏟ 误差 ⋅ x i ⏟ 输入 \frac{\partial L_i}{\partial w} = 2 \cdot \underbrace{(w x_i - y_i)}_{\text{误差}} \cdot \underbrace{x_i}_{\text{输入}} ∂ w ∂ L i = 2 ⋅ 误差 ( w x i − y i ) ⋅ 输入 x i 误差 (wxᵢ − yᵢ) :预测减真实,误差越大梯度越大,参数要调得越猛;误差为 0 时梯度为 0。 乘输入 xᵢ :输入越大的样本, w 的微小变化对预测影响越大,对梯度的贡献也越大。 对全部样本求平均,得到本例的 批量梯度公式 (代入三个样本并化简): g ( w ) = 2 3 ∑ i ( w x i − y i ) x i = 28 3 ( w − 2 ) ≈ 9.33 ( w − 2 ) g(w) = \frac{2}{3}\sum_{i}(w x_i - y_i)x_i = \frac{28}{3}(w - 2) \approx 9.33\,(w - 2) g ( w ) = 3 2 ∑ i ( w x i − y i ) x i = 3 28 ( w − 2 ) ≈ 9.33 ( w − 2 ) 这个 g(w) = 9.33·(w − 2) 会贯穿全文: w 离目标 2 越远,梯度越大。 顺带厘清 BGD / SGD / Mini-batch :三者的唯一区别是 每次更新用多少资料算梯度 。BGD 用全部样本、一个 epoch 更新 1 次,最稳但最慢;SGD 每个样本更新一次、抖动大但能跳出局部最优;Mini-batch 是两者折中,也是实践主流。本文为聚焦优化器本身,统一用全部样本算梯度(BGD 方式)。 1. SGD:最朴素的梯度下降 逻辑 每一步只听 当前梯度 的,走完就忘: w ← w − l r ⋅ g t w \leftarrow w - lr \cdot g_t w ← w − l r ⋅ g t 存在的问题 在"狭长山谷"型的损失曲面上(一个方向很陡、一个方向很平缓),SGD 会 在陡的方向来回震荡,在平缓但正确的方向上磨蹭 ,收敛慢。这正是后续优化器要解决的核心痛点。 逐步计算(lr = 0.01) 步 w(更新前) 梯度 g w(更新后) 1 0 −18.67 0.187 2 0.187 — 0.359 3 0.359 — 0.517 4 0.517 — 0.662 四步走到 0.662,平稳但慢。 PyTorch optimizer = torch.optim.SGD(model.parameters(), lr= 0.01 ) 注意:PyTorch 的 torch.optim.SGD 实际是 mini-batch,batch 大小由 DataLoader 决定,叫 SGD 只是习惯。 2. Momentum:给梯度下降加惯性 逻辑 更新时不只看当前梯度,还 累积之前几步的方向 ——像给下山的小球加了惯性。引入"速度"变量 v : v t = β ⋅ v t − 1 + g t w ← w − l r ⋅ v t v_t = \beta \cdot v_{t-1} + g_t \qquad w \leftarrow w - lr \cdot v_t v t = β ⋅ v t − 1 + g t w ← w − l r ⋅ v t 其中 β 通常取 0.9 :这次的速度 = 保留 90% 的上次速度 + 这次的新梯度。 关键直觉:一致则加速,震荡则抵消 方向一致的维度 :每步梯度同号,累加起来越滚越快, 加速前进 。 来回震荡的维度 :这步正、下步负,累加时 正负抵消 ,抖动被抹平。 正好治了 SGD 的两个毛病。 逐步计算(lr = 0.01,β = 0.9,v₀ = 0) 步 w 梯度 g 速度 v w(更新后) 1 0 −18.67 −18.67 0.187 2 0.187 −16.92 −33.72 0.524 3 0.524 −13.77 −44.12 0.965 4 0.965 −9.66 −49.37 1.459 注意第 2 步:速度 v₂ = −33.72 比当前梯度 −16.92 大了近一倍——因为方向一致,历史速度被累加进来了。 对比 SGD(同样 4 步) 步 SGD 的 w Momentum 的 w 1 0.187 0.187 2 0.359 0.524 3 0.517 0.965 4 0.662 1.459 方向一致时,Momentum 明显冲得更快。 冲过头会自动纠正 Momentum 加速快,代价是可能冲过目标。一旦 w 越过 2、梯度变正号,速度 v 里存的负惯性会被新正梯度 先抵消再拉回 ,表现为在目标附近 摆动几下、摆幅递减、最后停在 2 ,像滚进碗底的球。 PyTorch optimizer = torch.optim.SGD(model.parameters(), lr= 0.01 , momentum= 0.9 ) 3. AdaGrad:给每个参数配专属学习率 逻辑 Momentum 管的是 方向 ;AdaGrad 管的是 步长 ——给每个参数配一个专属学习率, 某参数历史梯度越大,学习率就自动调得越小 。 累积历史梯度的 平方 : G t = G t − 1 + g t 2 w ← w − l r G t + ϵ ⋅ g t G_t = G_{t-1} + g_t^2 \qquad w \leftarrow w - \frac{lr}{\sqrt{G_t} + \epsilon} \cdot g_t G t = G t − 1 + g t 2 w ← w − G t + ϵ l r ⋅ g t G 越大 → 分母越大 → 有效学习率越小。等于"更新得越多,越往后越谨慎"。 解决的问题 模型里有的参数频繁更新(梯度大)、有的很少更新(梯度小),用同一个学习率没法同时照顾。AdaGrad 让"动得多的减速、动得少的保持大步",尤其利于稀疏特征。 逐步计算(lr = 0.5,G₀ = 0) 步 w 梯度 g G(累积) 有效学习率 w(更新后) 1 0 −18.67 348.5 0.0268 0.500 2 0.500 −14.00 544.5 0.0214 0.800 3 0.800 −11.20 669.9 0.0193 1.016 4 1.016 −9.18 754.2 0.0182 1.183 有效学习率单调递减 (0.0268 → 0.0182)——这是 AdaGrad 最鲜明的特征。 致命缺点 G 只增不减、永不衰减,学习率会一路降到接近 0。在真实的长时间训练中, 模型还没学好就"走不动了" 。这就是下一个优化器要修的问题。 PyTorch optimizer = torch.optim.Adagrad(model.parameters(), lr= 0.5 ) 4. RMSProp:修好 AdaGrad 的学习率衰竭 逻辑 AdaGrad 的病根是"无限累加"。RMSProp 的解法: 别累加所有历史,只关注最近一段——旧的逐渐遗忘。 把累加换成 指数移动平均(EMA) : G t = γ ⋅ G t − 1 + ( 1 − γ ) ⋅ g t 2 w ← w − l r G t + ϵ ⋅ g t G_t = \gamma \cdot G_{t-1} + (1-\gamma) \cdot g_t^2 \qquad w \leftarrow w - \frac{lr}{\sqrt{G_t} + \epsilon} \cdot g_t G t = γ ⋅ G t − 1 + ( 1 − γ ) ⋅ g t 2 w ← w − G t + ϵ l r ⋅ g t γ 通常取 0.9 :保留 90% 旧累积(很久以前的按 0.9、0.81、0.729… 指数衰减被遗忘),掺入 10% 当前梯度平方。于是 G 反映"最近梯度大概多大",会随梯度变小而变小, 不会无限膨胀 。 逐步计算(lr = 0.5,γ = 0.9,G₀ = 0) 步 w 梯度 g G(移动平均) 有效学习率 w(更新后) 1 0 −18.67 34.85 0.0847 1.581 2 1.581 −3.91 32.90 0.0872 1.922 3 1.922 −0.73 29.66 0.0918 1.989 4 1.989 −0.103 26.69 0.0968 1.999 精髓在第 2 步:梯度从 −18.67 掉到 −3.91(接近目标了), G 也跟着往下走 (34.85 → 32.90),不像 AdaGrad 只增不减。 对比 AdaGrad 步 AdaGrad 有效学习率 RMSProp 有效学习率 AdaGrad 的 w RMSProp 的 w 1 0.0268 0.0847 0.500 1.581 2 0.0214 ↓ 0.0872 0.800 1.922 3 0.0193 ↓ 0.0918 1.016 1.989 4 0.0182 ↓ 0.0968 1.183 1.999 学习率走势 :AdaGrad 单调下降(被 G 累加拖累);RMSProp 平稳甚至回升(G 能随梯度变小而回落)。 收敛速度 :AdaGrad 4 步才到 1.183;RMSProp 4 步已到 1.999,几乎收敛。 PyTorch optimizer = torch.optim.RMSprop(model.parameters(), lr= 0.01 , alpha= 0.9 ) # alpha 即 γ 5. Adam:Momentum + RMSProp 合体 逻辑 Adam = Momentum(管方向)+ RMSProp(管步长) ,两套机制同时用。它同时维护两个累积量: m t = β 1 m t − 1 + ( 1 − β 1 ) g t (一阶动量,方向,来自 Momentum) m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t \qquad \text{(一阶动量,方向,来自 Momentum)} m t = β 1 m t − 1 + ( 1 − β 1 ) g t (一阶动量,方向,来自 Momentum ) v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 (二阶动量,步长,来自 RMSProp) v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2 \qquad \text{(二阶动量,步长,来自 RMSProp)} v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 (二阶动量,步长,来自 RMSProp ) 默认 β₁=0.9,β₂=0.999。 偏差修正 (Adam 特有的关键细节): m ^ t = m t 1 − β 1 t v ^ t = v t 1 − β 2 t \hat{m}_t = \frac{m_t}{1-\beta_1^t} \qquad \hat{v}_t = \frac{v_t}{1-\beta_2^t} m ^ t = 1 − β 1 t m t v ^ t = 1 − β 2 t v t 更新 : w ← w − l r ⋅ m ^ t v ^ t + ϵ w \leftarrow w - lr \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} w ← w − l r ⋅ v ^ t + ϵ m ^ t 分子是 Momentum 的方向,分母是 RMSProp 的自适应缩放,合在一行就是 Adam。 为什么需要偏差修正 m₀ 、 v₀ 都从 0 开始,第一步 m₁ = 0.1·g₁ 严重低估真实梯度(偏向 0)。偏差修正把它除以 (1−β₁ᵗ) 放大回来:第一步 1−0.9¹ = 0.1 ,于是 m̂₁ = m₁/0.1 = g₁ ,修正回真实梯度。随 t 增大修正因子趋于 1, 只在训练早期起作用 ,专治冷启动低估。 逐步计算(lr = 0.5,β₁=0.9,β₂=0.999,m₀=v₀=0) 步 w m̂(方向) √v̂(缩放) 更新量 w(更新后) 1 0 −18.67 18.67 +0.500 0.500 2 0.500 −16.21 16.50 +0.491 0.991 3 0.991 −13.71 14.52 +0.472 1.463 4 1.463 −11.17 12.82 +0.436 1.899 每步更新量都很接近 (0.5、0.491、0.472、0.436),非常平稳。因为分子 m̂ 和分母 √v̂ 随梯度同比例缩小,相除后步长被"归一化",不会梯度大就猛冲、小就磨蹭。这让 Adam 对学习率不敏感、训练稳定,是它成为默认选择的关键原因。 PyTorch optimizer = torch.optim.Adam(model.parameters(), lr= 0.001 , betas=( 0.9 , 0.999 ), eps= 1e-8 ) lr=0.001 是经典默认值,很多任务开箱即用。训练大型语言模型时的实际默认往往是 AdamW (Adam 的权重衰减改进版)。 五大优化器全景对比 用同一个例子,把整条演进线摆在一起(第 4 步的 w ): 优化器 核心机制 解决了什么问题 第4步 w SGD 直接用当前梯度 —(基线) 0.662 Momentum 累积梯度 方向 SGD 的震荡与磨蹭 1.459 AdaGrad 累加梯度 平方 ,自适应步长 各参数需要不同学习率 1.183 RMSProp 移动平均梯度平方 AdaGrad 学习率衰竭到 0 1.999 Adam Momentum + RMSProp + 偏差修正 方向、步长同时自适应 1.899 关于数值 :具体数字和 lr 选择有关,这里重点看 机制差异 而非绝对快慢。RMSProp 在这个特定例子里恰好收敛最快,但 Adam 的优势在于对各种问题的 普适稳定性 ——真实任务里它通常是最省心的默认选择。 一句话串起整条线 : SGD 打基础 → Momentum 加方向惯性 → AdaGrad 引入自适应步长 → RMSProp 修好步长衰竭 → Adam 合二为一再加偏差修正。 理解了每一步"解决了上一个的什么问题",这五个优化器就串成了一个完整逻辑链,而不是五个孤立的公式。
- 上一篇 > 从 SGD 到 Adam:用一个例子讲透深度学习五大增强器
- 下一篇 > 页面接口换图后不刷新,刷新页面后才看得到新图

