优化器对比(SGD、Adam、AdamW、Lion)

一句话概述

优化器是深度学习的"引擎"——决定了参数如何根据梯度进行更新。SGD(随机梯度下降)是最基础的优化器,简单但收敛慢;SGD+Momentum加入"惯性"(动量项),像滚下坡的球越滚越快;Adam(2014年)结合了Momentum和RMSProp的自适应学习率,成为最通用的优化器——每个参数都有独立的自适应学习率;AdamW(2017年)修正了Adam中权重衰减的实现错误,将L2正则化和自适应学习率解耦,显著提升了泛化性能;Lion(2023年,Google)简化了Adam的设计——只使用符号梯度+动量+权重衰减,在部分任务上超越AdamW且更省显存。选择优化器的核心原则:简单任务用SGD+Momentum(泛化好但需要仔细调参),通用任务用AdamW(默认推荐),大模型探索Lion(收敛快、省显存)。

💡 核心要点:①SGD+Momentum加入惯性项加速收敛,泛化性好但需要精细调参 ②Adam自适应学习率,每个参数独立调整,收敛快但可能泛化差 ③AdamW修正了权重衰减的实现,是对Adam的重要改进,推荐默认使用 ④Lion简化设计只用符号梯度,在大模型和视觉任务上表现出色 ⑤优化器选择要根据任务规模和类型:小任务SGD、通用AdamW、大模型Lion

教学与演示

一、从SGD到SGD+Momentum:速度的进化

是什么(定义):SGD参数更新公式为θ_t+1 = θ_t - η·g_t,每次只沿梯度的反方向走固定步长。这种"一步一个脚印"的方式在高曲率方向(狭长山谷形的损失面)上效率极低——梯度在窄方向上反复震荡,在宽方向上进展缓慢。SGD+Momentum引入动量项v_t:v_t = β·v_{t-1} + g_t,θ_{t+1} = θ_t - η·v_t。动量像是"惯性"——累积了历史梯度方向,在震荡方向相互抵消,在一致方向加速前进。典型β=0.9。

大白话 SGD像一个人在浓雾中走下山——每走一步就停下来重新判断方向,走得慢而且在狭窄的"沟里"左右摇摆。SGD+Momentum像是推着一个有惯性的大球下山——球会"记住"之前的运动方向,在一直向下的方向上越滚越快,在左右晃动的方向上自然抵消。这样过沟的速度就快多了。

为什么(原理):损失函数在参数空间中通常有"狭长山谷"——不同方向的曲率差异很大(Hessian矩阵的条件数大)。沿高曲率方向梯度大但方向频繁翻转(震荡),沿低曲率方向梯度小但方向一致(需要走很远离最优)。动量通过指数移动平均(EMA)平滑了梯度方向:震荡方向的梯度正负交替→被平均消掉;一致方向的梯度不断叠加→加速前进。

import numpy as np

# SGD vs SGD+Momentum 的对比实现
# 在一个狭长山谷形的损失面上演示两者的差异

def rosenbrock_loss(x, y, a=1, b=100):
    """Rosenbrock函数(香蕉函数)——狭长山谷形的经典测试函数"""
    return (a - x) ** 2 + b * (y - x ** 2) ** 2

def rosenbrock_grad(x, y, a=1, b=100):
    """Rosenbrock函数的梯度"""
    grad_x = -2 * (a - x) - 4 * b * x * (y - x ** 2)
    grad_y = 2 * b * (y - x ** 2)
    return np.array([grad_x, grad_y])


# 优化器实现
def sgd_step(params, grads, lr):
    return params - lr * grads


