当前位置: 首页 > news >正文

深度学习篇---SGD优化器

SGD优化器简述:

在 PyTorch 中使用 SGD(随机梯度下降)优化器非常直接,它是最基础也最常用的优化器之一。下面我会用通俗易懂的方式讲解,并提供完整的代码示例。

SGD 优化器的核心概念

SGD 的工作原理很简单:

  • 计算模型参数的梯度(损失函数对参数的偏导数)
  • 按照梯度的反方向更新参数,更新幅度由学习率控制
  • 可以添加动量(momentum)来加速收敛,减少震荡

使用步骤

  1. 定义模型
  2. 定义损失函数
  3. 初始化 SGD 优化器,指定要优化的参数和超参数
  4. 在训练循环中:
    • 前向传播计算预测值
    • 计算损失
    • 反向传播计算梯度
    • 用优化器更新参数

详细代码示例

import torch
import torch.nn as nn
import matplotlib.pyplot as plt# 1. 准备数据
# 生成一些带噪声的线性数据 y = 3x + 2 + 噪声
x = torch.randn(100, 1) * 10  # 100个随机数
y = 3 * x + 2 + torch.randn(100, 1) * 3  # 加入噪声# 2. 定义模型:简单的线性模型 y = wx + b
class LinearModel(nn.Module):def __init__(self):super().__init__()# 定义一个线性层,输入1维,输出1维self.linear = nn.Linear(in_features=1, out_features=1)def forward(self, x):return self.linear(x)# 初始化模型
model = LinearModel()# 3. 定义损失函数:均方误差
loss_function = nn.MSELoss()# 4. 定义SGD优化器
# 参数说明:
# - model.parameters():需要优化的模型参数
# - lr=0.01:学习率,控制更新步长
# - momentum=0.9:动量,加速收敛,减少震荡(可选)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)# 5. 训练模型
epochs = 100  # 训练轮次
losses = []   # 记录损失变化for epoch in range(epochs):# 前向传播:计算模型预测值y_pred = model(x)# 计算损失loss = loss_function(y_pred, y)losses.append(loss.item())# 清空之前的梯度(非常重要!)optimizer.zero_grad()# 反向传播:计算梯度loss.backward()# 用SGD优化器更新参数optimizer.step()# 每10轮打印一次信息if (epoch + 1) % 10 == 0:# 获取当前的权重和偏置w, b = model.linear.weight.item(), model.linear.bias.item()print(f"轮次: {epoch+1}, 损失: {loss.item():.4f}, 权重: {w:.4f}, 偏置: {b:.4f}")# 6. 结果可视化
plt.figure(figsize=(12, 5))# 绘制数据点和拟合直线
plt.subplot(1, 2, 1)
plt.scatter(x.numpy(), y.numpy(), label='数据点')
plt.plot(x.numpy(), model(x).detach().numpy(), 'r-', label=f'拟合线: y={w:.2f}x+{b:.2f}')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()# 绘制损失变化曲线
plt.subplot(1, 2, 2)
plt.plot(range(epochs), losses)
plt.xlabel('轮次')
plt.ylabel('损失值')
plt.title('损失变化曲线')plt.tight_layout()
plt.show()

代码解析

  1. SGD 优化器的初始化

    optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
    
    • model.parameters():告诉优化器需要优化哪些参数
    • lr:学习率,控制每一步更新的幅度,过大会导致不收敛,过小会导致收敛太慢
    • momentum:动量,可选参数,模拟物理中的惯性,帮助优化器跳出局部最小值
  2. 训练循环中的关键步骤

    • optimizer.zero_grad():清空之前计算的梯度,否则梯度会累积
    • loss.backward():自动计算所有参数的梯度
    • optimizer.step():根据计算出的梯度更新参数
  3. 学习率的选择

    • 通常 SGD 的学习率在 0.001 到 0.1 之间
    • 如果损失波动很大,说明学习率可能太大
    • 如果损失下降很慢,说明学习率可能太小
  4. 动量的作用

    • 加入动量后,优化器不仅考虑当前梯度,还会考虑之前的更新方向
    • 这有助于加速收敛,特别是在平坦区域
    • 通常动量值设置在 0.9 左右

运行这段代码后,你会看到模型的权重和偏置逐渐接近真实值(3 和 2),同时损失值不断下降,这就是 SGD 优化器在起作用。

SGD优化器深度解析:

SGD 优化器深度解析

SGD(随机梯度下降)虽然基础,但理解透彻后能帮你更好地掌握其他复杂优化器。它的核心公式是:
参数新值 = 参数旧值 - 学习率 × 梯度

可以想象成下山:梯度是当前的坡度方向,学习率是步长,每一步都朝着坡度最陡的反方向走。

