从梯度下降到 Adam —— 一步步推导出深度学习最强优化器

在这里插入图片描述

小可(一脸好奇):每次训练深度学习模型,默认优化器都是Adam?

博士(推了推眼镜):没错!Adam是优化界的万金油,几乎成了训练模型的第一选择。


引言:为什么是 Adam?

如果你学过深度学习,一定对 Adam(Adaptive Moment Estimation,自适应矩估计)这个名字不陌生。在 PyTorch 中,torch.optim.Adam 几乎是所有深度学习教程中第一个出现的优化器。在 TensorFlow 中,Adam 同样是默认的优化选项。从计算机视觉到自然语言处理,从推荐系统到强化学习,Adam 的身影无处不在。

但是你有没有想过——为什么是 Adam?它是怎么从最原始的梯度下降法,一步一步进化成现在这个样子的?

Adam 并不是凭空出现的。它是经过了几十年的优化器研究,从最简单的梯度下降法开始,经过动量法(Momentum)、RMSprop 等多个阶段的迭代进化,最终由 Kingma 和 Ba 在 2014 年的论文《Adam: A Method for Stochastic Optimization》中正式提出的。

这篇论文至今已被引用超过 10 万次,是深度学习领域引用量最高的论文之一。

今天,我们就从小可和博士的对话开始,从梯度下降法讲起,一步一步带着你手把手地推导出 Adam 优化器的所有细节。保证通俗易懂!

准备好了吗?让我们开始!


第一章:回到最初的起点——梯度下降法

在这里插入图片描述

小可(歪头):那Adam到底是怎么来的呢?

博士(微笑):让我们回到最初的起点——最原始的梯度下降法。


1.1 什么是梯度下降法?

梯度下降法(Gradient Descent, GD)是机器学习和深度学习中最基础、最核心的优化算法。它的历史可以追溯到 19 世纪,由法国数学家奥古斯丁·路易·柯西(Augustin-Louis Cauchy)在 1847 年首次提出。

它的核心原理非常简单:

计算当前的梯度,然后沿着梯度的反方向走一步。

用数学公式表达就是:

θ=θ−η⋅∇L(θ)\theta = \theta - \eta \cdot \nabla L(\theta)θ=θ−η⋅∇L(θ)

其中:

  • θ(theta):模型的参数(可以是一个标量,也可以是一个向量)
  • η(eta):学习率(learning rate),控制每一步走多远
  • ∇L(θ):损失函数 L 关于参数 θ 的梯度(导数向量)

这个公式的含义是:计算当前参数位置下损失函数的梯度,然后沿着梯度的反方向(负梯度方向)走一小步。为什么要走反方向?因为梯度的方向是函数值增长最快的方向,而我们要最小化损失函数,所以要走相反的方向。

1.2 梯度的几何意义

梯度是一个向量,它有两个重要的性质:

  1. 方向:梯度指向函数值增长最快的方向
  2. 大小:梯度的模(长度)表示函数值在该方向上的变化率

所以,梯度的反方向就是函数值下降最快的方向。这就是梯度下降法的名称来源——沿着梯度的反方向"下降"。

在一维情况下,梯度就是导数:

dLdθ>0⇒函数在增加⇒应该往左走(减小θ)\frac{dL}{d\theta} > 0 \quad \Rightarrow \quad \text{函数在增加} \quad \Rightarrow \quad \text{应该往左走(减小θ)}dθdL​>0⇒函数在增加⇒应该往左走(减小θ)
dLdθ<0⇒函数在减少⇒应该往右走(增大θ)\frac{dL}{d\theta} < 0 \quad \Rightarrow \quad \text{函数在减少} \quad \Rightarrow \quad \text{应该往右走(增大θ)}dθdL​<0⇒函数在减少⇒应该往右走(增大θ)

在多维情况下,梯度是一个向量:

∇L(θ)=(∂L∂θ1,∂L∂θ2,⋯ ,∂L∂θn)\nabla L(\theta) = \left(\frac{\partial L}{\partial \theta_1}, \frac{\partial L}{\partial \theta_2}, \cdots, \frac{\partial L}{\partial \theta_n}\right)∇L(θ)=(∂θ1​∂L​,∂θ2​∂L​,⋯,∂θn​∂L​)

1.3 学习率的选择

学习率 η 是梯度下降法中最重要的超参数:

  • 学习率太大:步子太大,可能会跨过最小值,甚至发散(越优化损失越大)
  • 学习率太小:步子太小,收敛速度极慢,可能需要几万次迭代才能到达最优解
  • 学习率适中:能够在合理的步数内收敛到最优解

选择合适的学习率是一门艺术,也是后续优化器研究的核心动机之一。

1.4 损失函数的碗状曲面

在这里插入图片描述

小可(惊讶):损失函数的图像长成一个碗的形状?

博士:对!想象一个碗状的曲面,最低点就是我们要找的最优解。

想象一下,你的损失函数的图像长成了一个碗状(凸函数)。这个碗的底部就是我们要找的最小值点。

对于一个简单的二次函数 L(θ) = θ²,它的图像就是一个标准的碗状曲线:

  • 当 θ > 0 时,梯度为正,参数向左移动
  • 当 θ < 0 时,梯度为负,参数向右移动
  • 最终都会收敛到 θ = 0 这个最小值点

训练开始时,参数被随机初始化在某个位置——可能在碗的边缘、碗壁上,或者碗底的某个地方。

最理想的情况是:参数沿着碗壁直接滑下去,最终到达碗底的最小点。就像你往碗里放一颗玻璃球,它会自然地滚到最低点。

但问题是……现实并没有那么完美。


第二章:理想很丰满,现实很骨感

在这里插入图片描述

小可(疑惑):理想情况下参数应该沿着最优路径直达最低点?

博士(摇头):但现实很骨感——批量梯度下降的路径并不每次都指向最小点。

2.1 批量梯度下降的困境

在实际的深度学习训练中,我们几乎不会使用全量梯度下降(Full-batch Gradient Descent,用所有训练数据计算梯度),而是使用批量梯度下降(Mini-batch Gradient Descent),即每次只用一小部分数据(比如 32 张、64 张、128 张图片)来估计梯度。

为什么要这样做呢?原因有三:

  1. 计算效率:用全部数据计算梯度太慢了。如果训练集有 100 万张图片,每次更新参数都要遍历全部 100 万张,训练时间会非常长。使用小批量数据可以大大加速训练。假设 batch size = 128,那么每个 epoch 只需要 100万/128 ≈ 7812 次参数更新。

  2. 内存限制:现代神经网络动辄上亿参数,如果把全部数据都加载到内存中同时计算梯度,内存很可能不够用。小批量数据可以控制内存消耗。

  3. 泛化能力:小批量梯度引入的噪声有助于模型逃离尖锐的局部最优,找到泛化能力更好的平坦最优解。研究表明,适度的噪声反而对模型泛化有帮助。

这就带来了一个问题:

并不是每一个数据批次的梯度方向都指向最小点。

举个例子:假设你正在训练一个猫的图片分类器。你的训练数据集中既有奶牛猫,也有狸花猫。

  • 批次 A 抽到的是奶牛猫 → 计算出的梯度方向指向"奶牛猫的最优解"
  • 批次 B 抽到的是狸花猫 → 计算出的梯度方向指向"狸花猫的最优解"

这两只猫的特征不同(奶牛猫是黑白花纹、圆脸、胖;狸花猫是条纹、尖脸、瘦),所以算出来的梯度方向自然不同。

在这里插入图片描述

小可:为什么不同批次的梯度方向会不一样?

博士:因为每个批次的数据特征不同,算出来的导数方向自然有偏差。

更进一步说,即使是同一品种的猫——不同的奶牛猫之间,它们的毛色分布、脸部特征、体型大小也各不相同。因此,即使是两个不同的"奶牛猫"批次,算出来的梯度也会存在差异。

这就是小批量梯度下降的随机性来源:每个批次都是对全量数据的一个随机采样,而随机采样必然带来估计误差。从统计学的角度来看,小批量梯度是真实梯度的一个无偏估计:

E[∇Lmini-batch]=∇Lfull-batch\mathbb{E}[\nabla L_{\text{mini-batch}}] = \nabla L_{\text{full-batch}}E[∇Lmini-batch​]=∇Lfull-batch​

虽然期望是无偏的,但方差是存在的。方差越大,每个批次的梯度方向与真实梯度方向的偏差就越大。

2.2 震荡现象请添加图片描述

小可:所以参数更新时会上下震荡?

博士:是的!纵向不断震荡,但横向又在逐步逼近最小值。

当不同批次的梯度方向不一致时,参数的更新路径就会出现震荡:

  • 在某个维度上(纵向),梯度忽正忽负,参数上下震荡
  • 在另一个维度上(横向),梯度方向相对稳定,参数逐步逼近最小值

这种震荡是低效的——它意味着我们在浪费计算资源在"原地踏步"上。每一步都在震荡方向上做了无用功,只有在前进方向上的分量才是有效的。

如果能把震荡抵消掉,同时保持前进方向的加速,那岂不是更好?

2.3 为什么震荡是不好的?

震荡带来的问题不仅仅是效率低下,还包括:

  1. 收敛速度慢:大量的迭代步骤被浪费在震荡上,实际的前进速度大打折扣
  2. 可能发散:如果学习率过大,震荡幅度可能越来越大,导致损失函数不降反升
  3. 难以精确定位最优解:即使接近最优解,震荡也会让参数在最优解附近来回跳动,无法精确收敛

第三章:灵感闪现——抵消震荡的想法

在这里插入图片描述

小可(灵光一闪):如果能抵消上下的震荡,速度不就更快了吗?