class SGDMomentum:
    """带Momentum的SGD"""
    def __init__(self, lr, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.velocity = None
    
    def step(self, params, grads):
        if self.velocity is None:
            self.velocity = np.zeros_like(params)
        self.velocity = self.momentum * self.velocity + grads
        return params - self.lr * self.velocity


# 对比两种优化器在Rosenbrock函数上的收敛轨迹
np.random.seed(42)

init_x, init_y = -1.5, 2.0
total_steps = 2000

# SGD
params_sgd = np.array([init_x, init_y], dtype=float)
trajectory_sgd = [params_sgd.copy()]
for t in range(total_steps):
    grads = rosenbrock_grad(*params_sgd)
    params_sgd = sgd_step(params_sgd, grads, lr=0.002)
    trajectory_sgd.append(params_sgd.copy())

# SGD + Momentum
params_mom = np.array([init_x, init_y], dtype=float)
optimizer_mom = SGDMomentum(lr=0.002, momentum=0.9)
trajectory_mom = [params_mom.copy()]
for t in range(total_steps):
    grads = rosenbrock_grad(*params_mom)
    params_mom = optimizer_mom.step(params_mom, grads)
    trajectory_mom.append(params_mom.copy())

print("=== SGD vs SGD+Momentum:Rosenbrock函数优化 ===\n")

print(f"初始位置: ({init_x}, {init_y})")
print(f"最优位置: (1, 1)\n")

# 对比收敛速度
for name, traj in [("SGD", trajectory_sgd), ("SGD+Momentum", trajectory_mom)]:
    final_pos = traj[-1]
    final_loss = rosenbrock_loss(*final_pos)
    dist_to_opt = np.linalg.norm(final_pos - np.array([1.0, 1.0]))
    
    print(f"  {name}:")
    print(f"    最终位置: ({final_pos[0]:.6f}, {final_pos[1]:.6f})")
    print(f"    最终损失: {final_loss:.2e}")
    print(f"    距离最优: {dist_to_opt:.2e}")

# 对比振荡程度(用y方向的方差衡量)
sgd_y_std = np.std([p[1] for p in trajectory_sgd[-500:]])
mom_y_std = np.std([p[1] for p in trajectory_mom[-500:]])
print(f"\n  最后500步的y方向振荡标准差:")
print(f"    SGD: {sgd_y_std:.4f}")
print(f"    SGD+Momentum: {mom_y_std:.4f}")
print(f"    振荡减少: {(1 - mom_y_std / sgd_y_std) * 100:.1f}%")

print("\n→ SGD+Momentum的动量有效减少了峡谷中的横向振荡")
print("→ 动量在向下方向加速,在震荡方向平滑")
SGD与SGD+Momentum更新公式\(\begin{aligned} \text{SGD:}&\quad \theta_{t+1} = \theta_t - \eta \cdot g_t \\ \text{SGD+Momentum:}&\quad v_t = \beta v_{t-1} + g_t,\quad \theta_{t+1} = \theta_t - \eta \cdot v_t \end{aligned}\)

二、Adam与AdamW:自适应学习率的王者

是什么(定义):Adam(Adaptive Moment Estimation)由Kingma和Ba于2014年提出,结合了Momentum(一阶矩估计m_t)和RMSProp(二阶矩估计v_t)的思想。每个参数有独立的自适应学习率——梯度大的参数学习率自动变小,梯度小的参数学习率自动变大。m_t = β₁·m_{t-1} + (1-β₁)·g_t(动量),v_t = β₂·v_{t-1} + (1-β₂)·g_t²(自适应缩放),加上偏差修正后:θ_{t+1} = θ_t - η·m̂t/(√v̂_t+ε)。AdamW由Loshchilov和Hutter于2017年提出修正——Adam中权重衰减等价于L2正则化会干扰自适应学习率的计算,AdamW将权重衰减从梯度中分离:θ{t+1} = θ_t - η·(m̂_t/(√v̂_t+ε) + λ·θ_t),其中λ是权重衰减系数。

大白话 Adam可以理解为给每个参数配备了"私人教练"。有的参数需要大步快走(梯度小→学习率自动放大),有的参数需要小步慢走(梯度大→学习率自动缩小)。同时Adam还用"记忆"机制(动量)防止震荡。但Adam有个bug——它把"减肥"(权重衰减)和"吃东西的量"(梯度)混在一起算,导致减肥效果受食物影响。AdamW修正了这个bug——把减肥和吃分开,先算吃多少(自适应学习率),再独立减去一个"固定减肥量"(解耦权重衰减)。

为什么(原理):Adam成功的关键是自适应学习率。在稀疏梯度场景(如NLP中的词嵌入——只有少数词在每个batch中出现),不同参数的更新频率差异巨大。SGD对稀疏参数更新极慢(因为它们很少被更新),而Adam自动给稀疏参数分配更大的学习率。此外,Adam对超参数相对不敏感——默认设置(η=1e-3, β₁=0.9, β₂=0.999)在大多数任务上都工作良好。AdamW的改进看似微小但影响深远——解耦权重衰减让正则化强度和自适应学习率互不干扰,显著提升了泛化性能。

import numpy as np

# Adam和AdamW的完整实现与对比
# 展示自适应学习率和解耦权重衰减

class Adam:
    """Adam优化器完整实现"""
    def __init__(self, lr=0.001, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01):
        self.lr = lr
        self.beta1, self.beta2 = betas
        self.eps = eps
        self.weight_decay = weight_decay
        self.m = None  # 一阶矩(动量)
        self.v = None  # 二阶矩(自适应缩放)
        self.t = 0
    
    def step(self, params, grads):
        if self.m is None:
            self.m = np.zeros_like(params)
            self.v = np.zeros_like(params)
        
        self.t += 1
        
        # 更新一阶矩和二阶矩
        self.m = self.beta1 * self.m + (1 - self.beta1) * grads
        self.v = self.beta2 * self.v + (1 - self.beta2) * (grads ** 2)
        
        # 偏差修正(抵消初始化偏差)
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)
        
        # 原始Adam:L2正则化 + 自适应学习率(耦合在一起)
        # 等价于:grads_l2 = grads + weight_decay * params
        update = m_hat / (np.sqrt(v_hat) + self.eps) + self.weight_decay * params
        
        return params - self.lr * update


