深度学习篇---SGD优化器
SGD优化器简述:
在 PyTorch 中使用 SGD(随机梯度下降)优化器非常直接,它是最基础也最常用的优化器之一。下面我会用通俗易懂的方式讲解,并提供完整的代码示例。
SGD 优化器的核心概念
SGD 的工作原理很简单:
- 计算模型参数的梯度(损失函数对参数的偏导数)
- 按照梯度的反方向更新参数,更新幅度由学习率控制
- 可以添加动量(momentum)来加速收敛,减少震荡
使用步骤
- 定义模型
- 定义损失函数
- 初始化 SGD 优化器,指定要优化的参数和超参数
- 在训练循环中:
- 前向传播计算预测值
- 计算损失
- 反向传播计算梯度
- 用优化器更新参数
详细代码示例
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()
代码解析
SGD 优化器的初始化:
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
model.parameters()
:告诉优化器需要优化哪些参数lr
:学习率,控制每一步更新的幅度,过大会导致不收敛,过小会导致收敛太慢momentum
:动量,可选参数,模拟物理中的惯性,帮助优化器跳出局部最小值
训练循环中的关键步骤:
optimizer.zero_grad()
:清空之前计算的梯度,否则梯度会累积loss.backward()
:自动计算所有参数的梯度optimizer.step()
:根据计算出的梯度更新参数
学习率的选择:
- 通常 SGD 的学习率在 0.001 到 0.1 之间
- 如果损失波动很大,说明学习率可能太大
- 如果损失下降很慢,说明学习率可能太小
动量的作用:
- 加入动量后,优化器不仅考虑当前梯度,还会考虑之前的更新方向
- 这有助于加速收敛,特别是在平坦区域
- 通常动量值设置在 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)
,再根据损失曲线调整参数。