AI/ML 详解:优化的几何与前沿

Day 43 · 2026-06-29 · 难度:进阶(数学密集)
面向:有编程经验的非 AI 方向工程师

Day 10 我们讲了「怎么走」——AdamW、学习率、梯度下降的基础机制。今天往深一层问:训练的「地形」长什么样?为什么有的谷底泛化好、有的差?以及当 Adam 不够用时,前沿优化器从哪些方向突破。这是一期关于优化的几何学的内容。

损失景观与平坦极小Loss Landscape & Flat Minima

几何直觉泛化
一句话类比

训练神经网络 = 在一个上亿维的「地形」里找谷底。但谷底有两种:一种是宽阔平坦的盆地,一种是窄而深的尖缝。类比你的系统配置——平坦极小像容差大的配置(某个参数漂移一点照常工作),尖锐极小像踩在钢丝上的配置(任意参数偏一点点系统就崩)。一个模型泛化好不好,很大程度上取决于它停在哪种谷底。

它解决什么问题 + 工作机制

损失函数 L(w) 是参数 w(动辄上亿维)的函数,训练就是用梯度下降找 L 的极小点。但这里有个谜:两个训练 loss 都接近 0 的模型,为什么一个测试集表现好、一个差?

关键洞察(「平坦极小」概念由 Hochreiter & Schmidhuber 1990 年代提出):测试集的 loss 曲面相对训练集会有微小平移(因为训练/测试数据分布略有差异)。如果你停在的极小点很「平」,平移后你仍处在低 loss 区——泛化好;如果很「尖」,平移一点 loss 就飙升——泛化差。平坦 = 对数据分布的扰动鲁棒。

Li et al. 2018 用一种叫 filter normalization 的技术,把上亿维曲面投影到 2D 画出来,做出一个漂亮的发现:ResNet 的残差连接(skip connection)会显著把原本尖锐、混乱、布满沟壑的地形「抚平」成光滑盆地——这从几何上解释了为什么深网加了 skip connection 之后突然变得好训练。

同一维度的 loss 横截面(实线=训练集,虚线=测试集,二者略有平移)

平坦极小(泛化好)
\________/ ← 训练谷底
 \._._._._._/ ← 测试平移后,仍在低 loss 区

尖锐极小(泛化差)
\  / ← 训练谷底(一样低)
 \./  ↑ 测试平移一点,loss 就飙升
↑ 训练 loss 相同,几何形状决定了泛化天差地别
代码示例
import torch, copy

# 量化一个训练好的模型所在极小点的"锐度":
# 在权重邻域里随机扰动,看 loss 最多涨多少
def sharpness(model, loss_fn, batch, rho=0.05, trials=10):
    base = loss_fn(model, batch).item()
    worst = base
    for _ in range(trials):
        m2 = copy.deepcopy(model)
        with torch.no_grad():
            for p in m2.parameters():
                # 半径正比于参数自身尺度的随机扰动
                p.add_(torch.randn_like(p) * rho * p.norm())
        worst = max(worst, loss_fn(m2, batch).item())
    return worst - base   # 邻域最坏 loss 比中心高多少 = 锐度
# 锐度越小 → 极小点越"平" → 通常泛化越好
常见误区 + 实践场景
误区:「训练 loss 越低越好」。错。两个 loss 都逼近 0 的模型,泛化可能天差地别——决定性因素是极小点的几何形状,不是 loss 数值本身。盯着训练 loss 调到极致,反而可能掉进尖锐的过拟合谷。
📌 超级个体场景:BigCat 微调出一个小模型准备上线时,除了看验证 loss,可以用上面的 sharpness 跑一下——同样验证表现下,锐度更低的那个版本对线上数据漂移更稳,值得优先上线。
Takeaway + 思考题
💡 神经网络的泛化能力,写在「极小点的几何形状」里,而不只在 loss 数值里。
🤔 「平坦 = 鲁棒」这个直觉,和你做分布式系统时刻意「留容差余量、不把参数卡在临界值」的工程哲学,是不是同一件事的两种说法?