class AdamW:
    """AdamW优化器——修正权重衰减的实现"""
    def __init__(self, lr=0.001, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01):
        self.lr = lr
        self.beta1, self.beta2 = betas
        self.eps = eps
        self.weight_decay = weight_decay
        self.m = None
        self.v = None
        self.t = 0
    
    def step(self, params, grads):
        if self.m is None:
            self.m = np.zeros_like(params)
            self.v = np.zeros_like(params)
        
        self.t += 1
        
        # 一阶矩和二阶矩更新
        self.m = self.beta1 * self.m + (1 - self.beta1) * grads
        self.v = self.beta2 * self.v + (1 - self.beta2) * (grads ** 2)
        
        # 偏差修正
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)
        
        # AdamW:先做自适应学习率更新,再独立做权重衰减(解耦)
        update = m_hat / (np.sqrt(v_hat) + self.eps)
        new_params = params - self.lr * update  # 自适应更新
        new_params = new_params - self.lr * self.weight_decay * params  # 解耦权重衰减
        
        return new_params


# 对比Adam和AdamW的更新差异
print("=== Adam vs AdamW:解耦权重衰减 ===\n")

np.random.seed(42)

# 模拟一个参数更新的场景
param = np.array([0.5, 1.0, -0.8, 2.0, -1.5])
grad = np.array([0.1, 0.05, -0.03, 0.2, -0.1])

adam = Adam(lr=0.01, weight_decay=0.01)
adamw = AdamW(lr=0.01, weight_decay=0.01)

# 经过几轮迭代后观察差异
param_a = param.copy()
param_w = param.copy()

print(f"初始参数: {param}")
print(f"当前梯度: {grad}\n")

for step in range(5):
    # 模拟梯度变化
    g_a = grad + np.random.randn(5) * 0.02
    g_w = g_a.copy()
    
    param_a = adam.step(param_a, g_a)
    param_w = adamw.step(param_w, g_w)
    
    print(f"Round {step + 1}:")
    print(f"  Adam:  {np.round(param_a, 4)}")
    print(f"  AdamW: {np.round(param_w, 4)}")

