学习率调度与预热


文档摘要

学习率调度与预热 本节摘要:学习率是最重要的单一超参数——不是架构、不是数据量、不是激活函数。如果只能调一个,就调学习率。把它设成 0.1,训练发散——损失 3 步内冲到无穷;设成 0.0001,训练爬行——100 轮后模型几乎没挪窝;设成 0.01,前 50 轮好好的,之后损失在一个永远到不了的最小周围震荡,因为步子太大。最优学习率不是常数,它在训练中变化:早期你要大步快速铺地盘,后期你要小步钻进尖锐最小。90% 精度的模型与 95% 精度的模型,差距常就在调度上。过去三年发表的每个主流模型都用学习率调度:Llama 3 用峰值 lr=3e-4、2000 步预热、余弦衰减到 3e-5;GPT-3 用 lr=6e-4、3.75 亿 token 预热。

学习率调度与预热

本节摘要:学习率是最重要的单一超参数——不是架构、不是数据量、不是激活函数。如果只能调一个,就调学习率。把它设成 0.1,训练发散——损失 3 步内冲到无穷;设成 0.0001,训练爬行——100 轮后模型几乎没挪窝;设成 0.01,前 50 轮好好的,之后损失在一个永远到不了的最小周围震荡,因为步子太大。最优学习率不是常数,它在训练中变化:早期你要大步快速铺地盘,后期你要小步钻进尖锐最小。90% 精度的模型与 95% 精度的模型,差距常就在调度上。过去三年发表的每个主流模型都用学习率调度:Llama 3 用峰值 lr=3e-4、2000 步预热、余弦衰减到 3e-5;GPT-3 用 lr=6e-4、3.75 亿 token 预热。这些不是拍脑袋,是耗资数百万美元的超参扫描结果。本节从零实现常数、阶跃衰减、余弦退火、预热+余弦、1cycle 五种调度,演示学习率选错的三种失败模式(发散/停滞/震荡),讲清 Adam 类优化器为何需要预热,并在同一任务上比较五种调度的收敛速度。

学习目标

阅读完本节,你应当能够:

  1. 从零实现常数、阶跃衰减、余弦退火、预热+余弦、1cycle 五种学习率调度。
  2. 演示学习率选择的三种失败模式:发散(太高)、停滞(太低)、震荡(不衰减)
  3. 解释 Adam 类优化器为何需要预热,以及它如何稳定训练初期。
  4. 在同一任务上比较五种调度的收敛速度,并按训练预算选择合适的调度。

一、问题与直觉

把学习率设成 0.1——训练发散,损失 3 步冲到无穷。设成 0.0001——训练爬行,100 轮后模型几乎没动。设成 0.01——前 50 轮好好的,之后损失在一个永远到不了的最小周围震荡,因为步子太大。

最优学习率不是常数,它在训练中变化。早期你要大步快速铺地盘,后期你要小步钻进尖锐最小。90% 与 95% 精度的模型,差距常就在调度上。

过去三年发表的每个主流模型都用学习率调度。Llama 3 用峰值 lr=3e-4、2000 步预热、余弦衰减到 3e-5;GPT-3 用 lr=6e-4、3.75 亿 token 预热。这些不是拍脑袋,是耗资数百万美元的超参扫描结果。你必须懂调度,因为默认值不会正好适合你的问题——微调预训练模型与从零训练的调度不同,batch 变大预热期要变,训练在第 10000 步崩了,你得知道是调度问题还是别的。

常数学习率

最简:挑一个数,每步都用它。

lr(t) = lr_0

几乎从不最优:要么对训练末期太高(在最周围震荡),要么对初期太低(小步浪费算力)。小模型和调试时还行,任何训练超一小时的都用它就是糟糕选择。

阶跃衰减

ResNet 时代的老办法:在固定轮次把学习率乘一个因子(通常 10 倍降)。

lr(t) = lr_0 * gamma^(floor(epoch / step_size))

gamma = 0.1、step_size = 30 意味着每 30 轮学习率降 10 倍,ResNet-50 就这么用——lr=0.1,在第 30、60、90 轮各降 10 倍。问题:最优衰减点依赖数据集和架构,换个问题就得重调何时降;切换突兀,学习率突变时损失可能跳。

余弦退火

按余弦曲线从最大学习率平滑衰减到最小:

lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * t / T))

t 是当前步,T 是总步数。t=0 时余弦项为 1,lr = lr_max;t=T 时余弦项为 −1,lr = lr_min。衰减起初温和、中间加速、末尾再变温和。这是多数现代训练的默认,除了 lr_max 与 lr_min 无超参可调。余弦形状契合一个经验观察:大部分学习发生在训练中段——你希望在关键期有合理步长。

预热:为何要从小开始

Adam 等自适应优化器维护梯度均值与方差的滑动估计。第 0 步这些估计初始化为零,头几次梯度更新基于垃圾统计。若此时学习率大,模型会迈出又大又瞎的步。预热修补这一点:从一个很小的学习率(常为 lr_max / warmup_steps 甚至零)开始,在前 N 步线性升到 lr_max,等你达到完整学习率时,Adam 的统计已稳定。

lr(t) = lr_max * (t / warmup_steps) 当 t < warmup_steps

典型预热:总训练步的 1~5%。Llama 3 训约 1.8 万亿 token、预热 2000 步,GPT-3 预热 3.75 亿 token。

线性预热 + 余弦衰减

现代默认:线性升,再余弦降。

if t < warmup_steps: lr(t) = lr_max * (t / warmup_steps) else: progress = (t - warmup_steps) / (total_steps - warmup_steps) lr(t) = lr_min + 0.5 * (lr_max - lr_min) * (1 + cos(pi * progress))

