cosinedecay仅单次衰减不重启,需用cosinedecayrestarts实现周期性余弦退火;其通过first_decay_steps、t_mul、m_mul控制周期长度与重启起点,自定义调度器可支持预热、epoch对齐等扩展。

为什么直接用 CosineDecay 会发现学习率不重启
很多人用 tf.keras.optimizers.schedules.CosineDecay 后发现:学习率只衰减一次,到训练末尾就停在最低值,不像论文里说的“余弦退火”那样周期性回暖。这是因为 CosineDecay 本质是单周期余弦衰减(从 initial_learning_rate 平滑降到 0),不是带重启的余弦退火(Cosine Annealing with Restarts)。
它适合简单衰减场景,但如果你要复现 SGDR、FastAI 或 Vision Transformer 常用的带重启余弦调度,得换方案。
用 CosineDecayRestarts 实现真正重启式退火
CosineDecayRestarts 是 TensorFlow 原生支持重启余弦退火的调度器,它按固定周期重置余弦曲线起点,每个周期内学习率从高到低再“热启动”到高值。
关键参数含义:
-
initial_learning_rate:每个周期开始时的学习率(不是全局初始值) -
first_decay_steps:第一个周期的步数(比如 1000 步) -
t_mul:周期长度缩放因子(t_mul=1.0表示等长周期;t_mul=2.0表示后续周期翻倍) -
m_mul:每个新周期起始学习率的衰减系数(m_mul=1.0表示每轮都从相同高度重启;m_mul=0.9表示逐轮降低起点)
示例:
lr_schedule = tf.keras.optimizers.schedules.CosineDecayRestarts(
initial_learning_rate=0.01,
first_decay_steps=500,
t_mul=2.0,
m_mul=0.8
)
这会产生:第 1 轮 500 步 → 第 2 轮 1000 步 → 第 3 轮 2000 步,且每轮起始学习率依次为 0.01 → 0.008 → 0.0064…
常见报错和兼容性注意点
用错参数或版本不匹配时容易出以下问题:
-
ValueError: 't_mul' must be a float greater than 0:传了整数如t_mul=2,必须写成t_mul=2.0 - 训练中学习率突变为 NaN:检查
first_decay_steps是否远小于实际训练总步数(例如设成 10,但模型跑 10000 步),会导致过早进入高频震荡阶段,数值不稳定 - Keras 模型编译时报
TypeError: cannot convert float to Tensor:说明你把调度器对象直接当标量用了,正确用法是传给optimizer.learning_rate(TF 2.11+ 推荐用tf.keras.optimizers.Adam(learning_rate=lr_schedule)) - TF 2.8 之前版本不支持
m_mul小于 1 的递减重启,若需该行为,建议升级或手写tf.keras.optimizers.schedules.LearningRateSchedule子类
什么时候该放弃内置调度器,自己写一个
内置 CosineDecayRestarts 灵活度有限——比如你要实现“前 10% 步线性预热 + 后面接带重启余弦”,或者按 epoch(而非 step)计数,又或者想在每次重启时注入随机扰动(如 FastAI 的 cosine_with_warmup + cutmix 联动),这时候就得自定义。
最小可用自定义模板(按 step 计):
class CustomCosineRestart(tf.keras.optimizers.schedules.LearningRateSchedule):
def __init__(self, init_lr, period_steps, restart_mult=1.0, alpha=0.0):
self.init_lr = init_lr
self.period_steps = period_steps
self.restart_mult = restart_mult
self.alpha = alpha # 最小学习率比例
<pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">def __call__(self, step):
step = tf.cast(step, tf.float32)
cycle_step = step % self.period_steps
cycle_num = step // self.period_steps
lr_max = self.init_lr * (self.restart_mult ** cycle_num)
cosine_decay = 0.5 * (1.0 + tf.cos(np.pi * cycle_step / self.period_steps))
return lr_max * (self.alpha + (1.0 - self.alpha) * cosine_decay)这种写法可控性强,但要注意:所有计算必须用 tf.*
np.*(静态图下推荐 tf.math),不能混用 Python 原生 math 或 time。最易被忽略的是:重启周期和 batch size、dataset length 的对齐——如果一个 epoch 只有 200 步,却设 first_decay_steps=1000,那 5 个 epoch 才完成一轮退火,容易误判收敛状态。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