博士(竖起大拇指):聪明!把当前移动向量和过去移动向量相加,反方向就抵消了。

3.1 向量相加的魔力

假设参数更新到某个位置,当前的梯度告诉你:"往右下方猛冲!"但是你回头看了一眼:“哎?我上一步是往左上方冲的。”

这时候,如果我们把当前即将移动的向量和过去已经移动的向量加起来会发生什么?

在这里插入图片描述

小可(恍然大悟):反方向的向量相加,就相互抵消了?

博士:对!相反方向抵消,相同方向叠加——这就是氮气加速!

  • 相反方向的移动:相互抵消 → 震荡被抑制
  • 相同方向的移动:叠加增强 → 前进速度更快

这就像是开车的时候遇到颠簸的路面——如果你能利用车的惯性来缓冲颠簸,行驶就会更平稳、更快。

让我们用具体的向量来说明:

  • 上一步移动向量:v_prev = (-3, 2)(向左上)
  • 当前步移动向量:g_curr = (3, -1)(向右下)

如果直接相加:

v_new = v_prev + g_curr = (-3, 2) + (3, -1) = (0, 1)

纵向分量(2 + (-1) = 1)被部分抵消了,而横向分量(-3 + 3 = 0)也抵消了。但如果在连续多步中,前进方向的分量是一致的,它们就会不断叠加,越来越快。

3.2 动量法(Momentum)的概念

在这里插入图片描述

小可(兴奋):这就是动量梯度下降法!

博士:利用过去的惯性来修正现在的冲动,又快又稳。

这个想法就是动量梯度下降法(Momentum SGD)的核心思想:

利用历史梯度来修正当前的更新方向和步长。

动量法最早由 Polyak 在 1964 年提出,后来被 Qian 在 1999 年引入机器学习领域。它的灵感来源于物理学中的动量概念。

在物理学中,动量 = 质量 × 速度。一个运动的物体有惯性,会保持运动方向继续前进,直到外力改变它的状态。动量梯度下降法就是借用了这个物理概念:

  • 梯度就像是施加在参数上的"力"
  • 历史梯度的累积就像是物体的"动量"
  • 遇到反方向 → 惯性抵消震荡
  • 遇到同方向 → 惯性加速前进

动量法带来了三个关键改进:

  1. 减少震荡 → 更新路径更平滑
  2. 加速收敛 → 到达最优解所需的步数更少
  3. 逃离浅局部最优 → 惯性可以帮助冲出浅的局部最优点

第四章:动量法的数学公式

在这里插入图片描述

小可(认真):动量法的公式怎么写?

博士:v = β·v_prev + (1-β)·∇L,然后用v来更新参数。

4.1 动量法的完整推导

原始的梯度下降法:

θ=θ−η⋅∇L(θ)\theta = \theta - \eta \cdot \nabla L(\theta)θ=θ−η⋅∇L(θ)

按照动量法的思路,我们需要引入一个变量 v 来存储过去时刻的梯度信息:

第一步:更新速度(累积历史梯度)

v=β⋅v+(1−β)⋅∇L(θ)v = \beta \cdot v + (1 - \beta) \cdot \nabla L(\theta)v=β⋅v+(1−β)⋅∇L(θ)

第二步:用速度更新参数

θ=θ−η⋅v\theta = \theta - \eta \cdot vθ=θ−η⋅v

其中 β 是一个超参数,通常取值为 0.9。

注意:有些文献中使用的是简化版本的动量公式 v = β·v + ∇L(没有 (1-β) 的系数)。这两种写法都是正确的,但含义略有不同。我们这里使用带 (1-β) 的版本,因为它与后续的 Adam 优化器保持一致。

4.2 指数加权移动平均(EWMA)

在这里插入图片描述

小可:这里β的取值是多少?

博士:β通常取0.9,这就是大名鼎鼎的指数加权移动平均公式。

让我们展开看看 v 的累积过程(假设 β = 0.9):

v1=0.1⋅g1v2=0.9⋅v1+0.1⋅g2=0.09⋅g1+0.1⋅g2v3=0.9⋅v2+0.1⋅g3=0.081⋅g1+0.09⋅g2+0.1⋅g3v4=0.9⋅v3+0.1⋅g4=0.0729⋅g1+0.081⋅g2+0.09⋅g3+0.1⋅g4⋮vt=∑i=1t(1−β)⋅βt−i⋅gi\begin{aligned} v_1 &= 0.1 \cdot g_1 \\ v_2 &= 0.9 \cdot v_1 + 0.1 \cdot g_2 = 0.09 \cdot g_1 + 0.1 \cdot g_2 \\ v_3 &= 0.9 \cdot v_2 + 0.1 \cdot g_3 = 0.081 \cdot g_1 + 0.09 \cdot g_2 + 0.1 \cdot g_3 \\ v_4 &= 0.9 \cdot v_3 + 0.1 \cdot g_4 = 0.0729 \cdot g_1 + 0.081 \cdot g_2 + 0.09 \cdot g_3 + 0.1 \cdot g_4 \\ &\vdots \\ v_t &= \sum_{i=1}^{t} (1-\beta) \cdot \beta^{t-i} \cdot g_i \end{aligned}v1​v2​v3​v4​vt​​=0.1⋅g1​=0.9⋅v1​+0.1⋅g2​=0.09⋅g1​+0.1⋅g2​=0.9⋅v2​+0.1⋅g3​=0.081⋅g1​+0.09⋅g2​+0.1⋅g3​=0.9⋅v3​+0.1⋅g4​=0.0729⋅g1​+0.081⋅g2​+0.09⋅g3​+0.1⋅g4​⋮=i=1∑t​(1−β)⋅βt−i⋅gi​​

可以看到,越近的梯度权重越大,权重以指数形式衰减。β = 0.9 大约相当于取最近 10 个梯度的加权平均。

这个公式有一个非常直观的解释:当前时刻的速度 v_t 是过去所有梯度的加权和,权重按照 β 的幂次衰减。最近的梯度权重最大,远处的梯度权重逐渐减小。

这就是指数加权移动平均(Exponentially Weighted Moving Average, EWMA)——它在时间序列分析、金融建模、信号处理等领域都有广泛应用。

4.3 β 的有效窗口

β 的值决定了"记忆"的长度。有一个经验公式:

有效窗口≈11−β\text{有效窗口} \approx \frac{1}{1 - \beta}有效窗口≈1−β1​

  • β = 0.9 → 有效窗口 ≈ 10 步
  • β = 0.95 → 有效窗口 ≈ 20 步
  • β = 0.99 → 有效窗口 ≈ 100 步
  • β = 0.999 → 有效窗口 ≈ 1000 步

这意味着,β = 0.9 时,大约过去 10 步的梯度对当前速度有显著影响。更久远的梯度虽然也有贡献,但权重已经非常小了。

4.4 代码实现

让我们用 Python 来实现一个简单的动量梯度下降:

import numpy as np

def momentum_gd(grad_fn, theta_init, lr=0.01, beta=0.9, n_iters=1000):
    """动量梯度下降"""
    theta = theta_init.copy()
    v = np.zeros_like(theta)  # 速度初始化为0
    
    for t in range(n_iters):
        # 计算当前梯度
        grad = grad_fn(theta)
        
        # 更新速度(指数加权移动平均)
        v = beta * v + (1 - beta) * grad
        
        # 用速度更新参数
        theta = theta - lr * v
    
    return theta

第五章:固定学习率的问题

在这里插入图片描述

小可(思考):但动量法的学习率η是固定的?

博士:对,固定的学习率在靠近最小点时容易来回震荡。

5.1 学习率的两难困境

动量法虽然解决了方向问题,但还有一个关键参数没有解决——学习率 η。

学习率在训练开始时就固定下来了。这带来了一个问题:当参数逐渐接近最小点时,如果还按照固定的学习率去更新,参数就会在最小值附近来回震荡,很难真正收敛到最优解。

5.2 山谷地形困境

在这里插入图片描述

小可(皱眉):遇到这种一边陡一边缓的山谷地形怎么办?

博士:纵向梯度大步子容易冲过头,横向梯度小步子像蚂蚁挪动。

更复杂的情况是:损失函数的等高线是狭长的山谷形状——一个方向很陡(梯度大),另一个方向很缓(梯度小)。

这种情况在高维空间中非常常见。考虑一个二维的二次损失函数:

L(θ1,θ2)=12(aθ12+bθ22)L(\theta_1, \theta_2) = \frac{1}{2}(a\theta_1^2 + b\theta_2^2)L(θ1​,θ2​)=21​(aθ12​+bθ22​)

其中 a >> b(比如 a = 100, b = 1)。这意味着:

  • 在 θ₁ 方向上,损失函数很陡峭(曲率大)
  • 在 θ₂ 方向上,损失函数很平缓(曲率小)

梯度的两个分量分别为:

∂L∂θ1=aθ1,∂L∂θ2=bθ2\frac{\partial L}{\partial \theta_1} = a\theta_1, \quad \frac{\partial L}{\partial \theta_2} = b\theta_2∂θ1​∂L​=aθ1​,∂θ2​∂L​=bθ2​

  • 纵向(陡坡 θ₁):梯度大 → η × 梯度 = 大步子 → 容易冲过山谷、来回震荡
  • 横向(缓坡 θ₂):梯度小 → η × 梯度 = 小步子 → 像蚂蚁一样缓慢挪动

如果用同一个学习率去更新所有参数,就会出现"纵向冲过头、横向走不动"的尴尬局面。

在这里插入图片描述

小可:最优路线应该是更直接的曲线?

博士:如果梯度大时学习率自动缩小,梯度小时自动增大就好了。

