从理论到代码:手把手拆解PyTorch中Adam优化器的每一步计算
2026/5/8 20:54:40 网站建设 项目流程

从理论到代码:手把手拆解PyTorch中Adam优化器的每一步计算

在深度学习模型的训练过程中,优化算法的选择直接影响着模型的收敛速度和最终性能。Adam优化器因其自适应学习率和动量机制的双重优势,成为众多研究者和工程师的首选。但你是否真正理解Adam在每一步更新中究竟做了哪些计算?本文将带你深入PyTorch的torch.optim.Adam实现,从数学公式到代码逻辑,逐行解析这个"黑盒子"的内部运作机制。

1. Adam优化器的数学基础

Adam(Adaptive Moment Estimation)结合了动量(Momentum)和RMSProp两种优化算法的思想,通过计算梯度的一阶矩(均值)和二阶矩(未中心化的方差)估计来动态调整每个参数的学习率。其核心公式可以分为以下几个部分:

1.1 动量计算:一阶矩估计

Adam首先计算梯度的指数移动平均(EMA),这类似于传统动量方法:

m_t = β₁ * m_{t-1} + (1 - β₁) * g_t

其中:

  • m_t是当前时间步的一阶矩估计
  • β₁是衰减率(通常设为0.9)
  • g_t是当前梯度

1.2 自适应学习率:二阶矩估计

同时,Adam还计算梯度平方的指数移动平均,用于自适应调整学习率:

v_t = β₂ * v_{t-1} + (1 - β₂) * g_t²

其中:

  • v_t是当前时间步的二阶矩估计
  • β₂是另一个衰减率(通常设为0.999)

1.3 偏差校正

由于在初始时间步,矩估计偏向于0,Adam引入了偏差校正机制:

m̂_t = m_t / (1 - β₁^t) v̂_t = v_t / (1 - β₂^t)

1.4 参数更新

最终,参数更新公式为:

θ_t = θ_{t-1} - α * m̂_t / (√v̂_t + ε)

其中:

  • α是初始学习率
  • ε是为了数值稳定性添加的小常数(默认1e-8)

2. PyTorch实现解析

让我们深入PyTorch的torch.optim.Adam源码,看看这些数学公式如何转化为实际的代码逻辑。以下是简化后的关键实现步骤:

2.1 初始化阶段

def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0, amsgrad=False): defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay, amsgrad=amsgrad) super(Adam, self).__init__(params, defaults)

关键参数说明:

  • params:待优化的模型参数
  • lr:学习率(默认1e-3)
  • betas:动量衰减系数(β₁, β₂)
  • eps:数值稳定项
  • weight_decay:L2正则化系数
  • amsgrad:是否使用AMSGrad变体

2.2 单步更新(step函数)

def step(self, closure=None): loss = None if closure is not None: loss = closure() for group in self.param_groups: for p in group['params']: if p.grad is None: continue grad = p.grad.data # 实现权重衰减(L2正则化) if group['weight_decay'] != 0: grad = grad.add(p.data, alpha=group['weight_decay']) # 获取状态 state = self.state[p] # 初始化状态 if len(state) == 0: state['step'] = 0 state['exp_avg'] = torch.zeros_like(p.data) state['exp_avg_sq'] = torch.zeros_like(p.data) if group['amsgrad']: state['max_exp_avg_sq'] = torch.zeros_like(p.data) # 更新状态 exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq'] beta1, beta2 = group['betas'] state['step'] += 1 bias_correction1 = 1 - beta1 ** state['step'] bias_correction2 = 1 - beta2 ** state['step'] # 更新一阶矩估计 exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1) # 更新二阶矩估计 exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2) # 计算偏差校正后的估计 denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps']) step_size = group['lr'] / bias_correction1 # 更新参数 p.data.addcdiv_(exp_avg, denom, value=-step_size) return loss

3. 数值示例演算

为了更好地理解Adam的计算过程,让我们通过一个简单的数值例子来手动演算几个步骤。