# 关键差异分析
print(f"\n【Adam和AdamW的关键差异】")
print(f"Adam: 权重衰减与自适应学习率耦合")
print(f"  - 更新 = lr * (自适应缩放 + weight_decay * param)")
print(f"  - 权重衰减强度受到自适应学习率缩放的影响")
print(f"AdamW: 权重衰减与自适应学习率解耦")
print(f"  - 先做自适应缩放更新,再独立减去 weight_decay * param")
print(f"  - 权重衰减强度恒定,不受梯度分布影响")
print(f"\n→ AdamW在泛化性能上通常优于Adam")
print(f"→ AdamW现已成为PyTorch的默认推荐优化器")
Adam更新公式\(\begin{aligned} m_t &= \beta_1 m_{t-1} + (1-\beta_1)g_t \quad (\text{一阶矩})\\ v_t &= \beta_2 v_{t-1} + (1-\beta_2)g_t^2 \quad (\text{二阶矩})\\ \hat{m}_t &= \frac{m_t}{1-\beta_1^t},\quad \hat{v}_t = \frac{v_t}{1-\beta_2^t} \quad (\text{偏差修正})\\ \theta_{t+1} &= \theta_t - \frac{\eta}{\sqrt{\hat{v}_t} + \epsilon} \hat{m}_t \end{aligned}\)

三、Lion与优化器全景对比

是什么(定义):Lion(EvoLved Sign Momentum)是Google于2023年通过"进化搜索"发现的优化器,已在Google的ViT、扩散模型等训练中验证。它的更新规则惊人地简单——只使用梯度和动量的符号(sign),不需要二阶矩估计:u_t = β₁·m_t + (1-β₁)·g_t(计算更新方向),θ_{t+1} = θ_t - η·sign(u_t)(只用符号),m_{t+1} = β₂·m_t + (1-β₂)·g_t(更新动量)。因为只有sign操作,Lion比AdamW节省了v(二阶矩)的显存,且sign的固定步长具有一定的正则化效果。

大白话 Lion是"大道至简"的极致。Adam像个精密仪器——记住梯度的"速度"(动量)和"加速度"(二阶矩),每个参数都精细调节。Lion则像个果断的将军——只问"往哪个方向走?"(sign),不问"走多远?"(每次都走固定步伐)。简单粗暴的设计居然效果更好——因为固定步长让模型对所有参数一视同仁,不会过度偏向"经常被更新的参数"(这恰好起到了正则化效果)。而且Lion还省了一半显存(不需要存二阶矩v)。

怎么做(完整优化器选择指南)

import numpy as np

# 所有主流优化器的对比实现
# 在一个多极值点的复杂损失面上演示

class SGDMomentum:
    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = 0
    
    def step(self, param, grad):
        self.v = self.momentum * self.v + grad
        return param - self.lr * self.v


class RMSProp:
    """RMSProp:只用二阶矩,不用动量"""
    def __init__(self, lr=0.001, beta=0.99, eps=1e-8):
        self.lr = lr
        self.beta = beta
        self.eps = eps
        self.v = 1.0  # 二阶矩的指数移动平均
    
    def step(self, param, grad):
        self.v = self.beta * self.v + (1 - self.beta) * (grad ** 2)
        return param - self.lr * grad / (np.sqrt(self.v) + self.eps)


class AdamOptimizer:
    def __init__(self, lr=0.001, betas=(0.9, 0.999), eps=1e-8):
        self.lr = lr
        self.beta1, self.beta2 = betas
        self.eps = eps
        self.m = 0
        self.v = 0
        self.t = 0
    
    def step(self, param, grad):
        self.t += 1
        self.m = self.beta1 * self.m + (1 - self.beta1) * grad
        self.v = self.beta2 * self.v + (1 - self.beta2) * (grad ** 2)
        m_hat = self.m / (1 - self.beta1 ** self.t)
        v_hat = self.v / (1 - self.beta2 ** self.t)
        return param - self.lr * m_hat / (np.sqrt(v_hat) + self.eps)


