概念
学习率调度(Learning Rate Scheduling)是指在模型训练过程中动态调整优化器的学习率,以平衡收敛速度与稳定性,从而提升模型性能。固定学习率往往难以兼顾训练初期的快速下降和后期的精细收敛,而学习率调度策略通过在不同训练阶段智能地增减学习率,帮助模型更高效、更稳定地找到最优解。
常见的调度策略包括:Step Decay(每隔固定轮数衰减学习率)、Exponential Decay(指数衰减)、Cosine Annealing(余弦退火,平滑降至零)、Warmup(训练初期线性或非线性地从小学习率升温)、以及组合策略如 Warmup + Cosine Decay(广泛用于Transformer等大模型)。此外,还有自适应调度方法如 ReduceLROnPlateau(当验证损失停滞时降低学习率)和带重启的 SGDR(Stochastic Gradient Descent with Restarts)。这些策略可根据任务、模型结构和数据规模灵活选用。
这里只介绍最常用的,Warmup + 余弦退火(Cosine Annealing with Warmup),适合于Transformer 系列模型(如 BERT、ViT、LLaMA)、ResNet 等。
Warmup
Warmup(学习率预热) 是一种在训练初期逐步增大学习率的调度策略,通常从一个很小的值(甚至0)线性增长到设定的初始学习率。其主要作用是缓解训练初期因参数随机初始化和优化器状态不稳定(如 Adam 的动量估计偏差)导致的梯度震荡或发散问题,尤其在使用大批次(large batch size)或 Transformer 类模型时效果显著。
示例:
def adjust_learning_rate(optimizer, base_lr, max_iters, cur_iters, warmup_iter=None, power=0.9): if warmup_iter is not None and cur_iters < warmup_iter: warmup_ratio = 0.1 # 从 base_lr 的 10% 开始 lr = base_lr * (warmup_ratio + (1 - warmup_ratio) * cur_iters / max(1, warmup_iter - 1)) else: lr = base_lr * ((1 - float(cur_iters - warmup_iter) / (max_iters - warmup_iter)) ** power) for param_group in optimizer.param_groups: param_group['lr'] = lr return lr我这里并没有使用scheduler.step()之类的操作,而是通过语句param_group['lr'] = lr # ← 直接赋值!直接修改 optimizer.param_groups 中的 'lr' 字段。
如果使用的是 torch.optim.lr_scheduler 中的调度器(比如 StepLR, CosineAnnealingWarmRestarts, LambdaLR 等),它们已经内置了 .step() 方法,只需在训练循环中调用它,学习率就会自动更新。
在train()函数中:
optimizer.zero_grad() loss.backward() if args.grad_clip > 0: torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip) optimizer.step() # 学习率调度 total_iters = args.epochs * len(train_loader) warmup_iters = args.warmup_epochs * len(train_loader) cur_iter = (epoch - 1) * len(train_loader) + step current_lr = adjust_learning_rate(optimizer, args.lr_rate, total_iters, cur_iter, warmup_iters, power=0.9)前 warmup_iter 步:学习率从 0.1 * base_lr 线性增长到 base_lr;
之后:使用多项式衰减(Polynomial Decay),指数为 power=0.9(即 "poly" 调度)。
余弦退火(Cosine Annealing)
余弦退火是一种学习率调度策略,它让学习率按照余弦函数从初始值平滑地衰减到一个最小值(如 eta_min)。
其核心思想是:在训练初期使用较大学习率快速收敛,在后期使用较小学习率精细调整模型参数。而 CosineAnnealingWarmRestarts 是其增强版本——每当学习率衰减到最低点时,“重启” 学习率回到初始值,重新开始新一轮余弦衰减,形成周期性探索与收敛的机制。这种方式有助于跳出局部最优,提升模型泛化能力。
示例:
import torch import torch.nn as nn import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR import matplotlib.pyplot as plt # 1. 构造一个简单模型 model = nn.Linear(10, 1) # 2. 优化器(初始学习率 = 0.1) optimizer = optim.SGD(model.parameters(), lr=0.1) # 3. 余弦退火调度器:周期 T_max = 50 个 epoch,最小学习率 eta_min = 0 scheduler = CosineAnnealingLR(optimizer, T_max=50, eta_min=0.0) # 4. 记录学习率变化 lrs = [] # 5. 模拟训练过程(100 个 epoch) for epoch in range(100): # 假装做了一次训练(这里省略 forward/backward) # 更新学习率(每个 epoch 结束后调用) scheduler.step() # 记录当前学习率 current_lr = scheduler.get_last_lr()[0] lrs.append(current_lr) if epoch % 10 == 0: print(f"Epoch {epoch:3d} | LR: {current_lr:.6f}") # 6. 可视化学习率变化 plt.figure(figsize=(8, 4)) plt.plot(lrs) plt.title("Cosine Annealing Learning Rate") plt.xlabel("Epoch") plt.ylabel("Learning Rate") plt.grid(True) plt.show()组合使用示例
import torch import torch.nn as nn import torch.optim as optim from torch.optim.lr_scheduler import LambdaLR, CosineAnnealingLR import matplotlib.pyplot as plt # 超参数 total_epochs = 100 warmup_epochs = 10 initial_lr = 1e-3 eta_min = 1e-6 # 模拟模型和优化器 model = nn.Linear(10, 1) optimizer = optim.Adam(model.parameters(), lr=initial_lr) # 第一阶段:Warmup(线性增长) def warmup_lambda(epoch): if epoch < warmup_epochs: return float(epoch + 1) / float(warmup_epochs) else: return 1.0 warmup_scheduler = LambdaLR(optimizer, lr_lambda=warmup_lambda) # 第二阶段:余弦退火(从 warmup 结束后开始) # 注意:CosineAnnealingLR 的 T_max 是从它开始算起的周期长度 cosine_scheduler = CosineAnnealingLR( optimizer, T_max=total_epochs - warmup_epochs, eta_min=eta_min ) # 记录学习率变化 lrs = [] for epoch in range(total_epochs): if epoch < warmup_epochs: # Warmup 阶段 lr = optimizer.param_groups[0]['lr'] lrs.append(lr) warmup_scheduler.step() else: # 切换到余弦退火(注意:第一次调用时 epoch == warmup_epochs) if epoch == warmup_epochs: # 重置优化器的学习率为 warmup 最终值(避免跳变) # 实际上 LambdaLR 已经设为 initial_lr,所以通常不需要额外操作 pass lr = optimizer.param_groups[0]['lr'] lrs.append(lr) cosine_scheduler.step() # 可视化学习率变化 plt.figure(figsize=(10, 4)) plt.plot(lrs, label='Learning Rate') plt.axvline(x=warmup_epochs - 1, color='r', linestyle='--', label='End of Warmup') plt.title('Warmup + Cosine Annealing Learning Rate Schedule') plt.xlabel('Epoch') plt.ylabel('Learning Rate') plt.legend() plt.grid(True) plt.show()