锐度感知最小化Sharpness-Aware Minimization (SAM)

优化目标鲁棒
一句话类比

如果平坦极小更好,能不能把「找平坦」直接写进训练目标?SAM 就是这么做的。它像混沌工程 / 故障注入——不满足于「当前配置能跑」,而是主动问「在我周围最坏的扰动下还能跑吗?」,专挑邻域里最糟的点来优化,逼自己进入宽盆地。

它解决什么问题 + 工作机制

普通 SGD 只最小化当前点的 loss L(w)。但 Keskar et al. 2016 发现一个著名现象——大 batch 泛化鸿沟:用大 batch 训练容易掉进尖锐极小(因为大 batch 的梯度噪声小,少了把模型「抖出尖缝」的随机扰动),导致泛化变差。

Foret et al. 2020 提出的 SAM 把优化目标从「最小化当前 loss」改成 min-max

minw   max‖ε‖≤ρ   L(w + ε)

逐符号拆解:w 是参数,ε 是加在权重上的扰动向量ρ(rho)是邻域半径(一个超参,比如 0.05),‖·‖ 是向量范数。内层 max 的含义是「在半径 ρ 的小球里,loss 最高能到多少」;外层 min 是「最小化那个最坏值」。直觉:不再问脚下 loss 多低,而问邻域内最高的 loss 多低——只有宽平的盆地才能让「邻域最坏值」也很低。

实现上每步要做两次前向:第一次算梯度,找到「最锐方向」 ε̂ = ρ·g/‖g‖(往坡最陡处先爬一步);第二次在 w+ε̂ 处重新算梯度,这个梯度才用来真正更新。代价是训练慢约一倍,换更好的泛化。

SAM 的两步(在尖锐谷里)

 /\  当前点 w 在尖谷底
/ ●\
   先沿梯度爬到邻域最坏点 w+ε̂(看清这个谷有多尖)
  那一点的梯度更新 → 把 w 推离尖谷、推向宽盆地
↑ 普通 SGD 只看 ● 处梯度;SAM 看"最坏邻居"处的梯度
代码示例
# SAM 的一步:先爬到邻域最差点,再从那里下降(两次前向)
def sam_step(model, loss_fn, batch, optimizer, rho=0.05):
    loss_fn(model, batch).backward()
    # 1) 计算扰动 ê = ρ·g/‖g‖,把权重推到"最锐"方向
    gn = torch.norm(torch.stack(
        [p.grad.norm() for p in model.parameters()]))
    eps = {}
    with torch.no_grad():
        for p in model.parameters():
            e = p.grad * rho / (gn + 1e-12); eps[p] = e; p.add_(e)
    optimizer.zero_grad()
    # 2) 在扰动点重新算梯度——这才是真正用于更新的方向
    loss_fn(model, batch).backward()
    with torch.no_grad():
        for p in model.parameters(): p.sub_(eps[p])  # 退回原点
    optimizer.step()      # 用"邻域最差点"的梯度更新 → 逼向平整区
常见误区 + 实践场景
误区:「SAM 总能提升,无脑加上就行」。不一定。它翻倍了计算成本;在本来就很平的任务上收益很小;收益最显著的是数据量小、容易过拟合的场景(如小数据集视觉任务)。
📌 个人项目场景:BigCat 在小数据集上微调模型(最容易过拟合)时,SAM 是性价比很高的「泛化保险」——多花一倍训练时间,换一个对新数据更稳的模型。
Takeaway + 思考题
💡 SAM 把「找鲁棒解」从训练后的事后祈祷,变成了优化目标本身
🤔 min-max「对最坏情况优化」的思路,和对抗训练、以及你做容量规划时按 p99 而非均值设计系统——是不是同一个数学骨架?

二阶方法Second-Order Methods (K-FAC / Shampoo)

曲率预条件
一句话类比

一阶方法(SGD / Adam)像只看脚下坡度走路——在又窄又长的「山谷」里会左右横跳、走 Z 字,半天下不到底。二阶方法多看一样东西:曲率(地形怎么弯的),于是能直接朝谷底斜切过去。类比:一阶像只看瞬时梯度的拥塞控制,二阶像还掌握了「这条链路的曲率特性」从而一步切到位。