Llama、GPT、PaLM 及多数现代 Transformer 都用这套:预热防早期不稳,余弦衰减让模型沉入好的最小。

1cycle 策略

Leslie Smith(2018)的发现:训练前半段把学习率从低升到高,后半段再降回去。反直觉——为什么中途要加大学习率?理论是:高学习率给优化轨迹加噪,起正则作用,模型在升段探索更多损失面、找到更好的盆;降段则在最好的盆里精修。

阶段 1 (0 到 T/2): lr 从 lr_max/25 升到 lr_max 阶段 2 (T/2 到 T): lr 从 lr_max 降到 lr_max/10000

在固定算力预算下,1cycle 常比余弦退火训练更快,代价是你必须提前知道总步数。

调度形状

决策流程

已发表模型的真实数值

二、从零实现

完整代码见原课程 phases/03-deep-learning-core/09-learning-rate-schedules/code/ 相应文件。

Step 1:调度函数

每个函数接收当前步、返回该步学习率。

import math def constant_schedule(step, lr=0.01, **kwargs): return lr def step_decay_schedule(step, lr=0.1, step_size=100, gamma=0.1, **kwargs): return lr * (gamma ** (step // step_size)) def cosine_schedule(step, lr=0.01, total_steps=1000, lr_min=1e-5, **kwargs): if step >= total_steps: return lr_min return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * step / total_steps)) def warmup_cosine_schedule(step, lr=0.01, total_steps=1000, warmup_steps=100, lr_min=1e-5, **kwargs): if step < warmup_steps: return lr * step / warmup_steps progress = (step - warmup_steps) / (total_steps - warmup_steps) return lr_min + 0.5 * (lr - lr_min) * (1 + math.cos(math.pi * progress)) def one_cycle_schedule(step, lr=0.01, total_steps=1000, **kwargs): mid = max(total_steps // 2, 1) if step < mid: return (lr / 25) + (lr - lr / 25) * step / mid else: progress = (step - mid) / max(total_steps - mid, 1) return lr * (1 - progress) + (lr / 10000) * progress

Step 2:可视化全部调度

打印基于文本的图,展示每种调度在训练中的演化。

Step 3:训练网络

沿用前几节圆形数据集上的两层网络,但现在轮换调度。

Step 4:比较全部调度

用每种调度训练同一个网络,比较最终损失与收敛行为。预热+余弦通常最稳、1cycle 在固定算力下最快。

Step 5:LR 太高 vs 太低

演示三种失败模式:lr=1.0 发散(NaN)、lr=0.0001 几乎不动、lr=0.01 收敛但末期震荡。这正是「学习率是最重要超参数」的最直观证据。

三、框架对比

PyTorch 在 torch.optim.lr_scheduler 里提供调度器:

import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR, StepLR model = nn.Sequential(nn.Linear(10, 64), nn.ReLU(), nn.Linear(64, 1)) optimizer = optim.Adam(model.parameters(), lr=3e-4) scheduler = CosineAnnealingLR(optimizer, T_max=1000, eta_min=1e-5) for step in range(1000): loss = train_step(model, optimizer) scheduler.step()

预热 + 余弦用 lambda 调度器或 HuggingFace 的 get_cosine_schedule_with_warmup:

from transformers import get_cosine_schedule_with_warmup scheduler = get_cosine_schedule_with_warmup( optimizer, num_warmup_steps=2000, num_training_steps=100000, )

HuggingFace 这个函数是多数 Llama、GPT 微调脚本用的。拿不准时,用预热 + 余弦,预热占总步的 3~5%,几乎万能。

四、可复用产物

本节产出(位于原课程 outputs/):

  • prompt-lr-schedule-advisor.md:一个提示,为你的训练设置推荐正确的学习率调度与超参数。

五、练习

  1. Easy:实现指数衰减 lr(t) = lr_0 × gamma^t(gamma = 0.999),在圆形数据上与余弦退火比较。
  2. Medium:实现学习率范围测试(Leslie Smith)——训练几百步、把 LR 从 1e-7 指数升到 1,画损失 vs LR,最优 max LR 在损失开始上升之前。
  3. Hard:实现带热重启的余弦退火(SGDR):每 T 步把 LR 重置到 lr_max 再衰减,在更长训练上与标准余弦比较。

本节要点回顾

  1. 学习率是最重要的单一超参数:比架构、数据量、激活函数都关键,10 倍变化比任何架构决策都重要。
  2. 最优学习率不是常数:早期要大步铺地盘,后期要小步钻最小,90% 与 95% 精度的差距常在调度。
  3. 常数调度几乎从不最优:末期太高会震荡,初期太低浪费算力,只适合小模型与调试。
  4. 阶跃衰减是 ResNet 时代办法:切换突兀、依赖数据/架构,换问题要重调。
  5. 余弦退火是现代默认:平滑、除 lr_max/lr_min 外无超参,契合「大部分学习发生在中段」的观察。
  6. 预热弥补 Adam 冷启动:头几步统计是垃圾,大 LR 会迈瞎步,线性升到峰值让统计先稳定。
  7. 预热 + 余弦是 Llama/GPT/PaLM 的标配:预热占总步 1~5%,几乎万能。
  8. 1cycle 反直觉但快:先升后降,高 LR 起正则作用探索损失面,需提前知道总步数。
  9. 三种失败模式:太高→发散(NaN)、太低→停滞、不衰减→末期震荡。

下一节,我们把前 9 节的全部零件——层、网络、反向传播、激活、损失、优化器、正则、初始化、调度——焊成一个属于自己的迷你深度学习框架。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U