完整代码示例(带更多细节)

下面在之前线性回归的基础上,增加学习率调度、不同动量对比和可视化,帮助你直观理解参数影响:

import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import numpy as np# 1. 准备更贴近真实场景的数据
np.random.seed(42)
torch.manual_seed(42)# 生成非线性可分数据(模拟更复杂场景)
x = torch.linspace(-10, 10, 200).view(-1, 1)  # 200个从-10到10的点
y = 0.5 * x**2 + 2 * x + 3 + torch.randn_like(x) * 5  # 二次函数+噪声# 2. 定义一个稍复杂的模型(包含非线性层)
class NonLinearModel(nn.Module):def __init__(self):super().__init__()self.layer1 = nn.Linear(1, 10)  # 第一层:1→10维self.activation = nn.ReLU()     # 非线性激活self.layer2 = nn.Linear(10, 1)  # 第二层:10→1维def forward(self, x):x = self.layer1(x)x = self.activation(x)return self.layer2(x)# 初始化两个模型,用于对比不同动量的效果
model_with_momentum = NonLinearModel()
model_no_momentum = NonLinearModel()  # 复制结构但参数独立# 3. 定义损失函数(均方误差)
loss_fn = nn.MSELoss()# 4. 初始化两个SGD优化器(带动量 vs 无动量)
# 带动量的SGD(适合复杂模型)
optimizer_with_momentum = torch.optim.SGD(model_with_momentum.parameters(),lr=0.001,        # 学习率momentum=0.9,    # 动量(0.9是常用值)weight_decay=1e-4  # 权重衰减(L2正则化,可选)
)# 无动量的SGD(基础版)
optimizer_no_momentum = torch.optim.SGD(model_no_momentum.parameters(),lr=0.001,momentum=0.0     # 关闭动量
)# 5. 定义学习率调度器(动态调整学习率,非常实用!)
# 每50轮将学习率乘以0.5
scheduler_with_momentum = torch.optim.lr_scheduler.StepLR(optimizer_with_momentum,step_size=50,gamma=0.5
)
scheduler_no_momentum = torch.optim.lr_scheduler.StepLR(optimizer_no_momentum,step_size=50,gamma=0.5
)# 6. 训练两个模型并记录损失
epochs = 300
losses_with_momentum = []
losses_no_momentum = []for epoch in range(epochs):# 训练带动量的模型model_with_momentum.train()  # 切换到训练模式y_pred_m = model_with_momentum(x)loss_m = loss_fn(y_pred_m, y)losses_with_momentum.append(loss_m.item())optimizer_with_momentum.zero_grad()  # 清空梯度loss_m.backward()                    # 反向传播optimizer_with_momentum.step()       # 更新参数scheduler_with_momentum.step()       # 调整学习率# 训练无动量的模型(相同步骤)model_no_momentum.train()y_pred_nm = model_no_momentum(x)loss_nm = loss_fn(y_pred_nm, y)losses_no_momentum.append(loss_nm.item())optimizer_no_momentum.zero_grad()loss_nm.backward()optimizer_no_momentum.step()scheduler_no_momentum.step()# 每50轮打印信息if (epoch + 1) % 50 == 0:print(f"轮次 {epoch+1}/{epochs}")print(f"带动量损失: {loss_m.item():.4f} | 无动量损失: {loss_nm.item():.4f}")print(f"当前学习率: {optimizer_with_momentum.param_groups[0]['lr']:.6f}\n")# 7. 结果可视化
plt.figure(figsize=(14, 6))# 绘制拟合效果对比
plt.subplot(1, 2, 1)
plt.scatter(x.numpy(), y.numpy(), alpha=0.5, label='原始数据')
plt.plot(x.numpy(), model_with_momentum(x).detach().numpy(), 'r-', linewidth=2, label='带动量SGD')
plt.plot(x.numpy(), model_no_momentum(x).detach().numpy(), 'b--', linewidth=2, label='无动量SGD')
plt.title('拟合效果对比')
plt.legend()# 绘制损失曲线对比
plt.subplot(1, 2, 2)
plt.plot(range(epochs), losses_with_momentum, 'r-', label='带动量 (momentum=0.9)')
plt.plot(range(epochs), losses_no_momentum, 'b--', label='无动量 (momentum=0.0)')
plt.axvline(x=50, color='gray', linestyle=':', label='学习率衰减点')
plt.axvline(x=100, color='gray', linestyle=':')
plt.axvline(x=150, color='gray', linestyle=':')
plt.title('损失下降曲线')
plt.xlabel('轮次')
plt.ylabel('损失值')
plt.legend()plt.tight_layout()
plt.show()

