一篇读懂学习率:训练里最重要的超参数

梯度下降只有一句话:沿负梯度方向更新参数。梯度告诉你往哪边走,却没告诉你这一步迈多大——把这句话补全的,就是学习率(learning rate):w ← w − lr·g。同一个模型、同一份数据,学习率差一点,训练曲线就是天壤之别。这篇「一叶一世界」只讲这一个旋钮。

它解决什么问题

把训练想象成下山:梯度是脚下坡度最陡的方向,学习率是步幅。步幅太大,一步跨过谷底,在对面的坡上越荡越高,loss 来回震荡甚至直接飞掉;步幅太小,一步挪一毫米,训练预算烧完了人还在半山腰。

它凭什么比其他超参数更值得先调?三个理由:

  1. 它直接乘在每一步的更新量上,调对了立竿见影,调错了满盘皆输;
  2. 其他超参都和它耦合:batch size 变了要跟着变(下文的线性缩放规则),优化器从 SGD 换成 AdamW 要跟着变(差一到两个数量级),模型变大了峰值也要跟着变;
  3. lr 没调对时,其他超参的对比实验结论都不可靠——同一组配置换个学习率,排名可能整个反转。

所以拿到一份新配置,第一件事永远是确认学习率。

它怎么工作

几何直觉:步长被最陡的方向绑架

损失面不是完美的碗,而是各方向曲率不同的椭圆碗。对凸二次面,梯度下降收敛要求学习率小于 2/λ(λ 取最大曲率方向的曲率)。麻烦在于:上限由最陡的方向决定,进度却常常卡在最平缓的方向上——为了不在陡方向跳出去,你被迫用小步子在平方向上磨。

这也是它与 batch size 的耦合点:batch 越大,梯度估计越稳,越扛得住大步长。Goyal 等人 2017 年的《Accurate, Large Minibatch SGD》把这点变成了可操作的一句话——线性缩放规则(linear scaling rule):minibatch 乘以 k,学习率也乘以 k。也正是这篇论文在把 batch 从 256 放大到 8K 时发现大学习率开局即发散,从而引入了 warmup。

一个能跑的实验:三种学习率的命运

空口无凭,跑一个最小实验。二维椭圆碗 f(x, y) = 0.5·(x² + 25y²),x 方向平缓、y 方向曲率大 25 倍,最小值在原点。从同一点 (−9, −2) 出发,只换学习率:

import numpy as np

DIVERGE = 1e6  # loss 超过这个值视为发散(数字仍在指数级上涨)

def loss(w):
    x, y = w
    return 0.5 * (x ** 2 + 25.0 * y ** 2)   # 椭圆碗:x 方向平缓,y 方向陡 25 倍

def grad(w):
    x, y = w
    return np.array([x, 25.0 * y])

def gradient_descent(lr, steps, w0):
    w = np.array(w0, dtype=float)
    rows = [(0, w[0], w[1], loss(w))]
    for k in range(1, steps + 1):
        w = w - lr * grad(w)
        l = loss(w)
        rows.append((k, w[0], w[1], l))
        if not np.isfinite(l) or l > DIVERGE:
            break
    return rows

def show(name, rows, every):
    print(f"\n== {name} ==")
    print(f"{'step':>6}{'x':>12}{'y':>12}{'loss':>14}")
    last = rows[-1][0]
    for k, x, y, l in rows:
        if k % every == 0 or k == last:
            mark = " <- 发散,提前终止" if k == last and l > DIVERGE else ""
            print(f"{k:>6}{x:12.4f}{y:12.4f}{l:14.3e}{mark}")

w0 = [-9.0, -2.0]
print("起点 w0 =", w0, "loss =", f"{loss(w0):.3e}")
show("过大 lr=0.085,跑 80 步", gradient_descent(0.085, 80, w0), 10)
show("合适 lr=0.060,跑 60 步", gradient_descent(0.060, 60, w0), 10)
show("过小 lr=0.001,跑 1000 步", gradient_descent(0.001, 1000, w0), 100)

实际运行输出:

起点 w0 = [-9.0, -2.0] loss = 9.050e+01

== 过大 lr=0.085,跑 80 步 ==
  step           x           y          loss
     0     -9.0000     -2.0000     9.050e+01
    10     -3.7021     -6.4946     5.341e+02
    20     -1.5229    -21.0902     5.561e+03
    30     -0.6264    -68.4866     5.863e+04
    40     -0.2577   -222.3980     6.183e+05
    43     -0.1974    316.6565     1.253e+06 <- 发散,提前终止

== 合适 lr=0.060,跑 60 步 ==
  step           x           y          loss
     0     -9.0000     -2.0000     9.050e+01
    10     -4.8475     -0.0020     1.175e+01
    20     -2.6110     -0.0000     3.409e+00
    30     -1.4063     -0.0000     9.888e-01
    40     -0.7575     -0.0000     2.869e-01
    50     -0.4080     -0.0000     8.322e-02
    60     -0.2197     -0.0000     2.414e-02

== 过小 lr=0.001,跑 1000 步 ==
  step           x           y          loss
     0     -9.0000     -2.0000     9.050e+01
   100     -8.1431     -0.1590     3.347e+01
   200     -7.3678     -0.0126     2.714e+01
   300     -6.6664     -0.0010     2.222e+01
   400     -6.0317     -0.0001     1.819e+01
   500     -5.4574     -0.0000     1.489e+01
   600     -4.9378     -0.0000     1.219e+01
   700     -4.4677     -0.0000     9.980e+00
   800     -4.0423     -0.0000     8.170e+00
   900     -3.6575     -0.0000     6.689e+00
  1000     -3.3093     -0.0000     5.476e+00

