跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

怎么在Python TensorFlow中实现余弦退火学习率_通过CosineDecay解决

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 原生支持重启余弦退火的调度器,它按固定周期重置余弦曲线起点,每个周期内学习率从高到低再“热启动”到高值。 关键参数含义: 立即学习 “ Python免费学习笔记(深入) ”;
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… Python 3.14.3 微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。 下载 常见报错和兼容性注意点 用错参数或版本不匹配时容易出以下问题:
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 # 最小学习率比例
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 才完成一轮退火,容易误判收敛状态。

相关文章