关键知识点详解

1. SGD 优化器的核心参数
  • params:必须传入模型参数(model.parameters()),告诉优化器要更新哪些值。

  • lr(学习率):最重要的参数!

    • 太大:参数更新幅度过大,可能在最优值附近震荡甚至发散(损失忽大忽小)。
    • 太小:收敛太慢,训练很久都达不到理想效果。
    • 建议值:通常从 0.01 开始试,复杂模型可减小到 0.001。
  • momentum(动量):

    • 作用:模拟物理中的 “惯性”,让优化器在平坦区域加速,在震荡区域平稳。
    • 原理:更新时不仅考虑当前梯度,还会加入上一次更新的方向(类似小球下坡时的惯性)。
    • 建议值:0.9(大部分场景通用),值越大惯性越强(但可能冲过最优值)。
    • 从代码结果能看到:带动量的模型损失下降更快更稳定。
  • weight_decay(权重衰减):

    • 作用:相当于 L2 正则化,防止模型过拟合(参数值过大)。
    • 原理:更新时给参数乘以一个小于 1 的系数,间接限制参数大小。
    • 建议值:1e-4 ~ 1e-2(根据模型复杂度调整)。
2. 训练循环中的必做步骤
  • optimizer.zero_grad():必须在反向传播前调用!
    因为 PyTorch 的梯度会累积(方便 RNN 等场景),不清空会导致梯度叠加,更新混乱。

  • loss.backward():自动计算所有参数的梯度(链式法则)。

  • optimizer.step():根据梯度更新参数(核心步骤)。

3. 实用技巧:学习率调度器

固定学习率很难适应整个训练过程(前期需要大步,后期需要微调)。代码中用了StepLR,每 50 轮将学习率减半,这是最常用的策略之一。
其他常用调度器:

  • CosineAnnealingLR:学习率按余弦曲线衰减(适合需要精细调优的场景)。
  • ReduceLROnPlateau:当损失不再下降时自动减小学习率(更智能)。
4. 什么时候该用 SGD?
  • 数据量极大时(SGD 内存占用比 Adam 小)。
  • 需要收敛到更优解时(SGD 可能找到比 Adam 更好的局部最优)。
  • 训练简单模型时(如线性回归、浅层 CNN)。
  • 调参经验丰富时(SGD 对学习率更敏感,需要更多调试)。

通过这个示例,你可以清晰看到:带动量的 SGD 收敛速度明显快于基础版,学习率调度能有效避免后期收敛停滞。实际使用时,建议先尝试SGD(lr=0.01, momentum=0.9),再根据损失曲线调整参数。

http://www.xdnf.cn/news/1437715.html

相关文章:

  • 「数据获取」《安徽建设统计年鉴》(2002-2007)(2004、2006缺失)(获取方式看绑定的资源)
  • spring boot驴友结伴游网站的设计与实现(代码+数据库+LW)
  • 使用Global Watersheds提取水文站控制区域(水文站上下游 流域水系等)
  • 【自记】Python 中 简化装饰器使用的便捷写法语法糖(Syntactic Sugar)示例
  • 复刻 Python 实现的小智语音客户端项目py-xiaozhi日记
  • 【算法笔记 day six】二分算法的第三部分
  • 手写Muduo网络库核心代码1-- noncopyable、Timestamp、InetAddress、Channel 最详细讲解
  • 测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击!
  • maven【maven】技术详解
  • ARM编译器生成的AXF文件解析
  • 平衡车-ADC采集电池电压
  • 综合诊断板CAN时间戳稳定性测试报告8.28
  • Linux内核进程管理子系统有什么第四十回 —— 进程主结构详解(36)
  • 安装部署k3s
  • Java试题-选择题(29)
  • 算法题打卡力扣第3题:无重复字符的最长子串(mid)
  • Suno AI 新功能上线:照片也能唱歌啦!
  • Netty从0到1系列之NIO
  • 进程优先级(Process Priority)
  • 猫猫狐狐的“你今天有点怪怪的”侦察日记
  • CentOS7安装Nginx服务——为你的网站配置https协议和自定义服务端口
  • Java注解深度解析:从@ResponseStatus看注解奥秘
  • 大模型RAG项目实战:Pinecone向量数据库代码实践
  • 二叉树经典题目详解(下)
  • 【数据分享】31 省、342 个地级市、2532 个区县农业机械总动力面板数据(2000 - 2020)
  • MySQL数据库——概述及最基本的使用
  • Python实现浅拷贝的常用策略
  • Vite 插件 @vitejs/plugin-legacy 深度解析:旧浏览器兼容指南
  • 【Linux】信号量
  • 09.01总结