Fairseq 学习率调度器(Learning Rate Schedulers)完整指南:从 step_update 到 step 的调度机制与源码解析
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
导读
学习率调度器(Learning Rate Scheduler)是深度学习训练中控制模型收敛速度与最终精度的关键组件。本文以 IAD/fairseq 文档 为核心,系统讲解 Fairseq 学习率调度器的设计思想、注册机制与全部内置调度器(fixed、inverse_sqrt、cosine、reduce_lr_on_plateau、triangular、polynomial_decay、tri_stage、pass_through、manual)的配置参数与底层数学公式。读完本文,你将掌握如何在 IAD/fairseq 框架中通过--lr-scheduler与配套参数选择并调优任意调度策略,并理解调度器在训练循环中的真实调用时机。
一、核心机制:学习率在两个时机被更新
按 lr_scheduler.rst 的定义,Fairseq 的学习率调度器负责在训练过程中不断更新学习率,其更新时机分为两类:
step_update(num_updates):每完成一次梯度更新(update)后调用,用于实现基于步数的调度,例如逐 step 的线性 warmup、余弦退火、反平方根衰减等;step(epoch, val_loss=None):在每个 epoch 结束时调用,用于实现基于 epoch 的调度,例如固定分段衰减(按 epoch 切换学习率档位)、验证损失 plateau 检测等。
此外,Fairseq 还提供step_begin_epoch(epoch)钩子,在每个 epoch 开始时更新学习率(主要用于 fixed 与 polynomial_decay 这类按 epoch 生效的调度器)。这三类钩子构成了调度器与训练主循环之间的全部交互接口,所有内置调度器均在此基础上实现。
二、调度器注册机制与构建入口
Fairseq 通过注册表(registry)机制管理学习率调度器,所有实现统一放置在 fairseq/optim/lr_scheduler/ 目录下。入口代码位于init.py:
( build_lr_scheduler_, register_lr_scheduler, LR_SCHEDULER_REGISTRY, LR_SCHEDULER_DATACLASS_REGISTRY, ) = registry.setup_registry( "--lr-scheduler", base_class=FairseqLRScheduler, default="fixed" ) def build_lr_scheduler(cfg: DictConfig, optimizer): return build_lr_scheduler_(cfg, optimizer) # automatically import any Python files in the optim/lr_scheduler/ directory for file in os.listdir(os.path.dirname(__file__)): if file.endswith(".py") and not file.startswith("_"): importlib.import_module("fairseq.optim.lr_scheduler." + file_name)关键信息:
- 命令行参数为
--lr-scheduler,默认值为fixed; - 目录下每个
.py文件在 import 时被自动加载并完成自注册; - 各调度器通过
@register_lr_scheduler("名字", dataclass=xxxConfig)装饰器注册,并配套一个 dataclass 配置类,配置字段会通过gen_parser_from_dataclass自动生成命令行参数; - 注册表中同时维护了
LR_SCHEDULER_REGISTRY(类)与LR_SCHEDULER_DATACLASS_REGISTRY(配置 dataclass)两个映射。
三、基类 FairseqLRScheduler 的 API 约定
所有调度器继承自 fairseq_lr_scheduler.py 中的FairseqLRScheduler,其定义了一套完整的最小接口:
| 方法 | 调用时机 | 默认行为 |
|---|---|---|
step_begin_epoch(epoch) | 每个 epoch 开始 | 空实现(pass) |
step(epoch, val_loss=None) | 每个 epoch 结束 | 若传入 val_loss,则维护self.best = min(self.best, val_loss) |
step_update(num_updates) | 每次参数更新后 | 返回optimizer.get_lr()(即不改变学习率) |
state_dict()/load_state_dict() | 断点保存/恢复 | 保存/恢复{"best": ...} |
基类的__init__要求传入FairseqOptimizer实例,并持有self.cfg、self.optimizer与self.best。另外,类方法add_args(parser)会读取类上的__dataclass属性自动生成命令行参数——这就是配置类与 CLI 参数绑定的实现原理。同文件中还定义了仅接收argparse.Namespace旧式参数的LegacyFairseqLRScheduler,用于兼容 legacy 风格的调度器(如 manual)。
四、内置调度器逐一解析
以下调度器均可在--lr-scheduler=<名称>时启用,所有配置项既可通过命令行传入,也可写入 hydra/OmegaConf 配置文件(IAD/fairseq 的训练配置体系参见仓库中的 conf 目录)。
4.1 fixed:固定分段衰减(默认调度器)
实现见 fixed_schedule.py,注册名为fixed。它按 epoch 分段使用预先指定的学习率列表,是唯一允许--lr传入多个值(列表)的调度器之一:
| 参数 | 默认值 | 含义 |
|---|---|---|
lr(继承自optimization.lr) | 必填 | 学习率列表,如--lr 5e-4:1e-4,第 N 个 epoch 使用列表中第 N 个值 |
force_anneal | None | 若设置,则从指定 epoch 起强制按lr_shrink指数衰减 |
lr_shrink | 0.1 | 退火收缩因子,lr_new = lr * lr_shrink |
warmup_updates | 0 | 前 N 次更新做线性 warmup |
核心逻辑在get_next_lr(epoch):epoch < force_anneal时取lrs[min(epoch-1, len(lrs)-1)];否则lrs[-1] * lr_shrink ** (epoch + 1 - force_anneal)。step_begin_epoch在 epoch 开始时设置学习率,step_update负责 warmup 阶段的线性上升。
4.2 inverse_sqrt:反平方根衰减(Transformer 经典方案)
实现见 inverse_square_root_schedule.py,注册名为inverse_sqrt。这是大规模预训练中最常用的方案:
| 参数 | 默认值 | 含义 |
|---|---|---|
warmup_updates | 4000 | 前 N 次更新线性 warmup(默认值即体现该方案的主流设定) |
warmup_init_lr | -1(自动推导) | warmup 起始学习率;小于 0 时,若有 warmup 则取 0,否则取lr |
lr | 必填 | 峰值学习率(只允许单值,传列表会报错) |
数学定义(源码 docstring 明示):
- warmup 阶段:
lr = torch.linspace(warmup_init_lr, lr, warmup_updates)[update_num],即从warmup_init_lr线性升至峰值lr; - warmup 之后:
decay_factor = lr * sqrt(warmup_updates),lr = decay_factor / sqrt(update_num)——学习率随步数平方根反比衰减。
实现细节:__init__中若cfg.lr为多值列表会抛出 ValueError 并提示改用 fixed;decay_factor在初始化时一次性算好;step_update中num_updates < warmup_updates走线性段,否则走反平方根段。
4.3 cosine:余弦退火(支持 warmup 与周期缩放)
实现见 cosine_lr_scheduler.py,注册名为cosine。基于循环余弦函数(论文参考见源码 docstring),同时支持多周期(SGDR 风格):
| 参数 | 默认值 | 含义 |
|---|---|---|
warmup_updates | 0 | 前 N 次更新线性 warmup |
warmup_init_lr | -1 | warmup 起始学习率;小于 0 时自动取min_lr |
lr | 继承optimization.lr | 最大学习率,必须大于min_lr |
min_lr | 0.0 | 最小学习率 |
t_mult | 1.0 | 每个周期的长度增长因子 |
lr_period_updates | -1 | 每个周期的初始更新数;若不设置则由max_update - warmup_updates推导 |
lr_shrink | 0.1 | 每个周期结束后的退火收缩因子 |
max_update | 继承optimization.max_update | 总更新数,用于自动推导周期长度 |
数学定义(warmup 结束后):
lr = min_lr + 0.5 * (lr - min_lr) * (1 + cos(t_curr / t_i))其中t_curr为当前周期内的进度,t_i为当前周期长度,每个周期结束后t_i按t_mult缩放、lr与min_lr按lr_shrink收缩。step_update在t_mult != 1时使用对数运算计算当前周期编号i。
4.4 reduce_lr_on_plateau:验证损失平台期衰减
实现见 reduce_lr_on_plateau.py,注册名为reduce_lr_on_plateau。它直接封装了 PyTorch 的torch.optim.lr_scheduler.ReduceLROnPlateau,当验证损失连续若干 epoch 无改善时按因子收缩学习率:
| 参数 | 默认值 | 含义 |
|---|---|---|
lr_shrink | 0.1 | 衰减因子(对应 ReduceLROnPlateau 的 factor) |
lr_threshold | 1e-4 | 判定"新最优"的阈值,只关注显著改善 |
lr_patience | 0 | 连续无改善多少个 epoch 后降低学习率 |
warmup_updates | 0 | 可选的线性 warmup 阶段 |
warmup_init_lr | -1 | warmup 起始学习率 |
lr | 必填 | 峰值学习率(仅允许单值) |
maximize_best_checkpoint_metric | 继承 checkpoint 配置 | 为 True 时以mode="max"监控,否则mode="min" |
源码要点:构造时直接把self.optimizer.optimizer、patience、factor、mode、threshold传给 PyTorch 的 ReduceLROnPlateau;step(epoch, val_loss)仅在 warmup 结束(self.warmup_end为 True)且传入 val_loss 时才真正调用self.lr_scheduler.step(val_loss),否则只更新last_epoch;state_dict保存best与last_epoch以支持断点恢复。
4.5 triangular:三角周期调度(循环学习率)
实现见 triangular_lr_scheduler.py,注册名为triangular。学习率在最小值与最大值之间线性往复(循环学习率,论文见源码 docstring):
| 参数 | 默认值 | 含义 |
|---|---|---|
max_lr | 必填(???) | 最大学习率,必须大于lr |
lr | 继承optimization.lr | 作为最小学习率 |
lr_period_updates | 5000 | 每个周期(cycle)包含的更新数,半个周期即stepsize |
lr_shrink | 0.1 | 每个周期结束后的收缩因子 |
shrink_min | False | 为 True 时最小学习率也随周期收缩,否则保持lr不变 |
实现要点:stepsize = lr_period_updates // 2;每个周期后lr_shrink ** cycle收缩上下界;lr = min_lr + (max_lr - min_lr) * max(0, 1 - x),其中x = |num_updates / stepsize - 2*(cycle+1) + 1|形成三角波形。
4.6 polynomial_decay:多项式退火
实现见 polynomial_decay_schedule.py,注册名为polynomial_decay。学习率从峰值按幂律衰减到设定的终点值:
| 参数 | 默认值 | 含义 |
|---|---|---|
warmup_updates | 0 | 前 N 次更新线性 warmup |
force_anneal | None | 指定 epoch 起强制退火 |
end_learning_rate | 0.0 | 衰减终点学习率 |
power | 1.0 | 衰减指数(power=1 即线性衰减) |
total_num_update | 继承optimization.max_update | 完成衰减的总更新数 |
lr | 必填 | 峰值学习率 |
step_update的三种分支:warmup 阶段线性上升;num_updates >= total_num_update后锁定在end_learning_rate;中间阶段按lr = lr_range * pct_remaining ** power + end_learning_rate衰减,其中pct_remaining = 1 - (num_updates - warmup) / (total_num_update - warmup)。
4.7 tri_stage:三段式调度(warmup + hold + decay)
实现见 tri_stage_lr_scheduler.py,注册名为tri_stage。实现论文 1904.08779(源码 docstring 注明)中的三段式调度,与 inverse_sqrt 类似但更精细:
| 参数 | 默认值 | 含义 |
|---|---|---|
warmup_steps | 0 | warmup 阶段步数 |
hold_steps | 0 | hold 阶段步数(学习率保持峰值) |
decay_steps | 0 | 指数衰减阶段步数 |
phase_ratio | None | 若设置,按max_update自动计算三段步数(比例和须为 1.0) |
init_lr_scale | 0.01 | warmup 起始学习率为lr * init_lr_scale |
final_lr_scale | 0.01 | 衰减终点学习率为lr * final_lr_scale |
max_update | 继承optimization.max_update | 总更新数,用于 phase_ratio 推导 |
四阶段数学定义(源码 docstring 明示):
- warmup:
init_lr = init_lr_scale * lr,lr = linspace(init_lr, lr, warmup_steps)[update_num]; - hold:
lr = lr(保持峰值); - decay:
decay_factor = -log(final_lr_scale) / decay_steps,lr = lr * exp(-(update_num - warmup_steps - decay_steps) * decay_factor)(指数衰减); - 结束之后:
lr = lr * final_lr_scale(恒定保持)。
实现上通过_decide_stage(update_step)返回(stage, steps_in_stage)四元状态机(0=warmup、1=hold、2=decay、3=constant),step_update根据阶段分别计算。
4.8 pass_through:完全委托给优化器
实现见 pass_through.py,注册名为pass_through。它不实现任何调度逻辑,而是把三个钩子与状态序列化全部转发给优化器自带的lr_scheduler:
def __init__(self, cfg, optimizer): assert ( hasattr(optimizer, "lr_scheduler") and optimizer.lr_scheduler is not None ), "Pass-through schedule can only be used with optimizers with their own schedulers"适用前提是优化器自身内建调度器(构造时会强制校验),适用于使用torch.optim原生 scheduler 或自带调度功能的优化器场景。
4.9 manual:人工指定 epoch/update 到学习率映射
实现见 manual_lr_scheduler.py,注册名为manual(继承LegacyFairseqLRScheduler,使用旧式 args 接口)。通过两个字符串参数直接指定任意时刻的学习率:
| 参数 | 默认值 | 含义 |
|---|---|---|
--epoch2lr | "{}" | 字典字符串,如"{5: 1e-4, 10-20: 5e-5}",按 epoch 设定学习率 |
--update2lr | "{}" | 字典字符串,按 update 步数设定学习率 |
parse_manuallr_args使用ast.literal_eval解析字典,支持"1"单值、"1,2,3"逗号列表(每个键取同一值)与"1-5"范围展开三种键写法。get_next_lr取所有<= 当前epoch/update的键中最大的那个对应的学习率,即"就近生效"策略。
五、实战:命令行与配置中的完整用法
5.1 通过命令行选择调度器
IAD/fairseq 的训练入口为 train.py(fairseq_cli 风格),调度器参数以--lr-scheduler开头。例如:
# 反平方根衰减(Transformer 预训练经典配置) python train.py --lr-scheduler inverse_sqrt --lr 0.001 --warmup-updates 4000 # 余弦退火(含 1000 步 warmup) python train.py --lr-scheduler cosine --lr 0.001 --min-lr 1e-5 \ --warmup-updates 1000 --max-update 500000 # 固定分段衰减(多段学习率) python train.py --lr-scheduler fixed --lr 5e-4:1e-4:5e-5 # 验证损失平台期衰减 python train.py --lr-scheduler reduce_lr_on_plateau --lr 0.001 \ --lr-patience 3 --lr-shrink 0.5 # 三段式调度(按比例自动切分) python train.py --lr-scheduler tri_stage --lr 0.001 \ --max-update 100000 --phase-ratio 0.1,0.4,0.5 # 人工指定(epoch 5 起用 1e-4,epoch 10-20 用 5e-5) python train.py --lr-scheduler manual --lr 0.001 \ --epoch2lr "{5: 1e-4, 10-20: 5e-5}"注意:inverse_sqrt、cosine、reduce_lr_on_plateau、triangular、tri_stage均只允许单值--lr,传入多值列表会在构造时抛出 ValueError 并提示改用fixed。
5.2 配置优先级与参数继承
从源码可见,多数调度器配置通过II("optimization.lr")、II("optimization.max_update")等插值引用(OmegaConf II)继承全局优化配置,因此--lr、--max-update是全局参数,调度器自动读取;调度器私有参数(如--warmup-updates、--lr-shrink、--min-lr)则由各自 dataclass 通过add_args自动生成。
六、断点保存与恢复
FairseqLRScheduler基类定义了state_dict()/load_state_dict()契约(默认保存best)。各调度器在此基础上扩展:
FixedLRSchedule、ManualSchedule额外保存当前lr;ReduceLROnPlateauLRSchedule保存 PyTorch 调度器的best与last_epoch;PassThroughScheduleSchedule直接委托优化器内建调度器的 state_dict。
训练中断后恢复时,调度器状态随 checkpoint 一并序列化,确保学习率曲线在断点处无缝衔接。
七、总结:如何选择合适的调度器
| 场景 | 推荐调度器 | 关键参数 |
|---|---|---|
| Transformer 大规模预训练 | inverse_sqrt | warmup-updates 4000 |
| 有明确总步数的收敛 | cosine/polynomial_decay | max-update、min-lr |
| 多阶段/固定分段学习率 | fixed | lr a:b:c、force-anneal |
| 依赖验证损失的早停式退火 | reduce_lr_on_plateau | lr-patience、lr-shrink |
| 循环学习率探索 | triangular | max-lr、lr-period-updates |
| 精细控制 warmup/hold/decay | tri_stage | phase-ratio |
| 完全人工控制 | manual | epoch2lr、update2lr |
本文所有结论均可在 lr_scheduler.rst 及 fairseq/optim/lr_scheduler/ 目录源码中直接验证,读者可按需深入研读对应实现文件,结合自身任务的数据规模与训练预算选择最合适的调度策略。
【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考