假设:

  • 参数θ初始值为5.0
  • 初始梯度g₁=2.0,g₂=1.5,g₃=0.5
  • β₁=0.9,β₂=0.999
  • α=0.001,ε=1e-8

第一步计算(t=1)

m₁ = 0.9*0 + 0.1*2.0 = 0.2 v₁ = 0.999*0 + 0.001*4.0 ≈ 0.004 m̂₁ = 0.2 / (1 - 0.9^1) = 0.2 / 0.1 = 2.0 v̂₁ = 0.004 / (1 - 0.999^1) ≈ 0.004 / 0.001 = 4.0 θ₁ = 5.0 - 0.001*2.0 / (√4.0 + 1e-8) ≈ 5.0 - 0.001 = 4.999

第二步计算(t=2)

m₂ = 0.9*0.2 + 0.1*1.5 = 0.18 + 0.15 = 0.33 v₂ = 0.999*0.004 + 0.001*2.25 ≈ 0.003996 + 0.00225 ≈ 0.006246 m̂₂ = 0.33 / (1 - 0.9^2) ≈ 0.33 / 0.19 ≈ 1.7368 v̂₂ = 0.006246 / (1 - 0.999^2) ≈ 0.006246 / 0.001999 ≈ 3.1246 θ₂ = 4.999 - 0.001*1.7368 / (√3.1246 + 1e-8) ≈ 4.999 - 0.00098 ≈ 4.99802

通过这种逐步计算,我们可以直观地看到Adam如何调整每个参数的学习率。

4. 关键实现细节与调优建议

4.1 偏差校正的重要性

在Adam的早期迭代中,由于β₁β₂接近1,矩估计会偏向于0。偏差校正通过除以(1 - β^t)来补偿这种偏差。在实际应用中:

  • 训练初期偏差校正影响显著
  • 随着t增大,校正因子趋近于1
  • 对于长期训练,可以省略校正(但PyTorch默认始终应用)

4.2 AMSGrad变体

amsgrad=True时,PyTorch会使用AMSGrad变体,它保持历史最大v_t值:

if group['amsgrad']: torch.maximum(state['max_exp_avg_sq'], exp_avg_sq, out=state['max_exp_avg_sq']) denom = (state['max_exp_avg_sq'].sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])

这种变体在某些情况下可以防止学习率过早下降,提高模型性能。

4.3 参数调优经验

根据实际项目经验,Adam的参数调优有以下建议:

参数默认值调整建议适用场景
lr1e-31e-4到1e-2大模型可适当降低
betas(0.9,0.999)保持默认除非有特殊需求
eps1e-81e-8到1e-6数值稳定性
weight_decay01e-4到1e-2防止过拟合
amsgradFalse测试两种设置某些任务可能有效

4.4 常见问题排查

当使用Adam优化器遇到问题时,可以检查以下几点:

  1. 梯度消失/爆炸

    • 检查exp_avgexp_avg_sq的值是否合理
    • 调整eps值(但通常保持默认即可)
  2. 收敛速度慢

    • 尝试增大学习率
    • 检查权重衰减是否设置过大
  3. 训练不稳定

    • 考虑使用AMSGrad变体
    • 检查梯度裁剪是否必要

5. 自定义Adam实现

为了更深入理解Adam的工作原理,我们可以实现一个简化版的Adam优化器:

class SimpleAdam: def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8): self.params = list(params) self.lr = lr self.beta1, self.beta2 = betas self.eps = eps self.state = {} for p in self.params: self.state[p] = { 'step': 0, 'm': torch.zeros_like(p.data), 'v': torch.zeros_like(p.data) } def step(self): for p in self.params: if p.grad is None: continue grad = p.grad.data state = self.state[p] state['step'] += 1 t = state['step'] # 更新一阶和二阶矩估计 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 ** t) v_hat = state['v'] / (1 - self.beta2 ** t) # 更新参数 p.data -= self.lr * m_hat / (v_hat.sqrt() + self.eps)

这个简化实现包含了Adam的核心逻辑,去掉了权重衰减、AMSGrad等高级功能,更适合教学和理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询