三种命运一目了然:

  • 过大(lr=0.085):稳定边界是 2/25 = 0.08,0.085 只越界一点点。x 方向照常收敛,y 方向却每步翻号再放大 12.5%,43 步 loss 从 90 冲破一百万——整锅汤被一个方向带飞。
  • 合适(lr=0.060):y 方向每步衰减一半(注意它其实是翻着号「跳着收敛」的,这正是不能再加大的原因),10 步内基本归零;x 方向稳扎稳打,60 步 loss 从 90.5 降到 0.024。
  • 过小(lr=0.001):陡方向很快收敛,平方向每步只走剩余距离的 0.1%,1000 步走完六成路,loss 还剩 5.5——慢到怀疑人生。

注意过小档的细节:先收敛的是陡方向,磨蹭的是平方向。上文说的「被最陡方向绑架」,这就是具象化。Adam 这类自适应优化器的卖点之一,就是按参数归一化步长,把方向之间的这种悬殊抹平。

不妨动手改一改这个实验:把 25 换成 1,椭圆碗退化成曲率均匀的圆碗,过大与合适的分界会往更高处移;把起始点挪到原点附近,过小档的差距也会缩小。学习率从来不是孤立的数字,它和「地形」绑定——换个模型、换个数据分布,结论就得重估。

调度器全家桶

学习率不必是常数,训练中途调它的「人为政策」就是调度器(scheduler)。调度的动机不难猜:训练后期已经走到谷底附近,等高线变密,同样的步幅会来回横跳,得把步子收小;而前期方向未明,步子也不敢放大。绝大多数调度器都在回答同一个问题——什么时候、以多快的速度把学习率降下去:

  • step decay:每过若干 epoch 把 lr 乘以 0.1。经典、简单,但台阶感强,拐点要人肉猜。
  • cosine annealing:沿余弦曲线平滑降到谷底,是当前大模型训练的主流底色。
  • warmup:开头几百到几千步把 lr 从 0 线性升到峰值。为什么开头要小?两个原因。其一,初始权重是随机的,前期梯度方向不可靠,大步子容易一步跨出好区域——大 batch 开局发散正是 Goyal 论文引入 warmup 的动机。其二,Adam 系优化器的二阶矩估计 v 在初期样本太少、方差极大,等效步长忽大忽小,RAdam 论文(《On the Variance of the Adaptive Learning Rate and Beyond》)把这一点讲透了——warmup 本质是给统计量热身。
  • WSD(warmup-stable-decay):MiniCPM 论文给出的骨架——短 warmup、长常数段、短衰减段。衰减段只占总 token 的 10% 就能拿到与 cosine 相当的效果(2.5% 不够),而且从稳定段任意 checkpoint 出发随时衰减都行,训练不必预设总预算,缩放律实验的成本从平方级降到线性。「持续训练」从此有了抓手。

主流 LLM 的实际选择(以下数字截至 2026-10-07,以公开报告为准):Llama 3 的 8B/70B/405B 峰值学习率分别为 3e-4、1.5e-4、8e-5——模型越大,峰值越小,405B 用 8,000 步线性 warmup 后 cosine 衰减;karpathy 的 nanoGPT 默认峰值 6e-4、warmup 2,000 步、cosine 降到十分之一;MiniCPM 用 WSD、峰值 0.01——它的 batch 接近 400 万 token,按线性缩放规则本就该用更大的步幅。

Adam 系优化器下,lr 是什么意思

SGD 的更新量等于 lr 乘以梯度,梯度尺度直接决定步幅,所以 SGD 时代的学习率动辄 0.1。Adam/AdamW 的更新量约等于 lr × m/√v:梯度先除以自身历史幅度的均方根,尺度被抹掉,剩下的近似是「每个参数方向走固定的距离」。所以 AdamW 的 lr 语义变了——它不再是「梯度放大倍数」,而是「每步走多远」,绝对值自然小得多。

预训练与微调差一到两个数量级:从头预训练时 AdamW 峰值常在 1e-4 ~ 1e-3 量级(Llama 3 8B 的 3e-4、nanoGPT 的 6e-4 都在其中)。微调则要小得多,因为预训练权重已经停在很好的极小值附近,大步会把它冲出盆地、抹掉已学到的能力:HuggingFace Trainer 的默认学习率是 5e-5;Qwen2 技术报告的 SFT 阶段从 7e-6 逐步降到 7e-7。LoRA 可以更大:只训练低秩增量、参数少得多,不容易整体跑飞——LoRA 论文在 GPT-3 175B 上,全参微调的最优 lr 是 5e-6,LoRA 用到 2e-4,差了 40 倍。

边界在哪

学习率不是万能药,它的效果被三件事限定:

  1. batch size:线性缩放规则只是一阶近似,batch 越大越会失灵,于是有了按层归一化的 LARS/LAMB 这类变体。
  2. 优化器家族:同一个数字在 SGD 和 AdamW 下含义完全不同,换优化器必须重调 lr。
  3. 数据与模型规模:模型越大峰值越小(Llama 3 的三档),训练 token 预算变了,峰值与衰减终点也该重估。

落到微调的实用建议:

  • 从推荐值起步:全参微调 1e-5 ~ 5e-5,LoRA 从 2e-4 试起,别一上来就网格搜索。
  • 看 loss 曲线形态诊断:前期抖成锯齿甚至上扬——warmup 不够或 lr 过大,降;后期平台期太长、压不下去——先怀疑 lr 偏小;曲线平稳下降但迟迟不到位——先调大 lr 再怀疑别的。

一句话收束:学习率是最便宜也最贵的旋钮——调它不花一分钱算力,调错烧光全部算力。

参考资料

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?