摘要优化器是深度学习模型训练的核心组件其选择直接影响模型的收敛速度、最终性能以及训练稳定性。本文系统梳理了从经典SGD到现代Adam系列的主流优化算法深入剖析各优化器的数学原理、优劣势及适用场景。通过PyTorch实现完整对比实验涵盖SGD、Adam、AdamW、RMSProp、AdaGrad等核心优化器以及学习率调度策略。实验表明不同优化器在收敛速度、泛化能力等方面存在显著差异实际应用中需根据任务特点进行合理选择。关键词深度学习优化器AdamSGD学习率调度PyTorch1. 引言深度学习的训练过程本质上是一个优化问题我们希望通过最小化损失函数来学习模型的参数。优化器Optimizer正是解决这一问题的核心算法。从1952年Robert Widrow提出的最小均方算法LMS开始优化算法经历了数十年的发展演进。从早期的批量梯度下降BGD到随机梯度下降SGD再到自适应学习率算法如Adam、RMSProp每一次算法革新都推动了深度学习在更大规模、更高复杂度任务上的突破。本文将深入剖析深度学习中主流优化器的数学原理与工程实践帮助读者建立完整的优化器知识体系。2. 优化问题基础2.1 损失函数的Landscape深度学习模型的损失函数通常是一个高维非凸函数。以一个简单的二维函数为例其landscape可能包含多种地形特征/\ / \ 局部最小值 / \ /\ / \ / \ / /\ \ / \ / / \ \ / \ ----/---/----\---X--------\---- 参数空间 \ / 鞍点 \ / \ / \/ 全局最小值三种关键地形局部最小值Local Minimum损失函数在某一小区域内达到最低点但在全局并非最低。传统观点曾认为局部最小值是训练的主要障碍但近年研究表明高维空间中的局部最小值往往具有接近全局最优的损失值。鞍点Saddle Point损失函数在某些方向上是局部最小在另一些方向上是局部最大。鞍点附近梯度接近零导致传统梯度下降算法容易停滞是训练中的主要挑战之一。梯度消失与梯度爆炸当网络层数加深时梯度在反向传播过程中可能指数级衰减梯度消失或指数级放大梯度爆炸严重影响深层网络的训练。2.2 优化器的核心作用优化器的核心任务是在参数空间中寻找损失函数的极小值点。具体而言给定损失函数 $J(\theta)$优化器通过以下迭代公式更新参数$$\theta_{t1} \theta_t - \alpha \cdot \nabla J(\theta_t)$$其中 $\alpha$ 是学习率$\nabla J(\theta_t)$ 是损失函数在当前参数处的梯度。优化器的改进主要体现在三个方面加速收敛更快地到达极小值区域逃离鞍点更好地处理高维非凸 landscape提高泛化找到的解具有更好的泛化能力3. 随机梯度下降SGD3.1 基本原理SGDStochastic Gradient Descent是深度学习中最基础也是最重要的优化算法。与批量梯度下降不同SGD每次只使用一个样本或一个小批量样本来计算梯度这使得它在处理大规模数据集时具有显著的计算优势。核心公式$$\theta \theta - \eta \cdot \nabla_\theta J(\theta; x^{(i)}, y^{(i)})$$其中$\theta$ 是模型参数$\eta$ 是学习率learning rate$\nabla_\theta J(\theta; x^{(i)}, y^{(i)})$ 是第 $i$ 个样本的损失函数梯度3.2 学习率的影响学习率是SGD中最关键的超参数其选择直接影响训练效果import numpy as np import matplotlib.pyplot as plt ​ def sgd_convergence(grad_func, lr, n_steps): 模拟SGD收敛过程 theta 5.0 # 初始参数值 trajectory [theta] for _ in range(n_steps): gradient grad_func(theta) # 模拟的梯度 theta theta - lr * gradient trajectory.append(theta) return trajectory ​ # 假设的凸函数 f(x) x^2其梯度为 2x grad lambda x: 2 * x ​ # 不同学习率的收敛情况 fig, axes plt.subplots(1, 3, figsize(15, 4)) ​ for ax, lr, title in zip(axes, [0.01, 0.3, 0.8], [学习率过小 (0.01), 学习率适中 (0.3), 学习率过大 (0.8)]): trajectory sgd_convergence(grad, lr, 20) ax.plot(trajectory, b-, linewidth2) ax.axhline(y0, colorr, linestyle--, label最优解) ax.set_title(title) ax.set_xlabel(迭代次数) ax.set_ylabel(参数值) ax.legend() ax.grid(True) ​ plt.tight_layout() plt.savefig(sgd_lr_effect.png, dpi150) plt.show()学习率选择建议学习率过小收敛速度极慢容易陷入局部最优学习率适中稳定收敛到全局最优学习率过大无法收敛在最优点附近震荡甚至发散3.3 动量Momentum标准SGD在陡峭方向上震荡剧烈而在平缓方向上进展缓慢。动量机制通过累积历史梯度信息来加速收敛、抑制震荡。动量更新公式$$v_t \beta \cdot v{t-1} (1 - \beta) \cdot \nabla\theta J(\theta)$$ $$\theta \theta - \eta \cdot v_t$$其中 $v_t$ 是速度项$\beta \in [0, 1)$ 是动量系数通常取0.9。import torch import torch.nn as nn import matplotlib.pyplot as plt ​ def momentum_sgd(grad_func, theta, lr, beta, n_steps): 模拟带动量的SGD grad_func: 梯度函数 theta: 初始参数 lr: 学习率 beta: 动量系数 v 0.0 # 初始速度 trajectory [theta] for _ in range(n_steps): gradient grad_func(theta) v beta * v (1 - beta) * gradient # 动量更新 theta theta - lr * v trajectory.append(theta) return trajectory ​ # Rosenbrock函数经典的非凸测试函数 # f(x, y) (1-x)^2 100(y-x^2)^2, 梯度复杂存在狭长山谷 ​ def rosenbrock_grad(theta): x, y theta[0], theta[1] dx -2*(1-x) - 400*x*(y-x**2) dy 200*(y - x**2) return np.array([dx, dy]) ​ # 初始点 theta0 np.array([-1.5, 1.5]) ​ # 对比有无动量 traj_no_momentum momentum_sgd(rosenbrock_grad, theta0.copy(), 0.0005, 0.0, 1000) traj_with_momentum momentum_sgd(rosenbrock_grad, theta0.copy(), 0.0005, 0.9, 1000) ​ print(f无动量: 最终位置 {traj_no_momentum[-1]}, 损失 {rosenbrock_loss(traj_no_momentum[-1]):.4f}) print(f有动量: 最终位置 {traj_with_momentum[-1]}, 损失 {rosenbrock_loss(traj_with_momentum[-1]):.4f})3.4 Nesterov动量Nesterov Accelerated GradientNAG是动量算法的一种改进版本。与标准动量不同NAG先根据历史速度做一个预测性跳跃然后再计算梯度进行校正。Nesterov动量公式$$v_t \beta \cdot v{t-1} (1 - \beta) \cdot \nabla\theta J(\theta - \eta \cdot \beta \cdot v_{t-1})$$ $$\theta \theta - \eta \cdot v_t$$def nesterov_momentum(grad_func, theta, lr, beta, n_steps): Nesterov动量SGD 关键区别先预测后校正 v np.zeros_like(theta, dtypefloat) trajectory [theta.copy()] for _ in range(n_steps): # 预测步骤沿着历史速度方向前移 theta_predict theta - lr * beta * v # 校正步骤在预测位置计算梯度 gradient grad_func(theta_predict) # 更新速度 v beta * v (1 - beta) * gradient # 更新参数 theta theta - lr * v trajectory.append(theta.copy()) return trajectoryNesterov vs 标准动量的核心差异特性标准动量Nesterov动量梯度计算位置当前参数 $\theta_t$预测位置 $\theta_t - \eta \cdot \beta \cdot v_t$动量累积方向历史速度方向历史速度方向 校正方向收敛速度较快更稳定、更快适用场景一般深度学习任务需要更稳定收敛的场景4. 自适应学习率优化器固定学习率的主要问题在于不同参数需要不同的学习率。例如频繁更新的参数可能需要更小的学习率稀疏特征对应的参数可能需要更大的学习率自适应学习率优化器通过自动调节每个参数的学习率来解决这一问题。4.1 AdaGradAdaGradAdaptive Gradient Algorithm通过记录每个参数历史梯度的平方和来实现自适应学习率。核心公式$$g_t \nabla\theta J(\theta_t) \quad \text{当前梯度}$$ $$r_t r{t-1} g_t \odot g_t \quad \text{累积平方梯度}$$ $$\theta_{t1} \theta_t - \frac{\eta}{\sqrt{r_t} \delta} \odot g_t$$其中 $\odot$ 表示逐元素乘法$\delta$ 通常取 $10^{-8}$ 防止除零。class AdaGrad: AdaGrad优化器实现 优点对稀疏数据友好 缺点学习率会单调递减后期可能过早收敛 def __init__(self, params, lr1.0, eps1e-8): self.params list(params) self.lr lr self.eps eps self.state {} # 存储累积平方梯度 def step(self): for param in self.params: if param.grad is None: continue grad param.grad.data # 初始化状态 if param not in self.state: self.state[param] torch.zeros_like(param.data) # 累积平方梯度 self.state[param] grad.pow(2) # 自适应学习率更新 # 学习率 lr / sqrt(累积平方和) adaptive_lr self.lr / (self.state[param].sqrt() self.eps) param.data - adaptive_lr * grad def zero_grad(self): for param in self.params: if param.grad is not None: param.grad.zero_()适用场景文本处理、词嵌入训练等存在大量稀疏特征的任务。4.2 RMSPropRMSPropRoot Mean Square Propagation由Geoff Hinton提出是对AdaGrad的改进。AdaGrad的致命问题在于学习率单调递减RMSProp通过引入指数移动平均来解决。核心公式$$E[g^2]t \beta \cdot E[g^2]{t-1} (1-\beta) \cdot g_t^2$$ $$\theta{t1} \theta_t - \frac{\eta}{\sqrt{E[g^2]t} \delta} \cdot g_t$$其中 $\beta$ 通常取0.9。class RMSProp: RMSProp优化器 使用指数移动平均代替直接累加避免学习率过快下降 def __init__(self, params, lr1e-3, alpha0.99, eps1e-8, weight_decay0): self.params list(params) self.lr lr self.alpha alpha # 指数移动平均系数 self.eps eps self.weight_decay weight_decay self.state {} # 存储指数移动平均的平方梯度 def step(self): for param in self.params: if param.grad is None: continue grad param.grad.data # L2正则化可选 if self.weight_decay ! 0: grad grad self.weight_decay * param.data # 初始化或更新指数移动平均 if param not in self.state: self.state[param] torch.zeros_like(param.data) self.state[param] self.alpha * self.state[param] (1 - self.alpha) * grad.pow(2) # 自适应学习率更新 adaptive_lr self.lr / (self.state[param].sqrt() self.eps) param.data - adaptive_lr * grad def zero_grad(self): for param in self.params: if param.grad is not None: param.grad.zero_()4.3 AdaDeltaAdaDelta是RMSProp的进一步改进其核心创新在于不需要人工设置全局学习率而是通过参数更新的二阶近似来自动计算学习率。核心公式$$E[g^2]t \beta \cdot E[g^2]{t-1} (1-\beta) \cdot g_t^2$$ $$\Delta\theta_t -\frac{\sqrt{E[\Delta\theta^2]{t-1} \epsilon}}{\sqrt{E[g^2]t \epsilon}} \cdot g_t$$ $$E[\Delta\theta^2]t \beta \cdot E[\Delta\theta^2]{t-1} (1-\beta) \cdot \Delta\theta_t^2$$class AdaDelta: AdaDelta优化器 不需要手动设置学习率通过累积参数更新的移动平均来自适应调节 def __init__(self, params, rho0.9, eps1e-6): self.params list(params) self.rho rho self.eps eps self.state {} # 存储EG和Edtheta def step(self): for param in self.params: if param.grad is None: continue grad param.grad.data # 初始化状态 if param not in self.state: self.state[param] { EG: torch.zeros_like(param.data), # 梯度平方的指数移动平均 Edtheta: torch.zeros_like(param.data) # 参数更新的指数移动平均 } state self.state[param] # 更新梯度平方的移动平均 state[EG] self.rho * state[EG] (1 - self.rho) * grad.pow(2) # 计算自适应学习率 # 注意Edtheta开方后作为学习率的分子 delta (state[Edtheta].sqrt() self.eps) / (state[EG].sqrt() self.eps) * grad # 更新参数 param.data - delta # 更新参数更新平方的移动平均 state[Edtheta] self.rho * state[Edtheta] (1 - self.rho) * delta.pow(2) def zero_grad(self): for param in self.params: if param.grad is not None: param.grad.zero_()5. Adam优化器AdamAdaptive Moment Estimation是目前最广泛使用的优化器它结合了动量法和RMSProp的优点同时引入了偏置修正机制。5.1 核心原理Adam维护两个指数移动平均一阶矩估计$m_t$类似动量估计梯度的一阶矩即梯度的均值二阶矩估计$v_t$类似RMSProp估计梯度的二阶矩即梯度的方差完整算法流程输入学习率 η矩估计衰减系数 β1, β2数值稳定常数 δ 初始化θ0, m00, v00, t0 ​ while 未收敛 do t t 1 # 计算梯度 g_t ∇θJ(θ_{t-1}) # 更新一阶矩估计动量 m_t β1 · m_{t-1} (1-β1) · g_t # 更新二阶矩估计RMSProp 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 θ_{t-1} - η · m̂_t / (√v̂_t δ) end while5.2 偏置修正Bias Correction为什么Adam需要偏置修正让我们分析一下初始化时 $m_0 0, v_0 0$在初期迭代中一阶矩$m_1 \beta_1 \cdot 0 (1-\beta_1) \cdot g_1 (1-\beta_1) \cdot g_1$由于 $\beta_1^t$ 接近0$m_1$ 被严重低估偏置修正通过除以 $(1 - \beta^t)$ 来校正这种低估确保早期估计的准确性。import torch import torch.nn as nn ​ class Adam: Adam优化器完整实现 结合了动量法和RMSProp的优点 def __init__(self, params, lr1e-3, beta10.9, beta20.999, eps1e-8, weight_decay0): self.params list(params) self.lr lr self.beta1 beta1 self.beta2 beta2 self.eps eps self.weight_decay weight_decay self.t 0 # 迭代计数器 self.state {} # 存储m和v def step(self): self.t 1 for param in self.params: if param.grad is None: continue grad param.grad.data # L2正则化 if self.weight_decay ! 0: grad grad self.weight_decay * param.data # 初始化或更新状态 if param not in self.state: self.state[param] { m: torch.zeros_like(param.data), v: torch.zeros_like(param.data) } state self.state[param] # 更新一阶矩估计动量 state[m] self.beta1 * state[m] (1 - self.beta1) * grad # 更新二阶矩估计 state[v] self.beta2 * state[v] (1 - self.beta2) * grad.pow(2) # 偏置修正 m_hat state[m] / (1 - self.beta1 ** self.t) v_hat state[v] / (1 - self.beta2 ** self.t) # 参数更新 param.data - self.lr * m_hat / (v_hat.sqrt() self.eps) def zero_grad(self): for param in self.params: if param.grad is not None: param.grad.zero_()5.3 PyTorch内置Adam使用示例import torch import torch.nn as nn import torch.optim as optim ​ # 定义模型 model nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.2), nn.Linear(128, 10) ) ​ # 创建Adam优化器 optimizer optim.Adam( model.parameters(), lr1e-3, # 学习率默认1e-3 betas(0.9, 0.999), # 一阶、二阶矩估计衰减系数 eps1e-8, # 数值稳定常数 weight_decay0, # 权重衰减L2正则化 amsgradFalse # 是否使用AMSGrad变体 ) ​ # 训练循环示例 for epoch in range(10): for batch_data, batch_labels in train_loader: optimizer.zero_grad() # 清零梯度 outputs model(batch_data) # 前向传播 loss criterion(outputs, batch_labels) loss.backward() # 反向传播 optimizer.step() # 更新参数6. AdamW优化器AdamWAdam with Weight Decay是Adam的改进版本解决了Adam中L2正则化与权重衰减不等价的问题。6.1 Adam与L2正则化的问题在标准Adam中L2正则化通过在梯度中添加 $\lambda \cdot \theta$ 来实现$$\theta_{t1} \theta_t - \eta \cdot \left( \frac{m_t}{1-\beta_1^t} / \sqrt{\frac{v_t}{1-\beta_2^t} \epsilon} \lambda \cdot \theta_t \right)$$问题在于Adam的自适应学习率会抵消L2正则化的效果使得正则化强度不可预测。6.2 AdamW的正确权重衰减AdamW将权重衰减与梯度解耦在参数更新时直接应用衰减class AdamW: AdamW Adam 正确的权重衰减 权重衰减不再通过梯度实现而是直接作用于参数 def __init__(self, params, lr1e-3, beta10.9, beta20.999, eps1e-8, weight_decay0.01): self.params list(params) self.lr lr self.beta1 beta1 self.beta2 beta2 self.eps eps self.weight_decay weight_decay self.t 0 self.state {} def step(self): self.t 1 for param in self.params: if param.grad is None: continue grad param.grad.data # 初始化状态 if param not in self.state: self.state[param] { m: torch.zeros_like(param.data), v: torch.zeros_like(param.data) } state self.state[param] # 更新一阶、二阶矩估计 state[m] self.beta1 * state[m] (1 - self.beta1) * grad state[v] self.beta2 * state[v] (1 - self.beta2) * grad.pow(2) # 偏置修正 m_hat state[m] / (1 - self.beta1 ** self.t) v_hat state[v] / (1 - self.beta2 ** self.t) # 关键区别权重衰减直接作用于参数而非梯度 # 先对参数进行衰减 param.data param.data * (1 - self.lr * self.weight_decay) # 再应用Adam更新 param.data - self.lr * m_hat / (v_hat.sqrt() self.eps) def zero_grad(self): for param in self.params: if param.grad is not None: param.grad.zero_()使用建议在Transformer架构BERT、GPT等的预训练中AdamW是标准配置通常配合 weight_decay0.01 使用。7. L-BFGS优化器L-BFGSLimited-memory Broyden-Fletcher-Goldfarb-Shanno是一种二阶优化算法通过存储有限的历史信息来近似计算 Hessian 矩阵的逆。特点收敛速度通常比一阶方法快内存占用 $O(m \cdot n)$其中 $m$ 是历史步数$n$ 是参数维度适合小规模数据集和参数较少的场景不适合大规模深度学习但可用于逻辑回归等传统ML任务# PyTorch中使用L-BFGS optimizer optim.LBFGS( model.parameters(), lr0.1, max_iter20, # 最大迭代次数 max_evalNone, # 最大函数评估次数 tolerance_grad1e-7, # 梯度容忍度 tolerance_change1e-9, # 参数变化容忍度 history_size100 # 历史步数内存占用 ) ​ def closure(): optimizer.zero_grad() output model(input) loss criterion(output, target) loss.backward() return loss ​ optimizer.step(closure)8. 学习率调度策略学习率是训练过程中最关键的超参数。学习率调度Learning Rate Scheduling允许学习率在训练过程中动态调整通常能够显著提升训练效果。8.1 Step LR阶梯学习率衰减每经过固定 epoch 数将学习率按固定比例衰减。import torch.optim as optim ​ # 每30个epoch将学习率降低为原来的10% scheduler optim.lr_scheduler.StepLR( optimizer, step_size30, # 衰减周期 gamma0.1 # 衰减系数 ) ​ for epoch in range(100): train(...) validate(...) scheduler.step() # 更新学习率8.2 Cosine Annealing余弦退火使用余弦函数周期性地降低学习率从最大值缓慢降到最小值。# Cosine Annealing学习率调度 scheduler optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max100, # 一个周期的最大迭代数 eta_min1e-6 # 最小学习率 )# Cosine Annealing with Warm Restarts scheduler optim.lr_scheduler.CosineAnnealingWarmRestarts( optimizer, T_020, # 第一个周期的长度 T_mult2, # 周期倍增因子 eta_min1e-6 )8.3 ReduceLROnPlateau早停式学习率衰减当监控指标如验证集损失不再改善时自动降低学习率。scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, # min表示监控指标降低时触发 factor0.1, # 学习率衰减比例 patience10, # 容忍epoch数 threshold0.0001, # 改善阈值 min_lr1e-7, # 最低学习率 verboseTrue # 打印学习率变化信息 ) ​ for epoch in range(100): train_loss train(...) val_loss validate(...) scheduler.step(val_loss) # 传入监控指标8.4 Warmup学习率预热训练初期从一个很小的学习率逐渐增加到目标学习率有助于稳定训练初期。class WarmupScheduler: 学习率预热调度器 先线性warmup再接其他调度器 def __init__(self, optimizer, warmup_epochs, base_schedulerNone): self.optimizer optimizer self.warmup_epochs warmup_epochs self.base_scheduler base_scheduler self.current_epoch 0 self.base_lrs [group[lr] for group in optimizer.param_groups] def step(self): if self.current_epoch self.warmup_epochs: # Warmup阶段线性增加学习率 factor (self.current_epoch 1) / self.warmup_epochs for param_group, base_lr in zip(self.optimizer.param_groups, self.base_lrs): param_group[lr] base_lr * factor elif self.base_scheduler: # Warmup完成后使用基础调度器 self.base_scheduler.step() self.current_epoch 1 ​ # 使用示例 base_scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max90) scheduler WarmupScheduler(optimizer, warmup_epochs5, base_schedulerbase_scheduler)9. 使用场景选择指南根据任务特点选择合适的优化器是工程实践中的重要能力。以下是各优化器的适用场景总结优化器适用场景不适用场景备注SGD MomentumCV任务ResNet等、需要最佳泛化性能超参数调优困难收敛慢但泛化好Adam默认首选、NLP任务、快速原型开发需要最佳泛化性能收敛快但泛化略差AdamWTransformer系列模型、预训练小数据集标准配置weight_decay0.01RMSPropRNNs、在线学习、稀疏数据-非凸问题表现好AdaGrad极度稀疏数据、文本处理深度学习学习率单调下降AdaDelta不想手动设置学习率-自适应能力强**L-BFGS小规模数据、逻辑回归、传统ML大规模深度学习内存占用高选择建议快速原型与探索阶段优先使用 Adam学习率默认 1e-3追求最佳性能CV尝试 SGD Momentum (lr0.01, momentum0.9) Cosine AnnealingTransformer模型AdamW (lr1e-4, weight_decay0.01)稀疏特征明显RMSProp 或 AdaGrad不稳定训练加入 Warmup 或使用 ReduceLROnPlateau10. PyTorch实战各优化器对比实验以下代码在MNIST数据集上对比各优化器的训练效果import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset import matplotlib.pyplot as plt import numpy as np ​ # 超参数设置 BATCH_SIZE 256 EPOCHS 20 LEARNING_RATE 1e-3 ​ # 检查设备 device torch.device(cuda if torch.cuda.is_available() else cpu) print(f使用设备: {device}) ​ # 1. 准备数据 # 使用PyTorch内置MNIST数据集 from torchvision import datasets, transforms ​ transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) ​ train_dataset datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform ) test_dataset datasets.MNIST( root./data, trainFalse, downloadTrue, transformtransform ) ​ train_loader DataLoader(train_dataset, batch_sizeBATCH_SIZE, shuffleTrue) test_loader DataLoader(test_dataset, batch_sizeBATCH_SIZE) ​ # 2. 定义模型 class SimpleNet(nn.Module): 简单的多层感知机 def __init__(self): super(SimpleNet, self).__init__() self.features nn.Sequential( nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, 128), nn.ReLU(), nn.Dropout(0.2), nn.Linear(128, 10) ) def forward(self, x): return self.features(x) ​ # 3. 训练函数 def train_model(model, optimizer, schedulerNone, epochsEPOCHS): 训练模型并返回训练历史 criterion nn.CrossEntropyLoss() history { train_loss: [], test_loss: [], train_acc: [], test_acc: [] } for epoch in range(epochs): # 训练阶段 model.train() train_loss 0.0 train_correct 0 train_total 0 for batch_x, batch_y in train_loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) optimizer.zero_grad() outputs model(batch_x) loss criterion(outputs, batch_y) loss.backward() optimizer.step() train_loss loss.item() _, predicted outputs.max(1) train_total batch_y.size(0) train_correct predicted.eq(batch_y).sum().item() # 更新学习率 if scheduler is not None: scheduler.step() # 记录训练指标 history[train_loss].append(train_loss / len(train_loader)) history[train_acc].append(100. * train_correct / train_total) # 测试阶段 model.eval() test_loss 0.0 test_correct 0 test_total 0 with torch.no_grad(): for batch_x, batch_y in test_loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) outputs model(batch_x) loss criterion(outputs, batch_y) test_loss loss.item() _, predicted outputs.max(1) test_total batch_y.size(0) test_correct predicted.eq(batch_y).sum().item() history[test_loss].append(test_loss / len(test_loader)) history[test_acc].append(100. * test_correct / test_total) print(fEpoch {epoch1:2d}/{EPOCHS} | fTrain Loss: {history[train_loss][-1]:.4f} | fTrain Acc: {history[train_acc][-1]:.2f}% | fTest Loss: {history[test_loss][-1]:.4f} | fTest Acc: {history[test_acc][-1]:.2f}%) return history ​ # 4. 定义要对比的优化器 optimizers_config { SGD (lr0.01, momentum0.9): { optimizer: lambda params: optim.SGD(params, lr0.01, momentum0.9), scheduler: lambda opt: optim.lr_scheduler.CosineAnnealingLR(opt, T_maxEPOCHS) }, SGD (lr0.1, momentum0.9): { optimizer: lambda params: optim.SGD(params, lr0.1, momentum0.9), scheduler: lambda opt: optim.lr_scheduler.CosineAnnealingLR(opt, T_maxEPOCHS) }, Adam (lr1e-3): { optimizer: lambda params: optim.Adam(params, lr1e-3), scheduler: None }, Adam (lr1e-4): { optimizer: lambda params: optim.Adam(params, lr1e-4), scheduler: None }, AdamW (lr1e-4, wd0.01): { optimizer: lambda params: optim.AdamW(params, lr1e-4, weight_decay0.01), scheduler: None }, RMSProp (lr1e-3): { optimizer: lambda params: optim.RMSprop(params, lr1e-3, alpha0.99), scheduler: None }, AdaGrad (lr0.01): { optimizer: lambda params: optim.Adagrad(params, lr0.01), scheduler: None } } ​ # 5. 运行对比实验 results {} ​ for name, config in optimizers_config.items(): print(f\n{*60}) print(f训练优化器: {name}) print(*60) # 创建新模型 model SimpleNet().to(device) # 创建优化器 optimizer config[optimizer](model.parameters()) # 创建学习率调度器 scheduler config[scheduler](optimizer) if config[scheduler] else None # 训练 history train_model(model, optimizer, scheduler) results[name] history ​ # 6. 可视化对比结果 fig, axes plt.subplots(1, 2, figsize(14, 5)) ​ # 绘制损失曲线 ax1 axes[0] for name, history in results.items(): ax1.plot(history[train_loss], labelname, linewidth2) ax1.set_xlabel(Epoch, fontsize12) ax1.set_ylabel(Training Loss, fontsize12) ax1.set_title(训练损失对比, fontsize14) ax1.legend(locupper right, fontsize9) ax1.grid(True, alpha0.3) ​ # 绘制准确率曲线 ax2 axes[1] for name, history in results.items(): ax2.plot(history[test_acc], labelname, linewidth2) ax2.set_xlabel(Epoch, fontsize12) ax2.set_ylabel(Test Accuracy (%), fontsize12) ax2.set_title(测试准确率对比, fontsize14) ax2.legend(loclower right, fontsize9) ax2.grid(True, alpha0.3) ​ plt.tight_layout() plt.savefig(optimizer_comparison.png, dpi150, bbox_inchestight) plt.show() ​ # 7. 打印最终结果汇总 print(\n *80) print(最终结果汇总) print(*80) print(f{优化器:35} {最终训练损失:15} {最终测试准确率:15}) print(-*80) ​ for name, history in results.items(): final_train_loss history[train_loss][-1] final_test_acc history[test_acc][-1] print(f{name:35} {final_train_loss:15.4f} {final_test_acc:15.2f}%) ​ # 找出最佳优化器 best_optimizer max(results.items(), keylambda x: x[1][test_acc][-1]) print(f\n最佳优化器: {best_optimizer[0]}) print(f最佳测试准确率: {best_optimizer[1][test_acc][-1]:.2f}%)实验结果解读运行上述代码后你将看到各优化器在MNIST上的表现差异。一般规律Adam系列收敛最快但最终准确率可能略低于SGDSGD Momentum收敛较慢但往往能达到更高的准确率AdaGrad学习率持续下降后期可能出现僵硬现象RMSProp收敛速度和准确性之间的良好平衡11. 总结本文系统梳理了深度学习中主流优化器的发展脉络与核心原理SGD系列基础但强大通过动量改进可达到优异性能自适应学习率系列AdaGrad、RMSProp、AdaDelta解决了学习率手动调节的难题Adam集大成者是目前工业界的默认首选AdamW解决了Adam中权重衰减的问题是Transformer时代的标准配置学习率调度配合优化器使用能够显著提升训练效果实践建议快速实验用Adam上线生产用SGDTransformer模型用AdamW配合学习率调度Cosine Annealing Warmup效果更佳没有银弹根据任务特点选择合适的优化器