class LionOptimizer:
    """Lion优化器——简化的符号动量"""
    def __init__(self, lr=1e-4, betas=(0.9, 0.99), weight_decay=0.01):
        self.lr = lr
        self.beta1, self.beta2 = betas
        self.weight_decay = weight_decay
        self.m = 0
    
    def step(self, param, grad):
        # 计算更新方向:β₁·m + (1-β₁)·g
        update_direction = self.beta1 * self.m + (1 - self.beta1) * grad
        # 只用符号决定方向
        param_new = param - self.lr * np.sign(update_direction)
        # 权重衰减(解耦)
        param_new = param_new - self.lr * self.weight_decay * param
        # 更新动量:β₂·m + (1-β₂)·g
        self.m = self.beta2 * self.m + (1 - self.beta2) * grad
        return param_new


# 对比所有优化器在多个初始点的收敛表现
print("=== 优化器全景对比 ===\n")

# 复杂的一维损失函数(多个极值点)
def complex_loss(x):
    return x ** 2 + 3 * np.sin(3 * x) + 0.5 * np.cos(8 * x)

def complex_grad(x):
    return 2 * x + 9 * np.cos(3 * x) - 4 * np.sin(8 * x)

# 对比三个不同的初始位置
init_positions = [2.0, 0.5, -2.5]
optimizers = {
    'SGD+Momentum': SGDMomentum(lr=0.01),
    'RMSProp': RMSProp(lr=0.01),
    'Adam': AdamOptimizer(lr=0.01),
    'Lion': LionOptimizer(lr=0.01),
}

for init_name, init_x in [("远点", 2.0), ("近点", 0.5), ("负远点", -2.5)]:
    print(f"【初始位置: {init_name} x0={init_x}】")
    for opt_name, opt_class in optimizers.items():
        opt = opt_class
        opt.__init__()  # 重置优化器
        x = init_x
        losses = []
        for _ in range(500):
            g = complex_grad(x)
            if opt_name == 'SGD+Momentum':
                x = opt.step(x, g)
            elif opt_name == 'RMSProp':
                opt.__init__(lr=0.01)
                x = opt.step(x, g)
            elif opt_name == 'Adam':
                opt.__init__(lr=0.01, betas=(0.9, 0.999))
                x = opt.step(x, g)
            elif opt_name == 'Lion':
                opt.__init__(lr=0.01)
                x = opt.step(x, g)
            losses.append(complex_loss(x))
        print(f"  {opt_name:15s}: 最终位置={x:.4f}, 最终损失={losses[-1]:.4f}, 最低损失={min(losses):.4f}")
    print()

# 显存和速度对比(定性)
print("【显存与速度对比(定性)】")
print(f"{'优化器':<15s} {'参数量倍数':>10s} {'速度':>8s} {'泛化性':>8s} {'调参难度':>8s}")
print("-" * 55)
print(f"  {'SGD':<13s} {'1x':>10s} {'中等':>8s} {'最好':>8s} {'高':>8s}")
print(f"  {'SGD+Momentum':<13s} {'1x':>10s} {'较快':>8s} {'很好':>8s} {'高':>8s}")
print(f"  {'Adam':<13s} {'3x':>10s} {'快':>8s} {'一般':>8s} {'低':>8s}")
print(f"  {'AdamW':<13s} {'3x':>10s} {'快':>8s} {'很好':>8s} {'低':>8s}")
print(f"  {'Lion':<13s} {'2x':>10s} {'更快':>8s} {'很好':>8s} {'低':>8s}")

print("\n→ SGD+Momentum:泛化最好但需要精细调参,适合小型任务")
print("→ AdamW:默认推荐,通用性强,收敛快,泛化好")
print("→ Lion:大模型探索,显存效率高,适合ViT/扩散模型")

概念关系图谱

