返回学习地图
llmPhase A · 第 3

02. 优化器

BGD/SGD/Momentum/AdaGrad/RMSprop/Adam:梯度下降算法进化史,从批量到自适应学习率,彻底搞懂优化器

21 个章节·按类别展开/折叠
知识

1.1 优化器到底是什么?——从零开始的理解

假设你被蒙上眼睛,扔进一座连绵起伏的大山里。你的任务是:找到这座山的最低点(全局最低谷)。你手里没有地图,没有GPS,只能靠两只脚来探索。

每走一步,你就用脚踩一踩地面:哪边的坡度最陡?然后朝那个方向迈一步。这就是最朴素的 梯度下降——摸着坡度走,总能走到谷底。

神经网络训练 = 找山谷最低点。这个山谷不是什么地理山脉,而是 损失函数(Loss Function) 的地形。山的每个位置代表一组权重参数 W,山的高度代表损失 L(W)。我们要找 损失最小的那组权重

优化器(Optimizer)就是决定:

  • 往哪个方向走?——负梯度方向(下山最快的方向)
  • 每一步走多大?——学习率(步长)
  • 怎么越走越聪明?——用历史经验加速收敛

一个完整的训练循环:

  1. 前向传播:输入数据经过网络,算出一个预测结果和对应的损失 L
  2. 反向传播:用链式法则,算出每个参数 w 对损失 L 的偏导数 ∂L/∂w(梯度)
  3. 更新参数:W_new = W_old - α × 梯度(朝负梯度的方向迈一步)
  4. 回到步骤1,直到损失不再下降

类比总结:

现实世界神经网络
山的位置 (x, y)参数权重 W
山的高度损失 L(W)
脚的探路(感知坡度)梯度 ∇L(W)(反向传播)
向下坡方向迈一步W = W - α·∇L
步长大小学习率 α
你(探索者)优化器 Optimizer
知识

1.2 梯度下降的数学本质——从导数一步步推导

先从最简单的情况开始:假设我们只有一个参数 w,损失函数是 f(w) = w²。这个函数在 w=0 时取最小值 0。我们来看怎么用梯度下降找到 w=0。

什么是导数?

导数 f’(w) 表示函数在 w 点的 瞬时变化率。f’(w) > 0 表示函数在 w 处上升,f’(w) < 0 表示下降。f’(w) = 0 表示可能是最低点或最高点。

对 f(w)=w² 求导:f’(w) = 2w。

  • 当 w=3,f’(3)=6 > 0 → 上升方向 → 要向左走(减小w)才能下降
  • 当 w=-2,f’(-2)=-4 < 0 → 下降方向 → 要向右走(增大w)才能下降

梯度下降的核心公式:

w_{new} = w_{old} - α × f’(w_{old})

为什么是减号?因为导数指向函数 上升 最快的方向,我们要下降,所以取 负方向

参数说明表:

符号名称含义类比
w_{old}当前参数值当前山上的位置你现在的坐标
w_{new}更新后的参数迈一步后的新位置你下一步的坐标
α学习率(步长)每一步走多大步子的大小
f’(w_{old})导数/梯度当前位置的坡度脚下地面的倾斜程度
-α·f’(w_{old})参数更新量朝哪个方向走多远一步的位移向量

数值例子——手算梯度下降:

设 w_0 = 3,α = 0.1,f(w)=w²,f’(w)=2w

第1步:w_0=3, f’(3)=6, 更新量=-0.1×6=-0.6, w_1=3-0.6=2.4, f(w_1)=5.76
第2步:w_1=2.4, f’(2.4)=4.8, 更新量=-0.1×4.8=-0.48, w_2=2.4-0.48=1.92, f(w_2)=3.69
第3步:w_2=1.92, f’(1.92)=3.84, 更新量=-0.384, w_3=1.536, f(w_3)=2.36
第4步:w_3=1.536, f’(1.536)=3.072, 更新量=-0.307, w_4=1.229, f(w_4)=1.51
第5步:w_4=1.229, f’(1.229)=2.458, 更新量=-0.246, w_5=0.983, f(w_5)=0.97
... 经过约20步,w ≈ 0.06,损失 ≈ 0.004
经过约30步,w ≈ 0.006,损失 ≈ 0.00004 —— 已接近全局最低点 w=0

注意到一个重要现象:越接近谷底,梯度越小(f’(w) → 0),参数的更新量越小,收敛变得平滑而缓慢。这是梯度下降的特点:前期快,后期慢

实际神经网络有 数百万到数十亿个参数,每个参数都有自己的梯度。优化器的任务就是协调好这庞大的更新过程。

知识

1.3 学习率——优化器最关键的旋钮

学习率 α 是优化器最重要的超参数。它决定了每一步参数更新的幅度。设得不对,训练直接失败。

三种常见情况:

  • α 过小(比如 0.0000001):每一步像蚂蚁爬,需要极多步才能到谷底。训练时间从几小时变成几周,而且可能困在很小的局部洼地里出不来。
  • α 过大(比如 0.5):一步跨太大,直接从谷底这头跳到了对面山坡上,甚至震荡发散,损失越优化越大。
  • α 刚好(比如 0.1):每一步稳健下降,n步后到达谷底。

用 f(w)=w² 演示不同学习率的效果:

αw_0=3w_1w_2w_3结果
0.013.002.942.882.82太慢,100步才到0.5
0.103.002.401.921.54良好,30步接近0
0.503.000.000.000.00刚好一步到0(巧合)
0.603.00-0.600.12-0.024震荡,但能收敛
1.003.00-3.003.00-3.00反复震荡永不收敛
1.103.00-3.604.32-5.18发散!损失越来越大

日常类比——做饭加盐:

  • 学习率 = 每次加盐的勺子大小
  • 损失 = 汤的咸度偏差(太淡或太咸都不好)
  • α 太小:每次加一粒盐,尝了100次还是淡的
  • α 太大:一勺下去直接咸死人,只能在“太咸”和“太淡”之间反复横跳
  • α 刚好:两三勺就调到完美咸度

实际训练中的学习率策略:通常从 α=0.001(1e-3)开始调整。随着训练进行,学习率通常会 衰减——开始时大步探索,后期小步精细调整。

练习

练习 1——梯度下降手算

练习

Q1:f(w)=2w²,初始 w=4,α=0.2。手动计算前3步的 w 值和损失值。

Q2:f(w)=w²+3w,初始 w=5,α=0.1。先求导,再手动计算前2步。

Q3:如果 α=0.5,用 f(w)=w²,w=3 开始,计算第1步参数。损失是变大还是变小了?为什么?

