从梯度下降到Adam优化器:深度学习优化算法演进与实战对比

1次阅读
没有评论

共计 2651 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

技术背景

梯度下降(Gradient Descent)作为深度学习的基础优化算法,其核心思想是通过迭代调整参数,使损失函数 $L(\theta)$ 最小化。参数更新公式为:

从梯度下降到 Adam 优化器:深度学习优化算法演进与实战对比

$$\theta_{t+1} = \theta_t – \eta \nabla_\theta L(\theta_t)$$

其中 $\eta$ 为学习率。但在实际应用中存在三个主要问题:

  1. 学习率敏感性 :固定学习率难以适应不同参数的特性,过大导致震荡,过小收敛缓慢
  2. 鞍点问题 :在高维空间中,梯度为零的点中约 80% 是鞍点而非局部极小值
  3. 参数尺度差异 :不同层 / 参数的梯度量级可能相差数个数量级

算法对比

算法 更新公式 时间复杂度 空间复杂度
SGD $\theta_{t+1} = \theta_t – \eta g_t$ $O(d)$ $O(d)$
Momentum $v_t = \gamma v_{t-1} + \eta g_t$
$\theta_{t+1} = \theta_t – v_t$
$O(d)$ $O(d)$
RMSprop $E[g^2]t = \beta E[g^2] g_t$} + (1-\beta)g_t^2$
$\theta_{t+1} = \theta_t – \frac{\eta}{\sqrt{E[g^2]_t + \epsilon}
$O(d)$ $O(d)$
Adam $m_t = \beta_1 m_{t-1} + (1-\beta_1)g_t$
$v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2$
$\hat{m}t = \frac{m_t}{1-\beta_1^t}$
$\hat{v}_t = \frac{v_t}{1-\beta_2^t}$
$\theta
_t$} = \theta_t – \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \hat{m
$O(d)$ $O(d)$

Adam 实现细节

自适应矩估计

Adam 同时计算梯度的一阶矩(均值)$m_t$ 和二阶矩(未中心化的方差)$v_t$:

$$m_t = \beta_1 m_{t-1} + (1-\beta_1)g_t$$
$$v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2$$

其中 $\beta_1=0.9$, $\beta_2=0.999$ 为经验值,控制指数衰减率。

偏差校正

由于初始时刻 $m_0=v_0=0$,早期估计会偏向零,因此引入校正项:

$$\hat{m}_t = \frac{m_t}{1-\beta_1^t}$$
$$\hat{v}_t = \frac{v_t}{1-\beta_2^t}$$

PyTorch 实现

import torch

class AdamWarmup(torch.optim.Optimizer):
    def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8,
                 weight_decay=0, warmup_steps=4000):
        defaults = dict(lr=lr, betas=betas, eps=eps,
                        weight_decay=weight_decay, warmup_steps=warmup_steps)
        super().__init__(params, defaults)

    def step(self):
        for group in self.param_groups:
            for p in group['params']:
                if p.grad is None:
                    continue

                grad = p.grad.data
                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)

                exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
                beta1, beta2 = group['betas']

                state['step'] += 1

                # 更新一阶和二阶矩估计
                exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
                exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)

                # 计算偏差校正
                bias_correction1 = 1 - beta1 ** state['step']
                bias_correction2 = 1 - beta2 ** state['step']

                # 学习率预热
                warmup_factor = min(state['step'] / group['warmup_steps'], 1.0)
                current_lr = group['lr'] * warmup_factor

                # 参数更新
                denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(group['eps'])
                step_size = current_lr / bias_correction1
                p.data.addcdiv_(exp_avg, denom, value=-step_size)

实验对比(Tesla V100 16GB)

MNIST 收敛曲线

  1. SGD (lr=0.1): 需要 15 个 epoch 达到 98% 准确率
  2. Adam (lr=1e-3): 仅需 5 个 epoch 达到同等精度
  3. 最终收敛时 Adam 比 SGD 高 0.3% 的测试准确率

GPU 内存占用(batch_size=128)

算法 显存占用 (MB)
SGD 1243
Adam 1261

生产建议

  1. 学习率设置
  2. 初始学习率经验公式:$\eta = 0.001 \times \sqrt{\text{batch_size}/256}$
  3. 对于 ViT 等模型:$\eta_{\text{base}} = 3e-4$, $\eta_{\text{head}} = 3e-5$

  4. SGD 适用场景

  5. BERT 微调:SGD 比 Adam 最终指标高 0.5-1.0%
  6. 图像超分辨率:SGD 更易找到 sharp minima

  7. 梯度裁剪

  8. L2 范数阈值:$\text{threshold} = 0.1 \times \sqrt{\text{num_parameters}}$
  9. 当使用混合精度训练时,阈值应缩小 2 - 4 倍

总结

Adam 通过自适应学习率和动量机制,在大多数深度学习任务中展现出显著优势。但在模型微调等需要精细参数调整的场景,SGD 仍保持竞争力。实际应用中建议:
1. 默认首选 Adam
2. 资源受限时使用 SGD
3. 关键任务同时尝试两种优化器

正文完
 0
评论(没有评论)