Skip to content

3.8 非凸优化与机器学习 ​

3.1 到 3.7 节的主要结论都以凸性为前提:KKT 条件在凸问题中是充要条件,近端梯度的 O(1/k) 速率需要凸目标,对偶间隙为零需要 Slater 条件。机器学习的目标函数几乎全部非凸:多层网络的复合结构、矩阵分解的乘积形式、隐变量模型的联合优化,都不满足凸性。

非凸带来的第一个变化是目标的重设。一般非凸问题的全局最优是 NP 难的,求全局最优在计算上不可行;可行的目标变成找到梯度很小、曲率不坏的稳定点,并让这些点具有良好的泛化表现。第二个变化是理论工具的更替:收敛性分析从「最优值误差」转向「梯度范数」与「二阶条件」,对泛化的解释需要引入隐式正则与损失景观的概念。

本节按这条线索展开。3.8.1 给出非凸问题的基本图景与梯度下降的收敛结论;3.8.2 讨论鞍点、逃逸机制与二阶方法;3.8.3 进入机器学习特有的现象:过参数化、插值、极小值的连通性与双下降;3.8.4 讨论隐式正则与平坦极小值;3.8.5 汇总深度学习训练实践中的优化配置。

3.8.1 非凸问题的基本图景 ​

最优性条件的变化 ​

3.3 节的一阶与二阶必要条件在非凸情形下仍然成立:x∗ 局部最优且可微时 ∇f(x∗)=0;二阶连续可微时还有 ∇2f(x∗)⪰0。变化的是充分性方向。凸函数上「∇f=0」直接推出全局最优;非凸函数上同样的条件只给出稳定点(stationary point),它可能是局部极小、局部极大或鞍点。满足一阶条件且 Hessian 半正定的点称为二阶稳定点(second-order stationary point),它仍然可能是局部极大之外的坏点,但排除了曲率明确的鞍点。

定义上的排序是:全局最优 ⊂ 局部最优 ⊂ 二阶稳定点 ⊂ 一阶稳定点。非凸优化算法的实际目标落在后两类上:证明算法能到达一阶或二阶稳定点,再用实验检验这些点是否足够好。

非凸问题的难解性 ​

非凸问题的全局最优在一般情形下是 NP 难的,这是问题本身的性质:稀疏回归、带秩约束的矩阵分解、含离散变量的连续松弛,都能嵌入一般的非凸优化框架,而这些问题的精确求解已知没有多项式时间算法,因此不存在能普遍保证找到全局最优的算法。可行的目标只能由三项组成:找到梯度范数小于阈值的点(一阶稳定);在合理步数内找到二阶稳定点;稳定点的目标值分布足够集中(大多数稳定点的函数值相近),使选择哪个稳定点不影响结果。

第三项是机器学习中非凸优化能够成功的关键假设,3.8.3 与 3.8.4 节给出支持它的证据与限制。

梯度下降收敛到一阶稳定点 ​

把 3.3 节的下降引理用到非凸情形,可以得到一个不依赖凸性、只依赖光滑性与目标有下界的结论。设 ∇f 以 L 为 Lipschitz 常数,步长取 1/L,则每次迭代满足

f(xk+1)≤f(xk)−12L∥∇f(xk)∥2

把 K 步的下降量相加,并利用 f 有下界(记 Δ=f(x0)−f∗):

mink≤K∥∇f(xk)∥2≤2LΔK⟹K=O(LΔϵ2) 步使某个 ∥∇f(xk)∥≤ϵ

结论是 O(1/ϵ2) 的迭代复杂度,与凸情形对比可以看清代价:凸问题要达到 f(x)−f∗≤ϵ 只需 O(1/ϵ) 步(因为梯度小直接推出目标值接近最优)。非凸情形多出的因子 1/ϵ 换来的是无需凸性,而且结论针对梯度范数这一更弱的目标。

区间 K=O(1/ϵ2) 的最优性在一般假设下无法改进:存在光滑且梯度可算的函数类,任何一阶方法的复杂度都不低于这一量级。算法改进的空间只在常数因子、对 L 的依赖方式与二阶信息的利用上。

数值实验:多极小值问题的吸引域 ​

第一个实验展示非凸优化对初值的依赖。目标函数取

f(x,y)=(x2−1)2+(y2−1)2

它有两个方向的解耦结构:每个变量的极小值在 ±1 处,因此共有四个全局极小点 (±1,±1);坐标轴上的点 (0,0)、(±1,0)、(0,±1) 是稳定点,(0,0) 是局部极大,其余四个是鞍点。从 41×41 的网格初值出发用固定步长的梯度下降,统计每个极小点的吸引域。

python
import numpy as np

def grad2(p):
    return np.array([4 * p[0] * (p[0] ** 2 - 1), 4 * p[1] * (p[1] ** 2 - 1)])