Q4:假设损失函数在某个参数处的梯度是 0——这意味着什么?下一步该怎么办?

Q5:用自己的话解释:为什么梯度下降用 梯度方向而不是正梯度方向?

查看答案

A1:f(w)=2w²,f’(w)=4w

第1步:w₀=4, f’(4)=16, 更新=-0.2×16=-3.2, w₁=0.8, f(w₁)=2×0.64=1.28
第2步:w₁=0.8, f’(0.8)=3.2, 更新=-0.2×3.2=-0.64, w₂=0.16, f(w₂)=2×0.0256=0.0512
第3步:w₂=0.16, f’(0.16)=0.64, 更新=-0.2×0.64=-0.128, w₃=0.032, f(w₃)=2×0.001024=0.002048

A2:f(w)=w²+3w,f’(w)=2w+3

第1步:w₀=5, f’(5)=13, 更新=-0.1×13=-1.3, w₁=3.7, f(w₁)=13.69+11.1=24.79
第2步:w₁=3.7, f’(3.7)=7.4+3=10.4, 更新=-1.04, w₂=2.66, f(w₂)=7.08+7.98=15.06
(实际最小值在 w=-1.5 处)

A3:w₀=3, f’(3)=6, 更新=-0.5×6=-3, w₁=0, f(0)=0。损失从 f(3)=9 降到了 0。α=0.5 对这个特殊函数恰好一步到最小值,但一般情况下是巧合,不表示 α=0.5 总是好的。

A4:梯度为0意味着参数处于 临界点——可能是局部最低点、局部最高点或鞍点。如果是真正的极小值点,那已经找到了,停止更新。如果是鞍点,需要添加动量或扰动来逃脱。

A5:因为导数 f’(w) 指向函数 上升最快 的方向。我们要最小化损失,所以需要朝反方向走——即 负梯度方向。数学上就是 w_{new}=w_{old}-α·f’(w_{old}) 中的减号。

知识

2.1 BGD(批量梯度下降)——用全部数据算一次方向

假设你有 N=1000 条训练数据。BGD 的做法是:用全部1000条数据计算梯度,然后更新一次参数

公式:

W_{new} = W - α × (1/N) ∑_{i=1}^{N} ∇L_i(W)

其中 ∇L_i(W) 是第 i 条数据对当前参数 W 的梯度。1/N 的作用是取平均,让梯度大小不受数据量影响。

参数说明表:

符号含义类比
N总训练样本数指南上所有城市
∑_{i=1}^{N} ∇L_i(W)所有样本的梯度之和问所有人“该往哪走”,然后把方向加起来
(1/N)∑平均梯度取所有人的“平均意见”
α×平均梯度参数更新量按“平均意见”迈一步

数值例子:

假设只有 3 条数据,损失函数 L(W)=W²(简化版,实际每个样本损失不同)

数据1的梯度:∇L₁=2 × 5 = 10  (对应W=5的位置)
数据2的梯度:∇L₂=2 × 3 = 6   (W=3)
数据3的梯度:∇L₃=2 × 1 = 2   (W=1)
平均梯度 = (10+6+2)/3 = 18/3 = 6
α=0.1 → 更新量 = -0.1×6 = -0.6
新的 W = (5+3+1)/3 - 0.6 = 3 - 0.6 = 2.4
(这里假设我们用一个W代表所有参数的简化情况)

类比——全班旅游投票:

全班 N 个同学投票决定去哪里玩。BGD = 先问每一个人,统计全部意见后,取一个平均方向,然后出发。方向很精准,但问完所有人要很久很久。

BGD 的优缺点:

  • ✅ 梯度方向准确,因为用了全部数据
  • ✅ 每一步损失一定下降(或持平),不会震荡
  • ❌ 计算极慢——每更新一步得算 N 条数据的梯度
  • ❌ 如果 N=100万,一次更新就要算100万次前向和反向传播,几个小时才能更新一次
  • ❌ 不能在线学习(新数据来了不能即时更新)

实际使用:BGD 几乎没有人在大模型训练中使用了,太慢了。

知识

2.2 SGD(随机梯度下降)——随机抽一个人问方向

SGD 和 BGD 只有一个区别:每次只用一个随机样本计算梯度

公式:

W_{new} = W - α × ∇L_i(W)   (i 是随机选的一个样本)

数值例子——SGD 与 BGD 对比:

还是上面3条数据,W=3,α=0.1:

BGD: 平均梯度=(10+6+2)/3=6, 更新=-0.6, 新W=2.4(每次都一样)

SGD(假设随机顺序:数据2→数据3→数据1→数据2...):
第1步:抽到数据2, ∇L=6, 更新=-0.6, W=3-0.6=2.4
第2步:抽到数据3, ∇L=4, 更新=-0.4, W=2.4-0.4=2.0
第3步:抽到数据1, ∇L=10, 更新=-1.0, W=2.0-1.0=1.0
第4步:抽到数据2, ∇L=2, 更新=-0.2, W=1.0-0.2=0.8
...

可以看到 SGD 每一步的方向有 噪声(波动),但整体仍然在往正确的方向走。

类比——拍脑袋决策 vs 全面调研:

SGD 就像领导 随机拦下一个员工问意见,不等别人就直接调头走了。有时候问到的人给出离谱的建议,你就走了弯路(噪声大)。但更多时候,这个随机问路的方式让你 动作超快——别人还在收集意见时你已经走出十步了。

SGD 的优缺点:

  • ✅ 计算极快——每步只用1条数据
  • ✅ 可以流式/在线学习——新数据来了立刻更新
  • ✅ 噪声有好处——可能跳过局部最小值(像热力学扰动让人摆脱困境)
  • ❌ 梯度噪声大——更新方向不一定准确
  • ❌ 不会严格收敛——会在最小值附近来回震荡
  • ❌ 不能利用 GPU 的大规模并行计算优势(一次只算一条数据太浪费 GPU)
知识

2.3 Mini-batch SGD(小批量梯度下降)——实战之王

BGD 太慢,SGD 太颠,实战中没人用这两种极端。大家用的是 Mini-batch SGD。

做法:每次随机取一个小批量(mini-batch),比如 32 条或 128 条数据,用这一个批量的数据计算平均梯度,然后更新一次参数。

公式:

W_{new} = W - α × (1/B) ∑_{i=1}^{B} ∇L_i(W)

其中 B 是小批量的大小(batch size,也叫 batch_size)。

参数说明表:

参数典型值说明
batch_size (B)16, 32, 64, 128, 256每次用多少条数据算梯度
epoch1, 3, 10, 100完整遍历一次全部数据 = 1个epoch
iterationN / B每个epoch内的更新次数

