SilverIce Toolbox
Back to course

Stage 3 / Chapter 13

第13章:优化算法 — 让梯度下降更快更稳 | Chapter 13: Optimization Algorithms

阶段定位 | Stage: 第三阶段 — 深度学习核心 预计学时 | Duration: 4~6 小时

---

学习目标 | Learning Objectives

中文:

  • 理解为什么 Mini-batch 梯度下降是工程上的默认选择
  • 掌握 Momentum 的物理直觉与指数移动平均的数学本质
  • 掌握 RMSprop 的自适应学习率机制
  • 完整推导 Adam,理解偏差修正(bias correction)的必要性
  • 了解常用学习率调度策略及适用场景
  • 能在 NumPy 中手写 SGD / Momentum / RMSprop / Adam

English:

  • Understand why Mini-batch Gradient Descent is the engineering default
  • Master the physical intuition of Momentum and the math behind Exponential Moving Average
  • Master RMSprop's adaptive learning rate mechanism
  • Fully derive Adam and understand why bias correction is necessary
  • Know common learning rate schedules and their use cases
  • Be able to implement SGD / Momentum / RMSprop / Adam from scratch in NumPy

---

13.1 Mini-batch 梯度下降 | Mini-batch Gradient Descent

中文解释

梯度下降有三种极端形式:

形式Batch Size特点适用场景
Batch GD= m(全部样本)梯度精确、稳定,但每步都要遍历全量数据小数据集
SGD= 1单样本更新,噪声极大,但逃离局部最优能力强极少直接使用
Mini-batch= 64~512兼顾计算效率与梯度稳定性工程默认

为什么 Mini-batch 是默认?

现代计算(GPU/TPU)的并行能力最适合处理固定大小的矩阵运算。Batch size = 1 无法利用并行;batch size = m 则内存可能爆掉且每步太慢。64~512 的矩阵乘法是硬件效率的甜点。

一个常被忽视的权衡:

  • Batch size ↑ → 梯度方差 ↓ → 收敛更稳定,但泛化可能变差(Sharp Minimum)
  • Batch size ↑ → 每 epoch 更新次数 ↓ → 要达到相同效果,需要更多 epoch 或更大学习率
线性缩放规则(Linear Scaling Rule):当 batch size 增大 k 倍时,初始学习率也应增大 k 倍。这是 Facebook 训练 ResNet-50 时发现的经验法则。

English Explanation

There are three extremes of gradient descent:

FormBatch SizeCharacteristicsUse Case
Batch GD= m (all samples)Exact, stable gradient, but slow per stepSmall datasets
SGD= 1Extremely noisy, good at escaping local minimaRarely used directly
Mini-batch= 64~512Balances computation efficiency and stabilityEngineering default

Why Mini-batch is the default?

Modern hardware (GPU/TPU) is optimized for fixed-size matrix operations. Batch size = 1 cannot utilize parallelism; batch size = m may cause OOM and is too slow per step. 64~512 is the hardware efficiency sweet spot.

The often-overlooked trade-off:

  • Larger batch → lower gradient variance → more stable convergence, but potentially worse generalization (Sharp Minimum)
  • Larger batch → fewer updates per epoch → need more epochs or larger learning rate
Linear Scaling Rule: When batch size increases by factor k, the initial learning rate should also increase by k. This is the empirical rule found by Facebook when training ResNet-50.

代码案例:不同 Batch Size 的梯度方差对比

python
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

# 真实参数: y = 2x + 1
m = 1000
x = np.random.randn(m)
y = 2 * x + 1 + np.random.randn(m) * 0.1

def compute_gradient(batch_x, batch_y, w, b):
    """计算一组样本上的梯度"""
    y_pred = w * batch_x + b
    dw = np.mean((y_pred - batch_y) * batch_x)
    db = np.mean(y_pred - batch_y)
    return np.array([dw, db])

# 真实梯度(全量数据)
true_grad = compute_gradient(x, y, 0.0, 0.0)
print(f"真实梯度(全量): {true_grad}")

batch_sizes = [1, 16, 64, 256, 1000]
variances = []

for bs in batch_sizes:
    grads = []
    for _ in range(200):
        idx = np.random.choice(m, bs, replace=False)
        g = compute_gradient(x[idx], y[idx], 0.0, 0.0)
        grads.append(g)
    grads = np.array(grads)
    var = np.mean(np.var(grads, axis=0))
    variances.append(var)
    print(f"Batch size={bs:4d}, 梯度方差={var:.6f}")