mins = {(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0}
saddles = 0
steps_list = []
for a in np.linspace(-2, 2, 41):
    for b in np.linspace(-2, 2, 41):
        p = np.array([a, b])
        for k in range(1, 20001):
            p = p - 0.1 * grad2(p)
            if np.linalg.norm(grad2(p)) < 1e-8:
                break
        key = (int(np.round(p[0])), int(np.round(p[1])))
        if key in mins:
            mins[key] += 1
            steps_list.append(k)
        else:
            saddles += 1
print("41 x 41 = 1681 个起始点,步长 0.1:")
for key, cnt in mins.items():
    print(f"  收敛到极小值 {key}: {cnt} 个点")
print(f"  收敛到鞍点: {saddles} 个点(全部落在坐标轴上)")
print(f"  步数中位数 = {np.median(steps_list):.0f}")

输出:

text
41 x 41 = 1681 个起始点,步长 0.1:
  收敛到极小值 (1, 1): 400 个点
  收敛到极小值 (1, -1): 400 个点
  收敛到极小值 (-1, 1): 400 个点
  收敛到极小值 (-1, -1): 400 个点
  收敛到鞍点: 81 个点(全部落在坐标轴上)
  步数中位数 = 17

四个极小点的吸引域各占 400 个网格点,完全对称,这是函数关于两个坐标轴分别对称的结果。收敛速度很快(中位数 17 步),原因是四阶多项式在远离原点时梯度增长快,步长 0.1 在绝大部分区域都稳定。

81 个点收敛到鞍点,它们全部落在坐标轴上(a=0 或 b=0)。原因是坐标轴在迭代下不变:初值落在 x=0 上时,梯度在该方向的分量恒为零,迭代永远留在这条直线上,最终停在轴上的鞍点。这个现象在实际优化中对应参数对称性问题:网络中存在置换对称与尺度对称时,某些初值或结构会使梯度在部分方向上恒为零,随机初始化与对称性打破(随机偏置、不同的初始化尺度)就是应对手段。

非凸问题的「好解」标准

这个实验说明了两个要点:吸引域的大小由函数结构决定,与算法无关;鞍点只有在特殊初值下才会被「停留」,一般的初值都会被负曲率方向推开(3.8.2 节量化这一逃逸过程)。实际训练中很少精确落在鞍点上,更常见的是平的区域(梯度很小但不是鞍点,例如 Sigmoid 饱和区),那里梯度衰减导致迭代停滞,处理手段(归一化、残差连接、激活函数的选择)针对的正是这种区域。

3.8.2 鞍点、逃逸与二阶方法 ​

鞍点的判定与梯度下降的动力学 ​

鞍点是梯度为零而 Hessian 不定的点:存在负特征值的方向,函数在该方向下降;也存在正特征值的方向,函数在该方向上升。Hessian 负特征值的个数称为指标(index),指标为 k 的鞍点在损失景观的几何分析中扮演重要角色。

梯度下降在鞍点附近的动力学可以用线性化读出。在鞍点 xs 附近记 dk=xk−xs,迭代写作

dk+1=(I−η∇2f(xs))dk

即每个 Hessian 特征方向独立演化:特征值为 λi 的方向按因子 |1−ηλi| 缩放。对 λi>0(上升方向),因子小于 1,分量衰减;对 λi<0(下降方向),因子变成 1+η|λi|>1,分量指数增长。梯度下降会自行离开鞍点,逃逸速度由负特征值的幅度决定。这一结论有两个重要的定量含义。

逃逸时间与初始扰动的大小成对数关系:初始扰动为 ϵ 时,需要约 log⁡(1/ϵ)/log⁡(1+η|λ|) 步让负曲率方向的分量放大到常数。也就是说初值小到 10−12 也只增加有限步数,这是梯度下降在鞍点附近表现良好的原因之一。逃逸时间与步长成反比:步长越小(或负特征值越小),逃逸越慢,极端情形(|λ| 很小、步长很小)会看到长时间的平台。

二阶稳定与算法修正 ​

把「逃逸」从被动等待变成主动利用,得到二阶方法。三种典型手段:

负曲率步。检测到 Hessian 有负特征值时,沿对应特征向量移动一步。检测手段是(截断)Lanczos 迭代或幂法求最小特征值方向,或者用「Hessian-向量乘积」的有限差分估计((∇f(x+hv)−∇f(x))/h)。代价是若干次梯度求值。

信赖域与三次正则化。3.5 节的信赖域方法本身就处理不定的二阶模型:在半径约束下沿负曲率方向前进。三次正则化的子问题 minp∇fTp+12pTBp+M6∥p∥3 在不依赖半径的情况下保证有下降解,配合同样的检测手段,得到 O(1/ϵ1.5) 量级到达二阶稳定点的复杂度(比 O(1/ϵ2) 的一阶保证更快)。

扰动梯度下降与随机重启。每次迭代加小噪声,或从随机初值重启多次:噪声的作用是把分量从极小的初值抬高,缩短逃逸时间。理论结果是扰动梯度下降在多项式时间内到达二阶稳定点,界为 O(ϵ−4) 量级(针对 (ϵ,ϵ) 意义下的二阶稳定点),阶数高于一阶保证,含义是「理论上避开鞍点比找到一阶稳定点更贵,但仍然是多项式时间」。

高阶鞍点的处理

上面的动力学分析建立在鞍点附近的二次近似上。当 Hessian 在鞍点处有零特征值时(退化的高阶鞍点),负曲率方向的信号被高阶项掩盖,需要更高阶的修正或更强的噪声。这类情形在对称性强的模型中常见(例如 f(x)=x4 在 x=0 处梯度与 Hessian 都为零),对策是噪声注入与随机初始化,而不是依赖二阶信息。

数值实验:鞍点处的逃逸时间 ​

取一个具有严格鞍点的函数

f(x,y)=x22−y22+y44

它在 (0,0) 处是鞍点:Hessian 为 diag(1,−1),上升方向是 x,下降方向是 y;沿 y 方向函数在 ±1 处取得极小值,因此鞍点在两个极小值之间的山路上。梯度为 (x, −y+y3);固定步长 η 时 y 方向的迭代为 yk+1=yk(1+η−ηyk2),在 |y| 很小处以因子 1+η 增长。

python
import numpy as np

def grad_sad(p):
    return np.array([p[0], -p[1] + p[1] ** 3])

eta = 0.01
for y0 in [1e-3, 1e-2, 1e-1]:
    p = np.array([0.5, y0])
    for k in range(1, 100001):
        p = p - eta * grad_sad(p)
        if abs(p[1]) > 0.5:
            break
    print(f"y0 = {y0:.0e}: 梯度下降用 {k} 步使 |y| 超过 0.5")

print("对比:负曲率方向的一步")
y_after = 1e-3 + 0.1          # 沿 Hessian 负特征值方向前进 0.1
print(f"  起点 (0.5, 1e-3),沿负曲率方向走 0.1:y = {y_after:.4f}")

输出:

text
y0 = 1e-03: 梯度下降用 639 步使 |y| 超过 0.5
y0 = 1e-02: 梯度下降用 408 步使 |y| 超过 0.5
y0 = 1e-01: 梯度下降用 176 步使 |y| 超过 0.5
对比:负曲率方向的一步
  起点 (0.5, 1e-3),沿负曲率方向走 0.1:y = 0.1010

三点对照可以全部量化验证。线性化预测的逃逸步数为 log⁡(0.5/y0)/log⁡(1.01):y0=10−3 时为 625 步(实测 639,偏差 2%,来自四次项在 |y| 接近 0.5 时对增长速率的抑制),y0=10−2 时为 393 步(实测 408),y0=10−1 时为 162 步(实测 176)。初值缩小十倍只让逃逸步数增加约 230 步,这是对数依赖的直接体现。

负曲率步的对照更醒目:沿 Hessian 的负特征值方向走一步(步长 0.1),y 从 10−3 变成 0.101,相当于梯度下降 639 步中的前 470 步所做的事。这就是二阶方法在鞍点附近的优势来源,也是 3.5 节的信赖域方法在非凸问题上比梯度下降稳健的原因。

3.8.3 损失景观与过参数化 ​

插值、过参数化与解集的结构 ​

深度网络的参数个数通常远大于样本数,这样的模型是过参数化(over-parameterized)的:存在大量参数配置使训练误差为零,解的集合是一个高维流形,而不是孤立点。经典统计学的直觉(参数多于样本会过拟合)在这里被经验事实推翻:过参数化的网络在测试集上表现往往比参数较少的网络更好。

要解释这一现象,需要理解过参数化如何改变优化的几何。三条被反复观察到的结构。

插值解的存在性。当参数数超过样本数时,随机初始化附近的区域往往已经存在满意训练损失的解;优化算法的任务从「找到可行域」变成「在可行域内选择」。

极小值的连通性。过参数化网络的局部极小值之间可以用不显著升高损失的路径连接(模式连接)。两条路径的连接需要先做置换对齐(把两个网络的隐藏单元按相似度配对),未对齐时「路径」上的点可能对应完全不同的网络;对齐之后,线性插值路径往往已经足够。这一结构性与线性模型形成对比:线性模型的局部极小值一般不连通(有反例)。

线性网络的案例。把激活函数去掉(y=WL⋯W1x),深度线性网络等价于一个秩受约束的线性映射。这类问题的损失景观有可证明的性质:在平衡假设(相邻层权重的乘积结构满足某种对齐条件)下,所有局部极小值是全局的,且所有鞍点都是严格鞍点(Hessian 有负特征值)。这是少数能给出完整几何描述的模型,它的结论不能直接搬到非线性网络,但提供了「鞍点而非坏局部极小」这一图景的严格例子。

双下降 ​

模型复杂度与测试误差的关系在经典统计学中是 U 形曲线:复杂度到达某个最优点之前偏差主导,之后方差主导。真实的深度学习实践揭示的是另一条曲线:随模型规模(或训练时间)增大,测试误差先在插值阈值附近上升到一个尖峰,再继续下降到低于经典最优点水平的位置。这一现象称为双下降(double descent)。

用随机特征模型可以干净地重现它。模型为 y^=ϕ(x)Tw,其中 ϕ 是固定的随机特征映射(这里取 tanh 随机特征),待优化的是最后的线性层。样本数固定为 100,特征数 p 从 10 增加到 800:p<100 时模型欠参数化,最小二乘解唯一;p=100 时到达插值阈值;p>100 时欠定,最小二乘的最小范数解恰好插值训练数据。

python
import numpy as np

rng = np.random.default_rng(0)
n_train, n_test, d, trials = 100, 300, 5, 10
ps = [10, 30, 60, 90, 100, 110, 150, 250, 400, 800]
print(f"训练集 n = {n_train},测试集 {n_test},输入维数 {d},{trials} 次重复")
for p in ps:
    tr_err = te_err = 0.0
    for _ in range(trials):
        X_tr = rng.normal(size=(n_train, d))
        X_te = rng.normal(size=(n_test, d))
        W = rng.normal(size=(p, d))
        b = rng.uniform(0, 2 * np.pi, size=p)
        Phi_tr = np.tanh(X_tr @ W.T + b)          # 随机特征
        Phi_te = np.tanh(X_te @ W.T + b)
        wstar = rng.normal(size=d)
        y_tr = X_tr @ wstar + 0.3 * rng.normal(size=n_train)
        y_te = X_te @ wstar + 0.3 * rng.normal(size=n_test)
        w = np.linalg.lstsq(Phi_tr, y_tr, rcond=None)[0]   # 最小范数最小二乘
        tr_err += float(np.mean((Phi_tr @ w - y_tr) ** 2))
        te_err += float(np.mean((Phi_te @ w - y_te) ** 2))
    print(f"  p = {p:>3}: 训练误差 = {tr_err / trials:.4f}, 测试误差 = {te_err / trials:.4f}")

输出:

text
训练集 n = 100,测试集 300,输入维数 5,10 次重复
  p =  10: 训练误差 = 1.2508, 测试误差 = 2.4582
  p =  30: 训练误差 = 0.2429, 测试误差 = 2.7152
  p =  60: 训练误差 = 0.0654, 测试误差 = 4.9236
  p =  90: 训练误差 = 0.0087, 测试误差 = 40.9488
  p = 100: 训练误差 = 0.0000, 测试误差 = 7944.0903
  p = 110: 训练误差 = 0.0000, 测试误差 = 1216.3704
  p = 150: 训练误差 = 0.0000, 测试误差 = 3.2491
  p = 250: 训练误差 = 0.0000, 测试误差 = 0.6616
  p = 400: 训练误差 = 0.0000, 测试误差 = 0.4395
  p = 800: 训练误差 = 0.0000, 测试误差 = 0.2559

数据完整呈现了双下降的三个阶段。训练误差在 p=100 处降到零并保持(插值区),测试误差在插值阈值处冲到 7944,随后单调下降到 p=800 处的 0.26,低于欠参数化一侧的最好水平(p=10 处的 2.46)。

峰值的来源是有限样本的方差:p≈n 时设计矩阵的最小奇异值接近零,最小范数解的范数随之发散(∥w^∥∼1/σmin),对测试点的预测因此被放大。越过阈值后,模型的有效自由度不再增加,而解在过参数化空间中的「平均效应」使方差重新下降。峰值的极端高度(103 到 104 量级)也解释了为什么双下降在早期实验中容易被噪声掩盖:对数坐标下它才是一个显著的尖峰。

正则化抹平尖峰

给同一个模型加上小的 L2 正则化(λ=0.01 的岭回归),测试误差随 p 的变化变为平滑曲线:峰值消失,测试误差在 0.4 到 2.1 之间单调变化(练习题 3 给出数据)。原因是岭回归限制了系数范数,σmin→0 方向的放大被抑制。工程上的对应做法是权重衰减、早停与数据增强:它们都限制有效模型复杂度,使插值阈值的奇异性不被触发。

双下降也出现在其他轴上:样本量从小到大穿过参数数时(sample-wise),训练轮数增加穿过插值时刻时(epoch-wise)。这解释了「训练更久反而测试更好」的现象与早停策略的关系:早停不只是防止过拟合,也避免在插值阈值附近的方差放大区停留。

3.8.4 隐式正则与泛化 ​

梯度下降的隐式偏置 ​

过参数化下存在大量训练误差为零的解,梯度下降实际到达哪一个由算法自身决定。这一选择称为隐式偏置(implicit bias)或隐式正则。线性模型上有精确的结论:从零初始化出发、用小步长的梯度下降求解欠定最小二乘问题

minw ∥Xw−y∥2

收敛到最小范数解(min∥w∥ subject to Xw=y)。原因是迭代始终保持在一个不变的子空间内:从 w0=0 出发,每次更新加上的方向是 XT(Xwk−y),它属于 X 的行空间;而所有插值解中最小范数的那一个恰好落在行空间内(其零空间分量为零)。

python
import numpy as np

rng = np.random.default_rng(1)
n, d = 20, 100
X = rng.normal(size=(n, d))
w_star = rng.normal(size=d)
y = X @ w_star

L = float(np.linalg.eigvalsh(X @ X.T).max())      # 梯度 Lipschitz 常数
print(f"n = {n}, d = {d}, L = {L:.4f}")
w_gd = np.zeros(d)
for k in range(200000):
    g = X.T @ (X @ w_gd - y)
    w_gd = w_gd - g / L
    if np.linalg.norm(g) < 1e-12:
        break
w_min = np.linalg.lstsq(X, y, rcond=None)[0]      # 最小范数解
U, S, Vt = np.linalg.svd(X)
null_basis = Vt[n:]                               # 零空间的一组基
w_alt = w_min + null_basis.T @ (rng.normal(size=d - n) * 2.0)
print(f"梯度下降: {k} 步, 训练残差 = {np.linalg.norm(X @ w_gd - y):.3e}, "
      f"||w|| = {np.linalg.norm(w_gd):.6f}")
print(f"最小范数解: 训练残差 = {np.linalg.norm(X @ w_min - y):.3e}, "
      f"||w|| = {np.linalg.norm(w_min):.6f}")
print(f"另一个插值解: 训练残差 = {np.linalg.norm(X @ w_alt - y):.3e}, "
      f"||w|| = {np.linalg.norm(w_alt):.6f}")
print(f"||w_gd - w_min|| = {np.linalg.norm(w_gd - w_min):.3e}")
print(f"||w_gd - w_alt|| = {np.linalg.norm(w_gd - w_alt):.3e}")
print(f"梯度下降解在零空间上的分量: {np.linalg.norm(null_basis @ w_gd):.3e}")

输出:

text
n = 20, d = 100, L = 199.9507
梯度下降: 141 步, 训练残差 = 1.103e-13, ||w|| = 5.436566
最小范数解: 训练残差 = 8.722e-14, ||w|| = 5.436566
另一个插值解: 训练残差 = 8.221e-14, ||w|| = 19.133206
||w_gd - w_min|| = 1.959e-14
||w_gd - w_alt|| = 1.834e+01
梯度下降解在零空间上的分量: 3.138e-15

三组数字各说明一件事。梯度下降的解与最小范数解的距离为 2×10−14(参数范数 5.44),说明它精确收敛到最小范数解而不是某个随机插值解。构造的另一个插值解 walt(在零空间方向加上随机分量)训练残差同样为零,但参数范数为 19.13,与梯度下降解的距离为 18.34:训练误差完全不能区分这两个解,区别只由算法决定。第三组验证了机制:梯度下降解在零空间上的分量是 3×10−15,迭代确实被限制在行空间内。

逻辑回归有类似的结论:线性可分数据上梯度下降的范数持续增长而方向收敛到最大间隔方向(L2 最大间隔解)。这解释了为什么深度网络的训练中「损失持续下降但参数范数不断增大」不会破坏泛化:模型在范数增大的同时沿着隐式正则选择的方向移动。

早停、小步长与显式正则的对应 ​

隐式偏置与显式正则之间存在定量的对应关系。最小二乘问题上的分析给出:从零初始化用步长 η 做 k 步梯度下降,得到的解在谱滤波意义上接近岭回归:梯度下降对第 i 个奇异方向的放大因子是 1−(1−ησi2)k,岭回归是 σi2/(σi2+λ),两者在 λ≈1/(ηk) 的对应关系下形状相近。也就是说,早停等价于一种 L2 正则化,停止时间起正则参数的作用:训练越久有效正则越弱。

随机梯度带来第二层隐式正则。小批量梯度的噪声尺度大致为 η/batch 量级(学习率乘以噪声强度除以批大小),这一噪声在损失景观中起扩散作用:曲率大的方向(锐利方向)上噪声的放大效应强,迭代更容易离开锐利极小值,停留在平坦区域。这一机制说明为什么同样的模型、同样的数据,改变批大小或学习率会改变泛化表现。

平坦极小值与锐度 ​

锐度(sharpness)度量极小值附近损失的曲率。常用指标有 Hessian 的最大特征值 λmax、迹(等于梯度平方的期望,可用 Hutchinson 估计)、以及沿扰动方向的最大损失增量。经验观察是大批量训练倾向于收敛到锐利极小值,测试表现较差;小批量训练的极小值更平坦,测试表现更好。

这一观察需要谨慎表述,原因有三。其一,锐度的度量不具重参数化不变性:对网络做简单的可逆重参数化(例如整体缩放某一层的权重、相应缩小下一层)可以把任意锐利的最小值变成平坦的最小值,而函数本身与泛化表现没有变化。其二,平坦与泛化的因果方向不清楚:泛化好的模型可能恰好也更平坦,两者可能同时由数据与结构的其他性质决定。其三,基于锐度的算法(锐度感知最小化:在参数的小邻域内最大化损失再最小化,等价于惩罚梯度范数与曲率)在实践中确实改善了泛化,但改善的幅度依赖任务,也不能反过来证明「平坦即泛化」。

可行的做法是把平坦性当作优化过程的一个监控指标,与验证集指标一起使用,而不是把它当成泛化的理论保证。

3.8.5 深度学习优化实践 ​

优化器与权重衰减的解耦 ​

3.4 节的自适应方法(AdaGrad、RMSProp、Adam)在深度网络中普遍使用,其中两个工程细节影响明显。

权重衰减与 L2 正则化的区别。把 L2 项 λ2∥θ∥2 加进损失、让梯度承担它,这在自适应方法中不等于权重衰减:L2 项的梯度会进入二阶矩估计 v,被自适应缩放,于是参数范数大的方向受到的收缩比预期小。AdamW 把权重衰减从梯度中分离出来,直接对参数施加乘性收缩(θ←θ−η(Adam 更新+λθ)),收缩强度与自适应缩放无关。实践中的差别在参数尺度差异大的模型上明显(嵌入层与输出层、稀疏特征与稠密特征)。

ϵ 与数值稳定。自适应方法的分母含 v+ϵ,ϵ 的取值影响小梯度方向上的步长:ϵ 太小会让更新对梯度噪声敏感,太大则退化为 SGD。混合精度训练中还要注意 ϵ 与浮点格式的动态范围(需要 loss scaling 把小梯度放大到半精度可表示的范围)。

学习率、调度与批大小 ​

学习率是最重要的超参数,调度策略的作用与它相当。三种常见的调度:

预热(warmup)在训练开始阶段把学习率从很小的值线性升到目标值。必要性来自早期的不稳定:随机初始化下激活值与梯度的尺度尚未稳定,直接用大学习率容易造成初始化阶段的发散或损失尖峰,这一现象在 Transformer 类模型中尤为明显。

衰减在训练后期降低学习率,让迭代在最优点附近收得更紧。余弦退火(ηt=η02(1+cos⁡(πt/T)))、分段衰减(在固定轮数把学习率乘 0.1)与阶梯函数式的 warmup-stable-decay(长时间保持常值再快速衰减)是三种常用形式。它们的作用是把「探索与收敛」两件事分段:前期用大学习率快速穿越高损失区域,后期用小学习率精细收敛。

批大小与学习率的缩放。大批量训练减少了梯度噪声,常用做法是按线性缩放规则把学习率与批大小等比放大,同时配合预热。理论依据是噪声尺度与 η/batch 的关系:保持这一比值不变,迭代在损失景观中的扩散强度不变;噪声减小时需要更大的学习率来维持同样的探索能力。批大小进一步增大时线性规则失效(超过某个规模后噪声太小,学习率放大会破坏稳定性),需要 LARS、LAMB 这类按层归一化更新幅度的优化器。

梯度裁剪处理损失尖峰:对全局梯度范数设上限(g←g⋅min(1,τ/∥g∥)),在序列模型与强化学习中防止个别批次的异常梯度破坏训练。

归一化层与条件数 ​

批归一化与层归一化改变的是优化的几何,而不是模型的表达能力。它们的作用可以用 3.3 与 3.5 节的语言表述:归一化把各层的激活尺度固定,等价于对参数做了一种自适应的预条件,损失景观的梯度 Lipschitz 常数与 Hessian 条件数下降(经验上),允许更大的学习率。这一解释比「内部协变量偏移」的原始动机更接近实验证据:归一化带来的主要收益是优化稳定性与对超参数的鲁棒性,而不是统计分布意义上的稳定性。

数值实验:小网络上的优化器对比 ​

下面的实验在一个容易的非凸问题上比较四种配置。数据是两组同心圆(内圆与外环各 150 个点,叠加噪声),用 2→16→16→1 的 tanh 网络做二分类,交叉熵损失,小批量大小 32,训练 300 轮。

python
import numpy as np

def make_data(nn, seed):
    r = np.random.default_rng(seed)
    th = r.uniform(0, 2 * np.pi, nn)
    rad = np.where(r.random(nn) < 0.5, 0.4, 1.0) + 0.08 * r.normal(size=nn)
    Xd = np.stack([rad * np.cos(th), rad * np.sin(th)], axis=1)
    yd = (rad > 0.7).astype(float)
    return Xd, yd

X_tr, y_tr = make_data(300, 2)
X_te, y_te = make_data(300, 3)

def init(seed):
    r = np.random.default_rng(seed)
    return [r.normal(size=(2, 16)) * 0.7, np.zeros(16),
            r.normal(size=(16, 16)) * 0.5, np.zeros(16),
            r.normal(size=(16, 1)) * 0.5, np.zeros(1)]

def forward(params, Xin):
    W1, b1, W2, b2, W3, b3 = params
    a1 = np.tanh(Xin @ W1 + b1)
    a2 = np.tanh(a1 @ W2 + b2)
    p = 1 / (1 + np.exp(-(a2 @ W3 + b3)))
    return p[:, 0], (a1, a2, p[:, 0])

def backward(params, cache, Xin, yin):
    W1, b1, W2, b2, W3, b3 = params
    a1, a2, p = cache
    m = Xin.shape[0]
    dz = (p - yin)[:, None] / m
    gW3 = a2.T @ dz; gb3 = dz.sum(axis=0)
    dz2 = (dz @ W3.T) * (1 - a2 ** 2)
    gW2 = a1.T @ dz2; gb2 = dz2.sum(axis=0)
    dz1 = (dz2 @ W2.T) * (1 - a1 ** 2)
    gW1 = Xin.T @ dz1; gb1 = dz1.sum(axis=0)
    return [gW1, gb1, gW2, gb2, gW3, gb3]

def metrics(params, Xin, yin):
    p, _ = forward(params, Xin)
    eps = 1e-12
    loss = -float(np.mean(yin * np.log(p + eps) + (1 - yin) * np.log(1 - p + eps)))
    acc = float(np.mean((p > 0.5) == (yin > 0.5)))
    return loss, acc

def train(kind, lr, epochs=300, seed=0, batch=32, wd=0.0, cosine=False):
    params = init(seed)
    m_list = [np.zeros_like(p) for p in params]
    v_list = [np.zeros_like(p) for p in params]
    vel = [np.zeros_like(p) for p in params]
    rngb = np.random.default_rng(seed + 100)
    step = 0
    loss_hist = []
    for ep in range(epochs):
        idx = rngb.permutation(len(y_tr))
        for s in range(0, len(y_tr), batch):
            b_idx = idx[s:s + batch]
            Xb, yb = X_tr[b_idx], y_tr[b_idx]
            cur_lr = lr * 0.5 * (1 + np.cos(np.pi * ep / epochs)) if cosine else lr
            p, cache = forward(params, Xb)
            grads = backward(params, cache, Xb, yb)
            step += 1
            for i in range(6):
                if kind == "sgd":
                    vel[i] = 0.9 * vel[i] - cur_lr * grads[i]
                    params[i] = params[i] + vel[i]
                else:
                    gi = grads[i] + (wd * params[i] if kind == "adam" else 0.0)
                    m_list[i] = 0.9 * m_list[i] + 0.1 * gi
                    v_list[i] = 0.999 * v_list[i] + 0.001 * gi ** 2
                    mh = m_list[i] / (1 - 0.9 ** step)
                    vh = v_list[i] / (1 - 0.999 ** step)
                    upd = mh / (np.sqrt(vh) + 1e-8)
                    if kind == "adamw":
                        upd = upd + wd * params[i]
                    params[i] = params[i] - cur_lr * upd
        loss_hist.append(metrics(params, X_tr, y_tr)[0])
    return params, loss_hist

for name, kind, lr, kw in [
    ("SGD + 动量 (lr = 0.1)", "sgd", 0.1, {}),
    ("Adam (lr = 0.01)", "adam", 0.01, {}),
    ("Adam (lr = 0.5)", "adam", 0.5, {}),
    ("AdamW + 余弦 (lr = 0.01, wd = 0.01)", "adamw", 0.01, dict(wd=0.01, cosine=True)),
]:
    params, hist = train(kind, lr, **kw)
    tr_loss, tr_acc = metrics(params, X_tr, y_tr)
    te_loss, te_acc = metrics(params, X_te, y_te)
    print(f"{name}: 训练损失 = {tr_loss:.4f}, 训练准确率 = {tr_acc:.3f}, "
          f"测试损失 = {te_loss:.4f}, 测试准确率 = {te_acc:.3f}")
    print(f"  损失(epoch 10/50/100/300)= {hist[9]:.4f} / {hist[49]:.4f} / "
          f"{hist[99]:.4f} / {hist[-1]:.4f}")

输出:

text
SGD + 动量 (lr = 0.1): 训练损失 = 0.0002, 训练准确率 = 1.000, 测试损失 = 0.0008, 测试准确率 = 1.000
  损失(epoch 10/50/100/300)= 0.0155 / 0.0016 / 0.0007 / 0.0002
Adam (lr = 0.01): 训练损失 = 0.0001, 训练准确率 = 1.000, 测试损失 = 0.0017, 测试准确率 = 1.000
  损失(epoch 10/50/100/300)= 0.1733 / 0.0056 / 0.0013 / 0.0001
Adam (lr = 0.5): 训练损失 = 0.8515, 训练准确率 = 0.533, 测试损失 = 0.9500, 测试准确率 = 0.470
  损失(epoch 10/50/100/300)= 0.0399 / 0.7026 / 0.4781 / 0.8515
AdamW + 余弦 (lr = 0.01, wd = 0.01): 训练损失 = 0.0007, 训练准确率 = 1.000, 测试损失 = 0.0028, 测试准确率 = 1.000
  损失(epoch 10/50/100/300)= 0.1771 / 0.0067 / 0.0020 / 0.0007

三组观察。

容易的问题上优化器差别有限。三种稳定配置都在 300 轮内把训练与测试准确率推到 1.000,训练损失在 10−4 到 10−3 量级。这与本章多处出现的结论一致:优化器的选择在困难问题(病态、噪声大、深度大)上才显出差别,在简单问题上换优化器只是把收敛曲线平移。

学习率过大时自适应方法也会失败。lr=0.5 的 Adam 在第 10 轮损失已经降到 0.0399(与正常配置相当),随后持续恶化到 0.85,准确率掉到 0.533。原因是自适应步长的归一化作用把参数更新的幅度限制在 η 附近,η 超过损失景观的稳定区域时更新会反复越过极小值,权重持续增大(没有权重衰减约束)。这条曲线给出了一个排查规则:训练损失先下降后上升,且参数范数持续增大,首先检查学习率而不是模型结构。

调度的作用。AdamW 加余弦退火在同一学习率下的训练损失(0.0007)略高于 Adam(0.0001),测试损失(0.0028)略高,但它的损失曲线在后期更平滑。在简单问题上退火的收益看不出来;它真正的价值在于长时间训练与大规模模型的稳定性,以及「训练末期固定轮数内损失下降一个台阶」的可控性。

3.8.6 本节小结 ​

非凸优化的图景与工程对应

本节把非凸优化的内容分成理论图景与实践配置两层。理论上,非凸问题求全局最优是 NP 难的,梯度下降的结论转向梯度范数:固定步长下 mink≤K∥∇f(xk)∥2≤2LΔ/K,达到 ϵ 精度需要 O(1/ϵ2) 步,比凸情形的 O(1/ϵ) 多一个 1/ϵ 因子。鞍点的动力学分析给出逃逸时间的对数依赖(本例中初值缩小十倍只增加约 230 步,639 对 408 对 176),负曲率步一步完成梯度下降数百步的工作,二阶方法因此把复杂度改进到 O(1/ϵ1.5) 量级。1681 个网格初值中 1600 个收敛到四个极小值、81 个被坐标轴上的鞍点捕获,展示了吸引域与对称初值的影响。机器学习特有的现象是过参数化:插值解集合是高维流形,双下降曲线在插值阈值(p=n=100)处出现 7944 的测试误差尖峰后回落到 0.26;梯度下降在众多插值解中收敛到最小范数解(距离 2×10−14,零空间分量 3×10−15),训练误差无法区分它与范数 3.5 倍的另一个插值解,选择由算法决定。实践层面,权重衰减与梯度必须解耦(AdamW)、学习率需要预热与退火、批大小与学习率按噪声尺度匹配、归一化层的作用是改善条件数而非减少分布偏移,学习率过大时自适应方法也会发散(本例中 lr=0.5 的 Adam 从第 10 轮起持续恶化到 0.85 的损失)。

非凸优化的讨论到此为止。下一节处理最后一类不在梯度框架内的问题:目标函数没有解析梯度、梯度计算代价极高或根本不可导的场景,那里的工具是无梯度方法与贝叶斯优化。

练习题 ​

第 1 题 概念推导 ​

推导非凸情形下梯度下降的复杂度界:设 ∇f 以 L 为 Lipschitz 常数、f 有下界 f∗、步长取 1/L,证明 mink≤K∥∇f(xk)∥2≤2L(f(x0)−f∗)/K。说明为什么凸情形可以达到 O(1/K) 的目标值误差界,而非凸情形只能得到梯度范数的界。再说明一阶稳定点为什么不能保证是局部极小,并给出一个具体的反例。

参考答案

复杂度界:由下降引理(3.3 节),f 的 L-光滑性给出

f(xk+1)≤f(xk)+∇f(xk)T(xk+1−xk)+L2∥xk+1−xk∥2

代入 xk+1=xk−1L∇f(xk):

f(xk+1)≤f(xk)−1L∥∇f∥2+12L∥∇f∥2=f(xk)−12L∥∇f(xk)∥2

对 k=0 到 K−1 求和,左端用 f(xK)≥f∗ 放缩:

12L∑k=0K−1∥∇f(xk)∥2≤f(x0)−f(xK)≤Δ

而 mink≤K−1∥∇f(xk)∥2≤1K∑k∥∇f(xk)∥2,因此 mink∥∇f(xk)∥2≤2LΔK,令右侧等于 ϵ2 得 K=O(LΔ/ϵ2)。

凸情形的差别:凸函数上梯度小直接推出目标值接近最优:f(x)−f∗≤∇f(x)T(x−x∗),配合有界域或强凸性把梯度范数与最优值误差联系起来。非凸函数没有这种联系:一个梯度极小但函数值远高于全局最优的点(例如两个深谷之间的平坦山脊)完全可能。这就是结论只能停在梯度范数上的原因。

反例:f(x,y)=x2−y2 在 (0,0) 处满足 ∇f=0,但沿 y 方向函数下降,(0,0) 是鞍点而不是局部极小。更温和的例子是 f(x)=x3:f′(0)=0 且 f″(0)=0,(0,0) 是拐点,既不是极小也不是极大。加入二阶条件(∇2f⪰0)可以排除前一个例子,但排除不了所有坏点(例如 f(x,y)=x2−y4 在原点处 Hessian 半正定,函数沿 y 仍下降)。

第 2 题 计算推理 ​

对 f(x,y)=x22−y22+y44:(a) 求所有稳定点并判断类型(局部极小、鞍点);(b) 写出 (0,0) 处 Hessian 的特征值与特征向量;(c) 对步长 η,写出 y 方向的迭代并给出线性化增长因子,估计从 y0=10−4 出发使 |y|>0.5 需要多少步,与本节实验中 y0=10−3 的 639 步做比较。

参考答案

(a) 稳定点:∇f=(x, −y+y3)=0 给出 x=0 与 y(y2−1)=0,即 y∈{0,±1}。三个稳定点为 (0,0)、(0,1)、(0,−1)。Hessian 为 diag(1, −1+3y2):在 (0,±1) 处为 diag(1,2)≻0,是局部极小(且因为沿 x 方向二次增长、沿 y 方向四次项保证增长,它们也是全局极小);在 (0,0) 处为 diag(1,−1),是不定的,即鞍点。

(b) 特征分解:λ1=1 对应特征向量 (1,0)(上升方向),λ2=−1 对应 (0,1)(下降方向)。

(c) 迭代与逃逸:y 方向的梯度分量为 −y+y3,梯度下降给出

yk+1=yk+η(yk−yk3)=yk(1+η−ηyk2)

在 |yk| 很小时忽略三次项,yk+1≈(1+η)yk,增长因子 1+η。η=0.01 时 yk≈y0(1.01)k,要求 0.5≥y0(1.01)k 得

k≈log⁡(0.5/y0)log⁡1.01

y0=10−4:log⁡(5000)/log⁡(1.01)=8.517/0.00995≈856 步。y0=10−3:log⁡(500)/log⁡(1.01)=6.215/0.00995≈625 步,实验测得 639 步,偏差 2%。偏差来自两次忽略:三次项在 |y| 接近 0.5 处使增长变慢,而线性化把增长速率一直当作 1+η;此外 x 方向的耦合通过 Hessian 的独立方向假设被忽略(本例两个方向确实解耦,但仍有一次项的截断误差)。

第 3 题 代码验证 ​

用 3.8.3 节的双下降代码做三组实验并解释:(a) 把噪声标准差从 0.3 改为 1.0,观察峰值与两侧的水平如何变化;(b) 把最小二乘换成 λ=0.01 的岭回归,观察尖峰是否消失;(c) 把重复次数从 10 增加到 40,观察曲线是否更光滑。给出代码、输出与结论。

参考答案
python
import numpy as np

def run(lmbda, noise, trials, seed=0):
    rng = np.random.default_rng(seed)
    n_train, n_test, d = 100, 300, 5
    ps = [10, 30, 60, 90, 100, 110, 150, 250, 400]
    out = {}
    for p in ps:
        te = 0.0
        for _ in range(trials):
            X_tr = rng.normal(size=(n_train, d))
            X_te = rng.normal(size=(n_test, d))
            W = rng.normal(size=(p, d))
            b = rng.uniform(0, 2 * np.pi, size=p)
            Phi_tr = np.tanh(X_tr @ W.T + b)
            Phi_te = np.tanh(X_te @ W.T + b)
            wstar = rng.normal(size=d)
            y_tr = X_tr @ wstar + noise * rng.normal(size=n_train)
            y_te = X_te @ wstar + noise * rng.normal(size=n_test)
            if lmbda == 0.0:
                w = np.linalg.lstsq(Phi_tr, y_tr, rcond=None)[0]
            else:
                G = Phi_tr.T @ Phi_tr + lmbda * np.eye(p)
                w = np.linalg.solve(G, Phi_tr.T @ y_tr)
            te += float(np.mean((Phi_te @ w - y_te) ** 2))
        out[p] = te / trials
    return out

for name, kw in [("噪声 0.3 + 最小二乘", dict(lmbda=0.0, noise=0.3, trials=10)),
                 ("噪声 1.0 + 最小二乘", dict(lmbda=0.0, noise=1.0, trials=10)),
                 ("噪声 0.3 + 岭回归 lambda = 0.01", dict(lmbda=0.01, noise=0.3, trials=10))]:
    res = run(**kw)
    print(name)
    print("  " + "  ".join(f"p={p}: {res[p]:.4f}" for p in sorted(res)))

输出:

text
噪声 0.3 + 最小二乘
  p=10: 2.4582  p=30: 2.7152  p=60: 4.9236  p=90: 40.9488  p=100: 7944.0903  p=110: 1216.3704  p=150: 3.2491  p=250: 0.6616  p=400: 0.4395
噪声 1.0 + 最小二乘
  p=10: 3.5974  p=30: 4.9148  p=60: 24.7008  p=90: 399.2614  p=100: 70567.6303  p=110: 12269.0218  p=150: 30.5710  p=250: 6.3180  p=400: 4.3652
噪声 0.3 + 岭回归 lambda = 0.01
  p=10: 2.0777  p=30: 1.1318  p=60: 0.7834  p=90: 1.1167  p=100: 0.9061  p=110: 1.2525  p=150: 1.1116  p=250: 0.6108  p=400: 0.4267

结论:(a) 噪声增大到 1.0 时峰值从 7944 升到 70568(约 9 倍),两侧的水平也整体抬高(p=10 处 2.46→3.60,p=400 处 0.44→4.37)。峰值高度与噪声方差大致成正比:插值解在阈值附近的放大因子由设计矩阵的奇异值决定,与噪声强度无关,因此峰值随噪声方差等比放大。

(b) 岭回归(λ=0.01)把峰值完全抹平:测试误差在 0.43 到 2.08 之间单调变化,p=100 处为 0.9061,没有任何尖峰。原因是正则项限制了系数范数,p≈n 处接近奇异的方向不再被放大。这也说明双下降的尖峰是最小范数插值解的性质,不是过参数化模型固有的缺陷。

(c) 重复次数增加会让曲线更光滑,但不会改变形状:峰值位置固定在 p=n,高度是期望值(多次重复的平均),随机性的影响主要落在峰值附近(方差最大处)。报告这类实验时应当给出重复次数与误差棒,否则单次实验的峰值位置可能偏移。

第 4 题 综合应用 ​

某团队要训练一个参数量 109 量级的语言模型,训练数据 1011 个标记,硬件为若干节点的加速卡集群。请给出优化配置方案:优化器与权重衰减的设置、学习率与调度、批大小与梯度累积、梯度裁剪与数值精度、以及三到四项必须监控的指标;并说明在这个规模上为什么不能用本章凸优化部分的收敛性定理判断训练是否正常。

参考答案

优化器与权重衰减:用 AdamW(解耦权重衰减)。L2 式的权重衰减在自适应方法中会被二阶矩缩放,对参数尺度不同的层产生的收缩不一致(嵌入层与输出层的参数尺度差异大)。权重衰减系数对大部分参数取 0.1 量级,归一化层的参数与偏置通常不衰减(它们的尺度不应被压缩)。

学习率与调度:峰值学习率取 10−4 到 3×10−4 量级(与批大小匹配),前 1% 到 2% 的步数做线性预热,主体阶段保持常值或缓慢余弦下降,最后 10% 到 20% 步数快速衰减到峰值的 1% 到 10%(warmup-stable-decay 形式)。预热是必需的:初始化阶段激活与梯度尺度未稳定,大学习率会造成不可恢复的损失尖峰。

批大小与梯度累积:单卡放不下大 batch 时用梯度累积模拟目标批大小,按线性缩放规则调整学习率;批大小超过噪声尺度的临界值后线性规则失效,需要按层归一化更新的优化器(LAMB 一类)或改用更小的批。梯度累积与数据并行的组合要注意归一化的分母(按累积的样本数而非微批数)。

梯度裁剪与数值精度:全局范数裁剪(阈值取 1.0 量级)防止个别批次造成尖峰;混合精度训练时用 loss scaling 保护小梯度,并注意自适应方法的分母 ϵ 在半精度下的动态范围。

必须监控的指标:训练损失与验证损失的滑窗均值(看趋势而不是单点);梯度范数(裁剪触发率、增长趋势);参数更新范数与参数范数之比(更新过大说明学习率过高);激活与注意力的数值范围(异常值检测);以及固定评测集上的阶段性指标。

为什么不能套用凸优化的收敛定理:凸情形的定理给出「目标值与全局最优的差随迭代数下降」这类结论,依赖凸性把梯度小与目标值接近最优联系起来;语言模型的损失景观非凸、目标无下界可实现(每步都在变)、参数与激活的统计随训练演化,∇f 的 Lipschitz 常数没有可用的全局估计。可用的判据是 3.8.1 节的一阶稳定点视角(梯度范数与更新幅度)与 3.8.4 节的隐式正则视角(参数范数与有效复杂度),加上验证集指标与损失曲线的形态判断(尖峰、平台、发散)。理论的用途在于解释现象与设定监控项,不在于给出收敛保证。

常见错误 ​

错误 1 · 把凸优化的收敛结论直接搬到深度网络

原因:O(1/k) 的目标值速率、线性收敛的强凸结论、KKT 条件的最优性刻画都依赖凸性。非凸网络满足不了这些假设,硬套会得到错误预期(例如「训练到梯度为零即全局最优」)。更隐蔽的问题是「收敛」的定义不同:凸情形指目标值接近最优,非凸情形只能指梯度范数小或到达稳定点。

解决:报告指标与理论结论要对应。非凸训练用梯度范数、参数更新幅度、验证集指标三项监控;理论引用限于「下降到一阶稳定点」这类不依赖凸性的结论(O(1/ϵ2) 的步数界),或者在特定结构(线性网络、矩阵分解的平衡条件)下成立的专门结果。

错误 2 · 用训练损失单点判断训练正常

原因:非凸训练的损失曲线会有平台、尖峰与短期上升,单点的数值无法区分「正常波动」与「开始发散」。本节实验中 lr=0.5 的 Adam 在第 10 轮损失为 0.0399(看起来正常),到第 50 轮已经涨到 0.7026。只看某个时刻的损失会漏掉这类问题。

解决:监控滑动平均与趋势(近 100 步与近 1000 步均值之比),同时看梯度范数与参数范数。损失持续下降而参数范数快速增大是有效复杂度失控的信号;梯度范数突然放大多个数量级通常预示尖峰,需要检查数据批或降低学习率。

错误 3 · 把平坦性当作泛化的保证

原因:锐度度量(Hessian 最大特征值、扰动敏感度)不具有重参数化不变性:对相邻两层做权重缩放(W1→cW1、W2→W2/c)不改变网络函数与泛化表现,却可以把锐利极小值变成平坦极小值。基于锐度的比较因此可能只是在比较参数化方式。

解决:把平坦性作为辅助监控指标而不是结论依据;使用锐度感知最小化这类方法时,把它当作一种有效的正则化手段(会改善泛化)而不是泛化的解释;比较不同模型的平坦性时用与参数化无关的代理(例如函数空间上的扰动敏感度、加入噪声后的损失增量),并同时报告验证集指标。

错误 4 · 学习率、批大小与权重衰减三者独立调参

原因:三者在噪声尺度与有效步长上耦合。批大小决定梯度噪声(噪声方差与批大小成反比),学习率决定噪声的放大(有效噪声尺度与 η/batch 相关),权重衰减在自适应方法中与学习率共同决定参数收缩幅度(收缩量约 ηλ 每步)。独立调参会得到互相抵消的配置(例如大学习率配大批大小,噪声尺度反而正常,看起来「不需要预热」;或者学习率减小后没同步调整权重衰减,参数范数失控)。

解决:按耦合关系组织调参顺序:先确定批大小与学习率的比例(线性或平方根缩放),再确定权重衰减与学习率的乘积量级,最后调调度与预热。改变其中一项时按比例调整其余项,并在日志中记录三者的组合(而不是各自独立的数值)。