数值例子——完整的一次训练过程:

假设 N=1000 条数据,batch_size=32,α=0.01:

每个iteration:随机取32条 → 计算32个梯度 → 平均 → 更新参数
每个epoch: 1000/32 = 31.25 ≈ 32次iteration(最后一个batch可能不足32)
训练10个epoch: 共 10×32 = 320 次参数更新

三种方法对比表:

属性BGDMini-batch SGDSGD
每次用数据量全部 NB(如32)1
更新次数/epoch1N/BN
梯度噪声无(精确)中等大(高方差)
收敛速度极慢最快快但震荡
GPU利用率高(一次全算)(矩阵并行)极低(浪费GPU)
实际使用几乎不用99%场景用这个很少单独用

为什么 batch_size 通常取 32 或 128?

  • 2 的幂次(32, 64, 128, 256)对 GPU 内存对齐友好
  • 太小(<16):梯度噪声大,GPU并行能力没充分利用
  • 太大(>512):梯度更精确,但 GPU 显存可能不够,而且太大的 batch 会降低泛化能力(收敛到“尖锐”的极小值)
  • 经验法则:起步用 32,内存够就 128,再大收益递减

SGD/Mini-batch 的核心直觉:不要试图完美计算梯度再更新,而是 边走边修正。方向有一点偏差没关系,因为你会不断用新的信息调整方向。走得快比走得准更重要。

练习

练习 2——BGD/SGD/Mini-batch

练习

Q1:训练集有 50000 条数据,batch_size=128。一个 epoch 有多少次 iteration?训练 20 个 epoch 总共多少次 parameter update?

Q2:为什么说 SGD 的噪声有时候反而是好事?

Q3:BGD 每次一定要用 ∑/N 取平均梯度。如果不取平均,直接用梯度之和,会有什么问题?

Q4:你发现训练损失曲线在下降过程中有剧烈震荡。可能是什么原因?应该怎么调整?

Q5:如果 batch_size=1(即纯SGD),和 batch_size=1024 相比,训练速度哪个更快?泛化能力哪个更好?为什么?

查看答案

A1:每 epoch iteration = 50000/128 = 390.625 → 向上取整 = 391 次。20 个 epoch = 391×20 = 7820 次 parameter update。

A2:损失函数有很多 局部极小值(山里的许多小坑)。BGD 的精确梯度会稳稳掉进最近的坑(局部最优)出不来。而 SGD 的噪声就像随机踢了一脚——可能一脚把参数踢出小坑,继续寻找真正的全局最低点。这就是 随机性作为一种正则化

A3:如果不取平均,梯度之和 = N × 平均梯度。假设 N=100000,更新量 = α×N×平均梯度 ≈ 100000 × α × 平均梯度。这会导致更新量过大,参数直接发散。取平均保证了 步长不依赖于数据量

A4:可能原因:1) 学习率 α 太大;2) batch_size 太小导致噪声太大;3) 数据本身有噪声或异常值。解决方案:1) 减小 α(比如从0.01降到0.001);2) 增大 batch_size(从32增加到128);3) 检查数据质量。

A5:速度上:batch_size=1024 单步计算慢(需要算1024条数据),但每步更准,每 epoch 更新次数少(50000/1024 ≈ 49次),总时间可能略快。泛化上:batch_size=1 通常泛化更好(噪声带来正则化效果),batch_size=1024 泛化可能变差(梯度太准,陷入尖锐极小值)。实践中 batch_size 通常在 32~256 之间做权衡。

知识

3.1 Momentum(动量法)——让优化器有“惯性”

SGD 有什么问题?

想象一个窄而长的山谷:沿着山谷的方向(长轴)坡度很缓,需要慢慢走;垂直于山谷的方向(短轴)坡度很陡,左右震荡。SGD 在这种地形上会表现为:在山谷两侧反复摩擦、左右摇摆,但就是不怎么前进

Momentum 的解决方案——加入惯性:

就像从山顶推下一个球。球不会每步都重新“看坡度决定方向”,而是 保持之前的速度,加上当前坡度带来的加速度。即使某个时刻坡度水平方向为0,球也会因为惯性继续前进。

公式:

v_t = β × v_{t-1} + ∇L(W_t)
W_{t+1} = W_t - α × v_t

参数说明表:

符号名称含义典型值类比
v_t速度/动量当前步的更新方向(带历史信息)-球的当前速度
v_{t-1}上一步的速度上一步累计的更新方向-球上一步的速度
β动量衰减系数保留多少上一步的速度0.9地面摩擦力——摩擦力越小保留越多
∇L(W_t)当前梯度当前位置的坡度产生的力-当前坡度给球的加速度
α学习率总体步长缩放因子0.001~0.1球的质量(质量越大越难加速/减速)

数值例子——一步步手算 Momentum:

假设在某方向上的梯度序列为:g=[3, 2, 1, 0.5, 0.1, -0.2, -0.1, 0],β=0.9,α=0.1

初始化:v=0

第1步:梯度g=3
  v = 0.9×0 + 3 = 3
  ΔW = -0.1×3 = -0.3
  新W = W - 0.3

第2步:梯度g=2
  v = 0.9×3 + 2 = 2.7 + 2 = 4.7
  ΔW = -0.1×4.7 = -0.47
  新W = W - 0.47

第3步:梯度g=1
  v = 0.9×4.7 + 1 = 4.23 + 1 = 5.23
  ΔW = -0.1×5.23 = -0.523
  新W = W - 0.523

第4步:梯度g=0.5
  v = 0.9×5.23 + 0.5 = 4.707 + 0.5 = 5.207
  ΔW = -0.521

第5步:梯度g=0.1
  v = 0.9×5.207 + 0.1 = 4.686 + 0.1 = 4.786
  ΔW = -0.479

第6步:梯度g=-0.2(注意梯度方向反转了!)
  v = 0.9×4.786 + (-0.2) = 4.307 - 0.2 = 4.107
  ΔW = -0.411
  (虽然反转了,但惯性很大,只减速了一点,仍然前进)

第7步:梯度g=-0.1
  v = 0.9×4.107 + (-0.1) = 3.696 - 0.1 = 3.596
  ΔW = -0.360

第8步:梯度g=0
  v = 0.9×3.596 + 0 = 3.236
  ΔW = -0.324
  (即使梯度为0,惯性仍让参数继续前进!)

与不加 Momentum 的 SGD 对比:

不加 Momentum:第6步梯度为负→直接往回走;第8步梯度为0→完全不更新。加了 Momentum:即使梯度方向变了或为0,惯性让参数继续朝原方向前进,同时逐渐减速,不会突然转向。

