TF-Models NLP 训练优化体系:OptimizerFactory、学习率调度与 Warmup 机制源码详解
【免费下载链接】modelsModels and examples built with TensorFlow项目地址: https://gitcode.com/GitHub_Trending/mode/models
本篇围绕 TF-Models(TensorFlow Official Models)NLP 子项目中的官方文档《Optimizer and Learning Rate Scheduler》,系统讲解official.modeling.optimization包的用法:如何通过一份字典配置驱动优化器工厂(OptimizerFactory)构建优化器、学习率衰减与 warmup 调度。读完本文,你将掌握 NLP 训练(如 BERT 预训练/微调、Transformer 翻译)中优化配置的完整写法、各调度的参数默认值与底层实现原理,以及如何在自定义 Task 中替换或扩展优化器。
一、优化配置体系总览:三大组件 + 可选 EMA
TFM 将「优化器」「学习率调度」「warmup 调度」统一封装在一个数据类OptimizationConfig中,定义于 optimization_config.py:
@dataclasses.dataclass class OptimizationConfig(base_config.Config): optimizer: OptimizerConfig = dataclasses.field(default_factory=OptimizerConfig) ema: Optional[opt_cfg.EMAConfig] = None learning_rate: LrConfig = dataclasses.field(default_factory=LrConfig) warmup: WarmupConfig = dataclasses.field(default_factory=WarmupConfig)其中:
optimizer、learning_rate为必填字段(工厂初始化时会校验 type 非空,缺失即抛出ValueError,见 optimizer_factory.py);warmup为可选字段,用于在训练初期稳定优化过程;ema为可选的指数移动平均(Exponential Moving Average)配置,若指定,工厂会用 EMA 包装器包一层优化器(仅限 legacy 优化器路径)。
每个组件内部再使用type字段(oneof 配置,实现见 oneof.py)声明具体类型,类型名与同名的子配置字段一一对应。三个组件各自的可选 type 集合分别由 OptimizerConfig、LrConfig 与 WarmupConfig 三个数据类枚举。
二、构建流程:四步完成优化器与学习率构造
按官方文档(optimization.md)的描述,通过OptimizerFactory构建优化器与学习率调度的标准流程是:
- 定义优化配置(含优化器、学习率调度、可选 warmup);
- 用配置初始化
OptimizationConfig与OptimizerFactory; - 调用
build_learning_rate()构建学习率调度; - 调用
build_optimizer(lr)构建优化器实例。
完整示例:SGD 优化器 + 分段常数(stepwise)学习率 + 线性 warmup:
params = {'optimizer': { 'type': 'sgd', 'sgd': {'momentum': 0.9}}, 'learning_rate': {'type': 'stepwise', 'stepwise': { 'boundaries': [10000, 20000], 'values': [0.1, 0.01, 0.001]}}, 'warmup': {'type': 'linear', 'linear': {'warmup_steps': 500, 'warmup_learning_rate': 0.01}}} # Defines optimization config from a dictionary. opt_config = optimization.OptimizationConfig(params) # Initializes an optimization factory from optimization config. opt_factory = optimization.OptimizerFactory(opt_config) # Builds the desired learning rate scheduling instance. lr = opt_factory.build_learning_rate() # Builds the optimizer instance with the desired learning rate schedule. optimizer = opt_factory.build_optimizer(lr)该示例与 OptimizerFactory 类 docstring 中的官方示例逐字一致,可直接复制使用。两个关键成员函数的实现逻辑:
build_learning_rate()(源码):若learning_rate.type == 'constant',直接返回标量学习率;否则从LR_CLS表中取对应调度类,用配置的as_dict()展开构造;若配置了 warmup,再用WARMUP_CLS表中的 warmup 类把基础调度包一层;build_optimizer(lr)(源码):先把优化器配置转字典,删除值为 None 的裁剪参数(clipnorm/clipvalue/global_clipnorm)避免传给 Keras 优化器报错,注入learning_rate,再从LEGACY_OPTIMIZERS_CLS(默认)或NEW_OPTIMIZERS_CLS表中实例化。
三、支持的优化器与梯度裁剪
3.1 优化器注册表
当前仓库中优化器类按「legacy / new」两条路径注册于 optimizer_factory.py。两条路径共享的部分为:
SHARED_OPTIMIZERS = { 'sgd_experimental': tf_keras.optimizers.experimental.SGD, 'adam_experimental': tf_keras.optimizers.experimental.Adam, 'adamw': legacy_adamw.AdamWeightDecay, 'adamw_experimental': tf_keras.optimizers.experimental.AdamW, 'lamb': lamb.LAMB, 'lars': lars.LARS, 'slide': slide_optimizer.SLIDE, 'adafactor': adafactor_optimizer.Adafactor, 'adafactor_keras': tf_keras.optimizers.Adafactor, }- legacy 路径(
use_legacy_optimizer=True,默认):在共享表基础上追加'sgd'、'adam'、'rmsprop'、'adagrad',映射到tf_keras.optimizers.legacy系列(源码)。官方文档给出的OPTIMIZERS_CLS最小集合(sgd/adam/adamw/lamb/rmsprop)即来源于此; - new 路径(
use_legacy_optimizer=False):sgd/adam/rmsprop/adagrad映射到tf_keras.optimizers.experimental系列(源码)。注意两条约束:新 Keras 优化器不支持decay参数(工厂会主动报错,见 第 241-245 行),且 EMA 包装仅 legacy 路径可用(第 248-254 行)。
单元测试 optimizer_factory_test.py 以参数化方式对sgd、rmsprop、adam、adamw、lamb、lars、adagrad逐一验证:用 constant 学习率构造后,断言实例类型与get_config()均与对应 Keras 类一致,可作为配置正确性的参照。
3.2 通用梯度裁剪
所有优化器共享基类 BaseOptimizerConfig 中的三种裁剪字段:
| 字段 | 含义 |
|---|---|
clipnorm | 单个梯度 L2 范数超过该值时按范数裁剪 |
clipvalue | 单个梯度绝对值超过该值时按值裁剪 |
global_clipnorm | 所有梯度整体范数不超过该值 |
三者默认均为None(不启用)。文档示例:RMSprop + 折扣因子 0.9 + 全局范数裁剪 10.0:
params = {'optimizer': { 'type': 'rmsprop', 'rmsprop': {'rho': 0.9, 'global_clipnorm': 10.0}}}3.3 各优化器专属参数与默认值
各优化器配置字段与其对应 Keras 优化器构造参数一一对应,定义于 optimizer_config.py。常用默认值汇总如下:
| 优化器 type | 专属字段(默认值) | 配置类 |
|---|---|---|
sgd | decay=0.0、nesterov=False、momentum=0.0 | SGDConfig |
rmsprop | rho=0.9、momentum=0.0、epsilon=1e-7、centered=False | RMSPropConfig |
adam | beta_1=0.9、beta_2=0.999、epsilon=1e-7、amsgrad=False | AdamConfig |
adamw | 除 Adam 参数外,weight_decay_rate=0.0、include_in_weight_decay、exclude_from_weight_decay、gradient_clip_norm=1.0 | AdamWeightDecayConfig |
lamb | beta_1=0.9、beta_2=0.999、epsilon=1e-6、weight_decay_rate=0.0,以及正则形式的exclude_from_weight_decay/exclude_from_layer_adaptation | LAMBConfig |
lars | momentum=0.9、eeta=0.001、weight_decay_rate=0.0、nesterov=False、classic_momentum=True | LARSConfig |
adagrad | initial_accumulator_value=0.1、epsilon=1e-7 | AdagradConfig |
slide | weight_decay_type="inner"、norm_type="layer"、sparse_layer_learning_rate=0.1等 | SLIDEConfig |
adafactor/adafactor_keras | factored=True、decay_rate=0.8、clipping_threshold=1.0、relative_step=True等 | AdafactorConfig |
其中exclude_from_weight_decay/exclude_from_layer_adaptation采用「变量名包含子串即排除」的匹配方式,例如可指定['batch_normalization', 'bias']让 BN 与偏置不参与权重衰减(见 LARSConfig docstring)。
另外OptimizationConfig还支持顶层可选的ema字段(EMAConfig,含average_decay=0.99、start_step=0、dynamic_decay=True等),配置后build_optimizer会用ExponentialMovingAverage包装返回的优化器(源码)。
四、学习率调度:类型、offset 机制与参数默认值
4.1 调度类型映射表
learning_rate.type支持的取值由 LR_CLS 定义。需要说明的是:当前仓库源码已演进为带offset包装的调度类(相对官方文档中列出的原生 Keras 调度),并新增了若干类型:
LR_CLS = { 'stepwise': lr_schedule.PiecewiseConstantDecayWithOffset, 'polynomial': lr_schedule.PolynomialDecayWithOffset, 'exponential': lr_schedule.ExponentialDecayWithOffset, 'cosine': lr_schedule.CosineDecayWithOffset, 'cosine_restarts': lr_schedule.CosineDecayRestartsWithOffset, 'power': lr_schedule.DirectPowerDecay, 'power_linear': lr_schedule.PowerAndLinearDecay, 'power_with_offset': lr_schedule.PowerDecayWithOffset, 'step_cosine_with_offset': lr_schedule.StepCosineDecayWithOffset, }除以上调度外,还可指定constant学习率(此时build_learning_rate直接返回标量,见 ConstantLrConfig,默认learning_rate=0.1)。
4.2 offset 包装器:让「替换式 warmup」成为可能
当前实现中多数调度类由工厂函数_make_offset_wrapper动态生成(lr_schedule.py)。其核心语义为:
new_class_object(step) == base_lr_class_object(step - offset)即对传入的 step 先减去offset再按基础 Keras 调度计算。这使得「从头开始训练」与「从检查点恢复训练(step 已有历史步数)」可以共用同一套配置语义,且 offset 参数在各 LR 配置数据类中均可显式指定(默认 0)。
4.3 各调度参数与默认值
各调度的配置字段定义于 learning_rate_config.py:
| type | 关键字段与默认值 | 配置类 |
|---|---|---|
stepwise | boundaries(严格递增整数列表)、values(比 boundaries 多一个元素)、offset=0 | StepwiseLrConfig |
exponential | initial_learning_rate、decay_steps、decay_rate、staircase、offset=0 | ExponentialLrConfig |
polynomial | initial_learning_rate、decay_steps、end_learning_rate=0.0001、power=1.0、cycle=False、offset=0 | PolynomialLrConfig |
cosine | initial_learning_rate、decay_steps、alpha=0.0(终值占初始学习率的比例)、offset=0 | CosineLrConfig |
cosine_restarts | 另含first_decay_steps、t_mul=2.0、m_mul=1.0 | CosineRestartsLrConfig |
power | initial_learning_rate、power=-0.5(默认按 sqrt 衰减) | DirectPowerLrConfig |
power_linear | total_decay_steps、power=-0.5、linear_decay_fraction=0.1、offset=0 | PowerAndLinearDecayLrConfig |
power_with_offset | offset=0、pre_offset_learning_rate=1.0e6(offset 前的恒定 LR 兼作上限) | PowerDecayWithOffsetLrConfig |
step_cosine_with_offset | boundaries与values等长,区间之间做余弦衰减 | StepCosineLrConfig |
文档给出的典型示例:cosine 衰减(decay_steps=20000)+ 前 500 步线性 warmup:
params = {'learning_rate': {'type': 'cosine', 'cosine': {'decay_steps': 20000}}, 'warmup': {'type': 'linear', 'linear': {'warmup_steps': 500}}}step_cosine_with_offset的行为可以从 StepCosineDecayWithOffset docstring 直观理解:boundaries=[100000, 110000]、values=[1.0, 0.5]时,0~100000 步间学习率从 1.0 余弦衰减到 0.5,100000~110000 步间从 0.5 余弦衰减到 0。
五、Warmup 机制:如何与基础学习率组合
5.1 组合规则
按官方文档描述,学习率调度以step为输入返回当前学习率值;warmup 用于稳定训练,从较低学习率逐渐升起到常规衰减调度的「初始值」。二者的组合规则为:
- 步数在
[0, warmup_steps)区间:learning_rate = warmup(step); - 步数在
[warmup_steps, train_steps)区间:learning_rate = lr(step); - warmup 的终点值不是独立配置的常数,而是从基础学习率调度推断得出,即
learning_rate(warmup_steps) == warmup(warmup_steps); - 注意 warmup 是替换而非延迟:warmup 阶段并不把常规衰减曲线整体向后平移 warmup_steps,而是在 warmup 结束后直接按原 schedule 在对应步数上取值。
这一语义在 LinearWarmup 中可以直接印证:构造函数中self._final_warmup_lr = after_warmup_lr_sched(warmup_steps)(warmup 终值取自基础调度在 warmup_steps 处的取值),而__call__用tf.cond(global_step < warmup_steps, linear_warmup_lr, after_warmup_lr)切换两个分支,两个分支均使用原始step而非偏移后的 step。
5.2 两种 warmup 的实现与公式
warmup 类型由 WARMUP_CLS 枚举:linear与polynomial。
线性 warmup(LinearWarmup 源码):
learning_rate = warmup_lr + step / warmup_steps * (final_warmup_lr - warmup_lr)配置字段见 LinearWarmupConfig:warmup_learning_rate=0(warmup 起点)、warmup_steps(必填)。
多项式 warmup(PolynomialWarmUp 源码):以initial_learning_rate * (step/warmup_steps)^power递增,其中initial_learning_rate同样取自基础调度在 warmup_steps 处的值;注意实现中用tf.math.maximum(step, 1.0)规避 step 为 0 时的除零问题。配置字段见 PolynomialWarmupConfig:power=1、warmup_steps(必填)。
5.3 观察 warmup 曲线的一个陷阱
学习率值按summary_interval定期记录到 TensorBoard summary(该间隔由运行时配置定义,见 config_definitions.py)。官方文档特别指出:如果warmup_steps小于summary_interval,summary 中将看不到 warmup 阶段的取值——排查 warmup 是否生效时,需留意这一点。
六、训练任务中的实际调用链
在 TFM 的训练框架里,优化器并非由用户直接创建,而是在 task 中构建。BaseTask.create_optimizer 的调用链为:
opt_factory = optimization.OptimizerFactory(optimizer_config) optimizer = opt_factory.build_optimizer( opt_factory.build_learning_rate(), gradient_transformers=gradient_transformers) if runtime_config: optimizer = performance.configure_optimizer( optimizer, use_float16=runtime_config.mixed_precision_dtype == "float16", loss_scale=runtime_config.loss_scale)从源码结构看,该方法在工厂之上还叠加了三层能力:
- 差分隐私:若传入
dp_config,会注入clip_l2_norm+add_noise两个梯度变换器(第 86-96 行); - 混合精度:对 float16 训练自动配置 loss scaling,避免上/下溢(第 103-109 行);
- 可扩展钩子:
build_optimizer本身接受gradient_aggregator、gradient_transformers、postprocessor三个可选参数(源码),可在应用梯度前做任意变换。测试 test_gradient_aggregator 演示了用 aggregator 将梯度置零的用法。
在 Task 中自定义优化器:官方文档指出,优化器与学习率在 task 中创建,如果训练任务需要不同的优化器或学习率调度,可覆写 task 的create_optimizer类方法。这样既保留工厂的配置化构建,又能针对特定任务注入自定义逻辑(例如额外的梯度变换或包装器)。
七、扩展新的优化器
文档给出的三步扩展流程,与当前源码结构完全对应:
- 继承基类实现自定义优化器:创建继承自
tf_keras.optimizers.Optimizer的子类; - 添加配置字段:在 optimizer_config.py 中新增一个继承
BaseOptimizerConfig的 dataclass,并在 OptimizerConfig 中注册对应字段(oneof 要求type名与字段名一致); - 注册优化器类:将类加入 optimizer_factory.py 的注册表。当前源码为此提供了显式 API register_optimizer_cls,按
use_legacy_optimizer分别写入LEGACY_OPTIMIZERS_CLS或NEW_OPTIMIZERS_CLS,重复注册会抛出ValueError;其 docstring 同时提醒:用户仍需继承配置数据类才能与OptimizerFactory配合使用。
新增学习率调度同理:在 lr_schedule.py 中实现LearningRateSchedule子类(可复用_make_offset_wrapper获得 offset 能力),在LR_CLS/WARMUP_CLS中注册,并补充对应的 LrConfig/WarmupConfig 字段。
八、调参时的重要考量
官方文档最后强调两个与优化配置强耦合的因素,直接决定配置是否「自洽」:
- Batch size:改变批大小通常要求同步缩放学习率取值与训练步数,修改 batch size 时必须相应调整这些数值,否则等效训练量与衰减节奏都会偏离设计;
- Train steps:训练总步数与
decay_steps(cosine/polynomial/exponential)、boundaries(stepwise)等字段高度相关,只改其一会产生非预期行为。
此外结合第五节的结论可补充:修改warmup_steps时,由于 warmup 终点值由lr(warmup_steps)推断,warmup 步数变化会连带改变 warmup 曲线的斜率形态,而不会改变 warmup 结束后的衰减轨迹。
小结
TF-Models NLP 的优化体系把「优化器选型、学习率衰减、warmup 稳定」三件事收敛到一份字典化配置中:OptimizationConfig负责声明,OptimizerFactory负责构建,base_task.py负责在训练管线中落地。文档中的最小示例(SGD + stepwise + 线性 warmup、RMSprop + 全局范数裁剪、cosine + 线性 warmup)在当前仓库源码中均可直接运行;而源码在文档基础上进一步提供了 legacy/experimental 双路径优化器、offset 调度包装、EMA 包装与差分隐私梯度变换等扩展点,这些正是大型 NLP 预训练与多场景微调配置管理的关键基础设施。
【免费下载链接】modelsModels and examples built with TensorFlow项目地址: https://gitcode.com/GitHub_Trending/mode/models
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考