它解决什么问题 + 工作机制

梯度 g 是一阶导(坡度)。理论上最优的 Newton 法Hessian 矩阵 H(二阶导,描述曲率)做预条件(preconditioner)

Δw = − H−1 g

它能纠正「病态」山谷——即各方向尺度差异巨大、Adam 会横跳的那种地形。但 pain point 致命:Hn×nn = 上亿参数 → 既存不下、也求不了逆(复杂度 O(n²)~O(n³))。这就像你绝不会去存一个上亿节点的全连接邻接矩阵

于是有两条「用结构假设把 O(n²) 砍成可承受」的近似路线:

  • K-FAC(Martens & Grosse 2015):把每一层的 Fisher 矩阵(曲率的一种)近似成两个小矩阵的 Kronecker 积 A⊗B——把一个大块分解成两个小因子,既存得下、又可逆。
  • Shampoo(Gupta et al. 2018):为参数张量的每个维度各维护一个小预条件矩阵,而不是一个跨所有参数的巨阵。

两者本质都是「用结构化分解逼近全曲率」。2024–2025 年这类方法(及其变体,如 SOAP)在大模型预训练里重新走红,因为它们能用更少的步数收敛——在超大规模下,步数省下来就是真金白银的 wall-clock。

病态山谷俯视图(横向陡、纵向缓)

一阶 (SGD/Adam)   二阶 (K-FAC/Shampoo)
●╲          
 ╱●           ╲
●╲ ← Z 字横跳      ╲ ← 用曲率拉直
 ╱●             ◎ 直奔谷底
↑ 曲率信息让步子在"陡方向缩、缓方向放",少走弯路
代码示例
# Adam 只用对角"自适应"(把每个参数当独立的);
# 二阶方法用曲率做预条件,能感知参数间的耦合。
from torch_optimizer import Shampoo  # pip install torch-optimizer

opt = Shampoo(
    model.parameters(), lr=1e-3,
    # 为张量每个维度各维护一个小预条件矩阵,
    # 而非一个 n×n 巨阵——把"全曲率"分解成小因子
    update_freq=20)   # 每 20 步才更新一次预条件子(摊薄开销)

for batch in loader:
    opt.zero_grad()
    loss_fn(model, batch).backward()
    opt.step()
常见误区 + 实践场景
误区:「二阶一定比 Adam 快」。不一定。每步的预条件计算和存储开销大,只有当「省下的步数」超过「每步变贵的成本」时才划算;而且实现复杂、对超参更敏感。所以它不是处处适用,而是超大规模预训练这种「步数极贵」场景里的利器。
📌 跨学科理解场景:当 BigCat 读到大厂预训练用了 Shampoo / SOAP,能一眼看穿——这不是炫技,而是在超大规模下「用每步更多计算,换更少总步数」的工程换算。
Takeaway + 思考题
💡 二阶方法的全部艺术,是「在不存全曲率矩阵的前提下,尽量利用曲率信息」。
🤔 Kronecker 分解「用结构假设把 O(n²) 降维」的思路,和你用稀疏 / 分块 / 低秩近似处理大矩阵,是不是同一套压缩哲学?

符号动量与现代优化器Lion & Sophia

前沿优化器
一句话类比

Adam 统治了近十年后,2023 年冒出两个挑战者。Lion 像「极简主义重构」——砍掉 Adam 一半的状态(只留动量),更新只取方向的符号;Sophia 像「轻量二阶」——偷偷估一点曲率,但只花极小代价。

它解决什么问题 + 工作机制