日常类比——开车:

  • SGD = 你每走一步都停下来,重新看路牌决定方向
  • Momentum = 你开车时带着速度,踩刹车要一段距离才能停下来,踩油门速度是逐渐加上去的
  • β = 车的惯性系数(0.9=冰面开车,惯性大;0.1=沙地开车,摩擦力大)
  • 梯度 = 油门(正梯度)或刹车(负梯度)
知识

3.2 Momentum 在“窄峡谷”地形上为什么有效?

问题场景:一个峡谷,长轴方向(谷底方向)梯度=0.1,短轴方向(震荡方向)梯度=±5。

SGD 的行为:

第1步:向左震荡5 + 前进0.1 = 方向5.01(几乎横向走)
第2步:向右震荡5 + 前进0.1 = 方向-4.99(又横跳回来)
... 每一步都在左右横跳,前进极其缓慢

Momentum 的行为(β=0.9):

第1步:v = 0.9×0 + [5, 0.1] = [5, 0.1]
第2步:v = 0.9×[5,0.1] + [-5,0.1] = [4.5,-0.09] + [-5,0.1] = [-0.5, 0.01]
第3步:v = 0.9×[-0.5,0.01] + [5,0.1] = [-0.45,0.009] + [5,0.1] = [4.55, 0.109]
第4步:v = 0.9×[4.55,0.109] + [-5,0.1] = [4.095,0.098] + [-5,0.1] = [-0.905, 0.198]
...
关键:震荡方向(短轴)的梯度正负交替,累积后正负抵消;
前进方向(长轴)的梯度同向,累积后叠加加速!

结论:Momentum 天然 抑制震荡、加速前进。震荡方向的梯度一正一负互相抵消,前进方向的梯度不断叠加。就像一个秋千,你只在它荡回来时推一下,它就越荡越高——Momentum 就是在正确的时机“顺势推一把”。

Nesterov Momentum(NAG)——比标准 Momentum 更聪明:

NAG 的想法是:既然我们知道当前速度会带惯性往前走,为什么不先“预见”一下到哪?

标准 Momentum:
  1. 算当前位置的梯度 g
  2. 更新速度:v = βv + g
  3. 更新参数:W = W - αv

Nesterov Momentum:
  1. 先按惯性“跳一步”到预估位置:W_temp = W - βv
  2. 在预估位置计算梯度 g_lookahead
  3. 更新速度:v = βv + g_lookahead
  4. 更新参数:W = W - αv

NAG 的 提前看一步 行为,让它在接近最小值时不会冲过头,收敛更稳定。就像司机快到路口时提前松油门,而不是到路口才急刹车。

练习

练习 3——Momentum

练习

Q1:初始 v=0,梯度序列 g=[2, 3, 4, 5],β=0.8,α=0.1。手算前3步的 v 值和更新量。

Q2:β=0.9 和 β=0.5 有什么区别?什么时候应该用更大的 β?

Q3:梯度方向突然逆转(从+10变成-10),Momentum 会立刻反向吗?还是要花几步?请用 β=0.9 手算说明。

Q4:Nesterov Momentum 为什么比标准 Momentum 收敛更快?

Q5:用自己的话解释:为什么 Momentum 能解决峡谷地形中的震荡问题?

查看答案

A1:

第1步:v = 0.8×0 + 2 = 2, 更新 = -0.1×2 = -0.2
第2步:v = 0.8×2 + 3 = 1.6+3 = 4.6, 更新 = -0.1×4.6 = -0.46
第3步:v = 0.8×4.6 + 4 = 3.68+4 = 7.68, 更新 = -0.1×7.68 = -0.768

A2:β=0.9 保留了 90% 的上一步速度,惯性很大,适合地形连续、方向一致的情况。β=0.5 只保留50%,惯性小,响应快,适合梯度频繁变化、噪声大的情况。通常在平坦或较平滑的地形上用更大的 β(如0.9),在地形崎岖时用更小的 β(如0.5)。深度学习默认通用值就是 β=0.9。

A3:设初始 v=10,梯度突然从+10变成-10

转变前:v = 0.9×v_prev + 10 = 很大正值
第1步(梯度=-10):v = 0.9×10 + (-10) = 9-10 = -1 —— 方向终于反了,但很小
第2步(梯度=-10):v = 0.9×(-1) + (-10) = -0.9-10 = -10.9 —— 反向加速
第3步(梯度=-10):v = 0.9×(-10.9) + (-10) = -9.81-10 = -19.81 —— 反向全速

Momentum 需要约2步才能逆转方向。这就是“惯性”的表现——不会瞬间掉头。

A4:Nesterov 因为 提前看了一步 位置的梯度,当快到山谷时能提前减速(预估位置的梯度可能更平缓或已经反转),避免冲过头。标准 Momentum 要到“撞上”谷壁才反应,容易来回震荡几次。形象地说:NAG 像有“刹车灯”,标准 Momentum 像“撞墙才停”。

A5:峡谷地形中,短轴方向(震荡方向)的梯度正负交替出现。Momentum 的 v_t = βv_{t-1} + g_t 对历史有平滑作用。正负交替的梯度在累积中互相抵消(+5 -5 +5 -5 ≈ 0),而长轴方向同向的梯度不断叠加(+0.1 +0.1 +0.1 +0.1 → 累积变大)。最终效果:震荡被抑制,前进被加速。

知识

4.1 AdaGrad——不同参数用不同学习率

一个问题:前面的 BGD、SGD、Momentum 对所有参数都用同一个学习率 α。但实际中,不同参数的梯度数量级差异很大。

例子:神经网络中,靠近输入的层和靠近输出的层,梯度大小可能差1000倍。用一个统一的学习率,要么对 “大梯度参数”震荡,要么对 “小梯度参数”学不动。

AdaGrad 的想法:经常更新的参数降学习率(因为梯度大,已经学了很多),不常更新的参数保持高学习率(因为梯度小,需要多学)。

公式:

G_t = G_{t-1} + (∇L(W_t))²   (每个参数的梯度平方累积)
W_{t+1} = W_t - α / (√(G_t) + ε) × ∇L(W_t)

参数说明表:

符号含义类比
G_t历史梯度平方的累积和计步器——记录每个参数“走了多少步”
(∇L)²当前梯度的平方这次走的步幅的平方
√(G_t)梯度累积量的平方根根据“总运动量”调整步长
ε数值稳定常数防止除以0
α / (√(G_t) + ε)自适应学习率每个参数自己“算”出来的专属步长

数值例子:

有两个参数 w₁ 和 w₂,初始化 α=0.1,ε=1e-8(忽略 ε 简化计算):

时间步     w₁的梯度    w₂的梯度    G(w₁)    G(w₂)    w₁的学习率    w₂的学习率
t=1       0.5          0.01         0.25       0.0001     0.1/0.5=0.2    0.1/0.01=10
t=2       0.3          0.02         0.34       0.0005     0.1/0.58=0.17  0.1/0.022=4.5
t=3       0.2          0.01         0.38       0.0006     0.1/0.62=0.16  0.1/0.024=4.08
t=10      ...          ...          大约2.0    大约0.002   0.1/1.4=0.07   0.1/0.045=2.2

可以看到:w₁(梯度大)的学习率从0.2快速下降到0.07
w₂(梯度小)的学习率一直保持很大(4-10倍于w₁)
这让w₂这个大梯度能快速追上——解决了学习率不均衡问题!

AdaGrad 的问题——学习率会降为零:

G_t 是 单调递增 的(不断累积正数)。随着训练进行,G_t → ∞,α/√(G_t) → 0。参数最终 完全停止学习。对于需要长期训练的任务(如大模型训练数百亿步),这不可接受。

日常类比——学英语:AdaGrad 像是给每个单词一个“努力计数器”。你每背一次单词,它的“努力”就累积一点,然后后续分配给它的复习时间就减少。缺点:越往后你学任何新东西都越来越慢,最后完全不想学了。

知识

4.2 RMSprop——修复 AdaGrad 的“学习率死亡”问题

关键改进:不用全部历史的梯度平方累积,而用 指数移动平均(EWMA)——只记住 “最近一段时间的梯度大小”,老梯度的影响逐渐衰减。

公式:

v_t = β₂ × v_{t-1} + (1 - β₂) × (∇L(W_t))²
W_{t+1} = W_t - α / (√(v_t) + ε) × ∇L(W_t)

与 AdaGrad 的区别:

对比项AdaGradRMSprop
累计方式求和 G = ∑g²移动平均 v = β₂·v + (1-β₂)·g²
历史影响永不衰减,全部保留指数衰减,越旧权重越小
学习率单调递减 → 最终为0动态调整,不会归零
适用场景稀疏特征(NLP/推荐系统)通用(几乎所有任务)

参数说明表:

符号名称含义典型值
v_t梯度平方的移动平均计算最近的“梯度大小”-
β₂衰减率历史信息的保留比例0.999
1-β₂新信息比重当前梯度平方占的权重0.001
α全局学习率基础步长0.001
ε数值稳定常数防止除以01e-8

数值例子——RMSprop vs AdaGrad:

设每步梯度恒为 0.5,对比两种方法的 G_t 或 v_t 变化(β₂=0.9):

时间      AdaGrad(G)        RMSprop(v)
t=1      0.25              0.1×0 + 0.9×0.25 = 0.225
t=2      0.50              0.9×0.225 + 0.1×0.25 = 0.203+0.025 = 0.228
t=3      0.75              0.9×0.228 + 0.025 = 0.205+0.025 = 0.230
t=10     2.50              0.232
t=100    25.0              0.249
t=1000   250.0             0.250

AdaGrad: G_t 线性增长,学习率 α/√(G_t) 从0.2→0.02→0.006... 一直下降到0
RMSprop: v_t 趋于稳定值 0.25,学习率 α/√(0.25)=0.1/0.5=0.2 保持不变!

日常类比——记笔记:

  • AdaGrad = 把所有笔记越堆越高,最后要找一条信息需要翻完整座山
  • RMSprop = 用一个笔记本,不断翻新——最新记的放在最上面,旧的内容慢慢被覆盖

RMSprop 完美解决了 AdaGrad 的学习率衰减到零的问题,是 Adam 算法的重要组成部分。

练习

练习 4——AdaGrad & RMSprop

练习

Q1:为什么 AdaGrad 中 G_t 要累加梯度的 平方 而不是直接用梯度?

Q2:梯度序列 g=[3, 2, 1, 0.5, 0.2],α=0.01。用 AdaGrad 手算前3步的学习率(忽略 ε)。

Q3:梯度序列同 Q2,用 RMSprop(β₂=0.9,α=0.01)手算前3步的学习率。对比 AdaGrad 的差异。

Q4:为什么 RMSprop 的 β₂ 典型值是 0.999 这么大?如果改成 0.9 会怎样?

Q5:在自然语言处理(NLP)任务中,一些词频繁出现(如“的”“是”),一些词很少出现。AdaGrad 的每个参数自适应学习率机制为什么对这种场景特别有利?

查看答案

A1:因为梯度有正有负,直接累加会正负抵消 = 0。平方(或绝对值)确保累加值永远为正,能正确反映“该参数历史上梯度的大小”。取 √(G_t) 是为了把量级恢复到与梯度一致(因为平方放大了量级)。

A2:

t=1: g=3, G=9, lr=0.01/3=0.0033
t=2: g=2, G=9+4=13, lr=0.01/√13=0.01/3.606=0.00277
t=3: g=1, G=13+1=14, lr=0.01/√14=0.01/3.742=0.00267

A3:

β₂=0.9
t=1: g=3, g²=9, v=0.9×0+0.1×9=0.9, lr=0.01/√0.9=0.01/0.949=0.01054
t=2: g=2, g²=4, v=0.9×0.9+0.1×4=0.81+0.4=1.21, lr=0.01/√1.21=0.01/1.1=0.00909
t=3: g=1, g²=1, v=0.9×1.21+0.1×1=1.089+0.1=1.189, lr=0.01/√1.189=0.01/1.09=0.00917

差异:AdaGrad的学习率单调递减(0.0033→0.00277→0.00267),
RMSprop的学习率动态调整(0.0105→0.0091→0.0092),先降后稳,不会一直降到零。

A4:β₂=0.999 意味着 v 会保留近 1000 步的梯度平方信息,对梯度大小的估计非常平滑稳定。如果改成 β₂=0.9,则只保留近 10 步的信息,v 对梯度变化更敏感,自适应学习率波动更大。大模型训练中通常用 0.999 或 0.9999 获得平滑的梯度估计。

A5:NLP 中词向量的更新极度不均衡。“的”这个字每次出现都会被更新,它的 G 累积很大,学习率很快变小→已经学好了,不再需要大幅调整。而“变压器”这种低频词出现的次数少,G 累积小,学习率保持很大→当它出现时能快速学。AdaGrad 的这种“给稀有特征高学习率”的特性在 NLP 和推荐系统中非常有效。

知识