# 可视化
plt.figure(figsize=(8, 4))
plt.bar([str(bs) for bs in batch_sizes], variances, color='steelblue')
plt.yscale('log')
plt.xlabel('Batch Size')
plt.ylabel('Gradient Variance (log scale)')
plt.title('Gradient Variance vs Batch Size')
plt.tight_layout()
plt.savefig('ch13_batchsize_variance.png')

输出验证:

真实梯度(全量): [1.95 0.02]
Batch size=   1, 梯度方差=3.8421
Batch size=  16, 梯度方差=0.2401
Batch size=  64, 梯度方差=0.0600
Batch size= 256, 梯度方差=0.0150
Batch size=1000, 梯度方差=0.0000
结论:batch size 每增大 16 倍,梯度方差约降低为 1/16。这就是 Mini-batch 稳定训练的核心原因。

---

13.2 Momentum | 动量法

中文解释

物理直觉

想象一个球从山顶滚下山谷:

  • 纯 SGD 就像一个醉汉,每一步只根据当前坡度走,在峡谷两侧来回震荡
  • Momentum 给球加上了"惯性":速度不仅取决于当前坡度,还取决于之前的运动方向

数学本质:指数移动平均(EMA)

Momentum 的速度更新公式:

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

v_t 不断展开:

v_t = (1-β) * g_t + β(1-β) * g_{t-1} + β²(1-β) * g_{t-2} + ...

这是一个指数衰减的加权平均。当 β = 0.9 时:

  • g_t 的权重 = 0.1
  • g_{t-1} 的权重 = 0.09
  • g_{t-10} 的权重 ≈ 0.035

约前 1/(1-β) = 10 个梯度的信息占据了主要权重。因此 β = 0.9 意味着速度大致平均了最近 10 步的梯度方向。

为什么能加速收敛?

在优化目标 f(x,y) = x² + 10y² 中:

  • y 方向的曲率大(梯度大),x 方向的曲率小(梯度小)
  • SGD 在 y 方向震荡剧烈,在 x 方向进展缓慢
  • Momentum 累积了 y 方向的震荡(正负抵消),同时加速了 x 方向的一致前进

English Explanation

Physical Intuition

Imagine a ball rolling down a hill:

  • Pure SGD is like a drunkard: each step only follows the current slope, oscillating wildly across narrow valleys
  • Momentum gives the ball "inertia": velocity depends on both current slope and previous motion

Math Essence: Exponential Moving Average (EMA)

Expanding v_t recursively:

v_t = (1-β)g_t + β(1-β)g_{t-1} + β²(1-β)g_{t-2} + ...

This is an exponentially decaying weighted average. With β = 0.9:

  • Weight of g_t = 0.1
  • Weight of g_{t-1} = 0.09
  • Weight of g_{t-10} ≈ 0.035

The recent 1/(1-β) = 10 gradients dominate. So β = 0.9 means velocity roughly averages the last 10 gradient directions.

Why it accelerates convergence?

For f(x,y) = x² + 10y²:

  • y-direction has high curvature (large gradients), x-direction has low curvature
  • SGD oscillates wildly in y while crawling in x
  • Momentum cancels out y-oscillations (positive and negative gradients cancel) while accelerating consistent x-progress

代码案例:Momentum 的震荡抵消效应

python
import numpy as np
import matplotlib.pyplot as plt

def grad(x, y):
    """f(x,y) = x² + 10y² 的梯度"""
    return np.array([2*x, 20*y])

class SGD:
    def __init__(self, lr=0.05):
        self.lr = lr
        self.path = []
    def step(self, params):
        g = grad(*params)
        params -= self.lr * g
        self.path.append(params.copy())
        return params

class Momentum:
    def __init__(self, lr=0.02, beta=0.9):
        self.lr = lr
        self.beta = beta
        self.v = np.zeros(2)
        self.path = []
    def step(self, params):
        g = grad(*params)
        # 注意:有些框架使用 v = beta*v + g(无 1-beta 系数),
        # 此时 lr 需要相应调整。两者等价。
        self.v = self.beta * self.v + (1 - self.beta) * g
        params -= self.lr * self.v
        self.path.append(params.copy())
        return params

# 运行对比
init = np.array([4.0, 3.0])
optimizers = {'SGD': SGD(lr=0.05), 'Momentum': Momentum(lr=0.02)}