概念核心含义与AI的关系关联概念
SGD沿梯度反方向走固定步长最基础的优化器,泛化好但收敛慢随机梯度下降、学习率
Momentum累积历史梯度(惯性)加速收敛解决SGD在震荡方向效率低的问题SGD、NAG
Adam自适应学习率+动量最通用的优化器,默认首选动量、RMSProp
AdamW解耦权重衰减的Adam修正版PyTorch默认推荐,泛化优于Adam权重衰减、L2正则化
Lion简化的符号动量优化器大模型和视觉任务的新选择,省显存符号梯度、进化搜索
RMSProp二阶矩自适应学习率Hinton课程提出,是Adam的重要灵感自适应学习率

重点答疑

Q1: 为什么Adam收敛快但SGD泛化好?

这是经典的"自适应优化器泛化差距"现象。原因:①Adam的自适应学习率使参数可以自由选择"快车道"和"慢车道",容易在训练数据上找到"捷径"(overfit),而SGD的均匀学习率隐式地偏好平坦的最小值(flat minima),这通常泛化更好;②Adam的二阶矩估计引入了噪声和偏差,可能导致训练和测试时的不一致。不过AdamW和现代正则化技术(数据增强、dropout等)已经大幅缩小了这一差距。

Q2: AdamW和Adam究竟差在哪里?值得切换吗?

核心差异:Adam中权重衰减(weight decay)和L2正则化等价,且耦合在自适应学习率中——更新量 = η * (自适应缩放 + λ * θ),λ的实际效果被自适应缩放扭曲。AdamW改为真正的解耦权重衰减:更新量 = η * 自适应缩放 - η * λ * θ,λ不受梯度分布影响。结论:应该切换——几乎所有主流框架(PyTorch 2.0+,HuggingFace Transformers)都已默认使用AdamW,它是Adam的直接升级版。

Q3: Lion应该在什么场景下使用?

Lion在Google的大规模实验中表现出色:①Vision Transformer(ViT)训练——比AdamW快且准确率相当;②扩散模型(Imagen)训练——生成质量更好;③语言模型——在中等规模(<10B参数)的llm预训练中有效。但Lion对学习率敏感——推荐lr=1e-4(比Adam的1e-3小一个数量级),且建议配合较大的batch size。小规模实验建议先用AdamW,大规模探索可尝试Lion。

章节单词汇总

英文音标术语/释义
SGD (Stochastic Gradient Descent)/stoʊˈkæstɪk/随机梯度下降,每次用一个batch的梯度更新
Momentum/moʊˈmentəm/动量,累积历史梯度以加速收敛和抑制震荡
Nesterov Momentum/neˈsterɒv/Nesterov加速梯度,在"前瞻"位置计算梯度
AdaGrad/ˈeɪdə ɡræd/自适应学习率根据历史梯度平方累加衰减
RMSProp/ɑːr em es prɑːp/使用指数移动平均替代AdaGrad的梯度平方累加
Adam/ˈædəm/自适应矩估计,结合动量和自适应学习率
AdamW/ˈædəm ˈdʌbəljuː/修正权重衰减实现的Adam变体
Lion/ˈlaɪən/进化发现的符号动量优化器

面试练习

Q1 [单选] Adam相比SGD+Momentum的核心优势是什么?

  • A. 计算速度更快
  • B. 每个参数有独立的自适应学习率
  • C. 不需要学习率
  • D. 显存占用更少
解答:Adam的核心创新是自适应学习率——通过二阶矩估计v_t为每个参数独立调整学习率,梯度大的参数自动减小步长,梯度小的参数自动增大步长。这让Adam对超参数不敏感,在各种任务上开箱即用。

Q2 [单选] AdamW相对于Adam的关键改进是什么?

  • A. 使用更大的学习率
  • B. 引入二阶矩估计
  • C. 将权重衰减与自适应学习率解耦
  • D. 移除动量项
解答:AdamW将权重衰减(weight decay)从梯度更新中分离——先做自适应学习率更新,再独立做权重衰减。这纠正了原始Adam中L2正则化和自适应学习率耦合的问题,显著提升了泛化性能。

Q3 [多选] 以下哪些是SGD+Momentum相对于纯SGD的优势?

  • A. 震荡方向被平滑,收敛更稳定
  • B. 一致方向加速,收敛更快
  • C. 更容易逃离局部最优
  • D. 减少显存占用
  • E. 对学习率敏感度降低