5.1 Adam——Momentum + RMSprop 的完美融合

Adam = Adaptive Moment Estimation(自适应矩估计)

这是目前 最常用、最推荐的首选优化器。它将 Momentum(动量)和 RMSprop(自适应学习率)两个思想融合在一起,同时解决了两个问题:

  • Momentum 部分:用指数移动平均平滑梯度方向,抑制震荡、加速收敛
  • RMSprop 部分:用梯度平方的指数移动平均给每个参数自适应学习率

完整公式(高中数学版本):

m_t = β₁ × m_{t-1} + (1 - β₁) × g_t          (1)  动量:梯度的移动平均
v_t = β₂ × v_{t-1} + (1 - β₂) × g_t²        (2)  自适应:梯度平方的移动平均

m̂_t = m_t / (1 - β₁^t)                     (3)  偏差校正
v̂_t = v_t / (1 - β₂^t)                     (4)  偏差校正

W_{t+1} = W_t - α / (√(v̂_t) + ε) × m̂_t    (5)  最终更新

参数说明表:

符号名称含义典型值类比
g_t当前梯度当前位置损失函数的坡度-脚底下地面的倾斜方向
m_t一阶矩(动量)梯度的移动平均,代表方向-速度的方向(向哪边冲)
v_t二阶矩(自适应)梯度平方的移动平均,代表大小-路面的颠簸程度
β₁一阶矩衰减率控制动量保留多少历史0.9车辆的惯性系数
β₂二阶矩衰减率控制自适应量的平滑程度0.999路面感知的“记忆长度”
m̂_t偏差校正后动量修正初始零偏后的方向-校准后的指南针
v̂_t偏差校正后自适应量修正初始零偏后的梯度大小-校准后的速度表
α学习率全局步长0.001驾驶的“油门基准”
ε稳定常数防止除以01e-8安全缓冲垫

为什么需要 “偏差校正”?

Adam 初始化时 m_0=0, v_0=0。前几步的移动平均值会严重偏向0(因为一开始全是0)。偏差校正 = 把被0拉低的平均值 “拉伸回来”。公式 m̂_t = m_t/(1-β₁^t) 中,分母随 t 增大从接近0逐渐趋近1,正好补偿了初始的“零偏”。

例子:β₁=0.9, 前几步 m_t 和 m̂_t 对比
t=1: m_1=0.0+0.1×g=0.1g, 校正 m̂=0.1g/(1-0.9)=0.1g/0.1=g   (校正后等于真实梯度g!)
t=2: m_2=0.9×0.1g+0.1×g=0.19g, 校正 m̂=0.19g/(1-0.81)=0.19g/0.19=g
t=3: m_3=0.9×0.19g+0.1×g=0.271g, 校正 m̂=0.271g/(1-0.729)=0.271g/0.271=g
实际上校正后 m̂_t 约等于 g 的移动平均值,初始偏差被完全消除!
知识

5.2 Adam 手算例子——一步步跑通整个流程

设 β₁=0.9, β₂=0.999, α=0.01, ε=1e-8(本算例中忽略 ε),梯度序列 g=[0.5, -1.0, 2.0, 0.3, -0.8]

初始: m_0=0, v_0=0

=== t=1, g=0.5 ===
m_1 = 0.9×0 + 0.1×0.5 = 0.05
v_1 = 0.999×0 + 0.001×0.25 = 0.00025
m̂_1 = 0.05 / (1 - 0.9^1) = 0.05/0.1 = 0.5
v̂_1 = 0.00025 / (1 - 0.999^1) = 0.00025/0.001 = 0.25
ΔW = -0.01 × 0.5 / √0.25 = -0.01 × 0.5/0.5 = -0.01

=== t=2, g=-1.0 ===
m_2 = 0.9×0.05 + 0.1×(-1.0) = 0.045 - 0.1 = -0.055
v_2 = 0.999×0.00025 + 0.001×1.0 = 0.00024975 + 0.001 = 0.00124975
m̂_2 = -0.055 / (1 - 0.9^2) = -0.055/(1-0.81) = -0.055/0.19 = -0.2895
v̂_2 = 0.00124975 / (1 - 0.999^2) = 0.00124975/(1-0.998001) = 0.00124975/0.001999 ≈ 0.625
ΔW = -0.01 × (-0.2895) / √0.625 = 0.002895/0.791 = 0.00366

=== t=3, g=2.0 ===
m_3 = 0.9×(-0.055) + 0.1×2.0 = -0.0495 + 0.2 = 0.1505
v_3 = 0.999×0.00124975 + 0.001×4.0 = 0.0012485 + 0.004 = 0.0052485
m̂_3 = 0.1505 / (1 - 0.9^3) = 0.1505/(1-0.729) = 0.1505/0.271 = 0.555
v̂_3 = 0.0052485 / (1 - 0.999^3) = 0.0052485/(1-0.997003) = 0.0052485/0.002997 ≈ 1.751
ΔW = -0.01 × 0.555 / √1.751 = -0.00555/1.323 = -0.00419

=== 总结 ===
t=1: ΔW=-0.01 (原始梯度0.5, 被缩小但方向正确)
t=2: ΔW=+0.00366 (梯度-1.0反向应减W, 但动量方向仍然正向! 惯性拉扯中)
t=3: ΔW=-0.00419 (梯度2.0改变方向, 动量跟上了)
对比纯SGD: t1:-0.005, t2:+0.01, t3:-0.02 (3倍于Adam的更新量)
Adam的更新量更小更平滑, 因为RMSprop感知到梯度大小在变, 自动调节了学习率

Adam 的直觉理解——自动驾驶:

  • SGD = 刚拿驾照的新手:每步都重新判断方向,方向盘大幅乱转
  • Momentum = 有经验的老司机:有预判,转弯顺滑
  • AdaGrad/RMSprop = 自适应的车:根据路况自动调节油门大小
  • Adam = 顶级自动驾驶:既有老司机的预判(Momentum),又能根据路况自动调油门(RMSprop)
知识

5.3 AdamW——Adam 的修复版(现代大模型的标准配置)

Adam 有一个隐藏问题——L2正则化(权重衰减)的实现方式不对。

问题:标准 Adam 中,L2正则化项 λ·W 被加在损失函数中参与梯度计算,然后梯度被 Adam 的移动平均值平滑和自适应学习率缩放。这导致 权重的衰减效果不稳定——被自适应学习率改变了衰减量。

AdamW 的修复:把权重衰减 从梯度计算中剥离出来,在 Adam 更新完成后,直接对权重做一次独立的衰减

