EdgeLM / fairseq 学习率调度器(LR Scheduler)完全指南:从基类到 10 种调度策略的源码级解析
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
本文以 edgelm/docs/lr_scheduler.rst 为骨架,深入 EdgeLM(基于 fairseq 框架)的学习率调度模块。你将从接口设计、参数配置到每种调度策略的数学原理与源码实现,全面掌握--lr-scheduler的选型方法,并能直接在训练脚本(如edgelm/fairseq_cli/train.py、edgelm/train.py)中配置出可复现的训练流程。
概览:学习率调度器如何工作
Learning Rate Schedulers(学习率调度器)在训练过程中动态更新学习率(learning rate)。在 fairseq / EdgeLM 中,学习率有两种更新时机:
- 每个 update 之后更新:通过
step_update(num_updates)方法,适合以步数(update / iteration)为基准的调度策略,如 cosine、inverse_sqrt; - 每个 epoch 边界更新:通过
step(epoch, val_loss=None)方法,适合以 epoch 为基准的策略,如 fixed、reduce_lr_on_plateau。
step_begin_epoch(epoch)则用于在某个 epoch 开始时更新学习率(如 fixed 调度的 epoch 切换)。所有调度器统一实现这三个钩子,由训练循环按需调用。
调度器的统一入口在 edgelm/fairseq/optim/lr_scheduler/init.py,它通过registry.setup_registry("--lr-scheduler", base_class=FairseqLRScheduler, default="fixed")注册命令行参数--lr-scheduler(默认值fixed),并在模块导入时自动加载目录下所有.py文件。这意味着新增一个调度器只需在该目录下新建一个文件并打上@register_lr_scheduler装饰器即可。
FairseqLRScheduler:所有调度器的抽象基类
基类定义在 edgelm/fairseq/optim/lr_scheduler/fairseq_lr_scheduler.py,它规定了调度器的统一契约:
| 方法 | 触发时机 | 默认行为 |
|---|---|---|
step_begin_epoch(epoch) | epoch 开始时 | 空操作(pass) |
step(epoch, val_loss=None) | epoch 结束时 | 用val_loss更新内部self.best(记录历史最优验证损失) |
step_update(num_updates) | 每次参数更新后 | 返回optimizer.get_lr()(保持当前学习率不变) |
基类还提供可选的序列化能力:state_dict()返回{"best": self.best},load_state_dict()恢复该状态,便于断点续训时恢复调度器状态。注意基类构造时会校验optimizer必须是FairseqOptimizer实例,否则抛出ValueError。
另外,add_args类方法通过gen_parser_from_dataclass将每个调度器配套的 dataclass 配置自动展开为命令行参数,这就是下文每个调度器参数化配置的来源。仓库中还保留了LegacyFairseqLRScheduler,用于兼容旧的argparse.Namespace参数风格。
十种内置调度策略详解
原文档通过automodule/autoclass索引了 5 个核心调度类;结合 edgelm/fairseq/optim/lr_scheduler/ 目录,仓库实际实现了 10 种策略(另含manual_lr_scheduler.py、pass_through.py两个特殊用途调度器)。下面逐一展开。
1. FixedSchedule(fixed):按 epoch 阶梯衰减
注册名:fixed(也是--lr-scheduler的默认值)。实现见 edgelm/fairseq/optim/lr_scheduler/fixed_schedule.py。
核心参数(来自FixedLRScheduleConfig):
| 参数 | 默认值 | 说明 |
|---|---|---|
--lr | 继承optimization.lr(列表) | 每个 epoch 对应的学习率序列,可传多个值实现逐 epoch 阶梯下降 |
--force-anneal | None | 强制在指定 epoch 后启动退火 |
--lr-shrink | 0.1 | 退火衰减系数,lr_new = lr * lr_shrink |
--warmup-updates | 0 | 前 N 步线性 warmup 到目标学习率 |
调度逻辑:get_next_lr(epoch)中,若未设置force_anneal或当前 epoch 未到该值,则取lr[min(epoch-1, len(lr)-1)](即按 epoch 逐级取列表中的学习率);一旦 epoch 超过force_anneal,则从最后一个学习率开始按lr_shrink ** (epoch + 1 - force_anneal)指数退火。
与其它策略的关键区别:它是为数不多在epoch 边界(step_begin_epoch)更新学习率的调度器,且允许传入多个--lr值——这是 cosine、inverse_sqrt 等"以 update 为基准"的策略所不允许的。
2. CosineSchedule(cosine):余弦周期退火 + 线性 warmup
注册名:cosine。实现见 edgelm/fairseq/optim/lr_scheduler/cosine_lr_scheduler.py,理论基础为 SGDR 论文(arXiv:1608.03983)。
核心参数(来自CosineLRScheduleConfig):
| 参数 | 默认值 | 说明 |
|---|---|---|
--warmup-updates | 0 | 前 N 步线性 warmup |
--warmup-init-lr | -1(自动设为min_lr) | warmup 起始学习率 |
--lr | 继承optimization.lr | 峰值学习率(必须大于min_lr) |
--min-lr | 0.0 | 余弦衰减的下界 |
--lr-period-updates | -1 | 每个周期的步数;若未设置则自动取max_update - warmup_updates |
--t-mult | 1.0 | 每个周期长度放大因子(SGDR 的周期倍增) |
--lr-shrink | 0.1 | 周期间退火收缩系数 |
数学公式(源码 docstring 与step_update一致):
- warmup 阶段:
lr = torch.linspace(warmup_init_lr, lr, warmup_updates)[update_num]; - 余弦阶段:
lr = min_lr + 0.5 * (lr - min_lr) * (1 + cos(π * t_curr / t_i)),其中t_curr是当前周期内的进度,t_i为当前周期长度(每轮按t_mult放大)。
构造时,若lr_period_updates <= 0,则断言必须设置--max-update,并将周期设为max_update - warmup_updates。由于每个周期结束后还会乘以lr_shrink ** i(其中i为周期序号),多周期 cosine 实际是"逐步收缩的余弦波",这在长训练中可帮助模型跳出局部最优。
3. InverseSquareRootSchedule(inverse_sqrt):逆平方根衰减(Transformer 经典策略)
注册名:inverse_sqrt。实现见 edgelm/fairseq/optim/lr_scheduler/inverse_square_root_schedule.py。
核心参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--warmup-updates | 4000 | 线性 warmup 步数(注意默认非 0,是各调度器中少数有默认 warmup 的) |
--warmup-init-lr | -1(有 warmup 时自动为 0,否则等于lr) | warmup 起始学习率 |
--lr | 继承optimization.lr | 峰值学习率 |
调度逻辑:warmup 阶段线性攀升到lr;之后按lr = decay_factor / sqrt(update_num)衰减,其中decay_factor = lr * sqrt(warmup_updates)——即从 warmup 终点恰好衔接、以逆平方根曲线单调衰减。这是 Transformer / fairseq 系列预训练模型最常用的配置,其动机是前期用 warmup 稳定训练、后期按步数缓慢降低学习率以精细收敛。该策略同样在step_update中更新、在 epoch 边界保持不变。
4. ReduceLROnPlateau:验证集停滞时自动降 LR
注册名:reduce_lr_on_plateau。实现见 edgelm/fairseq/optim/lr_scheduler/reduce_lr_on_plateau.py,内部直接封装torch.optim.lr_scheduler.ReduceLROnPlateau。
核心参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--lr-shrink | 0.1 | 停滞时学习率乘以的衰减因子(对应 PyTorch 的factor) |
--lr-threshold | 1e-4 | 判定"新最优"的阈值,只关注显著改善 |
--lr-patience | 0 | 连续多少个 epoch 无改善后降 LR |
--maximize-best-checkpoint-metric | 继承 checkpoint 配置 | 为True时按最大化模式(用于准确率等指标),否则按最小化模式(用于 loss) |
--warmup-updates/--warmup-init-lr | 0/-1 | 可选的线性 warmup 阶段 |
行为细节:step(epoch, val_loss)仅在 warmup 结束后调用底层 PyTorch 调度器(用val_loss判断是否 plateau);warmup 阶段则在step_update中线性递增。state_dict/load_state_dict同步维护了底层调度器的best与last_epoch,保证断点续训时 plateau 历史不丢失。该策略适合验证集指标(loss / BLEU 等)主导的调参场景。
5. TriangularSchedule(triangular):三角周期循环学习率
注册名:triangular。实现见 edgelm/fairseq/optim/lr_scheduler/triangular_lr_scheduler.py,理论基础为 Cyclical Learning Rates(arXiv:1506.01186)。
核心参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--max-lr | 必填(???) | 周期峰值学习率,必须大于--lr |
--lr-period-updates | 5000 | 每个周期的步数(cycle length),半周期stepsize = period // 2 |
--lr-shrink | 0.1 | 每个周期结束后的收缩系数 |
--shrink-min | False | 若为True,周期的下界min_lr也随lr_shrink收缩 |
--lr | 继承optimization.lr | 作为周期下界min_lr |
调度逻辑:step_update中先由cycle = floor(num_updates / (2 * stepsize))定位周期,再按x = |num_updates / stepsize - 2*(cycle+1) + 1|计算三角波,lr = min_lr + (max_lr - min_lr) * max(0, 1 - x)。周期内学习率从min_lr线性升到max_lr再降回,每个周期整体再乘lr_shrink ** cycle收缩。适合探索-收敛交替的循环调度实验。
6. TriStageLRSchedule(tri_stage):三段式(warmup → hold → decay)
注册名:tri_stage。实现见 edgelm/fairseq/optim/lr_scheduler/tri_stage_lr_scheduler.py,对应 arXiv:1904.08779 中的三阶段调度。
核心参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--warmup-steps/--hold-steps/--decay-steps | 0/0/0 | 三个阶段各自的步数(至少一者非 0) |
--phase-ratio | None | 若设置(如(0.1,0.4,0.5)),则按max_update的比例自动推导三段步数,三者之和必须为 1.0 |
--init-lr-scale | 0.01 | warmup 起点 =lr * init_lr_scale |
--final-lr-scale | 0.01 | decay 终点 =lr * final_lr_scale |
调度逻辑(_decide_stage将 update 步映射为 0~3 四段):
- warmup:从
init_lr = lr * init_lr_scale线性升到峰值lr; - hold:保持峰值
lr不变; - decay:按
lr = peak_lr * exp(-decay_factor * t)指数衰减,其中decay_factor = -ln(final_lr_scale) / decay_steps; - 之后:恒定在
final_lr = lr * final_lr_scale。
该策略是 inverse_sqrt 的"分段显式版",在机器翻译等需要"快速升到峰值、长时间保持、指数收尾"的训练中非常实用。
7. PolynomialDecayLRSchedule(polynomial_decay):多项式衰减
注册名:polynomial_decay。实现见 edgelm/fairseq/optim/lr_scheduler/polynomial_decay_schedule.py。
核心参数:--warmup-updates(默认 0)、--force-anneal(默认None)、--end-learning-rate(默认0.0,衰减终点)、--power(默认1.0,衰减指数)、--total-num-update(默认继承max_update,衰减总步数)。
调度逻辑:warmup 后,在total_num_update步内把学习率从lr多项式衰减到end_learning_rate,指数由power控制(power=1为线性衰减,power<1前期衰减快、power>1前期衰减慢)。当force_anneal设置且 epoch 超过它时,会改为按lr_shrink退火(与 fixed 类似)。
8. StepLRSchedule(step):按固定步长周期性衰减
注册名:step。实现见 edgelm/fairseq/optim/lr_scheduler/step_lr_scheduler.py。
核心参数:--lr-deacy-period(默认25000,每多少步衰减一次)、--lr-decay(默认0.5,衰减因子)、--min-lr(默认0.0)、--warmup-updates/--warmup-init-lr。
调度逻辑:每经过lr_deacy_period次 update,学习率乘以lr_decay,但不下探到min_lr以下。构造时有一组严格断言:lr_decay <= 1、min_lr >= 0、max_lr > min_lr、lr_deacy_period > 0。它适合"阶段式下降"的传统炼丹习惯,与 fixed 的 epoch 维度下降形成互补(step 是按 update 步数)。
9. ManualLRSchedule(manual)与 PassThroughSchedule(pass_through)
manual(manual_lr_scheduler.py):允许在训练过程中手工覆盖学习率,适合人工干预的调试/实验场景。pass_through(pass_through.py):透传调度器,不做任何改动,让优化器自身管理学习率(例如配合外部的学习率控制工具)。
注册机制与 CLI 参数映射
每个调度器的注册与配置生成遵循同一套模式:
- 定义
@dataclass配置类(继承FairseqDataclass),字段默认值即参数默认值,metadata["help"]即命令行帮助文本; - 用
@register_lr_scheduler("名称", dataclass=配置类)装饰实现类; FairseqLRScheduler.add_args通过gen_parser_from_dataclass把配置类自动展开为--xxx命令行参数;- 多个配置字段通过
II("optimization.lr")与全局的optimization.lr联动——即所有调度器共享的峰值学习率--lr定义在 optimization 组中,调度器只负责如何随时间变化。
训练时只需在命令行指定,例如:
# 使用 cosine 调度 + 10000 步 warmup,峰值 lr 5e-4 python train.py --lr-scheduler cosine --lr 0.0005 --warmup-updates 10000 --max-update 200000 # 使用 inverse_sqrt 调度(Transformer 经典配置) python train.py --lr-scheduler inverse_sqrt --lr 0.0005 --warmup-updates 4000 # 使用 fixed 调度,前 5 个 epoch 用 3e-4,之后每 epoch 乘 0.8 python train.py --lr-scheduler fixed --lr 0.0003:0.00024:0.000192 --lr-shrink 0.8其中--lr的冒号分隔列表会被解析为多值列表(fixed 逐 epoch 取用,其余调度器只取第一个值并在传入多个值时抛出ValueError提示改用 fixed)。
选型建议与实战要点
| 场景 | 推荐调度器 | 理由 |
|---|---|---|
| 预训练大模型(Transformer) | inverse_sqrt | 长训练稳定,warmup 后单调缓慢衰减,默认 4000 步 warmup |
| 微调 / 精调,训练步数明确 | cosine | 峰值后平滑衰减到min_lr,收敛质量好 |
| 验证指标驱动调参 | reduce_lr_on_plateau | 自动响应验证集停滞 |
| 多 epoch、显式阶梯 | fixed | 支持每 epoch 显式指定--lr列表 |
| 快速实验、探索学习率区间 | triangular | 循环扫描min_lr~max_lr |
实战要点:
- warmup 语义统一:绝大多数调度器(cosine、inverse_sqrt、reduce_lr_on_plateau、step、fixed、polynomial_decay)都支持
--warmup-updates+--warmup-init-lr,warmup 阶段一律在step_update中线性攀升,与主调度阶段平滑衔接; - 断点续训:基类及 plateau 实现均提供
state_dict/load_state_dict,恢复 checkpoint 时调度器状态会一并恢复,避免续训后学习率跳变; - epoch 与 update 的区分:
step()(epoch 粒度)主要服务于 fixed / plateau 这类"看验证集"的策略;step_update()(update 粒度)服务于 cosine / inverse_sqrt 等"按步数"策略,选择前先想清楚训练循环按哪个粒度推进(对应--max-epoch还是--max-update); - 注册扩展:若要自定义策略,复制任一调度器文件、改配置 dataclass 与
step_update实现并注册新名称即可,无需改动训练主循环。
小结
本文以 lr_scheduler.rst 索引的 5 个核心类为起点,结合 edgelm/fairseq/optim/lr_scheduler/ 下全部 10 种调度策略的实现源码,完整梳理了 EdgeLM/fairseq 学习率调度的统一抽象(FairseqLRScheduler三钩子 + dataclass 配置 + 注册表机制)、各策略的数学公式与关键参数、以及按场景的选型建议。学习率调度是训练稳定的基石,理解step与step_update两个更新粒度、--lr与--warmup-updates等参数的联动关系,就能在训练脚本中快速配置出符合预期的收敛曲线。
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考