从顶视图来看,最优路径应该是一条平滑的曲线直达谷底。但固定学习率导致的路径是 Z 字形的震荡。

5.3 为什么一个学习率不够?

在深度学习中,一个神经网络可能有数百万甚至数十亿个参数。每个参数的重要性不同,梯度大小也不同。用一个统一的学习率来更新所有参数,就像是用同一把尺子去量所有东西——显然是不够灵活的。

理想的情况是:每个参数都有自己的学习率。梯度大的参数应该用小学习率,梯度小的参数应该用大学习率。这就是自适应学习率(Adaptive Learning Rate)的核心思想。


第六章:RMSprop —— 自适应学习率的诞生

在这里插入图片描述

小可(灵机一动):用什么运算能实现大变小、小变大?

博士:除法!用梯度除以一个代表梯度强度的数就行。

6.1 除法的魔力

在数学上,实现"大变小、小变大"的运算是什么?答案是:除法!

如果我们能找到一个数,这个数代表了当前梯度的强度(坡度有多陡),然后用梯度除以这个数:

  • 梯度很大时:分母也很大 → 相除之后整体变小 → 防止冲过头 ✓
  • 梯度很小时:分母也很小 → 相除之后可能变大 → 加速收敛 ✓

6.2 衡量梯度强度

在这里插入图片描述

小可:怎么衡量梯度的强度呢?

博士:用梯度的平方!而且像动量法一样做指数加权移动平均。

为了衡量梯度的强度,我们需要注意:

  1. 梯度有正有负,所以不能直接使用梯度值本身。如果梯度是 -10 和 +10,它们的强度是一样的,但直接相加会抵消。
  2. 最方便的方法是看它的平方 g²,总是正的,能反映梯度的大小。
  3. 但只看当前梯度还不够 → 需要用指数加权移动平均来保留最近一段时间的地形路况

于是我们定义了一个新的变量 s:

s=β2⋅s+(1−β2)⋅(∇L)2s = \beta_2 \cdot s + (1 - \beta_2) \cdot (\nabla L)^2s=β2​⋅s+(1−β2​)⋅(∇L)2

  • 如果纵向的梯度一直很大 → s 会累积得非常大
  • 如果横向的梯度一直很小 → s 会保持非常小

然后我们用梯度除以 √s:

θ=θ−η⋅∇Ls+ε\theta = \theta - \eta \cdot \frac{\nabla L}{\sqrt{s} + \varepsilon}θ=θ−η⋅s​+ε∇L​

这里 ε 是一个很小的数(通常 10⁻⁸),用来防止分母为零的情况。

6.3 RMSprop 完整公式

在这里插入图片描述

小可:所以s记录最近梯度的陡峭程度?

博士:对!然后用梯度除以√s,就得到了RMSprop的更新规则。

RMSprop(Root Mean Square Propagation)是由 Geoffrey Hinton 在他的 Coursera 课程中提出的优化算法。它的完整更新规则如下:

步骤1:更新梯度平方的指数加权平均

s=β2⋅s+(1−β2)⋅(∇L)2s = \beta_2 \cdot s + (1 - \beta_2) \cdot (\nabla L)^2s=β2​⋅s+(1−β2​)⋅(∇L)2

步骤2:自适应学习率更新参数

θ=θ−η⋅∇Ls+ε\theta = \theta - \eta \cdot \frac{\nabla L}{\sqrt{s} + \varepsilon}θ=θ−η⋅s​+ε∇L​

参数设置:

  • β₂ = 0.999(记录更长时间内的梯度强度,比动量的 β 更大)
  • ε = 10⁻⁸(防止除零的极小常数)
  • η = 0.001(学习率,通常取较小的值)

RMSprop 的核心贡献是:为每个参数分配了自己的自适应学习率。梯度大的方向自动缩小学习率,梯度小的方向自动增大学习率。

6.4 RMSprop 的直觉理解

让我们用一个具体的例子来理解 RMSprop 的工作原理。

假设我们有两个参数 θ₁ 和 θ₂:

  • θ₁ 方向的梯度一直很大,比如 g₁ = 10
  • θ₂ 方向的梯度一直很小,比如 g₂ = 0.1

经过几步迭代后,s 的值大致为:

  • s₁ ≈ g₁² = 100
  • s₂ ≈ g₂² = 0.01

更新时:

  • θ₁ 的更新步长 ≈ η × 10 / √100 = η × 10 / 10 = η × 1
  • θ₂ 的更新步长 ≈ η × 0.1 / √0.01 = η × 0.1 / 0.1 = η × 1

神奇的事情发生了:虽然两个参数的梯度相差 100 倍,但经过 RMSprop 的自适应调节后,它们的实际更新步长变得差不多了!这就是自适应学习率的威力。

6.5 RMSprop 的代码实现

def rmsprop(grad_fn, theta_init, lr=0.001, beta2=0.999, eps=1e-8, n_iters=1000):
    """RMSprop 优化器"""
    theta = theta_init.copy()
    s = np.zeros_like(theta)  # 梯度平方的指数加权平均
    
    for t in range(n_iters):
        grad = grad_fn(theta)
        
        # 更新梯度平方的指数加权平均
        s = beta2 * s + (1 - beta2) * grad ** 2
        
        # 自适应学习率更新
        theta = theta - lr * grad / (np.sqrt(s) + eps)
    
    return theta

RMSprop 的核心贡献是:为每个参数分配了自己的自适应学习率。梯度大的方向自动缩小学习率,梯度小的方向自动增大学习率。


第七章:回顾优化史——两大神器

在这里插入图片描述

小可(点头):动量法管方向,RMSprop管步长?

博士:没错!动量抵消震荡指明方向,RMSprop根据路况自动调步长。

让我们回顾一下优化器的进化历程:

7.1 进化时间线

  1. SGD(随机梯度下降,1950s-1960s):最基础的优化方法

    • 公式:θ = θ - η·∇L
    • 优点:简单直观,内存占用小
    • 缺点:震荡严重,收敛慢,学习率需要手动调节
  2. Momentum(动量法,1964/1999):引入历史梯度

    • 公式:v = β·v + (1-β)·∇L;θ = θ - η·v
    • 改进:减少震荡,加速收敛
    • 不足:学习率仍然固定
  3. AdaGrad(2011):累积所有历史梯度的平方

    • 公式:s = s + (∇L)²;θ = θ - η·∇L/(√s + ε)
    • 改进:自适应学习率
    • 不足:s 单调递增,学习率会越来越小,可能导致训练过早停止
  4. RMSprop(2012):AdaGrad 的改进版

    • 公式:s = β₂·s + (1-β₂)·(∇L)²;θ = θ - η·∇L/(√s+ε)
    • 改进:用 EWMA 代替简单累积,解决了 AdaGrad 学习率单调递减的问题
    • 不足:没有利用动量加速
  5. Adam(2014):Momentum + RMSprop 的完美结合

    • 公式:见第八章
    • 改进:同时利用了动量和自适应学习率

7.2 自然的想法

在这里插入图片描述

小可(激动):把两个方法组合在一起?

博士(微笑):动量法的v做分子,RMSprop的√s做分母——这就是Adam!

一个极其自然的想法诞生了:

如果我们把动量法和 RMSprop 结合在一起,岂不是嘎嘎猛!

动量法解决了方向问题——抵消震荡,加速前进。RMSprop 解决了步长问题——自适应调节学习率。两者结合,不就是完美的优化器吗?


第八章:Adam 合体

8.1 Adam 的工作流程

Adam(Adaptive Moment Estimation,自适应矩估计)由 Diederik P. Kingma 和 Jimmy Ba 在 2014 年提出。它的工作流程就是把动量法和 RMSprop 组装起来:

第一步:计算一阶矩估计(动量)

v=β1⋅v+(1−β1)⋅∇Lv = \beta_1 \cdot v + (1 - \beta_1) \cdot \nabla Lv=β1​⋅v+(1−β1​)⋅∇L

第二步:计算二阶矩估计(RMSprop)

s=β2⋅s+(1−β2)⋅(∇L)2s = \beta_2 \cdot s + (1 - \beta_2) \cdot (\nabla L)^2s=β2​⋅s+(1−β2​)⋅(∇L)2

第三步:合体更新参数

θ=θ−η⋅vs+ε\theta = \theta - \eta \cdot \frac{v}{\sqrt{s} + \varepsilon}θ=θ−η⋅s​+εv​

注意:动量法和 RMSprop 的 β 参数不是同一个,所以需要有所区分,分别用 β₁ 和 β₂ 表示。

  • β₁ 通常取 0.9(控制动量的"记忆"长度 ≈ 10 步)
  • β₂ 通常取 0.999(控制 RMSprop 的"记忆"长度 ≈ 1000 步)

为什么 β₂ 比 β₁ 大很多?因为二阶矩(梯度平方)的变化通常比一阶矩(梯度本身)更剧烈,需要更长的记忆窗口来平滑。

在这里插入图片描述

小可:Adam的完整公式长什么样?

博士:分子是动量v,分母是√s+ε,两个β要区分开来。

到这里,你已经基本成功地推导出了 Adam 的公式!

但是……为什么说是"基本成功"而不是"完全成功"呢?

8.2 "矩"的含义

Adam 的全称是 Adaptive Moment Estimation(自适应矩估计)。这里的"矩"指的是统计学中的矩:

  • 一阶矩(First Moment):均值。在 Adam 中,v 就是梯度的一阶矩估计(指数加权平均)
  • 二阶矩(Second Moment):未中心化的方差。在 Adam 中,s 就是梯度的二阶矩估计(梯度平方的指数加权平均)