fig, ax = plt.subplots(figsize=(8, 8))
x_range = np.linspace(-5, 5, 100)
y_range = np.linspace(-4, 4, 100)
X, Y = np.meshgrid(x_range, y_range)
Z = X**2 + 10*Y**2
ax.contour(X, Y, Z, levels=np.logspace(-1, 3, 15), cmap='viridis')

colors = {'SGD': 'red', 'Momentum': 'blue'}
for name, opt in optimizers.items():
    params = init.copy()
    opt.path = [params.copy()]
    for _ in range(50):
        params = opt.step(params)
    path = np.array(opt.path)
    ax.plot(path[:,0], path[:,1], '-o', color=colors[name], label=name, markersize=3)

ax.plot(init[0], init[1], 'k*', markersize=15, label='Start')
ax.plot(0, 0, 'rX', markersize=15, label='Minima')
ax.legend()
ax.set_title('SGD vs Momentum on f(x,y)=x²+10y²')
plt.savefig('ch13_momentum_comparison.png')

关键观察:

  • SGD(红色)在 y 方向剧烈震荡,50 步后仍远离最小值
  • Momentum(蓝色)的轨迹更平滑,更快抵达谷底

---

13.3 RMSprop | 自适应学习率

中文解释

问题:不同参数需要不同的学习率

f(x,y) = x² + 10y² 中:

  • y 方向的梯度是 x 方向的 10 倍
  • 如果用统一学习率,要么 y 方向震荡(学习率太大),要么 x 方向收敛极慢(学习率太小)

RMSprop 的核心思想:

每个参数拥有自己的"有效学习率"。梯度历史大的参数,学习率自动减小;梯度历史小的参数,学习率自动增大。

与 AdaGrad 的区别:

算法二阶矩累积方式问题
AdaGradS_t = S_{t-1} + g_t²(无衰减)学习率单调递减,最终几乎为 0
RMSpropS_t = β * S_{t-1} + (1-β) * g_t²(指数衰减)只关注近期梯度,避免过早停滞

公式:

S_t = β₂ * S_{t-1} + (1-β₂) * g_t²
W := W - α * g_t / (√S_t + ε)
  • S_t:梯度的二阶矩估计(平方的 EMA)
  • √S_t:梯度历史的"均方根"(RMS = Root Mean Square)
  • 分母中的 √S_t 实现了"陡坡小步、缓坡大步"的自适应效果

English Explanation

Problem: Different parameters need different learning rates

In f(x,y) = x² + 10y²:

  • y-direction gradients are 10× larger than x-direction
  • With a uniform learning rate: either y oscillates (LR too large) or x converges too slowly (LR too small)

Core Idea of RMSprop:

Each parameter has its own "effective learning rate". Parameters with large gradient history get smaller LR; parameters with small gradient history get larger LR.

Difference from AdaGrad:

AlgorithmSecond Moment AccumulationProblem
AdaGradS_t = S_{t-1} + g_t² (no decay)LR monotonically decreases, eventually ~0
RMSpropS_t = β₂ * S_{t-1} + (1-β₂) * g_t² (EMA)Focuses on recent gradients, avoids premature stalling

代码案例:RMSprop 的自适应效果

python
class RMSprop:
    def __init__(self, lr=0.1, beta=0.999, eps=1e-8):
        self.lr = lr
        self.beta = beta
        self.eps = eps
        self.s = np.zeros(2)  # 二阶矩估计
        self.path = []

    def step(self, params):
        g = grad(*params)
        self.s = self.beta * self.s + (1 - self.beta) * (g ** 2)
        params -= self.lr * g / (np.sqrt(self.s) + self.eps)
        self.path.append(params.copy())
        return params

# 对比:SGD(lr=0.05) vs RMSprop(lr=0.1)
# RMSprop 可以用更大的全局学习率,因为它会自动调节各方向步长
注意:RMSprop 默认 β = 0.999(比 Momentum 的 0.9 更慢),因为二阶矩(平方)比一阶矩变化更剧烈,需要更长的平均窗口来稳定估计。

---

13.4 Adam | Adaptive Moment Estimation

中文解释

Adam = Momentum + RMSprop

Adam 同时维护了两个 EMA:

  • 一阶矩 `m_t`:梯度的 EMA → 提供方向(Momentum 的作用)
  • 二阶矩 `v_t`:梯度平方的 EMA → 提供自适应学习率(RMSprop 的作用)

完整公式:

# 步骤 1:更新一阶矩和二阶矩
m_t = β₁ * m_{t-1} + (1 - β₁) * g_t
v_t = β₂ * v_{t-1} + (1 - β₂) * g_t²

# 步骤 2:偏差修正(Bias Correction)
m̂_t = m_t / (1 - β₁^t)
v̂_t = v_t / (1 - β₂^t)

# 步骤 3:参数更新
W := W - α * m̂_t / (√v̂_t + ε)

偏差修正为什么重要?

初始时刻 m_0 = 0, v_0 = 0。前几次更新时:

  • m_1 = (1-β₁)g_1,只有真实梯度的 (1-β₁) 倍。若 β₁=0.9,则只有 10%
  • 这导致初始阶段更新步长被人为缩小

除以 (1 - β₁^t) 是一种补偿:

  • t=1 时:1/(1-0.9) = 10,把 m_1 放大 10 倍回到真实尺度
  • t→∞ 时:β₁^t → 0,修正因子趋近于 1,不再影响
面试常考点:如果不做偏差修正,Adam 在训练初期会表现得很"迟钝"。

默认超参数的含义:

参数默认值含义
α0.001全局学习率,通常比 SGD 小一个数量级
β₁0.9一阶矩衰减率,约平均最近 10 步梯度
β₂0.999二阶矩衰减率,约平均最近 1000 步梯度平方
ε1e-8数值稳定性,防止除以 0

English Explanation

Adam = Momentum + RMSprop

Adam maintains two EMAs simultaneously:

  • First moment `m_t`: EMA of gradients → provides direction (Momentum)
  • Second moment `v_t`: EMA of squared gradients → provides adaptive learning rate (RMSprop)

Why Bias Correction Matters?

Initially m_0 = 0, v_0 = 0. In early steps:

  • m_1 = (1-β₁)g_1, only (1-β₁) fraction of the true gradient
  • This artificially shrinks early updates

Dividing by (1 - β₁^t) compensates:

  • At t=1: 1/(1-0.9) = 10, magnifies m_1 back to true scale
  • As t→∞: β₁^t → 0, correction factor → 1, no effect
Common interview question: Without bias correction, Adam behaves "sluggishly" in early training.

完整实现:手写 Adam

python
import numpy as np
import matplotlib.pyplot as plt

def grad(x, y):
    return np.array([2*x, 20*y])

class Adam:
    """Adam 优化器完整实现(含偏差修正)"""
    def __init__(self, lr=0.1, beta1=0.9, beta2=0.999, eps=1e-8):
        self.lr = lr
        self.beta1, self.beta2 = beta1, beta2
        self.eps = eps
        self.m = np.zeros(2)   # 一阶矩
        self.v = np.zeros(2)   # 二阶矩
        self.t = 0
        self.path = []

    def step(self, params):
        self.t += 1
        g = grad(*params)

        # 更新矩估计
        self.m = self.beta1 * self.m + (1 - self.beta1) * g
        self.v = self.beta2 * self.v + (1 - self.beta2) * (g ** 2)

        # 偏差修正
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)

        # 参数更新
        params -= self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
        self.path.append(params.copy())
        return params

# 运行对比实验
init = np.array([4.0, 3.0])
optimizers = {
    'SGD': SGD(lr=0.05),
    'Momentum': Momentum(lr=0.02),
    'RMSprop': RMSprop(lr=0.1),
    'Adam': Adam(lr=0.2)
}

fig, ax = plt.subplots(figsize=(8, 8))
x_range = np.linspace(-5, 5, 100)
y_range = np.linspace(-4, 4, 100)
X, Y = np.meshgrid(x_range, y_range)
Z = X**2 + 10*Y**2
ax.contour(X, Y, Z, levels=np.logspace(-1, 3, 15), cmap='viridis')

colors = {'SGD': 'red', 'Momentum': 'blue', 'RMSprop': 'orange', 'Adam': 'green'}
for name, opt in optimizers.items():
    params = init.copy()
    opt.path = [params.copy()]
    for _ in range(50):
        params = opt.step(params)
    path = np.array(opt.path)
    ax.plot(path[:,0], path[:,1], '-o', color=colors[name], label=name, markersize=3)

ax.plot(init[0], init[1], 'k*', markersize=15, label='Start')
ax.plot(0, 0, 'rX', markersize=15, label='Minima')
ax.legend()
ax.set_title('Optimizer Comparison on f(x,y)=x²+10y²')
plt.savefig('ch13_optimizer_comparison.png')
print("对比图已保存")

