第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:
| Form | Batch Size | Characteristics | Use Case |
|---|---|---|---|
| Batch GD | = m (all samples) | Exact, stable gradient, but slow per step | Small datasets |
| SGD | = 1 | Extremely noisy, good at escaping local minima | Rarely used directly |
| Mini-batch | = 64~512 | Balances computation efficiency and stability | Engineering 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 的梯度方差对比
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.1g_{t-1}的权重 = 0.09g_{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 的震荡抵消效应
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 的区别:
| 算法 | 二阶矩累积方式 | 问题 |
|---|---|---|
| AdaGrad | S_t = S_{t-1} + g_t²(无衰减) | 学习率单调递减,最终几乎为 0 |
| RMSprop | S_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:
| Algorithm | Second Moment Accumulation | Problem |
|---|---|---|
| AdaGrad | S_t = S_{t-1} + g_t² (no decay) | LR monotonically decreases, eventually ~0 |
| RMSprop | S_t = β₂ * S_{t-1} + (1-β₂) * g_t² (EMA) | Focuses on recent gradients, avoids premature stalling |
代码案例:RMSprop 的自适应效果
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, magnifiesm_1back 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
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.0012Adam 在 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.
代码案例:四种调度策略对比
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 的正确实现
# 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
- 手写 Adam:不参考任何代码,独立写出完整的 Adam 优化器类(含偏差修正)。在
f(x,y) = x² + 10y²上与 NumPy 的梯度下降结果对比验证。
- 偏差修正实验:对比 "有偏差修正的 Adam" 和 "无偏差修正的 Adam" 在前 20 步的更新步长。画出两条收敛曲线,观察初期差异。
- 不同学习率调度对比:在相同的简单神经网络(如 MNIST 分类)上,分别使用固定 LR、Step Decay、Cosine Annealing、Warmup+Cosine 训练 10 个 epoch。记录并对比验证集准确率曲线。
- Adam vs AdamW:在任意 PyTorch 模型上,分别用
torch.optim.Adam和torch.optim.AdamW(相同超参数)训练,观察权重衰减的实际效果差异。提示:可以打印某一层权重的 L2 norm 变化。
- Batch Size 缩放实验:在 CIFAR-10 或合成数据集上,固定训练总步数,分别用 batch size = 32, 128, 512 训练。验证"线性缩放规则"(lr 随 batch size 同比增大)是否能让不同 batch size 达到相近的收敛效果。
- 面试题:解释为什么 Transformer 预训练几乎总是使用 Adam + Warmup,而不是 SGD + Momentum?(提示:从梯度稀疏性、注意力初始化、训练稳定性三个角度思考)