Adam 同时维护梯度的一阶矩和二阶矩估计,并用它们来自适应地调整每个参数的更新步长。


第九章:偏差修正——最后的拼图

9.1 初始化为零的问题

在这里插入图片描述

小可:v和s初始化为0会有什么问题?

博士:第一步算出来只有真实值的1/10,步子迈得特别小!

让我们来看一个具体的例子:

  • 假设 v₀ = 0(初始化为零)
  • 当前梯度 ∇L = 30
  • β = 0.9

套进公式:

v1=0.9×0+0.1×30=3v_1 = 0.9 \times 0 + 0.1 \times 30 = 3v1​=0.9×0+0.1×30=3

真实梯度是 30,但估计值只有 3 —— 差了 10 倍!

原因:公式强行把 90% 的权重给了初始化的 0,导致第一步算出来的估计值只有真实梯度的 1/10。

如果不修正的话,Adam 在刚起步的时候步子会迈得特别特别小,半天进入不了状态。这被称为冷启动问题(Cold Start Problem)。

9.2 偏差修正推导

在这里插入图片描述

小可:第一步只有0.1倍,那第二步呢?

博士:第二步要除以(1-β²),第三步除以(1-β³),以此类推。

既然我们知道估计值只有原本大小的 (1-β) 倍,那直接除以 (1-β) 不就还原了吗?

但是,当 t = 2 的时候呢?累计的权重还会只是 (1-β) 这么大吗?

我们来简单推导一下:

t = 1:
v1=0.1⋅g1v_1 = 0.1 \cdot g_1v1​=0.1⋅g1​
累计权重=0.1=1−0.9=1−β1\text{累计权重} = 0.1 = 1 - 0.9 = 1 - \beta^1累计权重=0.1=1−0.9=1−β1
修正:v^1=v11−β1=0.1⋅g10.1=g1✓ 还原!\text{修正:}\hat{v}_1 = \frac{v_1}{1 - \beta^1} = \frac{0.1 \cdot g_1}{0.1} = g_1 \quad \text{✓ 还原!}修正:v^1​=1−β1v1​​=0.10.1⋅g1​​=g1​✓ 还原!

t = 2:
v2=0.9⋅v1+0.1⋅g2=0.09⋅g1+0.1⋅g2v_2 = 0.9 \cdot v_1 + 0.1 \cdot g_2 = 0.09 \cdot g_1 + 0.1 \cdot g_2v2​=0.9⋅v1​+0.1⋅g2​=0.09⋅g1​+0.1⋅g2​
累计权重=0.09+0.1=0.19=1−0.81=1−β2\text{累计权重} = 0.09 + 0.1 = 0.19 = 1 - 0.81 = 1 - \beta^2累计权重=0.09+0.1=0.19=1−0.81=1−β2
修正:v^2=v21−β2=0.09⋅g1+0.1⋅g20.19\text{修正:}\hat{v}_2 = \frac{v_2}{1 - \beta^2} = \frac{0.09 \cdot g_1 + 0.1 \cdot g_2}{0.19}修正:v^2​=1−β2v2​​=0.190.09⋅g1​+0.1⋅g2​​

t = 3:
v3=0.9⋅v2+0.1⋅g3=0.081⋅g1+0.09⋅g2+0.1⋅g3v_3 = 0.9 \cdot v_2 + 0.1 \cdot g_3 = 0.081 \cdot g_1 + 0.09 \cdot g_2 + 0.1 \cdot g_3v3​=0.9⋅v2​+0.1⋅g3​=0.081⋅g1​+0.09⋅g2​+0.1⋅g3​
累计权重=0.081+0.09+0.1=0.271=1−0.729=1−β3\text{累计权重} = 0.081 + 0.09 + 0.1 = 0.271 = 1 - 0.729 = 1 - \beta^3累计权重=0.081+0.09+0.1=0.271=1−0.729=1−β3

t 步通项:
累计权重=∑i=1t(1−β)⋅βt−i=(1−β)⋅1−βt1−β=1−βt\text{累计权重} = \sum_{i=1}^{t} (1-\beta) \cdot \beta^{t-i} = (1-\beta) \cdot \frac{1-\beta^t}{1-\beta} = 1 - \beta^t累计权重=i=1∑t​(1−β)⋅βt−i=(1−β)⋅1−β1−βt​=1−βt

v^t=vt1−β1t,s^t=st1−β2t\hat{v}_t = \frac{v_t}{1 - \beta_1^t}, \quad \hat{s}_t = \frac{s_t}{1 - \beta_2^t}v^t​=1−β1t​vt​​,s^t​=1−β2t​st​​

当 t 越来越大时,(1-βᵗ) 越来越接近 1,修正量就越来越小。当 t 足够大时(比如 t > 1000),偏差修正的影响几乎可以忽略不计。

在这里插入图片描述

小可:所以最终公式要除以(1-β^t)?

博士:对!这就是偏差修正。除以(1-β^t)后就是完整的Adam优化器。

9.3 完整的 Adam 优化器

最终的完整 Adam 优化器包含四个步骤:

步骤1:一阶矩估计
vt=β1⋅vt−1+(1−β1)⋅∇L(θt−1)v_t = \beta_1 \cdot v_{t-1} + (1 - \beta_1) \cdot \nabla L(\theta_{t-1})vt​=β1​⋅vt−1​+(1−β1​)⋅∇L(θt−1​)

步骤2:二阶矩估计
st=β2⋅st−1+(1−β2)⋅(∇L(θt−1))2s_t = \beta_2 \cdot s_{t-1} + (1 - \beta_2) \cdot (\nabla L(\theta_{t-1}))^2st​=β2​⋅st−1​+(1−β2​)⋅(∇L(θt−1​))2

步骤3:偏差修正
v^t=vt1−β1t\hat{v}_t = \frac{v_t}{1 - \beta_1^t}v^t​=1−β1t​vt​​
s^t=st1−β2t\hat{s}_t = \frac{s_t}{1 - \beta_2^t}s^t​=1−β2t​st​​

步骤4:参数更新
θt=θt−1−η⋅v^ts^t+ε\theta_t = \theta_{t-1} - \eta \cdot \frac{\hat{v}_t}{\sqrt{\hat{s}_t} + \varepsilon}θt​=θt−1​−η⋅s^t​​+εv^t​​

默认超参数(通常不需要调):

  • β₁ = 0.9(一阶矩衰减率)
  • β₂ = 0.999(二阶矩衰减率)
  • ε = 10⁻⁸(数值稳定性常数)
  • η = 0.001(学习率)

使用这个优化器来优化参数,就会如鱼得水!

9.4 偏差修正的直觉理解

偏差修正只在训练初期有明显影响。让我们看看修正因子随 t 的变化:

t1-β₁ᵗ (β₁=0.9)1-β₂ᵗ (β₂=0.999)
10.10.001
20.190.002
100.6510.010
1000.999970.095
1000≈ 1.00.632
10000≈ 1.00.99995

可以看到,对于 β₁ = 0.9,偏差修正在 t = 100 之后就几乎不需要了。而对于 β₂ = 0.999,偏差修正的影响会持续到 t = 10000 步左右。

这就是为什么偏差修正是 Adam 中非常重要的一环——在训练的早期阶段(尤其是前几百步),如果不做偏差修正,Adam 的更新步长会严重偏小。

9.5 Adam 的完整代码实现

def adam(grad_fn, theta_init, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8, n_iters=1000):
    """Adam 优化器完整实现"""
    theta = theta_init.copy()
    v = np.zeros_like(theta)  # 一阶矩
    s = np.zeros_like(theta)  # 二阶矩
    
    for t in range(1, n_iters + 1):
        grad = grad_fn(theta)
        
        # 更新一阶矩和二阶矩
        v = beta1 * v + (1 - beta1) * grad
        s = beta2 * s + (1 - beta2) * grad ** 2
        
        # 偏差修正
        v_hat = v / (1 - beta1 ** t)
        s_hat = s / (1 - beta2 ** t)
        
        # 参数更新
        theta = theta - lr * v_hat / (np.sqrt(s_hat) + eps)
    
    return theta

第十章:各优化器对比

在这里插入图片描述

小可(好奇):不同优化器的运动轨迹有什么差别?

博士:SGD震荡严重,Momentum改善方向,RMSprop调节步长,Adam又快又稳直达终点。

10.1 可视化对比

在损失函数的山谷地形上:

  • SGD 的路径是剧烈的 Z 字形震荡,像一条醉汉走的路
  • Momentum 改善了方向,路径变得平滑了一些,但仍然受限于固定学习率
  • RMSprop 自动调节了步长,路径更加合理,但缺乏动量加速
  • Adam 综合了两者优势,路径最平滑、收敛最快

10.2 各优化器详细对比

优化器提出时间核心机制超参数适用场景
SGD1950s固定学习率η简单模型、需要精细调参
SGD+Momentum1964/1999指数加权平均η, βCV 任务、需要更好泛化
AdaGrad2011累积梯度平方η, ε稀疏特征、NLP
RMSprop2012EWMA梯度平方η, β₂, εRNN、序列模型
Adam2014一阶矩+二阶矩η, β₁, β₂, ε通用深度学习
AdamW2017Adam + 权重衰减η, β₁, β₂, ε, λTransformer、大模型
AdaBelief2020信念驱动的自适应η, β₁, β₂, ε通用,Adam 改进版

10.3 什么时候不该用 Adam?