Adam 给每个参数存两个状态:一阶矩 m(动量)和二阶矩 v(梯度平方的滑动平均)。显存 ≈ 2× 参数量。pain point:到百亿参数时,优化器状态本身就吃掉巨量显存。两个挑战者从不同方向找便宜的更新规则:

  • Lion(Chen et al. 2023,「符号动量」):它本身是用程序搜索(把「找优化算法」当成在程序空间里搜索)自动发现的,不是人手设计。它只存动量 m(显存减半),更新量取符号:update = sign(β₁·m + (1−β₁)·g)sign 意味着每个参数的步长幅度都一样(只有方向不同),这本身是一种隐式正则。因为符号更新的等效步长偏大,论文指出 Lion 通常要用比 Adam 小 3–10 倍的学习率。
  • Sophia(Liu et al. 2023):专为 LLM 预训练设计,每隔几步才轻量估一次对角 Hessian 当预条件子,再对更新做逐元素裁剪(clipping)控制最坏步长——用很小的二阶信息换更快收敛。

共同主题:在「Adam 够用、但不够省 / 不够快」的缝隙里,从显存(Lion)和曲率(Sophia)两个方向各找了一条出路。

优化器状态对比(每个参数要存什么)

SGD 0 个 (最省,但慢/糙)
Adam 2 个:动量 m + 二阶矩 v (最稳的默认)
Lion 1 个:只有动量 m (显存减半,更新取符号)
Sophia ~2 个:m + 偶尔更新的对角 Hessian (轻量二阶)
↑ 优化器的进化,是在显存/计算/泛化之间重新分配预算
代码示例
from lion_pytorch import Lion  # pip install lion-pytorch

# Lion 只存动量(不存 Adam 的二阶矩 v)→ 优化器显存减半
opt = Lion(model.parameters(),
           lr=1e-4,            # 通常取 Adam 的 1/3~1/10
           weight_decay=1e-2)

# 核心更新规则(概念示意):
#   update = sign(β1·m + (1-β1)·g)   ← 只取符号,步长幅度统一
#   m      = β2·m + (1-β2)·g          ← 动量用另一组系数更新
for batch in loader:
    opt.zero_grad()
    loss_fn(model, batch).backward()
    opt.step()
常见误区 + 实践场景
误区:「新优化器一定全面碾压 Adam」。现实:Lion / Sophia 只在特定规模 / 任务上有优势,AdamW 仍是最稳的默认。而且换优化器必须重新调学习率(Lion 尤其敏感),盲目照搬常常比 Adam 更差。
📌 决策辅助场景:当 BigCat 读到「某新模型用 Lion 省了显存」,能理解这不是玄学,而是「砍掉一半优化器状态 + 符号更新」的明确取舍——并据此判断它是否适合自己的资源约束。
Takeaway + 思考题
💡 优化器的进化史,是一部「在显存、计算、泛化之间反复重新分配预算」的历史。
🤔 Lion 由程序搜索自动发现、而非人手设计——当「设计算法」本身可以被自动化,研究者的角色会怎么变?

深入资源Further Reading

深入思考Deep Questions