标准 Adam 的 L2 正则化(有问题):
  1. 计算损失梯度时包含 L2 项: g_t = g_loss + λ·W
  2. g_t 被 Adam 的 m/v 机制缩放
  3. 最终更新量 = -α·m̂/√(v̂) —— 权重衰减也被缩放了!

AdamW 的权重衰减(正确):
  1. 计算损失梯度时不含 L2: g_t = g_loss
  2. g_t 被 Adam 的 m/v 机制正常缩放
  3. 在更新最后额外加一步: W -= α·λ·W(或 W *= 1-α·λ)
  —— 权重衰减不再被自适应学习率干扰!

为什么这对大模型训练重要?

LLM 训练中,权重衰减帮助控制模型复杂度、防止过拟合。AdamW 让权重衰减的效果 可预测且稳定,是 GPT、LLaMA 系列的标准配置。

PyTorch 代码示例:

# Adam(旧方式)
import torch.optim as optim
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)

# AdamW(推荐方式)
optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)
# 区别:PyTorch 的 Adam 的 weight_decay 是错的(旧实现),
# AdamW 的 weight_decay 才是正确的权重衰减。
练习

练习 5——Adam 综合

练习

Q1:Adam 的四行公式分别代表什么?(用一句话概括每行)

Q2:β₁=0.9 的“有效记忆长度”是多少步?(提示:指数移动平均的半衰期公式 t½ ≈ ln(0.5)/ln(β))

Q3:梯度序列 g=[1, 1, 1, 1, 1, -10, 1, 1, 1, 1],对比 Adam 和纯 SGD(α=0.01)在第 6 步(大梯度-10出现时)的更新量差异。Adam 参数:β₁=0.9, β₂=0.999, α=0.01

Q4:为什么 AdamW 比标准 Adam 更适合训练大语言模型?

Q5:如果训练中损失突然“爆炸”(spike),用 Adam 优化器时可能是什么问题?怎么修复?

查看答案

A1:

公式(1) m_t = β₁·m_{t-1} + (1-β₁)·g_t  —— 梯度的移动平均(动量)
公式(2) v_t = β₂·v_{t-1} + (1-β₂)·g_t²  —— 梯度平方的移动平均(自适应)
公式(3) m̂ = m/(1-β₁^t)           —— 偏差校正(去初始化零偏)
公式(4) v̂ = v/(1-β₂^t)           —— 偏差校正
公式(5) W -= α·m̂/(√(v̂)+ε)        —— 最终更新

A2:β₁=0.9, ln(0.5)/ln(0.9) = -0.693/-0.105 ≈ 6.6步。动量大约保留最近 6-7 步的梯度信息。β₂=0.999 的有效记忆:ln(0.5)/ln(0.999) = -0.693/-0.001 ≈ 693步。自适应量保留约 700 步的梯度平方信息。

A3:

纯SGD: 第6步更新 = -0.01 × (-10) = +0.10(方向突然大幅反转)

Adam:
前5步累积:
  m_5 ≈ 1(5步平均约1)
  v_5 ≈ 1(5步平方平均约1)
  m̂_5=1, v̂_5=1(偏差校正后约1)
第5步更新 = -0.01×1/√1 = -0.01

第6步(g=-10):
  m_6 = 0.9×1 + 0.1×(-10) = 0.9-1.0 = -0.1
  v_6 = 0.999×1 + 0.001×100 = 0.999+0.1 = 1.099
  m̂_6 = -0.1/(1-0.9^6) = -0.1/(1-0.531) = -0.1/0.469 = -0.213
  v̂_6 = 1.099/(1-0.999^6) = 1.099/(1-0.994) = 1.099/0.006 ≈ 183
  更新 = -0.01 × (-0.213)/√183 = 0.00213/13.53 = 0.000158

对比: SGD 更新 +0.10(剧烈震荡), Adam 更新 +0.000158(几乎不变)
Adam 因为有动量平滑和大梯度-10被 v 感知到(v̂=183巨大)
自适应学习率 α/√(v̂)=0.01/13.53=0.00074
乘以动量 -0.213 得更新量 -0.000158,方向几乎没变!

A4:AdamW 的权重衰减 独立于梯度自适应机制。标准 Adam 的 L2 权重项被 α/√(v̂) 缩放后效果不可控——不同参数的“真实衰减量”不同。AdamW 的权重衰减直接作用在参数上,效果稳定、可预测。对于有数十亿参数的大模型,这种稳定性和可预测性极为重要。Hugging Face Transformers 库默认使用 AdamW 作为优化器。

A5:可能的 Adam 相关问题及修复:

  • 学习率太大:即使有自适应学习率,α=0.01 对大模型仍可能过大。修复:将 α 从 0.001 降到 0.0001
  • ε 太小:某些参数经过偏差校正后 v̂ 很小,α/√(v̂) 可能爆炸。修复:将 ε 从 1e-8 增大到 1e-6
  • 梯度裁剪不足:某个 mini-batch 产生异常大的梯度。修复:加梯度裁剪 clip_grad_norm(model.parameters(), max_norm=1.0)
  • 权重衰减过大:weight_decay 太大导参数被过度衰减。修复:减小 weight_decay
知识

6.1 六大优化器全面对比

进化路线图:

BGD → SGD → Mini-batch SGD(基础框架)

↓                           

Momentum(+惯性)→ Nesterov Momentum(+预见)

                          

AdaGrad(+每个参数自适应)→ RMSprop(+滑动窗口替代无限累积)

                          

Adam = Momentum + RMSprop + 偏差校正

AdamW = Adam + 正确权重衰减

完整对比表:

优化器核心公式超参数优点缺点适用场景
BGDW -= α×(1/N)∑∇Lα梯度精确,一定收敛极慢,无法在线学习小数据集、演示教学
SGDW -= α×∇L_iα极快,在线学习,能跳出局部最优噪声大,不严格收敛在线学习
Mini-batchW -= α×(1/B)∑∇Lα, B速度与精度平衡,GPU友好须选好batch_size几乎所有实际任务
Momentumv=βv+∇L
W-=αv
α, β=0.9抑制震荡,加速收敛可能冲过头峡谷地形、CV任务
AdaGradG+=g²
W-=αg/√(G)
α, ε=1e-8自适应学习率,适合稀疏特征学习率最终归零NLP、推荐系统
RMSpropv=βv+(1-β)g²
W-=αg/√(v)
α, β=0.999, ε=1e-8自适应学习率,不归零无动量(可震荡)通用(Momentum的“另一半”)
Adamm=β₁m+(1-β₁)g
v=β₂v+(1-β₂)g²
W-=α·m̂/(√v̂+ε)
α=0.001, β₁=0.9
β₂=0.999, ε=1e-8
Momentum+RMSprop=双重优势显存占用稍大(存m和v)通用首选!
AdamWAdam + 独立权重衰减同上 + weight_decay正确的权重衰减,更稳定多一个超参数大模型训练标准
知识

