初识深度学习——学习率调度与权重衰减

一、引言:学习率在深度学习中的作用

在之前的博客中,我们完成了数据增强、模型训练、保存和推理的完整流程。但如果你仔细观察训练过程,会发现一个有趣的现象:损失值在训练初期快速下降,但到了后期却卡住了,无论如何训练都难以继续改善。

这背后的原因",很可能就是学习率(Learning Rate) 设置不当。

学习率控制着模型参数更新的步长,是深度学习中最重要、也最难调的超参数之一:

  • 学习率太大:参数更新步子太大,损失值剧烈震荡,甚至发散,永远到不了最优点。

  • 学习率太小:参数更新步子太小,训练速度极慢,可能陷入局部最优无法跳出。

  • 固定学习率:训练初期合适的学习率,到了后期可能太大;后期合适的学习率,在初期又太小。

那么,有没有一种方法能让学习率在训练过程中自动调整 呢?答案是肯定的------学习率调度器(Learning Rate Scheduler)

本篇博客将基于完整代码,讲解两种常用的学习率调度策略:StepLRReduceLROnPlateau ,并介绍权重衰减(Weight Decay) 这一防止过拟合的重要技术。

二、学习率调度器概述

2.1 为什么需要学习率调度

想象一下你在一座山上寻找最低点:

  • 刚开始 :你离谷底还很远,应该大步流星 地快速下降------需要大学习率

  • 接近谷底 :你已经接近最低点,应该小步慢走 ,避免一脚跨过最低点------需要小学习率

如果全程使用同一个步长,要么走得慢,要么永远到不了最优点。学习率调度正是为了解决这个问题------随着训练进行,逐步减小学习率。

2.2 PyTorch中的调度器

PyTorch在 torch.optim.lr_scheduler 中提供了多种学习率调度器:

调度器 调整策略
StepLR 每隔固定步长,学习率乘以gamma
MultiStepLR 在指定里程碑处调整学习率
ExponentialLR 每个epoch学习率乘以gamma
CosineAnnealingLR 余弦退火,周期性调整
ReduceLROnPlateau 根据指标变化自动调整
OneCycleLR 一个周期内先升后降

代码中展示的是 StepLRReduceLROnPlateau 两种。

三、StepLR------固定步长衰减

3.1 基本用法

python 复制代码
scheduler = torch.optim.lr_scheduler.StepLR(
    optimizer, 
    step_size=10,    # 每10个epoch调整一次
    gamma=0.5        # 学习率乘以0.5
)

参数说明

参数 含义
optimizer 要调整的优化器
step_size 调整周期(多少epoch调整一次)
gamma 衰减因子,新学习率 = 旧学习率 × gamma

效果

  • Epoch 1-10:lr = 0.1

  • Epoch 11-20:lr = 0.05

  • Epoch 21-30:lr = 0.025

  • Epoch 31-40:lr = 0.0125

  • ...

学习率呈阶梯式下降,故名 "Step" LR。

3.2 如何在训练中使用

调度器需要在每个epoch结束后 调用 step()

python 复制代码
for epoch in range(epochs):
    train(...)          # 训练一个epoch
    test(...)           # 测试
    scheduler.step()    # 调整学习率

注意StepLRstep() 不需要参数,因为它只依赖于epoch计数。

3.3 StepLR的优缺点

优点

  • 简单直观,易于理解

  • 训练后期学习率变小,有助于精细收敛

缺点

  • 调整时机是预先设定的,无法根据实际训练情况自适应

  • 如果step_size设置不当,可能过早或过晚衰减

四、ReduceLROnPlateau------自适应学习率调整

4.1 基本用法

python 复制代码
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
    optimizer,
    mode='min',              # 监控指标是越小越好(如loss)
    factor=0.1,              # 学习率乘以0.1
    patience=10,             # 连续10次没有改善才调整
    threshold=0.0001,        # 改善小于该值认为没有改善
    threshold_mode='rel',    # 相对模式
    cooldown=0,              # 调整后等待0个epoch再重新监控
    min_lr=0,                # 学习率下限
    eps=1e-08                # 学习率变化的最小值
)

参数详解

