优化:把损失曲面一步步压到谷底


文档摘要

优化:把损失曲面一步步压到谷底 本节摘要:训练神经网络,本质上就是「找到山谷的底部」。你手上有损失函数(告诉你模型有多错)和梯度(告诉你哪个方向更糟),现在需要一套「下坡策略」。最朴素的做法是沿梯度反方向走一步、步长由学习率缩放——这就是梯度下降,能用,但有陷阱:学习率太大冲出山谷来回弹跳,太小则爬行万步;撞上鞍点就停下来却未到极小。深度学习里的每一个优化器,都在回答同一个问题:如何更快、更稳地到达谷底?

优化:把损失曲面一步步压到谷底

本节摘要:训练神经网络,本质上就是「找到山谷的底部」。你手上有损失函数(告诉你模型有多错)和梯度(告诉你哪个方向更糟),现在需要一套「下坡策略」。最朴素的做法是沿梯度反方向走一步、步长由学习率缩放——这就是梯度下降,能用,但有陷阱:学习率太大冲出山谷来回弹跳,太小则爬行万步;撞上鞍点就停下来却未到极小。深度学习里的每一个优化器,都在回答同一个问题:如何更快、更稳地到达谷底? 本节从零实现原始梯度下降、SGD+动量、Adam 三大主力,在经典的 Rosenbrock 函数(「易找难跟的窄弯谷」)上对比收敛;讲清动量如何像滚球一样积累速度、抑制震荡;讲清 Adam 如何用一阶矩与二阶矩的滑动平均,给每个权重分配自适应学习率;区分凸与非凸损失、解释高维下鞍点才是真障碍;最后给出学习率调度(步阶、余弦退火、warmup)的实战配置。

对应原课程:Phase 01 · Lesson 08 · optimization(原英文 phases/01-math-foundations/08-optimization/docs/en.md)。前置:第 4~5 节(导数、梯度、自动微分)。

学习目标

阅读完本节,你应当能够:

  1. 从零实现原始梯度下降、SGD+动量、Adam 三种优化器。
  2. 在 Rosenbrock 函数上对比三者的收敛,解释 Adam 为何能按权重自适应学习率
  3. 区分凸与非凸损失景观,解释高维下鞍点的角色。
  4. 配置学习率调度(步阶衰减、余弦退火、warmup)以稳定训练。

一、问题与直觉

你有损失函数(模型有多错)、有梯度(哪个方向更糟),现在需要一套下坡策略。

朴素做法:沿梯度反向走一步,步长由学习率缩放——这就是梯度下降,能用。但「能用」有前提:学习率太大冲出山谷来回弹跳,太小爬行万步,撞上鞍点就停下却没到极小。深度学习的每个优化器都在回答:如何更快更稳到谷底?

1.1 优化的含义

优化即「找到使函数最小(或最大)的输入值」。在 ML 里,函数是损失,输入是权重,训练即优化。

minimize L(w),其中 L=损失函数,w=模型权重(可能百万参数)

1.2 原始梯度下降

最简单的优化器:算损失对每个权重的梯度,把每个权重沿其梯度反方向移动,步长由学习率缩放。

w = w − lr · gradient

整个算法就这一行。

1.3 学习率:最重要的超参数

学习率控制步长,决定一切收敛行为。

没有公式告诉你正确学习率,靠实验找。常用起点:Adam 用 0.001,SGD+动量用 0.01。

1.4 Batch vs SGD vs Mini-batch

  • 批量梯度下降(Batch GD):在整个数据集上算梯度再走一步。稳定但慢。
  • 随机梯度下降(SGD):在单个随机样本上算梯度立即走步。噪声大但快。
  • 小批量(Mini-batch):折中,在 32~256 样本上算梯度再走步。这是大家实际用的
变体 批大小 梯度质量 每步速度 噪声
批量 GD 全数据集 精确
SGD 1 样本 极噪声
小批量 32~256 好估计 平衡 中等

SGD 与小批量的噪声不是 bug,它帮助逃出浅局部极小与鞍点。

1.5 动量:滚下山的球