虽然 Adam 是默认的优化器选择,但并不是所有情况下都是最优的:

  1. 需要极致泛化能力时:一些研究表明,SGD + Momentum 在图像分类等任务上可能达到更好的最终精度,虽然收敛速度更慢。这是因为 SGD 的噪声帮助模型找到了更平坦的最优解。

  2. 需要可复现性时:Adam 的自适应机制使得它对初始条件和学习率更敏感,可能不如 SGD 稳定。

  3. 训练大语言模型时:现代大语言模型通常使用 AdamW(Adam + 解耦权重衰减),而不是原始的 Adam。


第十一章:Adam 的变体与改进

11.1 AdamW:解耦权重衰减

Adam 的一个问题是 L2 正则化(权重衰减)在自适应学习率下的效果不好。AdamW 由 Loshchilov 和 Hutter 在 2017 年提出,将权重衰减从梯度更新中解耦出来:

θt=θt−1−η⋅(v^ts^t+ε+λ⋅θt−1)\theta_t = \theta_{t-1} - \eta \cdot \left(\frac{\hat{v}_t}{\sqrt{\hat{s}_t} + \varepsilon} + \lambda \cdot \theta_{t-1}\right)θt​=θt−1​−η⋅(s^t​​+εv^t​​+λ⋅θt−1​)

其中 λ 是权重衰减系数。AdamW 在训练 Transformer 等模型时表现更好,是目前大语言模型训练的标准选择。

11.2 AdaBelief:信念驱动的自适应

AdaBelief(2020)是 Adam 的一个改进版本。它的核心思想是:如果梯度与当前的动量预测一致("信念"强),就用大步长;如果不一致("信念"弱),就用小步长。

st=β2⋅st−1+(1−β2)⋅(∇L−vt)2+εs_t = \beta_2 \cdot s_{t-1} + (1 - \beta_2) \cdot (\nabla L - v_t)^2 + \varepsilonst​=β2​⋅st−1​+(1−β2​)⋅(∇L−vt​)2+ε

与 Adam 不同的是,AdaBelief 使用梯度与动量的差值来衡量梯度的可信度,而不是直接使用梯度的平方。

11.3 RAdam:Rectified Adam

RAdam(2019)通过限制 Adam 的自适应学习率范围,解决了 Adam 在训练后期可能出现的泛化能力下降问题。它的核心思想是在训练初期使用较小的自适应范围,随着训练的进行逐渐放开。


第十二章:思考题

视频最后,作者给大家留下了一个思考题:

我们刚才讨论的是对于初始化为零时的偏差修正。那我们能不能直接令初始值就等于梯度本身呢?也就是说,从第二个更新开始,才套用指数加权移动平均的公式?

这是一个很好的问题!如果把 v₀ 初始化为第一个梯度 g₁,而不是 0,那么第一步的估计就是准确的。但从第二步开始,仍然需要使用 EWMA 公式。

让我们分析一下这种方案的优劣:

优势:

  • 第一步不需要偏差修正,直接就是准确的梯度
  • 减少了计算量(少了一次除法)

劣势:

  • 需要特殊处理第一步(v₀ = g₁ 而不是 v₀ = 0)
  • 代码复杂度增加(需要判断 t == 1 的情况)
  • 对于二阶矩 s 来说,初始化为 g₁² 也可能带来问题

结论:偏差修正方案更加优雅和统一。它不需要对第一步做特殊处理,所有的步骤都使用相同的公式。而且,随着 t 的增大,偏差修正的影响自然衰减,不会对训练后期造成任何干扰。

欢迎大家在评论区讨论你的想法!


总结

在这里插入图片描述

小可(兴奋):从梯度下降到Adam,这一路好精彩!

博士:梯度下降→动量法→RMSprop→Adam,一步一进化,如鱼得水!

Adam 优化器进化链

SGD → Momentum → RMSprop → Adam
(基础)   (方向)    (步长)    (合体)
  ↓         ↓         ↓         ↓
θ-η∇L   v=βv+(1-β)g  s=βs+(1-β)g²  η·v̂/(√ŝ+ε)

核心要点回顾

  1. 梯度下降法是最基础的优化方法,公式简单但存在震荡和收敛慢的问题
  2. 动量法通过指数加权移动平均累积历史梯度,减少震荡、加速收敛,解决了方向问题
  3. RMSprop通过梯度平方的指数加权平均,实现每个参数的自适应学习率,解决了步长问题
  4. Adam将动量法和 RMSprop 完美结合,同时使用一阶矩和二阶矩估计,是深度学习中最常用的默认优化器
  5. 偏差修正解决了初始化为零时的冷启动问题,确保训练初期的更新步长是准确的

为什么选择从问题出发的推导方式?

视频作者一开始也想直接把术语和公式罗列出来,先列出公式再解释。但这样虽然对讲解者来说轻松很多,对观众来说却不那么直观。

最终采用了推导式的方法:从问题出发 → 发现问题 → 思考解决方案 → 一步步推导出结果。这种方法虽然视频时长会稍微长一些,但对理解来说更加友好。

这也是我们这篇博客采用的方式——不是死记硬背公式,而是理解每一个公式背后的为什么。

关键公式速查表

优化器更新公式
SGDθ = θ - η·∇L
Momentumv = β·v + (1-β)·∇L;θ = θ - η·v
RMSprops = β₂·s + (1-β₂)·(∇L)²;θ = θ - η·∇L/(√s+ε)
Adamv = β₁·v+(1-β₁)·∇L;s = β₂·s+(1-β₂)·(∇L)²;v̂=v/(1-β₁ᵗ);ŝ=s/(1-β₂ᵗ);θ = θ - η·v̂/(√ŝ+ε)

学习建议

  • 不要死记 Adam 的公式,而是理解每一步的动机:为什么要用动量?为什么要用梯度平方?为什么要偏差修正?
  • 自己动手推导一遍偏差修正公式,这是理解 Adam 的关键
  • 尝试用不同优化器训练同一个模型,观察收敛曲线的差异。你可以用 PyTorch 的 torch.optim.SGD、torch.optim.Adam 等来对比
  • 思考:Adam 一定是最优的吗? 在什么情况下 SGD 可能更好?(提示:泛化能力、平坦最优解)

PyTorch 实战代码

import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的神经网络
model = nn.Sequential(
    nn.Linear(784, 256),
    nn.ReLU(),
    nn.Linear(256, 10)
)

# 使用 Adam 优化器
optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-8)

# 训练循环
for epoch in range(num_epochs):
    for batch_inputs, batch_labels in dataloader:
        optimizer.zero_grad()
        outputs = model(batch_inputs)
        loss = criterion(outputs, batch_labels)
        loss.backward()
        optimizer.step()

只需要一行代码 optim.Adam(...),你就拥有了一个经过数十年研究进化的最强优化器!

学习率调度器与 Adam 的配合

虽然 Adam 自带自适应学习率,但在实际应用中,我们通常还会配合学习率调度器(Learning Rate Scheduler)一起使用,以在训练后期进一步精细调优。

常见的调度器包括:

  1. StepLR:每隔固定步数将学习率乘以一个衰减因子
  2. CosineAnnealingLR:按余弦曲线衰减学习率
  3. ReduceLROnPlateau:当验证集损失不再下降时降低学习率
  4. Warmup + Decay:先线性增长学习率,再按某种曲线衰减
from torch.optim.lr_scheduler import CosineAnnealingLR

optimizer = optim.Adam(model.parameters(), lr=0.001)
scheduler = CosineAnnealingLR(optimizer, T_max=num_epochs)

for epoch in range(num_epochs):
    train_one_epoch(model, optimizer)
    scheduler.step()  # 更新学习率

Warmup 策略在训练大模型时尤为重要。在训练的最初几个 epoch 中,模型参数随机初始化,梯度可能非常大且不稳定。如果直接用正常学习率训练,可能会导致训练崩溃。Warmup 的做法是:在前 N 个 epoch 中,将学习率从 0 线性增长到目标值,然后再按正常调度器衰减。

Adam 的内存开销

Adam 需要为每个参数维护两个额外的状态变量 v 和 s,因此它的内存开销是 SGD 的 3 倍(参数本身 + v + s)。对于大模型来说,这可能是一个不容忽视的问题。

以一个 7B 参数的语言模型为例:

  • 参数字节数:7 × 10⁹ × 4 bytes (FP32) = 28 GB
  • Adam 额外开销:28 GB × 2 = 56 GB
  • 总计:28 + 56 = 84 GB(仅优化器状态)

这就是为什么训练大语言模型通常需要使用混合精度训练(FP16/BF16)和分布式训练来降低内存消耗。

Adam 在不同框架中的实现

Adam 优化器在各大深度学习框架中都有实现:

PyTorch:

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

TensorFlow/Keras:

optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)

JAX (Optax):

import optax
optimizer = optax.adam(learning_rate=0.001)

各个框架的默认参数略有不同,但核心算法是一致的。


第十三章:Adam 的收敛性分析

13.1 理论保证

Adam 的原始论文(Kingma & Ba, 2014)证明了 Adam 在凸优化场景下的后悔界(Regret Bound):

R(T)=∑t=1Tft(θt)−min⁡θ∑t=1Tft(θ)≤O(T)R(T) = \sum_{t=1}^{T} f_t(\theta_t) - \min_{\theta} \sum_{t=1}^{T} f_t(\theta) \leq O(\sqrt{T})R(T)=t=1∑T​ft​(θt​)−θmin​t=1∑T​ft​(θ)≤O(T​)

这意味着,随着迭代次数 T 的增加,Adam 的累积损失与最优解的累积损失之间的差距以 T\sqrt{T}T​ 的速度增长。这是一个相当好的理论保证。

13.2 收敛速度

对于光滑凸函数,Adam 的收敛速度为 O(1/√T),与 SGD 相同。但对于非凸函数(如神经网络的损失面),Adam 通常比 SGD 收敛更快,因为它利用了梯度的历史信息来加速。