参数 含义
mode 'min'表示监控指标越小越好(如loss),'max'表示越大越好(如accuracy)
factor 学习率衰减因子,新学习率 = 旧学习率 × factor
patience "耐心值",连续多少个epoch没有改善才调整
threshold 判定"改善"的阈值
threshold_mode 'rel'相对模式,'abs'绝对模式
cooldown 调整后等待多少个epoch再重新开始监控
min_lr 学习率的下限,不会低于此值
eps 学习率变化的最小值,小于此值不调整

4.2 核心思想

ReduceLROnPlateau 的核心思想是:当监控指标(如loss)不再下降时,说明当前学习率可能太大了,应该减小学习率

与StepLR的"固定周期调整"不同,ReduceLROnPlateau是自适应的------它会观察指标的变化,只有在真正需要时才调整学习率。

4.3 如何在训练中使用

关键区别 :ReduceLROnPlateau的 step() 需要传入监控指标

python 复制代码
for epoch in range(epochs):
    train_loss = train(...)
    scheduler.step(train_loss)    # 传入loss,让调度器判断

或者像代码中那样,在训练循环的每个batch后调用:

python 复制代码
def train(dataloader, model, loss_fn, optimizer):
    model.train()
    for x, y in dataloader:
        # ... 前向传播、反向传播 ...
        loss_value = loss.item()
        scheduler.step(loss_value)    # 传入当前loss
        print(f"loss:{loss_value:>7f}")

注意 :代码中在每个batch后就调用 scheduler.step(loss_value),这意味着调度器会每个batch 都检查一次loss。这种方式更细粒度,但可能因为batch间loss波动而导致频繁调整。更常见的做法是每个epoch调用一次,传入该epoch的平均loss。

4.4 ReduceLROnPlateau的优缺点

优点

  • 自适应,根据实际训练情况调整

  • 不需要预先设定调整周期

  • 训练更稳定,避免过早/过晚衰减

缺点

  • 需要传入监控指标,使用稍复杂

  • 参数较多,需要根据任务调整

4.5 两种调度器对比

对比项 StepLR ReduceLROnPlateau
调整依据 固定的epoch数 监控指标的变化
是否自适应
step()参数 需要传入指标
适用场景 训练周期已知 训练周期不确定
调参难度

五、权重衰减------防止过拟合

5.1 什么是权重衰减

权重衰减(Weight Decay) ,也称为L2正则化,是一种防止模型过拟合的技术。

核心思想:在损失函数中添加一个与模型权重平方成正比的惩罚项:

其中 就是权重衰减系数。

为什么能防止过拟合?

  • 过拟合的模型往往权重值很大(对训练数据"死记硬背")

  • 加上权重平方惩罚后,模型会倾向于使用更小的权重值

  • 更小的权重意味着模型更"简单",对噪声不敏感,泛化能力更强

5.2 在优化器中设置权重衰减

python 复制代码
optimizer = torch.optim.Adam(
    model.parameters(), 
    lr=0.1,
    weight_decay=1e-4    # 权重衰减系数
)

注意 :代码中 weight_decay 参数并没有显式设置,默认为0(即不使用权重衰减)。如果需要启用,可以添加这个参数。

5.3 权重衰减 vs 学习率衰减

很多人容易混淆这两个概念:

概念 作用对象 目的
学习率衰减 优化器的学习率 让训练后期更精细,加速收敛
权重衰减 模型的权重参数 防止过拟合,提升泛化能力

两者是不同维度的技术,可以同时使用。

六、完整训练代码解析

6.1 数据准备

数据准备部分与之前一致,包括数据增强、自定义数据集、DataLoader:

python 复制代码
training_data = food_dataset(file_path='./train.txt', 
                             transform=data_transforms['trainda'])
test_data = food_dataset(file_path='./test.txt', 
                         transform=data_transforms['valid'])

train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)

6.2 训练函数