6.2 实战经验:怎么选择优化器?

一句话指南:

“先用 Adam/AdamW 快速跑通,再考虑要不要换。”

详细建议:

你的情况推荐优化器理由
刚入门,第一次跑神经网络Adam默认参数(0.001)通常就能工作,不需要太多调参
训练大语言模型(LLM)AdamW独立权重衰减稳定,是GPT/LLaMA的标准
图像分类/CNN/CV任务Adam 或 SGD + MomentumSGD+动量在CV社区有大量经验,更易调优
数据极度稀疏(NLP/推荐)Adam 或 AdaGrad自适应学习率对稀疏特征天然有利
需要极限泛化性能SGD + Momentum + 学习率调度SGD虽然需要更多调参,但在某些任务上泛化略优
资源受限(显存不够)SGD + MomentumAdam需要额外存m和v(显存翻倍),SGD只需存参数

调参建议:

  • 学习率 α:最重要的超参数。Adam 从 0.001 开始,不收敛就降(0.0003、0.0001),震荡太厉害也降
  • batch_size:起步 32,显存够用 128,再大收益递减
  • β₁(动量衰减):不要动!0.9 几乎总是最优
  • β₂(自适应衰减):不要动!0.999/0.9999 几乎总是最优
  • ε:不要动!1e-8 足够。如果训练不稳定可以试试 1e-6
  • weight_decay(AdamW):LLM 通常 0.01~0.1,CV 通常 0.0001~0.001

学习率调度(Learning Rate Schedule)——配合优化器使用:

光靠优化器还不够,好的学习率调度能显著提升训练效果:

  • Step Decay:每 N 个 epoch 学习率减半(如每30epoch×0.1)
  • Cosine Annealing:学习率按余弦曲线从大变小再变大(重启),帮助跳出局部最优
  • Warmup:前几步从小学习率逐步升到目标值,防止初始不稳定
  • ReduceLROnPlateau:损失不再下降时自动降低学习率

PyTorch 使用示例:

import torch.optim as optim
from torch.optim.lr_scheduler import CosineAnnealingLR

# 定义模型
model = MyNeuralNetwork()

# 选择优化器
optimizer = optim.AdamW(
    model.parameters(),
    lr=0.001,              # 学习率
    betas=(0.9, 0.999),    # β₁, β₂
    eps=1e-8,              # ε
    weight_decay=0.01      # 权重衰减
)

# 学习率调度器
scheduler = CosineAnnealingLR(
    optimizer,
    T_max=100,   # 余弦完成一个周期需要多少步
    eta_min=1e-6 # 最小学习率
)

# 训练循环
for epoch in range(100):
    for batch in dataloader:
        # 前向 + 反向 + 更新
        loss = model(batch)
        loss.backward()
        
        # 梯度裁剪(可选,推荐大模型训练时使用)
        torch.nn.utils.clip_grad_norm_(
            model.parameters(), max_norm=1.0)
        
        optimizer.step()
        optimizer.zero_grad()
    
    # 每 epoch 更新学习率
    scheduler.step()
练习

练习 6——优化器选择与综合应用

练习

Q1:你接到一个任务:训练一个 BERT 模型用于文本分类。你会选择什么优化器?学习率和超参数大概设多少?请给出理由。

Q2:训练过程中发现损失在大约 200 步后就不再下降了(plateau),但离理想的精度还差很远。应该怎么解决?

Q3:你的 GPU 只有 8GB 显存,想训练一个有 1 亿参数的模型。选 Adam 还是 SGD+Momentum?为什么?

Q4:用自己的话总结:Adam 相比 SGD+Momentum 的根本优势是什么?SGD+Momentum 相比 Adam 的根本优势又是什么?

Q5:一个模型用 Adam 训练时损失曲线:前 500 步正常下降,第 501 步突然飙升 100 倍,然后又恢复正常。分析可能的原因和解决方案。

查看答案

A1:推荐 AdamW(Hugging Face Transformers 的默认优化器)。超参数设置:lr=2e-5(BERT 微调的经典学习率),weight_decay=0.01,β₁=0.9, β₂=0.999。并配合 warmup(前 10% 的步数从 0 线性增长到 2e-5)。理由:BERT 微调是迁移学习场景,AdamW 的稳定性最好,2e-5 是经过海量实验确定的经典值。

A2:解决方案:

  • 使用 ReduceLROnPlateau 调度器——patience=10 步的话,验证损失 10 步没降就自动降低学习率(如×0.1)
  • 重启 Adam 的 m 和 v(用 optimizer.state = {} 清空)——有时候优化器的动量状态导致“踩坑”
  • 检查是否陷入局部最优——可以尝试 Cosine Annealing with Warm Restarts

A3:Adam 每个参数需要存 3 份 数据:参数 W(4字节)、动量 m(4字节)、自适应量 v(4字节)= 12 字节/参数。1 亿参数 = 1.2GB。加上模型本身和其他中间变量,8GB 非常勉强。SGD+Momentum 只需存 2 份:参数 W(4字节)+ v(4字节)= 8 字节/参数。1 亿参数 = 0.8GB。显存压力小很多。所以推荐 SGD + Momentum使用 Adam 的混合精度训练(梯度半精度,减少显存占用)。

A4:

  • Adam 的根本优势 = 自适应学习率。你不用花大量时间调学习率,Adam 自己会根据每个参数的历史梯度大小动态调整学习率,让所有参数都处于“合适”的更新节奏。
  • SGD+Momentum 的根本优势 = 泛化能力。SGD 的固定学习率(配合合适调度)在图像分类等任务上被证明泛化性能略优于 Adam。同时显存占用更小,而且在某些社区的调参经验积累远超 Adam。

A5:可能原因:

  1. 数据异常:当前 batch 包含异常标签或噪声数据。解决:检查数据质量,加数据清洗
  2. 梯度爆炸:梯度突然变得极大,v 来不及适应。解决:加梯度裁剪 clip_grad_norm(1.0)
  3. ε 太小:某个参数 v 极小,α/√(ε) 爆炸。解决:ε 从 1e-8 改为 1e-6
  4. 数值不稳定:特殊操作(如 softmax 后的 log)导致数值溢出。解决:用 log_softmax 替代 log(softmax)

单次 spike 如果不重复出现通常不影响最终收敛,但如果反复出现就需要检查原因。