2.5 优化器 (Optimizers) 2.5 优化器 (Optimizers) 2.5.1 优化器概述 深度学习模型的训练本质上是一个优化问题。我们的目标是找到一组模型参数,使得模型在给定任务上的表现最佳。这个“最佳表现”通常通过损失函数来衡量,损失函数值越小,代表模型预测结果与真实值之间的差距越小,模型性能越好。 优化器正是用于更新模型参数以最小化损失函数的算法。它基于梯度下降的思想,通过计算损失函数关于模型参数的梯度,并根据梯度信息调整参数,逐步逼近损失函数的最小值点。 梯度下降与优化器 梯度下降是优化器最核心的理论基础。简单来说,梯度指向损失函数增长最快的方向,而负梯度则指向损失函数下降最快的方向。
深度学习模型的训练本质上是一个优化问题。我们的目标是找到一组模型参数,使得模型在给定任务上的表现最佳。这个“最佳表现”通常通过损失函数来衡量,损失函数值越小,代表模型预测结果与真实值之间的差距越小,模型性能越好。
优化器正是用于更新模型参数以最小化损失函数的算法。它基于梯度下降的思想,通过计算损失函数关于模型参数的梯度,并根据梯度信息调整参数,逐步逼近损失函数的最小值点。
梯度下降与优化器
梯度下降是优化器最核心的理论基础。简单来说,梯度指向损失函数增长最快的方向,而负梯度则指向损失函数下降最快的方向。梯度下降算法的核心思想是沿着负梯度方向迭代更新参数,期望最终到达损失函数的局部最小值点或全局最小值点。
可以用下图来简单表示梯度下降的过程:
在上述流程中,优化器负责执行步骤 C 和 D,即计算梯度并更新参数。不同的优化器算法在梯度计算和参数更新策略上有所差异,从而影响模型的训练速度、收敛性以及最终性能。
PyTorch 提供了 torch.optim 模块,其中包含了各种常用的优化器算法,例如:
SGD (Stochastic Gradient Descent):随机梯度下降
Adam (Adaptive Moment Estimation):自适应矩估计
RMSprop (Root Mean Square Propagation):均方根传播
Adagrad (Adaptive Gradient Algorithm):自适应梯度算法
AdamW (Adam with Weight Decay):带有权重衰减的 Adam
ASGD (Averaged Stochastic Gradient Descent):平均随机梯度下降
LBFGS (Limited-memory Broyden-Fletcher-Goldfarb-Shanno):有限内存 BFGS
接下来我们将重点介绍 SGD、Adam 和 RMSprop 这三种最常用的优化器,并结合代码示例进行详细讲解。
SGD 是最基础也是最经典的优化器之一。它的核心思想是在每次迭代中,随机选取一个或一批样本(mini-batch)来计算梯度,并利用该梯度更新模型参数。
算法原理:
随机选取 mini-batch 数据 (从训练集中随机抽取一部分数据)
计算 mini-batch 损失函数 (基于选取的 mini-batch 数据计算损失值)
计算损失函数关于模型参数的梯度 (计算损失函数对模型参数的偏导数)
更新模型参数 (根据负梯度方向和学习率更新参数)
公式:
参数更新公式如下:
θ = θ - η * ∇L(θ; batch)
其中:
θ:模型参数
η:学习率 (learning rate),控制参数更新的步长
∇L(θ; batch):基于 mini-batch 数据计算的损失函数梯度
PyTorch 代码示例:
import torch import torch.nn as nn import torch.optim as optim # 1. 定义模型 class SimpleNet(nn.Module): def __init__(self): super(SimpleNet, self).__init__() self.fc = nn.Linear(10, 2) def forward(self, x): return self.fc(x) model = SimpleNet() # 2. 定义优化器 - SGD optimizer_sgd = optim.SGD(model.parameters(), lr=0.01) # lr 为学习率 # 3. 示例训练循环 (简化版) inputs = torch.randn(64, 10) # 模拟输入数据 targets = torch.randint(0, 2, (64,)) # 模拟目标数据 criterion = nn.CrossEntropyLoss() # 定义损失函数 optimizer_sgd.zero_grad() # 梯度清零 outputs = model(inputs) # 前向传播 loss = criterion(outputs, targets) # 计算损失 loss.backward() # 反向传播,计算梯度 optimizer_sgd.step() # 更新模型参数 print(f"Loss after SGD step: {loss.item():.4f}")
SGD 的参数:
lr (learning rate): 学习率,控制参数更新步长,是 SGD 最重要的超参数。
momentum (动量): 加速 SGD 在相关方向上的学习,并抑制振荡,有助于更快地收敛。
weight_decay (权重衰减): L2 正则化,防止过拟合。
dampening (阻尼): 用于动量的阻尼,通常不常用。
nesterov (Nesterov 动量): 一种改进的动量版本,可以提高收敛速度。
SGD 的优缺点:
优点:
简单有效: 算法简单,易于理解和实现。
计算效率高: 每次迭代计算量相对较小,尤其是在数据量较大时。
泛化性能好: 在某些情况下,SGD 的泛化性能优于更复杂的优化器。
缺点:
收敛速度慢: 在平坦区域或梯度变化剧烈的区域,收敛速度可能较慢。
容易陷入局部最小值: 对于非凸优化问题,SGD 可能陷入局部最小值而无法找到全局最优解。
对学习率敏感: 学习率的选择对 SGD 的性能影响很大,需要仔细调参。
适用场景:
适用于大规模数据集和计算资源有限的场景。
作为其他优化器的基准对比。
在模型结构较为简单,对收敛速度要求不高的情况下。
Mermaid 图示 - SGD 参数更新:
Adam 是一种自适应学习率优化器,它结合了 Momentum 和 RMSprop 的优点,能够根据参数的历史梯度信息动态调整学习率。Adam 广泛应用于各种深度学习任务,被认为是目前最流行的优化器之一。
算法原理:
Adam 算法维护了每个参数的两个移动平均值:
一阶矩估计 (first moment estimate) m_t: 类似于 Momentum 中的动量,是对梯度均值的估计。
二阶矩估计 (second moment estimate) v_t: 类似于 RMSprop 中的平方梯度均值,是对梯度方差的估计。
Adam 根据这两个矩估计值来调整每个参数的学习率。对于梯度变化平缓的参数,学习率会增大;对于梯度变化剧烈的参数,学习率会减小。
公式:
参数更新公式如下:
m_t = β_1 * m_{t-1} + (1 - β_1) * g_t v_t = β_2 * v_{t-1} + (1 - β_2) * g_t^2 m̂_t = m_t / (1 - β_1^t) v̂_t = v_t / (1 - β_2^t) θ_{t+1} = θ_t - η * m̂_t / (√v̂_t + ε)
其中:
g_t: 当前时刻 t 的梯度
m_t, v_t: 一阶矩和二阶矩的移动平均值
β_1, β_2: 矩估计的指数衰减率 (通常设置为接近 1 的值,如 0.9 和 0.999)
η: 学习率
ε: 防止分母为零的小常数 (通常设置为 1e-8)
m̂_t, v̂_t: 偏差校正后的一阶矩和二阶矩估计
PyTorch 代码示例:
# 1. 模型已定义 (SimpleNet) # 2. 定义优化器 - Adam optimizer_adam = optim.Adam(model.parameters(), lr=0.001) # lr 为学习率 # 3. 示例训练循环 (简化版) - 与 SGD 示例相同,只需替换优化器 inputs = torch.randn(64, 10) targets = torch.randint(0, 2, (64,)) criterion = nn.CrossEntropyLoss() optimizer_adam.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer_adam.step() print(f"Loss after Adam step: {loss.item():.4f}")
Adam 的参数:
lr (learning rate): 学习率,通常比 SGD 小,如 0.001 或 0.0001。
betas (β_1, β_2): 一阶矩和二阶矩估计的指数衰减率,默认值分别为 (0.9, 0.999)。
eps (ε): 防止分母为零的小常数,默认值为 1e-8。
weight_decay (权重衰减): L2 正则化,防止过拟合。
amsgrad (AMSGrad): 是否使用 AMSGrad 变体,AMSGrad 可以提高 Adam 在某些情况下的收敛性。
Adam 的优缺点:
优点:
自适应学习率: 能够自动调整每个参数的学习率,无需手动精细调整。
收敛速度快: 通常比 SGD 收敛速度更快。
效果稳定: 对超参数不敏感,鲁棒性较好。
适用于各种场景: 在各种深度学习任务中表现良好。
缺点:
可能泛化性稍差: 在某些情况下,Adam 的泛化性能可能不如 SGD。
需要更多内存: 需要维护一阶矩和二阶矩估计,占用更多内存。
适用场景:
几乎适用于所有深度学习任务,尤其是模型结构复杂、数据量较大的场景。
作为默认优化器的首选。
在需要快速收敛和对超参数不敏感的场景。
Mermaid 图示 - Adam 参数更新:
RMSprop 也是一种自适应学习率优化器,与 Adam 类似,它也能够根据参数的历史梯度信息动态调整学习率。RMSprop 主要解决了 Adagrad 优化器学习率下降过快的问题,在实践中也表现出良好的性能。
算法原理:
RMSprop 算法维护了每个参数的平方梯度移动平均值,并用该平均值来调整学习率。
公式:
参数更新公式如下:
s_t = ρ * s_{t-1} + (1 - ρ) * g_t^2 θ_{t+1} = θ_t - η / (√s_t + ε) * g_t
其中:
g_t: 当前时刻 t 的梯度
s_t: 平方梯度移动平均值
ρ: 平方梯度移动平均的指数衰减率 (通常设置为接近 1 的值,如 0.9)
η: 学习率
ε: 防止分母为零的小常数 (通常设置为 1e-8)
PyTorch 代码示例:
# 1. 模型已定义 (SimpleNet) # 2. 定义优化器 - RMSprop optimizer_rmsprop = optim.RMSprop(model.parameters(), lr=0.001) # lr 为学习率 # 3. 示例训练循环 (简化版) - 与 SGD 和 Adam 示例相同,只需替换优化器 inputs = torch.randn(64, 10) targets = torch.randint(0, 2, (64,)) criterion = nn.CrossEntropyLoss() optimizer_rmsprop.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer_rmsprop.step() print(f"Loss after RMSprop step: {loss.item():.4f}")
RMSprop 的参数:
lr (learning rate): 学习率,通常与 Adam 类似,如 0.001 或 0.0001。
alpha (ρ): 平方梯度移动平均的指数衰减率,默认值为 0.99。
eps (ε): 防止分母为零的小常数,默认值为 1e-8。
weight_decay (权重衰减): L2 正则化,防止过拟合。
momentum (动量): 可以添加动量项,进一步加速收敛。
centered (Centered RMSprop): 是否使用 Centered RMSprop 变体,Centered RMSprop 可以提高 RMSprop 在某些情况下的性能。
RMSprop 的优缺点:
优点:
自适应学习率: 能够自动调整每个参数的学习率。
收敛速度快: 通常比 SGD 收敛速度更快。
解决了 Adagrad 学习率下降过快的问题: 通过指数衰减平均平方梯度,避免学习率过早降为零。
效果稳定: 对超参数不敏感,鲁棒性较好。
缺点:
可能泛化性稍差: 在某些情况下,RMSprop 的泛化性能可能不如 SGD。
参数较多: 相比 SGD,RMSprop 的参数更多,需要更多内存。
适用场景:
适用于各种深度学习任务,尤其是在循环神经网络 (RNN) 等模型中表现良好。
作为 Adam 的替代选择。
在需要快速收敛和对超参数不敏感的场景。
Mermaid 图示 - RMSprop 参数更新:
不同的优化器有不同的参数,但一些核心参数是通用的,理解这些参数对于有效使用优化器至关重要。
学习率 (Learning Rate, lr): 学习率是最重要的超参数之一,它控制着参数更新的步长。学习率过大可能导致模型震荡,难以收敛;学习率过小则可能导致收敛速度过慢。通常需要根据具体任务和优化器类型进行调整。
动量 (Momentum): 动量用于加速 SGD 在相关方向上的学习,并抑制振荡。它通过累积之前的梯度信息,使得参数更新更加平滑和稳定。动量值通常设置为 0.9 或 0.99。
权重衰减 (Weight Decay): 权重衰减是一种正则化技术,也称为 L2 正则化。它通过在损失函数中添加模型参数的平方和项,惩罚模型参数过大,从而防止过拟合。权重衰减值通常设置为 0.0001 或 0.0005。
β_1, β_2 (Adam 参数): Adam 优化器中用于控制一阶矩和二阶矩估计的指数衰减率。默认值分别为 0.9 和 0.999。
ρ (RMSprop 参数): RMSprop 优化器中用于控制平方梯度移动平均的指数衰减率。默认值为 0.99。
ε (Epsilon): 一个很小的常数,用于防止在除法运算中分母为零,通常设置为 1e-8。
在 PyTorch 中使用优化器非常简单,主要包括以下几个步骤:
初始化优化器: 根据选择的优化器类型,实例化相应的优化器对象,并将模型参数和优化器超参数传入。
梯度清零 (optimizer.zero_grad()): 在每次迭代开始前,需要将优化器的梯度缓冲区清零,防止梯度累积。
反向传播 (loss.backward()): 计算损失函数关于模型参数的梯度。
参数更新 (optimizer.step()): 根据优化器算法,利用梯度信息更新模型参数。
学习率调整 (可选): 根据训练过程中的情况,动态调整学习率,例如使用学习率衰减策略。
代码示例 - 完整的训练循环 (包含优化器):
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset # 1. 定义模型 (SimpleNet - 已定义) # 2. 准备数据 (示例数据) X_train = torch.randn(1000, 10) y_train = torch.randint(0, 2, (1000,)) train_dataset = TensorDataset(X_train, y_train) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) # 3. 定义损失函数 criterion = nn.CrossEntropyLoss() # 4. 定义优化器 (Adam) optimizer = optim.Adam(model.parameters(), lr=0.001) # 5. 训练循环 epochs = 10 for epoch in range(epochs): for inputs, targets in train_loader: optimizer.zero_grad() # 梯度清零 outputs = model(inputs) # 前向传播 loss = criterion(outputs, targets) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 参数更新 print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}") print("Training finished!")
学习率调整 (Learning Rate Scheduling)
学习率调整是一种常用的优化技巧,可以在训练过程中动态调整学习率,以提高模型性能和收敛速度。PyTorch 提供了 torch.optim.lr_scheduler 模块,包含多种学习率调整策略,例如:
StepLR: 每隔一定步数将学习率乘以一个衰减系数。
MultiStepLR: 在指定的步数将学习率乘以一个衰减系数。
ExponentialLR: 每个 epoch 将学习率乘以一个衰减系数。
CosineAnnealingLR: 使用余弦函数周期性地调整学习率。
ReduceLROnPlateau: 当验证集损失不再下降时,降低学习率。
示例 - 使用 StepLR 学习率衰减:
# ... (模型, 数据, 损失函数, 优化器 定义) ... # 定义学习率调度器 - StepLR (每 3 个 epoch 学习率乘以 0.1) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1) # 训练循环 epochs = 10 for epoch in range(epochs): for inputs, targets in train_loader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() scheduler.step() # 在每个 epoch 结束时更新学习率 print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, LR: {optimizer.param_groups[0]['lr']:.6f}") print("Training finished!")
保存和加载优化器状态
与模型参数类似,优化器的状态 (例如动量缓冲区、矩估计等) 也需要保存和加载,以便在恢复训练或进行模型部署时使用。PyTorch 提供了 optimizer.state_dict() 和 optimizer.load_state_dict() 方法来实现优化器状态的保存和加载。
# 保存优化器状态 torch.save(optimizer.state_dict(), 'optimizer.pth') # 加载优化器状态 optimizer.load_state_dict(torch.load('optimizer.pth'))
如何选择合适的优化器:
没有一个优化器能够适用于所有任务。选择合适的优化器需要根据具体任务、模型结构和数据集特点进行尝试和选择。
Adam: 通常是首选的默认优化器,适用于大多数深度学习任务,尤其是模型结构复杂、数据量较大的场景。
SGD: 在数据量较小或模型结构简单的情况下,SGD 可能表现良好,并且泛化性能可能更优。如果计算资源有限,SGD 也是一个不错的选择。
RMSprop: 在循环神经网络 (RNN) 等模型中表现良好,可以作为 Adam 的替代选择。
优化器超参数调优:
优化器的超参数 (例如学习率、动量、权重衰减等) 对模型性能影响很大,需要进行调优。常用的调优方法包括:
手动调参: 根据经验和实验结果,手动调整超参数。
网格搜索 (Grid Search): 在预定义的超参数网格中,遍历所有可能的组合,并选择性能最佳的组合。
随机搜索 (Random Search): 在超参数空间中随机采样一定数量的组合,并选择性能最佳的组合。
贝叶斯优化 (Bayesian Optimization): 使用贝叶斯方法对超参数空间进行建模,并选择下一个最有希望的超参数组合进行尝试。
常见问题与解决方案:
训练不收敛或收敛速度慢: 可能是学习率过大或过小,可以尝试调整学习率。也可以尝试更换优化器,例如从 SGD 切换到 Adam 或 RMSprop。
模型过拟合: 可以尝试增加权重衰减 (weight decay) 值,或者使用更强的正则化方法。
梯度消失或梯度爆炸: 可能是模型结构或激活函数选择不当,可以尝试调整模型结构或使用 ReLU 等激活函数。也可以尝试使用梯度裁剪 (gradient clipping) 技术来缓解梯度爆炸问题。
优化器是深度学习模型训练的核心组件之一。理解不同优化器的原理、特点和适用场景,并掌握优化器的使用方法和调优技巧,对于构建高性能的深度学习模型至关重要。在 PyTorch 中,torch.optim 模块提供了丰富的优化器选择,结合学习率调整和超参数调优等技巧,可以有效地训练各种复杂的深度学习模型。希望本节的详细介绍能够帮助读者更好地理解和应用 PyTorch 中的优化器,提升模型构建能力。