解答:动量通过EMA平滑梯度方向——震荡抵消、一致加速,同时也赋予了"惯性"帮助逃离浅的局部最优,对学习率有一定容忍度。显存占用方面和纯SGD一样(只多存一个动量向量)。

Q4 [单选] Momentum的典型系数β是多少?

  • A. 0.5
  • B. 0.9
  • C. 0.99
  • D. 0.999
解答:β=0.9是动量系数的最常用值,意味着当前梯度贡献10%,历史动量贡献90%。β越大动量越"顽固"(方向记忆越长),β越小越敏捷。0.9是经验最优值。

Q5 [多选] 关于Lion优化器,以下哪些说法正确?

  • A. 只使用梯度和动量的符号(sign)
  • B. 比Adam省显存(不需要存储二阶矩v)
  • C. Google通过进化搜索发现
  • D. 在Transformer和CNN上必定优于Adam
  • E. 推荐学习率比Adam小一个数量级
解答:Lion只使用sign操作,无需二阶矩存储,通过进化搜索发现,推荐lr=1e-4(比Adam的1e-3小一个数量级)。Lion并非在所有任务上都优于AdamW——它在大模型和视觉任务上有优势,但小规模实验上不一定。

Q6 [单选] Adam中的偏差修正(bias correction)是为了解决什么问题?

  • A. 梯度消失
  • B. 学习率过大
  • C. 初始时刻m和v接近0导致的估计偏差
  • D. 梯度噪声
解答:Adam初始化m_0=0, v_0=0。在训练初期(t很小时),EMA估计m_t≈(1-β₁^t)·E[g],远小于真实梯度。偏差修正除以(1-β^t)补偿了初始偏向0的问题,保证早期更新方向正确。

Q7 [单选] 哪个优化器的显存占用最低?

  • A. SGD(1倍参数量)
  • B. Adam(3倍参数量)
  • C. Lion(2倍参数量)
  • D. AdamW(3倍参数量)
解答:SGD只存储参数本身(1倍)。SGD+Momentum需要额外的动量向量(2倍),Adam需要动量+二阶矩(3倍),Lion只需要动量(2倍)。实际大模型训练中,优化器显存开销是重要考虑因素。

Q8 [多选] 以下哪个场景最适合使用SGD+Momentum而非AdamW?

  • A. 小型CNN在CIFAR-10上需要最佳泛化性能
  • B. 有经验的研究者手动精细调参
  • C. 快速原型实验
  • D. Transformer预训练
  • E. 与特定的数据增强和正则化配合使用
解答:SGD+Momentum在小任务上配合精细调参和强大的正则化/增强,往往能获得比AdamW更好的泛化性能(如经典的ResNet在ImageNet上用SGD训练)。但大模型和快速实验用AdamW更省心。

Q9 [单选] Adam的β₂参数通常设为多少?

  • A. 0.9
  • B. 0.999
  • C. 0.99
  • D. 0.5
解答:β₂=0.999控制二阶矩估计的指数衰减,这意味着二阶矩的半衰期约为1000步(ln(0.5)/ln(0.999)≈693)。β₁=0.9控制动量衰减,半衰期约6-7步。β₂更"长久"——自适应学习率需要更长时间的平均。

Q10 [多选] 以下哪些是Nesterov加速梯度(NAG)相对于标准Momentum的特点?

  • A. 在当前参数加上动量后的"前瞻"位置计算梯度
  • B. 理论上在凸优化中有更好的收敛保证
  • C. NAG已经包含在Adam中
  • D. "先看一步再走"比"盲走"更明智
  • E. 显存占用比Momentum少
解答:NAG在θ_t + β·v_{t-1}(前瞻位置)计算梯度,而非当前θ_t。这种"先看一步"使其在凸问题上理论收敛更好。显存占用和Momentum一样(都是一个动量向量)。Adam包含的是标准Momentum而非NAG(NAdam是NAG版的Adam)。