原始梯度下降只看当前梯度。若梯度在窄谷里来回锯齿,进展缓慢。动量把历史梯度累积成速度项:

v = β · v + gradient w = w − lr · v

类比滚下山的球:它不会在每个颠簸处停下重启,而是在一致方向上加速、在震荡方向上阻尼。β(典型 0.9)控制保留多少历史。

1.6 Adam:自适应学习率

不同权重需要不同学习率。罕遇大梯度的权重,真遇到时该走大步;常遇巨大梯度的权重,该走小步。

Adam(自适应矩估计) 每个权重跟踪两件事:

  1. 一阶矩 m:梯度的滑动平均(像动量);
  2. 二阶矩 v:平方梯度的滑动平均(梯度幅度)。
m = β₁·m + (1−β₁)·gradient v = β₂·v + (1−β₂)·gradient² m̂ = m / (1−β₁ᵗ) 偏差校正 v̂ = v / (1−β₂ᵗ) 偏差校正 w = w − lr · m̂ / (√v̂ + ε)

除以 √v̂ 是关键洞见:大梯度的权重除以大数(有效步小),小梯度的权重除以小数(有效步大)。每个权重得到自己的自适应学习率。默认 lr=0.001, β₁=0.9, β₂=0.999, ε=1e-8,对多数问题都好用。

1.7 学习率调度

固定学习率是妥协:训练早期想大步快进,后期想小步精调。

调度 公式 用例
步阶衰减 每 N 轮 lr × factor 简单、手动控制
指数衰减 lr = lr₀ · decayᵗ 平滑下降
余弦退火 lr = lr_min + 0.5(lr_max−lr_min)(1 + cos(πt/T)) Transformer、现代训练
Warmup + 衰减 线性升温再衰减 大模型,防早期不稳

1.8 凸 vs 非凸

凸函数只有一个极小,梯度下降总能找到(如 f(x)=x²)。神经网络损失非凸,有许多局部极小、鞍点、平坦区。

实践中,高维神经网络的局部极小很少是问题——多数局部极小的损失接近全局极小。鞍点(某些方向极小、某些方向极大)才是真障碍。动量与小批量噪声帮助逃出。

1.9 损失景观可视化

损失是所有权重的函数。百万权重模型的损失景观活在 100 万+1 维空间。我们通过在权重空间随机选两个方向、沿它们切片画 2D 曲面来可视化。

💡 尖锐极小泛化差,平缓极小泛化好。这是 SGD+动量在最终测试精度上常胜过 Adam 的原因之一——它的噪声阻止陷入尖锐极小。

二、从零实现

完整源码见 phases/01-math-foundations/08-optimization/code/

2.1 测试函数:Rosenbrock

经典优化基准,极小在 (1,1),位于「易找难跟」的窄弯谷中:

def rosenbrock(p): x, y = p return (1 - x)**2 + 100 * (y - x**2)**2 def rosenbrock_gradient(p): x, y = p df_dx = -2*(1 - x) + 200*(y - x**2)*(-2*x) df_dy = 200*(y - x**2) return [df_dx, df_dy]

2.2 原始梯度下降

class GradientDescent: def __init__(self, lr=0.001): self.lr = lr def step(self, params, grads): return [p - self.lr * g for p, g in zip(params, grads)]

2.3 SGD + 动量

class SGDMomentum: def __init__(self, lr=0.001, momentum=0.9): self.lr, self.momentum, self.velocity = lr, momentum, None def step(self, params, grads): if self.velocity is None: self.velocity = [0.0]*len(params) self.velocity = [self.momentum*v + g for v, g in zip(self.velocity, grads)] return [p - self.lr*v for p, v in zip(params, self.velocity)]

2.4 Adam

class Adam: def __init__(self, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8): self.lr, self.b1, self.b2, self.eps = lr, beta1, beta2, epsilon self.m = self.v = None; self.t = 0 def step(self, params, grads): if self.m is None: self.m = [0.0]*len(params); self.v = [0.0]*len(params) self.t += 1 self.m = [self.b1*m + (1-self.b1)*g for m, g in zip(self.m, grads)] self.v = [self.b2*v + (1-self.b2)*g**2 for v, g in zip(self.v, grads)] m_hat = [m / (1 - self.b1**self.t) for m in self.m] # 偏差校正 v_hat = [v / (1 - self.b2**self.t) for v in self.v] return [p - self.lr*mh / (vh**0.5 + self.eps) for p, mh, vh in zip(params, m_hat, v_hat)]