1. 「平坦极小一定泛化好」是定论吗?有没有反例?
不是定论,恰恰是个活跃争论。Dinh et al. 2017(Sharp Minima Can Generalize For Deep Nets)给出一个犀利反驳:利用 ReLU 网络的正缩放不变性——把某层权重 ×k、下一层 ÷k,函数完全不变,但参数空间里的曲率(锐度)可以被任意放大或缩小。也就是说,同一个函数既能显得「尖」也能显得「平」,取决于你怎么参数化。这说明朴素的「锐度」定义不是泛化的可靠度量。后续工作(如自适应锐度、对重参数化不变的锐度度量)试图修补这个漏洞。这个争论的深层启示:geometry 直觉很有力,但「在什么坐标系下度量」本身就是个陷阱——和物理里「曲率依赖于度规」是同一类问题。所以 SAM 之所以仍然有效,更准确的解释或许不是「它找到了客观平坦的点」,而是「它的扰动机制在实践中起到了正则化作用」。
2. 为什么"梯度噪声"反而对泛化有益?小 batch 的随机性到底在做什么?
这是理解现代优化的核心反直觉之一。把 SGD 看成「梯度下降 + 噪声」:每个 batch 算出的梯度 ≈ 真梯度 + 一个随机扰动,batch 越小噪声越大。这个噪声有两个作用:(1) 逃离尖锐极小——尖谷又窄又陡,噪声容易把模型「抖出去」,最后只有宽平的盆地能「困住」带噪的轨迹,于是 SGD 天然偏好平坦解(这正好呼应 Keskar 的发现:大 batch 噪声小 → 困在尖谷 → 泛化差);(2) 类似退火——训练早期大噪声广泛探索,学习率衰减后噪声变小、精细收敛。换句话说,梯度噪声不是要消除的 bug,而是隐式正则化的 feature。这解释了为什么单纯「加大 batch + 等比例加大学习率」并不能无损加速训练——你同时也削弱了这个有益的噪声。这和你在分布式系统里见过的「适度抖动(jitter)反而避免惊群、避免共振」是同构的智慧。
3. Adam 是一种"对角近似的二阶方法"。既然如此,为什么真正的二阶方法没能取代它?
Adam 的 v(梯度平方滑动平均)其实可以看作 Fisher / Hessian 对角线的粗略估计——它给每个参数一个自适应步长,相当于「只保留曲率矩阵的对角线,忽略所有参数间的耦合」。真正的二阶方法(K-FAC / Shampoo)多保留了非对角的耦合信息,理论上更准。但 Adam 统治十年靠的是极致的性价比:对角近似让每步成本几乎和 SGD 一样,且对超参鲁棒、几乎到处能用。二阶方法的耦合信息虽好,但「计算 + 存储预条件子」的成本,常常吃掉「步数变少」带来的收益——只有在步数极其昂贵的超大规模预训练里,这笔账才翻正。这是一个经典的工程权衡题:近似的精度 vs 每步的成本,没有普适最优,只有「在你的规模下哪个划算」。它也解释了 Sophia 的设计哲学——别求全 Hessian,只要对角 + 偶尔更新 + 裁剪,把二阶的好处压到 Adam 量级的成本里。
4. Lion 由"算法搜索算法"自动发现。如果优化器能被搜索出来,这件事的边界在哪里?
Lion 来自一个迷人的范式:把「设计优化算法」形式化成在程序空间里搜索,让机器自己进化出更新规则。它的成功提出一个尖锐问题:哪些原本属于「研究者创造力」的东西,正在变成可搜索的优化问题?边界目前在几处:(1) 搜索空间的设计仍由人定义——你能搜出的算法不会超出你允许的算子组合,框定空间本身是创造性工作;(2) 泛化鸿沟——在小代理任务上搜出的算法未必在大规模上仍好,论文专门花力气做「程序选择与简化」来跨越这道沟;(3) 可解释性——搜出的规则(如 sign 动量)事后需要人去理解「为什么有效」,否则只是黑箱。深层意涵:这是 AI 研究的自指时刻——用机器学习改进机器学习的工具。它不会让研究者失业,而是把研究者的角色从「手工设计具体算法」上移到「设计搜索空间、定义目标、解释发现」。这和 BigCat 追求「AI 超级个体」的内核一致:把可自动化的下移给机器,把自己上移到更高的抽象层。
5. 把这一期串起来:从"地形"到"优化器",贯穿的是什么样的统一视角?
四个概念其实是同一个故事的不同切面,主线是「几何决定一切」。损失景观(概念 1)是地形本身——它的平坦/尖锐决定泛化;SAM(概念 2)是主动选择落点——不让你停在尖谷;二阶方法(概念 3)是读懂地形的曲率——好让步子走得更聪明;Lion/Sophia(概念 4)是在资源约束下对地形的实用近似。把它们连起来看,「训练神经网络」就从一个模糊的「调参炼丹」,变成一个清晰的命题:在一个上亿维的、形状由架构决定的地形上,用有限的显存和计算,找到一个对扰动鲁棒的宽盆地。每个工具都在回答这句话里的某个词——架构怎么塑形(skip connection 抚平地形)、怎么主动找宽盆地(SAM)、怎么走得快(二阶)、怎么走得省(Lion)。这种「把复杂现象还原为一个几何 + 资源的统一框架」的思维方式,正是复杂性科学、分布式系统、乃至佛学「缘起」观共享的底层美感——表象千变万化,结构可以惊人地简洁。