python 复制代码
def train(dataloader, model, loss_fn, optimizer):
    model.train()
    for x, y in dataloader:
        x, y = x.to(device), y.to(device)
        pred = model.forward(x)
        loss = loss_fn(pred, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        loss_value = loss.item()
        scheduler.step(loss_value)      # 关键:每个batch后调整学习率
        print(f"loss:{loss_value:>7f}")

与之前代码的区别 :多了 scheduler.step(loss_value) 这一行。

6.3 测试函数

python 复制代码
acc_s = []
loss_s = []
best_acc = 0

def test(dataloader, model, loss_fn):
    global best_acc
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model.forward(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")
    acc_s.append(correct)     # 记录准确率
    loss_s.append(test_loss)  # 记录损失

新增内容 :用 acc_sloss_s 两个列表记录每个epoch的准确率和损失,方便后续绘制学习曲线。

6.4 优化器与调度器配置

python 复制代码
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)

# 方案一:StepLR
# scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)

# 方案二:ReduceLROnPlateau
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, 
    mode='min', 
    factor=0.1,
    patience=10,
    threshold=0.0001,
    threshold_mode='rel',
    cooldown=0,
    min_lr=0,
    eps=1e-08
)

注意lr=0.1 是一个相对较大的初始学习率,配合调度器逐步衰减,可以让训练初期快速下降,后期精细调整。

6.5 训练循环

python 复制代码
epochs = 100
for t in range(epochs):
    print(f"Epoch {t+1}\n-----------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
test(test_dataloader, model, loss_fn)

注意 :这里 epochs = 100,是一个较大的训练轮数。有了学习率调度器,即使训练很多轮,也不会因为学习率过大而震荡,反而能在后期精细收敛。

七、学习率调度效果分析

7.1 学习率变化曲线

使用StepLR时,学习率变化如下:

python 复制代码
Epoch 1-10:   lr = 0.1
Epoch 11-20:  lr = 0.05
Epoch 21-30:  lr = 0.025
Epoch 31-40:  lr = 0.0125
...

使用ReduceLROnPlateau时,学习率会在loss不再下降时自动衰减。

7.2 训练效果对比

训练策略 收敛速度 最终精度 稳定性
固定学习率 初期快,后期震荡 中等
StepLR 较快 较高
ReduceLROnPlateau 自适应 最高 最好

7.3 学习曲线可视化(扩展)

可以结合 acc_sloss_s 绘制学习曲线:

python 复制代码
import matplotlib.pyplot as plt

epochs_range = range(1, len(acc_s) + 1)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc_s, 'b-', label='Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('准确率曲线')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss_s, 'r-', label='Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('损失曲线')
plt.legend()

plt.tight_layout()
plt.show()

八、总结

本篇博客围绕学习率调度权重衰减两大主题,系统讲解了:

知识点 核心内容
学习率调度 训练过程中动态调整学习率
StepLR 每隔固定epoch衰减,简单直观
ReduceLROnPlateau 根据指标变化自适应调整
权重衰减 L2正则化,防止过拟合
学习曲线 记录acc和loss,分析训练过程
相关推荐
vibecoding771 小时前
企业买国产模型怎么选(2026 年 9 月):先看备案,再算峰谷价、分档价与积分价,最后决定要不要多家并用
人工智能·大模型
AIGCmagic社区1 小时前
腾讯混元 AuK 技术报告拆读:1.5B 统一语音生成与编辑,蒸馏后 4 步无 CFG 提速 4.5 倍
人工智能·算法·aigc·ai多模态
Allen_LVyingbo1 小时前
医疗人工智能项目全生命周期管理系统:监管知识建模、工程实现与实证评估(下)
大数据·数据库·人工智能·python·自然语言处理·自动化
Forerror20261 小时前
MAI Gateway(魔芋企业级AI网关)从零讲起:大模型网关解决什么问题?小白也能看懂
人工智能·maigateway·finapi·企业级ai网关·大模型财务管控·企业级大模型治理网关
志栋智能1 小时前
超自动化巡检如何生成“有灵魂”的运维报告?
大数据·运维·人工智能·架构·自动化
千里码aicood1 小时前
pyqt基于pytorch的人脸交换系统设计(opencv)
人工智能·pyqt
凌风的跨境分享1 小时前
Temu运营避坑指南:制造地点信息填写合规要点与批量优化方法
运维·服务器·人工智能
来让爷抱一个1 小时前
2026 表示工程实战:八帧跳跃不许特征乱漂,百智云精灵图把激活引导写进素材包
人工智能·机器学习
熊猫钓鱼>_>1 小时前
从拍照到建模:HarmonyOS 7 3DGS端侧重建完整实战指南
人工智能·3d·ai·harmonyos·arkts·鸿蒙·3dgs