2.5 跑通对比

def optimize(optimizer, func, grad_func, start, steps=5000): params = list(start); history = [params[:]] for _ in range(steps): params = optimizer.step(params, grad_func(params)) history.append(params[:]) return history start = [-1.0, 1.0] for opt in [GradientDescent(lr=0.0005), SGDMomentum(lr=0.0001), Adam(lr=0.01)]: h = optimize(opt, rosenbrock, rosenbrock_gradient, start) print(f"final={h[-1]}, loss={rosenbrock(h[-1]):.8f}")

预期:Adam 收敛最快,SGD+动量路径更平滑,原始 GD 在窄谷里进展缓慢。

设计要点:Adam 的偏差校正 1−βᵗ 在训练初期至关重要——否则 m、v 从 0 启动会被严重低估,导致前几步几乎不动。

三、框架对比

实战中用 PyTorch 或 JAX 的优化器,它们处理参数组、权重衰减、梯度裁剪、GPU 加速:

import torch model = torch.nn.Linear(784, 10) sgd = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9) adam = torch.optim.Adam(model.parameters(), lr=0.001) adamw = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01) sched = torch.optim.lr_scheduler.CosineAnnealingLR(adam, T_max=100)

经验法则:

  • Adam(lr=0.001) 起步,对多数问题无需调参。
  • 需要最佳最终精度且能多调参时,换 **SGD+动量(lr=0.01, momentum=0.9)**。
  • Transformer 用 AdamW(Adam + 解耦权重衰减)。
  • 训练超过几轮就总用学习率调度
  • 训练不稳就降学习率;太慢就升。

四、可复用产物

  • outputs/prompt-optimizer-guide.md:一个帮你选择正确优化器的提示。
  • 本节构建的优化器类将在第 3 章(深度学习核心)从零训练神经网络时复现。

源码见 phases/01-math-foundations/08-optimization/code/

五、练习

  1. (Easy) 学习率扫描:在 Rosenbrock 上用 [0.0001, 0.0005, 0.001, 0.005, 0.01] 跑原始 GD,打印 5000 步后损失,找最大仍收敛的学习率。
  2. (Medium) 动量对比:用 [0.0, 0.5, 0.9, 0.99] 跑 SGD+动量,记录每步损失,哪个最快?哪个冲过头?
  3. (Medium) 鞍点逃逸:定义 f(x,y)=x²−y²(原点为鞍点),从 (0.01, 0.01) 出发,对比三种优化器谁能逃出。
  4. (Hard)GradientDescent 加指数衰减调度 lr = lr₀·0.999^step,在 Rosenbrock 上比较有/无衰减。

本节要点回顾

  1. 训练即优化:在权重空间中最小化损失,梯度告诉你下坡方向。
  2. 学习率是最重要的超参:太大发散、太小爬行,靠实验找,Adam 起点 0.001、SGD 起点 0.01。
  3. 小批量是实际标配(32~256),噪声不是 bug 而是逃离浅极小的特性。
  4. 动量把历史梯度累积成速度,抑制锯齿、加速一致方向,β 典型 0.9。
  5. Adam 跟踪每权重的一阶矩与二阶矩,除以 √v̂ 给每权重自适应学习率,偏差校正修复冷启动。
  6. 学习率调度:大步早期快进、小步后期精调;余弦退火与 warmup 是大模型标配。
  7. 凸函数有唯一极小,神经网络损失非凸;高维下鞍点比局部极小更棘手。
  8. 平缓极小泛化好于尖锐极小——这是 SGD+动量在最终精度上常胜 Adam 的原因之一。

下一节,我们换一种衡量「分布相似度」的方式——信息论:熵、交叉熵、KL 散度,以及它们如何统一分类损失、模型压缩与变分推断。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U