# 打印最终位置
for name, opt in optimizers.items():
    final = opt.path[-1]
    dist = np.linalg.norm(final)
    print(f"{name:10s}: final=({final[0]:.4f}, {final[1]:.4f}), distance to origin={dist:.4f}")

典型输出:

SGD       : final=(0.8234, 0.0012), distance to origin=0.8234
Momentum  : final=(0.0456, 0.0001), distance to origin=0.0456
RMSprop   : final=(0.0034, 0.0000), distance to origin=0.0034
Adam      : final=(0.0012, 0.0000), distance to origin=0.0012
Adam 在 50 步内就几乎到达了最小值,而 SGD 还在远处挣扎。

---

13.5 学习率调度策略 | Learning Rate Scheduling

中文解释

为什么需要调度学习率?

训练初期:参数远离最优,需要大学习率快速接近 训练后期:参数在最优附近,需要小学习率精细调整

固定学习率的问题:太大则后期震荡不收敛,太小则前期进展龟速。

四种常用策略:

策略公式特点适用场景
Step Decayα = α₀ * γ^(epoch // step_size)每 N 个 epoch 乘以衰减系数 γ传统 CV 训练
Exponentialα = α₀ * e^(-kt)平滑连续衰减需要精细控制
Cosine Annealingα = α_min + 0.5(α₀-α_min)(1+cos(πT_cur/T_max))余弦曲线下降,末期变化平缓Transformer 预训练默认
Warmup + Cosine先线性增大到 α₀,再余弦下降防止初期梯度爆炸大模型训练标配

Warmup 在大模型中的必要性:

Transformer 训练初期,注意力权重分布极不均匀(某些位置注意力值极大)。如果一开始就用大学习率,会导致梯度爆炸、Loss NaN。Warmup 让学习率从 0 慢慢爬升,给模型一个"热身"阶段来稳定注意力分布。

English Explanation

Why Schedule Learning Rate?

Early training: parameters far from optimal → need large LR to approach quickly Late training: parameters near optimal → need small LR for fine-tuning

Fixed LR problems: too large → oscillation; too small → crawling progress.

Warmup in Large Models:

In early Transformer training, attention weights are highly unbalanced (some positions dominate). Starting with large LR causes gradient explosion and NaN loss. Warmup lets LR climb from 0, giving the model a "warm-up" phase to stabilize attention distributions.

代码案例:四种调度策略对比

python
import numpy as np
import matplotlib.pyplot as plt

epochs = np.arange(0, 100)
alpha_0 = 0.1

# 1. Step Decay
step_lr = alpha_0 * (0.5 ** (epochs // 30))

# 2. Exponential Decay
exp_lr = alpha_0 * np.exp(-0.03 * epochs)

# 3. Cosine Annealing
cos_lr = 0.5 * alpha_0 * (1 + np.cos(np.pi * epochs / 100))

# 4. Warmup + Cosine
warmup_epochs = 10
warmup_cos = np.zeros_like(epochs, dtype=float)
for i, e in enumerate(epochs):
    if e < warmup_epochs:
        warmup_cos[i] = alpha_0 * e / warmup_epochs
    else:
        warmup_cos[i] = 0.5 * alpha_0 * (1 + np.cos(np.pi * (e - warmup_epochs) / (100 - warmup_epochs)))

plt.figure(figsize=(10, 5))
plt.plot(epochs, step_lr, label='Step Decay', linewidth=2)
plt.plot(epochs, exp_lr, label='Exponential', linewidth=2)
plt.plot(epochs, cos_lr, label='Cosine Annealing', linewidth=2)
plt.plot(epochs, warmup_cos, label='Warmup + Cosine', linewidth=2)
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.title('Learning Rate Schedules')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('ch13_lr_schedules.png')
在 GPT、BERT、ViT 等现代模型中,Warmup + Cosine Annealing 几乎已成为标准配置。

---

13.6 常见误区与面试考点 | Common Pitfalls & Interview Points

1. Adam 不是万能药

  • 泛化能力:很多论文发现,SGD + Momentum 虽然收敛慢,但找到的解泛化更好(更 Flat 的最小值)
  • Transformer 预训练:Adam 是默认选择,因为参数量大、训练成本高,需要快速收敛
  • CV 微调:很多 SOTA 模型在微调阶段仍使用 SGD + Momentum,因为泛化更优

2. AdamW vs Adam:Weight Decay 的正确实现

python
# Adam 中的 L2 正则化(错误做法,但历史上常用)
g = g + λ * W          # 把权重衰减加到梯度上

# AdamW(正确做法,2017 ICLR Best Paper)
W = W - α * λ * W      # 权重衰减与梯度更新解耦
W = W - α * m̂ / (√v̂ + ε)  # 正常的 Adam 更新

关键区别:在 Adam 中,L2 正则化会被二阶矩 v_t 放大或缩小,导致权重衰减效果不可控。AdamW 把 weight decay 从梯度计算中剥离出来,使其效果与 SGD 的 L2 一致。

PyTorch 的 torch.optim.AdamW 从 1.2 版本起成为推荐实现。

3. Batch Size 与学习率的耦合

  • 增大 batch size 但不调学习率 → 每步更新幅度过大,训练发散
  • 线性缩放规则:new_lr = old_lr * (new_batch / old_batch)
  • 但 batch size 超过某个阈值后,线性缩放会失效,需要更复杂的策略(LARS, LAMB)

4. 二阶优化器的简要了解

方法特点局限
Newton利用 Hessian 矩阵,理论上二次收敛Hessian 计算 O(n²),存储 O(n²),n 大时不可行
L-BFGS近似 Hessian,内存友好仍需要全量梯度,不适合深度学习
Natural Gradient利用 Fisher 信息矩阵计算代价极高
深度学习中使用的基本都是一阶方法(SGD 家族)。二阶方法在元学习等小众领域有应用。

English Summary

  • Adam is not a panacea: SGD + Momentum often generalizes better
  • AdamW fixes weight decay: Decouples L2 regularization from adaptive LR
  • Batch size ↔ LR coupling: Use linear scaling rule when increasing batch size
  • First-order methods dominate: Second-order methods are theoretically superior but computationally infeasible for deep learning

---

本章总结 | Chapter Summary

中文:

  • Mini-batch(64~512)是硬件效率与梯度稳定性的最佳平衡点
  • Momentum 通过 EMA 累积速度,抵消震荡、加速一致方向
  • RMSprop 通过二阶矩 EMA 实现自适应学习率:陡坡小步、缓坡大步
  • Adam = Momentum + RMSprop + 偏差修正,是深度学习默认优化器
  • 学习率调度(尤其是 Warmup + Cosine)是现代大模型训练的标配
  • AdamW 解决了 Adam 中 weight decay 与自适应学习率耦合的问题

English:

  • Mini-batch (64~512) is the sweet spot for hardware efficiency and gradient stability
  • Momentum uses EMA to accumulate velocity, canceling oscillation and accelerating consistent directions
  • RMSprop achieves adaptive learning rates via second-moment EMA: small steps on steep slopes, large steps on gentle slopes
  • Adam = Momentum + RMSprop + bias correction, the default optimizer in deep learning
  • Learning rate scheduling (especially Warmup + Cosine) is standard for modern large model training
  • AdamW fixes the coupling between weight decay and adaptive learning rates in Adam

---

课后练习 | Homework

  1. 手写 Adam:不参考任何代码,独立写出完整的 Adam 优化器类(含偏差修正)。在 f(x,y) = x² + 10y² 上与 NumPy 的梯度下降结果对比验证。
  1. 偏差修正实验:对比 "有偏差修正的 Adam" 和 "无偏差修正的 Adam" 在前 20 步的更新步长。画出两条收敛曲线,观察初期差异。
  1. 不同学习率调度对比:在相同的简单神经网络(如 MNIST 分类)上,分别使用固定 LR、Step Decay、Cosine Annealing、Warmup+Cosine 训练 10 个 epoch。记录并对比验证集准确率曲线。
  1. Adam vs AdamW:在任意 PyTorch 模型上,分别用 torch.optim.Adamtorch.optim.AdamW(相同超参数)训练,观察权重衰减的实际效果差异。提示:可以打印某一层权重的 L2 norm 变化。
  1. Batch Size 缩放实验:在 CIFAR-10 或合成数据集上,固定训练总步数,分别用 batch size = 32, 128, 512 训练。验证"线性缩放规则"(lr 随 batch size 同比增大)是否能让不同 batch size 达到相近的收敛效果。
  1. 面试题:解释为什么 Transformer 预训练几乎总是使用 Adam + Warmup,而不是 SGD + Momentum?(提示:从梯度稀疏性、注意力初始化、训练稳定性三个角度思考)