训练流程中的功能组件 - 优化器

flyfish

真实规律是 y = 2x + 3,我们给数据加一点噪声。模型只有 2 个可学习参数:权重 w 和偏置 b。训练的目标就是让模型自动学到 w,b
优化器主要是接收反向传播算出的梯度,按照自身算法规则更新模型的可学习参数(权重、偏置),驱动损失函数持续下降。

import torch

# --------------------------
# 1. 准备训练数据
# --------------------------
# 生成100个x样本,从0到10均匀分布
x = torch.linspace(0, 10, 100).reshape(-1, 1)
# 真实值 y = 2x + 3,加一点随机噪声模拟真实数据
y_true = 2 * x + 3 + torch.randn_like(x) * 0.5

# --------------------------
# 2. 初始化可学习参数 + 超参数
# --------------------------
# 随机初始化w和b,requires_grad=True 表示需要对它们求梯度
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)

learning_rate = 0.01  # 学习率
epochs = 200         # 训练总轮数

# --------------------------
# 3. 训练循环(手动实现优化器)
# --------------------------
for epoch in range(epochs):
    # 1前向传播:输入x,算出预测值
    y_pred = w * x + b

    #2 计算损失:均方误差(预测值和真实值的差的平方,取平均)
    loss = torch.mean((y_pred - y_true) ** 2)

    # 3 反向传播:自动算出w和b的梯度,存在 w.grad 和 b.grad 里
    loss.backward()

    # ==================================================
    # 这里就是优化器的逻辑
    # ==================================================
    # 关闭梯度追踪,只做纯数值更新,避免生成计算图浪费资源
    with torch.no_grad():
        # 核心更新公式:新参数 = 旧参数 - 学习率 × 梯度
        w -= learning_rate * w.grad
        b -= learning_rate * b.grad

    # 梯度清零:把w.grad和b.grad重置为0,避免下一轮累加旧梯度
    w.grad.zero_()
    b.grad.zero_()
    # ==================================================

    # 每20轮打印一次结果
    if (epoch + 1) % 40 == 0:
        print(f"轮次 {epoch+1:3d} | 损失: {loss.item():.4f} | w={w.item():.4f} | b={b.item():.4f}")

# 训练结束看最终结果
print(f"\n训练完成:最终w={w.item():.4f},最终b={b.item():.4f}")
print("真实值:w=2,b=3") 

代码中,with torch.no_grad() 里的两行 + 梯度清零,是最基础的随机梯度下降(SGD)优化器的功能。

它做两件事:

  1. 参数更新(对应PyTorch的 optimizer.step()
    按照固定公式 新参数 = 旧参数 - 学习率 × 梯度,逐个更新所有可学习参数。
    梯度告诉我们参数往哪个方向调能让损失变小,学习率控制调多大幅度。

  2. 梯度清零(对应PyTorch的 optimizer.zero_grad()
    PyTorch 默认会累加梯度,每轮更新完必须手动清零,否则下一轮的梯度会叠加上旧值,导致更新完全错误。

优化器就是参数更新规则的封装。

使用PyTorch自带的torch.optim实现

import torch

# 数据和参数初始化和上面完全一样
x = torch.linspace(0, 10, 100).reshape(-1, 1)
y_true = 2 * x + 3 + torch.randn_like(x) * 0.5
w = torch.randn(1, requires_grad=True)
b = torch.randn(1, requires_grad=True)
learning_rate = 0.01
epochs = 200

# SGD优化器:把所有参数交给它管理
optimizer = torch.optim.SGD([w, b], lr=learning_rate)

for epoch in range(epochs):
    y_pred = w * x + b
    loss = torch.mean((y_pred - y_true) ** 2)

    # 对应手动的 w.grad.zero_(); b.grad.zero_()
    optimizer.zero_grad()
    # 反向传播算梯度
    loss.backward()
    # 对应手动的 w -= lr*w.grad; b -= lr*b.grad
    optimizer.step()

    if (epoch + 1) % 40 == 0:
        print(f"轮次 {epoch+1:3d} | 损失: {loss.item():.4f} | w={w.item():.4f} | b={b.item():.4f}")

省去了逐个参数手写更新、逐个清零的麻烦
可以把epoch加大看效果

轮次  40 | 损失: 0.8172 | w=2.2211 | b=1.5349
轮次  80 | 损失: 0.6443 | w=2.1822 | b=1.7936
轮次 120 | 损失: 0.5282 | w=2.1504 | b=2.0055
轮次 160 | 损失: 0.4503 | w=2.1243 | b=2.1792
轮次 200 | 损失: 0.3979 | w=2.1029 | b=2.3215

在单轮训练迭代中,优化器的执行逻辑:

  1. 接收梯度输入:获取由反向传播算法计算得到的损失函数对各参数的一阶导数(梯度)∇L(θ)\nabla L(\boldsymbol{\theta})L(θ),该信息指明了参数空间中损失上升最快的方向与速率。
  2. 计算参数更新量:基于当前梯度、历史梯度状态与预设超参数,计算每个参数的更新方向与步长,生成参数更新向量Δθ\Delta\boldsymbol{\theta}Δθ。这是优化器的核心功能,不同优化算法的差异均体现于此:
    基础随机梯度下降(SGD)仅以全局学习率对梯度做线性缩放,即Δθ=−η⋅∇L(θ)\Delta\boldsymbol{\theta} = -\eta \cdot \nabla L(\boldsymbol{\theta})Δθ=ηL(θ),所有参数共享统一的步长缩放系数;
    自适应优化器(如Adam)会额外维护每个参数的梯度一阶矩(动量)与二阶矩(波动幅度)统计量,为每个参数动态适配更新步长,在梯度波动剧烈的维度收窄步长以抑制震荡,在梯度平缓的维度放大步长以加速收敛。
  3. 执行参数更新与状态维护:按照θ←θ+Δθ\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} + \Delta\boldsymbol{\theta}θθ+Δθ更新所有可学习参数的数值;同时清零参数的梯度缓存,避免迭代间梯度累加导致更新偏差,并更新优化算法所需的历史状态变量,供后续迭代使用。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