学习率衰减原理解释 | AI生成和翻译
问题: 为什么在训练后期学习率会衰减?nanoGPT 风格训练中学习率调度的原理是什么?
回答:
学习率衰减本质上是优化景观管理。其原理如下:
训练的两个阶段
早期阶段(高学习率,例如 0.47):
- 损失表面粗糙,布满次优谷底。大步长有助于快速穿越并逃离不良局部极小点。
- 模型远未收敛,因此大更新是安全的——你不会冒在良好解附近振荡的风险。
- 大更新带来的噪声有助于探索。
后期阶段(低学习率,例如 0.30–0.31):
- 你正进入收敛盆地——接近一个不错的解。
- 此时大步长会过冲良好极小点,并在解附近引入噪声。
- 小步长 = 更精细的调整,从而沉降到更低损失的谷底。
- 较低的学习率稳定了训练,减少了振荡。
这就是模拟退火在深度学习中的应用:开始时热(探索),冷却下来(利用)。
代码:余弦退火(nanoGPT 使用的方法)
def get_lr(it, warmup_iters, max_iters, lr_max=0.47, lr_min=0.1):
"""带线性预热的余弦退火。"""
# 线性预热
if it < warmup_iters:
return lr_max * it / warmup_iters
# 余弦衰减
progress = (it - warmup_iters) / (max_iters - warmup_iters)
return lr_min + 0.5 * (lr_max - lr_min) * (1 + math.cos(math.pi * progress))
# 在 step 107,319 / 130,000:
# progress ≈ 0.825
# lr ≈ 0.1 + 0.5 * (0.47 - 0.1) * (1 + cos(π * 0.825))
# lr ≈ 0.1 + 0.185 * (1 + cos(2.59)) ≈ 0.1 + 0.185 * (1 - 0.85) ≈ 0.13
等等,你的日志显示 0.30–0.31,这表明是不同的调度策略。很可能是线性衰减或预热 + 多项式衰减:
def get_lr_linear_decay(it, warmup_iters, total_iters, lr_max=0.47):
if it < warmup_iters:
return lr_max * it / warmup_iters
else:
decay_progress = (it - warmup_iters) / (total_iters - warmup_iters)
return lr_max * (1 - decay_progress) # 简单线性衰减
# 在训练进度 82.55%:
# decay_progress ≈ 0.825
# lr ≈ 0.47 * (1 - 0.825) ≈ 0.082 # 嗯,仍低于观测值
你的调度可能是一个具有更高 lr_min 的余弦调度,或者使用了预热 + 基于 step 的衰减。具体系数很重要。
为什么这在经验上很重要
根据 Andrej Karpathy 的 nanoGPT 和 GPT-2/3 论文:
- 固定高学习率 → 训练后期振荡并发散
- 固定低学习率 → 收敛慢,陷入早期不良极小点
- 衰减学习率 → 早期进展快 + 后期收敛平滑
你的损失曲线也证实了这一点:随着学习率衰减,损失从 2.78 降至 2.71,说明调度正在发挥作用。衰减让你能沉降到更好的损失区域而不会过冲。
更深层的见解
衰减实际上关于将步长与损失景观的曲率匹配:
- 早期:平坦、噪声大的地形 → 大步长没问题
- 后期:尖锐、狭窄的极小点(Hessian 特征值更大) → 需要小步长
现代调度器(余弦、多项式)是这种曲率的软代理。更“智能”的方法会使用自适应方法(Adam, AdamW),但这些方法也有各自的权衡。
对于 nanoGPT 规模的训练,带预热的余弦退火是黄金标准,因为它:
- 简单(无需调参)
- 经验上被证明有效(GPT-2 使用它)
- 理论合理(覆盖探索 → 利用)
你的训练看起来健康——在 82% 进度时,5 小时内损失下降 0.1 已经很不错了。衰减正在发挥作用。能分享一下最终检查点的损失值吗?