13.3 Reddi 等人的反例(2018)

值得注意的是,Reddi 等人在 2018 年发表了一篇论文《On the Convergence of Adam and Beyond》,指出 Adam 在某些特定场景下可能不收敛。他们构造了一个简单的在线优化问题,在这个问题上 Adam 会持续震荡而无法收敛到最优解。

基于这个问题,他们提出了 AMSGrad 优化器,它使用历史最大二阶矩而不是当前二阶矩来进行更新:

s^t=max⁡(s^t−1,st)\hat{s}_t = \max(\hat{s}_{t-1}, s_t)s^t​=max(s^t−1​,st​)

θt=θt−1−η⋅v^ts^t+ε\theta_t = \theta_{t-1} - \eta \cdot \frac{\hat{v}_t}{\sqrt{\hat{s}_t} + \varepsilon}θt​=θt−1​−η⋅s^t​​+εv^t​​

AMSGrad 保证了在某些条件下的收敛性,但在实际应用中,Adam 的表现通常仍然优于 AMSGrad。


第十四章:Adam 在不同任务中的表现

14.1 计算机视觉(CV)

在图像分类任务上,SGD + Momentum 传统上是首选优化器。许多经典的 CNN 模型(如 ResNet、VGG)的原始论文都使用 SGD + Momentum 进行训练。

然而,随着 Vision Transformer (ViT) 的兴起,AdamW 逐渐成为了 CV 任务的主流选择。ViT 及其变体(Swin Transformer、DeiT 等)几乎都使用 AdamW 进行训练。

典型配置:

  • 学习率:1e-3 到 3e-4
  • 权重衰减:0.05 到 0.1
  • Warmup:5 到 10 个 epoch
  • Cosine 衰减

14.2 自然语言处理(NLP)

在 NLP 领域,Adam 和 AdamW 几乎是标配。从 BERT 到 GPT 系列,从 T5 到 LLaMA,这些大语言模型都使用 AdamW 进行训练。

BERT 原始配置:

  • 学习率:2e-5 到 5e-5(微调阶段)
  • β₁ = 0.9, β₂ = 0.999
  • ε = 1e-6
  • 线性 warmup + 线性衰减

GPT 系列配置:

  • 学习率:6e-4 到 3e-4(预训练阶段)
  • 权重衰减:0.1
  • Cosine 衰减 + warmup

14.3 强化学习(RL)

在强化学习中,Adam 也是非常常用的优化器。无论是策略梯度方法(如 PPO)还是值函数方法(如 DQN),Adam 都能提供良好的训练稳定性。

PPO 的典型 Adam 配置:

  • 学习率:3e-4
  • ε = 1e-5(比默认值稍大,增加稳定性)

14.4 生成对抗网络(GAN)

GAN 的训练 notoriously 不稳定,Adam 在这里也扮演着重要角色。原始的 DCGAN 论文就推荐使用 Adam:

  • 生成器:Adam(lr=0.0002, β₁=0.5)
  • 判别器:Adam(lr=0.0002, β₁=0.5)

注意 β₁ = 0.5 而不是默认的 0.9。较小的 β₁ 使得动量更新更"敏捷",对快速变化的 GAN 训练更友好。


第十五章:超参数调优指南

15.1 学习率 η

学习率是 Adam 中最重要的超参数。虽然 Adam 有自适应学习率机制,但全局学习率 η 仍然决定了更新的"基准大小"。

推荐范围:

  • 默认值:1e-3
  • 微调预训练模型:1e-5 到 5e-5
  • 从零训练:1e-4 到 1e-3
  • 大模型预训练:1e-4 到 6e-4

调参策略:

  1. 从默认值 1e-3 开始
  2. 如果训练损失不下降 → 增大学习率
  3. 如果训练损失震荡 → 减小学习率
  4. 使用学习率搜索法:尝试 1e-4, 3e-4, 1e-3, 3e-3

15.2 β₁(一阶矩衰减率)

β₁ 控制动量的"记忆"长度。

  • 默认值:0.9(约 10 步的有效窗口)
  • GAN 训练:0.5(更敏捷的动量)
  • 如果需要更快的响应:减小到 0.8 或 0.85
  • 如果需要更平滑的更新:增大到 0.95

15.3 β₂(二阶矩衰减率)

β₂ 控制自适应学习率的"记忆"长度。

  • 默认值:0.999(约 1000 步的有效窗口)
  • 一般不建议修改这个值
  • 如果需要更短的记忆:减小到 0.99

15.4 ε(数值稳定性常数)

ε 是一个非常小的数,用来防止分母为零。

  • PyTorch 默认值:1e-8
  • TensorFlow 默认值:1e-7
  • 一般不建议修改
  • 在训练不稳定时,可以尝试增大到 1e-6 或 1e-5

15.5 权重衰减 λ

权重衰减(Weight Decay)是一种正则化技术,等价于 L2 正则化,但在 AdamW 中是解耦的。

  • 默认值:0(无权重衰减)
  • CV 任务:0.01 到 0.1
  • NLP 任务:0.01 到 0.1
  • 大模型:0.1

第十六章:Adam 背后的直觉——一个完整的比喻

让我们用一个完整的比喻来理解 Adam 的工作原理。

想象你是一个盲人,站在山上,想要走到山脚下的最低点。你手里有一根登山杖。

SGD:闭着眼睛走

你每走一步,就用登山杖戳一下地面,感受哪个方向最陡,然后往那个方向走一步。

问题:每次只戳一个点,信息有限。如果地面有小的凹凸,你可能会被误导,走错方向。

Momentum:记住最近的感觉

你不仅戳当前这个点,还记得最近几步的感受。如果最近几步都感觉"左边陡",那你就会更坚定地往左走。

好处:减少了被小凹凸误导的概率,走得更稳。

RMSprop:根据地面情况调整步子

你发现:如果地面很陡(梯度大),你走的步子应该小一点,否则会冲过头。如果地面很平(梯度小),你走的步子可以大一点,否则会走得太慢。

好处:自动适应地形,不会冲过头也不会走太慢。

Adam:两者的完美结合

你既记住了最近几步的感觉(动量),又根据地面的陡峭程度调整了步子(自适应学习率)。这样,你就能又快又稳地走到山脚。

好处:综合了动量和自适应学习率的优势,是最优的行走策略。

偏差修正:刚上山时的适应期

刚上山时,你对周围环境还不熟悉,可能步子会迈得特别小。偏差修正就像是给你一个"新手保护期"——在前几步中,帮你把步子调整到正常大小,让你更快地进入状态。


第十七章:常见误区与注意事项

17.1 Adam 一定比 SGD 好吗?

不是。 虽然 Adam 收敛速度通常更快,但 SGD + Momentum 在达到最优解时可能具有更好的泛化能力。这是因为 SGD 的噪声帮助模型找到了更平坦的最优解,而平坦最优解通常泛化能力更好。

在实践中:

  • Adam:快速收敛,适合快速实验和原型开发
  • SGD + Momentum:可能需要更长时间训练,但最终精度可能更高

17.2 Adam 对学习率不敏感?

错误。 虽然 Adam 有自适应学习率机制,但全局学习率 η 仍然非常重要。学习率太大,Adam 也会发散。学习率太小,Adam 收敛会非常慢。

Adam 对学习率的敏感程度比 SGD 低,但绝不是不敏感。

17.3 需要同时调整 β₁ 和 β₂ 吗?

通常不需要。 默认值(β₁=0.9, β₂=0.999)在绝大多数情况下都工作得很好。除非你有特殊的训练需求,否则不建议修改这两个值。

17.4 Adam 的 ε 可以设为 0 吗?

不建议。 ε 的作用是防止分母为零。虽然在实际训练中 s 很少精确等于 0,但在极端情况下(如梯度消失),s 可能变得非常小。此时如果没有 ε,除法就会溢出。


第十八章:Adam 的未来

18.1 Adam 的局限性

尽管 Adam 是最常用的优化器之一,但它仍然存在一些局限性:

  1. 泛化能力:在某些任务上,Adam 的泛化能力不如 SGD
  2. 内存开销:需要维护两个额外的状态变量
  3. 超参数敏感:虽然比 SGD 好一些,但仍然需要仔细调整学习率
  4. 收敛性保证:在某些特定场景下可能不收敛

18.2 新兴优化器

研究者们一直在努力改进 Adam,提出了一些新的优化器:

  • Lion(2023):使用符号操作代替 Adam 中的自适应机制,内存效率更高
  • Sophia(2023):利用 Hessian 信息来改进自适应学习率
  • Muon(2024):使用二阶矩的矩阵形式来改进 Adam

18.3 Adam 在大规模训练中的角色

随着大语言模型的兴起,AdamW 已经成为了大规模训练的标准优化器。从 GPT-3 到 LLaMA,从 PaLM 到 Claude,几乎所有现代大语言模型都使用 AdamW 进行训练。

在大规模分布式训练中,Adam 的主要挑战是通信开销——需要传输的参数和优化器状态数据量是 SGD 的 3 倍。因此,一些研究者正在探索更高效的优化器设计,以减少分布式训练的通信成本。


第十九章:动手实践——用不同优化器训练模型

19.1 实验设置

让我们用一个简单的实验来对比不同优化器的表现:

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms

# 加载 MNIST 数据集
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)

# 简单的全连接网络
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(28*28, 128)
        self.fc2 = nn.Linear(128, 10)
    
    def forward(self, x):
        x = x.view(-1, 28*28)
        x = torch.relu(self.fc1(x))
        return self.fc2(x)

# 训练函数
def train(model, optimizer, epochs=5):
    criterion = nn.CrossEntropyLoss()
    losses = []
    for epoch in range(epochs):
        epoch_loss = 0
        for batch_idx, (data, target) in enumerate(train_loader):
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, target)
            loss.backward()
            optimizer.step()
            epoch_loss += loss.item()
        avg_loss = epoch_loss / len(train_loader)
        losses.append(avg_loss)
        print(f"Epoch {epoch+1}: avg_loss = {avg_loss:.4f}")
    return losses

# 对比不同优化器
optimizers_config = {
    'SGD': lambda m: optim.SGD(m.parameters(), lr=0.01),
    'SGD+Momentum': lambda m: optim.SGD(m.parameters(), lr=0.01, momentum=0.9),
    'RMSprop': lambda m: optim.RMSprop(m.parameters(), lr=0.001),
    'Adam': lambda m: optim.Adam(m.parameters(), lr=0.001),
}

for name, opt_fn in optimizers_config.items():
    print(f"\n--- {name} ---")
    model = SimpleNet()
    optimizer = opt_fn(model)
    losses = train(model, optimizer)

19.2 预期结果

运行上述代码后,你通常会观察到:

优化器第1轮损失第5轮损失收敛速度
SGD较高较高慢
SGD+Momentum中等中等中等
RMSprop较低较低快
Adam最低最低最快

Adam 通常会在前几轮就达到最低的损失值,展现出最快的收敛速度。


第二十章:Adam 的数学本质——为什么叫"矩估计"?

20.1 统计学中的矩

在统计学中,矩(Moment)是描述概率分布特征的数值。最常见的矩包括:

  • 零阶矩:概率的总和 = 1(归一化条件)
  • 一阶矩:均值(Mean),描述分布的中心位置
  • 二阶矩:未中心化的方差,描述分布的离散程度
  • 三阶矩:偏度(Skewness),描述分布的不对称性
  • 四阶矩:峰度(Kurtosis),描述分布的尖锐程度

Adam 的名字"Adaptive Moment Estimation"就是指它自适应地估计梯度的一阶矩和二阶矩。

20.2 一阶矩估计

Adam 中的一阶矩估计 v 是梯度的指数加权移动平均:

vt=β1⋅vt−1+(1−β1)⋅gtv_t = \beta_1 \cdot v_{t-1} + (1 - \beta_1) \cdot g_tvt​=β1​⋅vt−1​+(1−β1​)⋅gt​

在统计学中,这就是对梯度分布均值的一个估计。如果梯度是一个随机变量,那么 v 就是这个随机变量的期望的一个近似。

20.3 二阶矩估计

Adam 中的二阶矩估计 s 是梯度平方的指数加权移动平均:

st=β2⋅st−1+(1−β2)⋅gt2s_t = \beta_2 \cdot s_{t-1} + (1 - \beta_2) \cdot g_t^2st​=β2​⋅st−1​+(1−β2​)⋅gt2​

这是对梯度平方的期望的一个估计。注意,这不是方差(方差需要减去均值),而是未中心化的二阶矩。

20.4 为什么是一阶矩和二阶矩?

一阶矩告诉我们梯度的"平均方向",二阶矩告诉我们梯度的"平均强度"。

  • 一阶矩(方向):告诉我们参数应该往哪个方向移动
  • 二阶矩(强度):告诉我们每个方向的地形有多陡峭

将两者结合起来,我们就能同时知道"往哪走"和"走多快"——这就是 Adam 的核心智慧。


第二十一章:梯度消失与梯度爆炸

21.1 什么是梯度消失?

在深度神经网络中,梯度在反向传播过程中会逐层相乘。如果每层的梯度都小于 1,那么经过多层之后,梯度会变得非常小——这就是梯度消失(Vanishing Gradient)问题。

数学上,如果每层的梯度约为 0.5,那么经过 10 层后:

0.510=11024≈0.0010.5^{10} = \frac{1}{1024} \approx 0.0010.510=10241​≈0.001

梯度缩小到了原来的千分之一!

21.2 什么是梯度爆炸?

相反,如果每层的梯度都大于 1,那么经过多层之后,梯度会变得非常大——这就是梯度爆炸(Exploding Gradient)问题。

如果每层的梯度约为 2,那么经过 10 层后:

210=10242^{10} = 1024210=1024

梯度扩大到了原来的 1024 倍!

21.3 Adam 如何应对梯度消失?

Adam 在应对梯度消失方面有一定的优势:

当梯度非常小时(梯度消失),s 也会很小。此时 Adam 的更新步长为:

Δθ=η⋅v^s^+ε\Delta\theta = \eta \cdot \frac{\hat{v}}{\sqrt{\hat{s}} + \varepsilon}Δθ=η⋅s^​+εv^​

由于 v 和 s 都很小,它们的比值可能并不小。这意味着即使梯度很小,Adam 仍然可以保持一定的更新速度。相比之下,SGD 的更新步长 η·g 会直接变得非常小。

21.4 Adam 如何应对梯度爆炸?

当梯度非常大时(梯度爆炸),s 会迅速增大。此时 √s 也会很大,Adam 的更新步长会被自动缩小:

Δθ=η⋅v^s^+ε≈η⋅v^s^\Delta\theta = \eta \cdot \frac{\hat{v}}{\sqrt{\hat{s}} + \varepsilon} \approx \eta \cdot \frac{\hat{v}}{\sqrt{\hat{s}}}Δθ=η⋅s^​+εv^​≈η⋅s^​v^​

由于 s ≈ v²(当 β₁ ≈ β₂ 时),所以:

Δθ≈η⋅v^∣v^∣=η⋅sign(v^)\Delta\theta \approx \eta \cdot \frac{\hat{v}}{|\hat{v}|} = \eta \cdot \text{sign}(\hat{v})Δθ≈η⋅∣v^∣v^​=η⋅sign(v^)

这意味着在梯度爆炸时,Adam 的更新步长会被限制在大约 η 的水平,不会无限增大。这提供了一定程度的"自我保护"机制。

不过,对于严重的梯度爆炸问题,仍然建议使用梯度裁剪(Gradient Clipping):

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

第二十二章:Adam 的训练动态可视化

22.1 训练曲线的典型模式

使用 Adam 训练时,损失曲线通常呈现以下模式:

  1. 快速下降期(前 10-20% 的训练时间):损失快速下降,Adam 的自适应机制帮助快速找到好的方向
  2. 平稳收敛期(中间 60-70% 的训练时间):损失缓慢但稳定地下降,模型逐渐接近最优解
  3. 精细调优期(最后 10-20% 的训练时间):如果使用学习率衰减,损失会进一步缓慢下降

22.2 如何判断训练是否正常?

正常信号:

  • 训练损失持续下降(不一定是单调下降,但总体趋势是下降的)
  • 验证损失也在下降(至少在前中期)
  • 梯度的范数在合理范围内(通常 0.1 到 10 之间)

异常信号:

  • 训练损失不下降或上升 → 学习率可能太大
  • 训练损失下降但验证损失上升 → 过拟合
  • 梯度范数非常大(> 100) → 可能梯度爆炸
  • 梯度范数非常小(< 1e-6) → 可能梯度消失

22.3 监控 Adam 的状态

# 监控优化器状态
for name, param in model.named_parameters():
    if param.grad is not None:
        grad_norm = param.grad.norm().item()
        print(f"{name}: grad_norm = {grad_norm:.6f}")

# 查看 Adam 的内部状态
for group in optimizer.param_groups:
    for p in group['params']:
        state = optimizer.state[p]
        if 'exp_avg' in state:  # v
            v_norm = state['exp_avg'].norm().item()
            s_norm = state['exp_avg_sq'].norm().item()
            print(f"v_norm = {v_norm:.6f}, s_norm = {s_norm:.6f}")

第二十三章:从一阶优化到二阶优化

23.1 一阶优化 vs 二阶优化

优化算法可以分为两大类:

一阶优化:只使用梯度(一阶导数)信息

  • SGD、Momentum、AdaGrad、RMSprop、Adam
  • 计算成本低,适合大规模问题
  • 收敛速度:O(1/√T) 到 O(1/T)

二阶优化:使用 Hessian 矩阵(二阶导数)信息

  • Newton’s Method、BFGS、L-BFGS
  • 计算成本高(需要计算和存储 Hessian 矩阵)
  • 收敛速度:O(1/T²) 到二次收敛

23.2 为什么深度学习不用 Newton 法?

Newton 法的更新公式是:

θ=θ−H−1⋅∇L\theta = \theta - H^{-1} \cdot \nabla Lθ=θ−H−1⋅∇L

其中 H 是 Hessian 矩阵(二阶导数矩阵)。

对于一个有 n 个参数的模型:

  • Hessian 矩阵的大小是 n × n
  • 计算 Hessian 的时间复杂度是 O(n²)
  • 求逆的时间复杂度是 O(n³)

对于一个有 1 亿参数的模型:

  • Hessian 矩阵需要 10¹⁶ 个元素
  • 存储需要约 80 PB(80,000 TB)!

这显然是不现实的。因此,深度学习中主要使用一阶优化方法。

23.3 Adam 与二阶方法的联系

有趣的是,Adam 的二阶矩估计 s 可以看作是对 Hessian 对角元素的一个近似。从这个角度来看,Adam 在一定程度上"模拟"了二阶方法的行为,但计算成本只有一阶方法的水平。

更具体地说:

  • Hessian 的对角元素 ≈ 梯度的方差 ≈ s
  • Newton 法的更新 ≈ ∇L / H ≈ ∇L / s ≈ Adam 的更新

这就是为什么 Adam 通常比 SGD 收敛更快——它在某种程度上利用了二阶信息。


第二十四章:Adam 常见问题 FAQ

Q1:Adam 的学习率应该设多少?

默认值是 0.001(1e-3)。对于大多数任务来说,这个值是一个很好的起点。如果你的模型比较大(比如 Transformer),可以尝试用 3e-4。如果你在做微调(fine-tuning),可以用更小的值,比如 1e-5 到 5e-5。

Q2:Adam 需要多少内存?

Adam 需要为每个参数维护两个状态变量(v 和 s),所以它的内存开销是参数量的 2 倍(加上参数本身,总共是 3 倍)。例如,一个 1 亿参数的模型,在 FP32 下需要:

  • 参数:400 MB
  • v 和 s:各 400 MB
  • 总计:约 1.2 GB

如果使用混合精度训练(FP16),Adam 仍然需要在 FP32 下维护状态,所以内存节省有限。

Q3:什么时候应该用 Adam,什么时候用 SGD?

  • 用 Adam:快速实验、原型开发、大多数深度学习任务
  • 用 SGD:追求极致精度、图像分类(CNN)、需要更好泛化能力

Q4:Adam 的 β₁ 和 β₂ 需要调吗?

大多数情况下不需要。默认值 β₁=0.9 和 β₂=0.999 在绝大多数任务上表现良好。唯一的例外是 GAN 训练,通常建议将 β₁ 设为 0.5。

Q5:Adam 会过拟合吗?

Adam 本身不会导致过拟合。过拟合是模型复杂度和数据量之间的关系问题,与优化器选择没有直接关系。不过,有研究表明 Adam 可能倾向于找到尖锐的最优解,而尖锐最优解的泛化能力可能不如平坦最优解。使用权重衰减和数据增强可以缓解这个问题。

Q6:为什么我的 Adam 训练不稳定?

可能的原因:

  1. 学习率太大 → 减小学习率
  2. 梯度爆炸 → 使用梯度裁剪
  3. 数据有问题 → 检查数据预处理
  4. 模型初始化不好 → 尝试不同的初始化方法
  5. Batch size 太小 → 增大 batch size

Q7:AdamW 和 Adam 有什么区别?

AdamW 将权重衰减(weight decay)从梯度更新中解耦出来。在原始的 Adam 中,L2 正则化和权重衰减是等价的,但在自适应学习率下它们不等价。AdamW 的解耦权重衰减在大多数情况下表现更好,特别是对于 Transformer 模型。

Q8:Adam 适合小数据集吗?

对于小数据集,优化器的选择影响相对较小。更重要的是模型的复杂度和正则化策略。不过,Adam 的快速收敛能力在小数据集上仍然是一个优势,因为它可以更快地找到好的解。


第二十五章:Adam 实战——调参案例研究

25.1 案例背景

假设我们要训练一个图像分类模型,使用 ResNet-50 在 CIFAR-10 数据集上进行训练。我们想对比 SGD 和 Adam 的表现。

25.2 实验一:SGD baseline

# SGD 配置
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)

训练 200 个 epoch 后,测试精度达到 95.2%。

25.3 实验二:Adam baseline

# Adam 配置
optimizer = optim.Adam(model.parameters(), lr=3e-4, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)

训练 200 个 epoch 后,测试精度达到 94.8%。

25.4 实验三:Adam + 更长的训练

# Adam + 400 epochs
optimizer = optim.Adam(model.parameters(), lr=3e-4, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=400)

训练 400 个 epoch 后,测试精度达到 95.5%,超过了 SGD。

25.5 实验四:AdamW

# AdamW 配置
optimizer = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.05)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)

训练 200 个 epoch 后,测试精度达到 95.3%,比原始 Adam 高 0.5%。

25.6 结论

从这个案例中,我们可以得出以下结论:

  1. Adam 收敛更快:在前 50 个 epoch 中,Adam 的精度已经接近 SGD 训练 200 个 epoch 的水平
  2. SGD 最终精度可能更高:在相同训练步数下,SGD 的最终精度可能略高于 Adam
  3. Adam 需要更长的训练:如果给 Adam 足够的训练时间,它也能达到甚至超过 SGD 的精度
  4. AdamW 通常是更好的选择:解耦权重衰减带来了更好的泛化能力

25.7 实用建议

基于这个案例,我们给出以下实用建议:

  • 快速实验阶段:用 Adam,收敛快,节省时间
  • 最终模型训练:如果追求极致精度,可以尝试 SGD + Momentum + 长训练
  • Transformer 模型:直接用 AdamW,这是经过验证的最佳实践
  • CNN 模型:SGD + Momentum 是经典选择,但 Adam/AdamW 也完全可行

25.8 学习率预热(Warmup)的重要性

在训练大模型时,Warmup 几乎是必须的步骤。它的作用是让模型在训练初期用较小的学习率慢慢"热身",避免在参数随机初始化的不稳定阶段因为学习率过大而崩溃。

Warmup 的实现方式很简单:在前 N 步中,将学习率从 0 线性增长到目标值。

# PyTorch 实现线性 Warmup
def linear_warmup(current_step, warmup_steps, base_lr):
    if current_step < warmup_steps:
        return base_lr * (current_step / warmup_steps)
    return base_lr

Warmup 的步数通常设为总训练步数的 5%-10%。例如,训练 100000 步的模型,Warmup 设为 5000-10000 步。

25.9 梯度裁剪实战

梯度裁剪是防止梯度爆炸的常用技术。在训练 RNN、Transformer 等模型时,梯度裁剪几乎是标配。

# 梯度裁剪:按范数裁剪
max_norm = 1.0
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)

# 梯度裁剪:按值裁剪
max_val = 0.5
torch.nn.utils.clip_grad_value_(model.parameters(), max_val)

clip_grad_norm_ 是更常用的方法。它会将所有参数的梯度范数缩放到不超过 max_norm。如果原始梯度范数已经小于 max_norm,则不做任何修改。

梯度裁剪的时机是在 backward() 之后、optimizer.step() 之前:

optimizer.zero_grad()
loss.backward()

# 梯度裁剪放在这里
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

optimizer.step()

第二十六章:Adam 的直觉理解——开车下山比喻

让我们用一个生活化的比喻来理解 Adam 的工作原理。

想象你是一个盲人司机,开着一辆车要从山顶开到山脚的停车场。你看不到路,但能通过方向盘感受到路面的坡度。

SGD:闭眼盲开

你每秒钟踩一下刹车,感受路面的坡度,然后猛打方向盘往坡下开。结果就是:车子左右摇摆,开得又慢又不安全。

Momentum:记住之前的手感

你不仅感受当前的坡度,还记得最近几秒的手感。如果之前几次都感觉要往左转,你就会更果断地往左转。这样开车就平稳多了,不会频繁左右摇摆。

RMSprop:根据路面自动调整速度

你发现了一个规律:如果坡度很陡(梯度大),车速应该慢一点,否则会冲出去。如果坡度很缓(梯度小),车速可以快一点,否则开到天黑都到不了。

Adam:两者的完美结合

你既记住了最近的手感(动量),又根据坡度自动调整车速(自适应学习率)。这样,你就能又快又安全地开到山脚。

偏差修正:新手保护期

刚上路时,你对周围环境还不熟悉,可能踩油门会特别小心翼翼。偏差修正就像是给你一个"新手保护期"——在前几步中,帮你把油门调整到正常大小,让你更快地进入状态。


附录:关键公式速查

梯度下降法

θ=θ−η⋅∇L(θ)\theta = \theta - \eta \cdot \nabla L(\theta)θ=θ−η⋅∇L(θ)

动量法

v=β⋅v+(1−β)⋅∇L(θ)v = \beta \cdot v + (1 - \beta) \cdot \nabla L(\theta)v=β⋅v+(1−β)⋅∇L(θ)
θ=θ−η⋅v\theta = \theta - \eta \cdot vθ=θ−η⋅v

RMSprop

s=β2⋅s+(1−β2)⋅(∇L)2s = \beta_2 \cdot s + (1 - \beta_2) \cdot (\nabla L)^2s=β2​⋅s+(1−β2​)⋅(∇L)2
θ=θ−η⋅∇Ls+ε\theta = \theta - \eta \cdot \frac{\nabla L}{\sqrt{s} + \varepsilon}θ=θ−η⋅s​+ε∇L​

Adam(完整版)

vt=β1⋅vt−1+(1−β1)⋅∇L(θt−1)v_t = \beta_1 \cdot v_{t-1} + (1 - \beta_1) \cdot \nabla L(\theta_{t-1})vt​=β1​⋅vt−1​+(1−β1​)⋅∇L(θt−1​)
st=β2⋅st−1+(1−β2)⋅(∇L(θt−1))2s_t = \beta_2 \cdot s_{t-1} + (1 - \beta_2) \cdot (\nabla L(\theta_{t-1}))^2st​=β2​⋅st−1​+(1−β2​)⋅(∇L(θt−1​))2
v^t=vt1−β1t\hat{v}_t = \frac{v_t}{1 - \beta_1^t}v^t​=1−β1t​vt​​
s^t=st1−β2t\hat{s}_t = \frac{s_t}{1 - \beta_2^t}s^t​=1−β2t​st​​
θt=θt−1−η⋅v^ts^t+ε\theta_t = \theta_{t-1} - \eta \cdot \frac{\hat{v}_t}{\sqrt{\hat{s}_t} + \varepsilon}θt​=θt−1​−η⋅s^t​​+εv^t​​

默认超参数:

  • β₁ = 0.9
  • β₂ = 0.999
  • ε = 10⁻⁸
  • η = 0.001

小可(挥手):今天的知识量好大,但是好过瘾!

博士(笑):只要你爱学就好!

更多推荐