3.8 非凸优化与机器学习
3.1 到 3.7 节的主要结论都以凸性为前提:KKT 条件在凸问题中是充要条件,近端梯度的
非凸带来的第一个变化是目标的重设。一般非凸问题的全局最优是 NP 难的,求全局最优在计算上不可行;可行的目标变成找到梯度很小、曲率不坏的稳定点,并让这些点具有良好的泛化表现。第二个变化是理论工具的更替:收敛性分析从「最优值误差」转向「梯度范数」与「二阶条件」,对泛化的解释需要引入隐式正则与损失景观的概念。
本节按这条线索展开。3.8.1 给出非凸问题的基本图景与梯度下降的收敛结论;3.8.2 讨论鞍点、逃逸机制与二阶方法;3.8.3 进入机器学习特有的现象:过参数化、插值、极小值的连通性与双下降;3.8.4 讨论隐式正则与平坦极小值;3.8.5 汇总深度学习训练实践中的优化配置。
3.8.1 非凸问题的基本图景
最优性条件的变化
3.3 节的一阶与二阶必要条件在非凸情形下仍然成立:
定义上的排序是:全局最优
非凸问题的难解性
非凸问题的全局最优在一般情形下是 NP 难的,这是问题本身的性质:稀疏回归、带秩约束的矩阵分解、含离散变量的连续松弛,都能嵌入一般的非凸优化框架,而这些问题的精确求解已知没有多项式时间算法,因此不存在能普遍保证找到全局最优的算法。可行的目标只能由三项组成:找到梯度范数小于阈值的点(一阶稳定);在合理步数内找到二阶稳定点;稳定点的目标值分布足够集中(大多数稳定点的函数值相近),使选择哪个稳定点不影响结果。
第三项是机器学习中非凸优化能够成功的关键假设,3.8.3 与 3.8.4 节给出支持它的证据与限制。
梯度下降收敛到一阶稳定点
把 3.3 节的下降引理用到非凸情形,可以得到一个不依赖凸性、只依赖光滑性与目标有下界的结论。设
把
结论是
区间
数值实验:多极小值问题的吸引域
第一个实验展示非凸优化对初值的依赖。目标函数取
它有两个方向的解耦结构:每个变量的极小值在
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}")输出:
41 x 41 = 1681 个起始点,步长 0.1:
收敛到极小值 (1, 1): 400 个点
收敛到极小值 (1, -1): 400 个点
收敛到极小值 (-1, 1): 400 个点
收敛到极小值 (-1, -1): 400 个点
收敛到鞍点: 81 个点(全部落在坐标轴上)
步数中位数 = 17四个极小点的吸引域各占
非凸问题的「好解」标准
这个实验说明了两个要点:吸引域的大小由函数结构决定,与算法无关;鞍点只有在特殊初值下才会被「停留」,一般的初值都会被负曲率方向推开(3.8.2 节量化这一逃逸过程)。实际训练中很少精确落在鞍点上,更常见的是平的区域(梯度很小但不是鞍点,例如 Sigmoid 饱和区),那里梯度衰减导致迭代停滞,处理手段(归一化、残差连接、激活函数的选择)针对的正是这种区域。
3.8.2 鞍点、逃逸与二阶方法
鞍点的判定与梯度下降的动力学
鞍点是梯度为零而 Hessian 不定的点:存在负特征值的方向,函数在该方向下降;也存在正特征值的方向,函数在该方向上升。Hessian 负特征值的个数称为指标(index),指标为
梯度下降在鞍点附近的动力学可以用线性化读出。在鞍点
即每个 Hessian 特征方向独立演化:特征值为
逃逸时间与初始扰动的大小成对数关系:初始扰动为
二阶稳定与算法修正
把「逃逸」从被动等待变成主动利用,得到二阶方法。三种典型手段:
负曲率步。检测到 Hessian 有负特征值时,沿对应特征向量移动一步。检测手段是(截断)Lanczos 迭代或幂法求最小特征值方向,或者用「Hessian-向量乘积」的有限差分估计(
信赖域与三次正则化。3.5 节的信赖域方法本身就处理不定的二阶模型:在半径约束下沿负曲率方向前进。三次正则化的子问题
扰动梯度下降与随机重启。每次迭代加小噪声,或从随机初值重启多次:噪声的作用是把分量从极小的初值抬高,缩短逃逸时间。理论结果是扰动梯度下降在多项式时间内到达二阶稳定点,界为
高阶鞍点的处理
上面的动力学分析建立在鞍点附近的二次近似上。当 Hessian 在鞍点处有零特征值时(退化的高阶鞍点),负曲率方向的信号被高阶项掩盖,需要更高阶的修正或更强的噪声。这类情形在对称性强的模型中常见(例如
数值实验:鞍点处的逃逸时间
取一个具有严格鞍点的函数
它在
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}")输出:
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三点对照可以全部量化验证。线性化预测的逃逸步数为
负曲率步的对照更醒目:沿 Hessian 的负特征值方向走一步(步长
3.8.3 损失景观与过参数化
插值、过参数化与解集的结构
深度网络的参数个数通常远大于样本数,这样的模型是过参数化(over-parameterized)的:存在大量参数配置使训练误差为零,解的集合是一个高维流形,而不是孤立点。经典统计学的直觉(参数多于样本会过拟合)在这里被经验事实推翻:过参数化的网络在测试集上表现往往比参数较少的网络更好。
要解释这一现象,需要理解过参数化如何改变优化的几何。三条被反复观察到的结构。
插值解的存在性。当参数数超过样本数时,随机初始化附近的区域往往已经存在满意训练损失的解;优化算法的任务从「找到可行域」变成「在可行域内选择」。
极小值的连通性。过参数化网络的局部极小值之间可以用不显著升高损失的路径连接(模式连接)。两条路径的连接需要先做置换对齐(把两个网络的隐藏单元按相似度配对),未对齐时「路径」上的点可能对应完全不同的网络;对齐之后,线性插值路径往往已经足够。这一结构性与线性模型形成对比:线性模型的局部极小值一般不连通(有反例)。
线性网络的案例。把激活函数去掉(
双下降
模型复杂度与测试误差的关系在经典统计学中是 U 形曲线:复杂度到达某个最优点之前偏差主导,之后方差主导。真实的深度学习实践揭示的是另一条曲线:随模型规模(或训练时间)增大,测试误差先在插值阈值附近上升到一个尖峰,再继续下降到低于经典最优点水平的位置。这一现象称为双下降(double descent)。
用随机特征模型可以干净地重现它。模型为
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}")输出:
训练集 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数据完整呈现了双下降的三个阶段。训练误差在
峰值的来源是有限样本的方差:
正则化抹平尖峰
给同一个模型加上小的
双下降也出现在其他轴上:样本量从小到大穿过参数数时(sample-wise),训练轮数增加穿过插值时刻时(epoch-wise)。这解释了「训练更久反而测试更好」的现象与早停策略的关系:早停不只是防止过拟合,也避免在插值阈值附近的方差放大区停留。
3.8.4 隐式正则与泛化
梯度下降的隐式偏置
过参数化下存在大量训练误差为零的解,梯度下降实际到达哪一个由算法自身决定。这一选择称为隐式偏置(implicit bias)或隐式正则。线性模型上有精确的结论:从零初始化出发、用小步长的梯度下降求解欠定最小二乘问题
收敛到最小范数解(
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}")输出:
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三组数字各说明一件事。梯度下降的解与最小范数解的距离为
逻辑回归有类似的结论:线性可分数据上梯度下降的范数持续增长而方向收敛到最大间隔方向(
早停、小步长与显式正则的对应
隐式偏置与显式正则之间存在定量的对应关系。最小二乘问题上的分析给出:从零初始化用步长
随机梯度带来第二层隐式正则。小批量梯度的噪声尺度大致为
平坦极小值与锐度
锐度(sharpness)度量极小值附近损失的曲率。常用指标有 Hessian 的最大特征值
这一观察需要谨慎表述,原因有三。其一,锐度的度量不具重参数化不变性:对网络做简单的可逆重参数化(例如整体缩放某一层的权重、相应缩小下一层)可以把任意锐利的最小值变成平坦的最小值,而函数本身与泛化表现没有变化。其二,平坦与泛化的因果方向不清楚:泛化好的模型可能恰好也更平坦,两者可能同时由数据与结构的其他性质决定。其三,基于锐度的算法(锐度感知最小化:在参数的小邻域内最大化损失再最小化,等价于惩罚梯度范数与曲率)在实践中确实改善了泛化,但改善的幅度依赖任务,也不能反过来证明「平坦即泛化」。
可行的做法是把平坦性当作优化过程的一个监控指标,与验证集指标一起使用,而不是把它当成泛化的理论保证。
3.8.5 深度学习优化实践
优化器与权重衰减的解耦
3.4 节的自适应方法(AdaGrad、RMSProp、Adam)在深度网络中普遍使用,其中两个工程细节影响明显。
权重衰减与
学习率、调度与批大小
学习率是最重要的超参数,调度策略的作用与它相当。三种常见的调度:
预热(warmup)在训练开始阶段把学习率从很小的值线性升到目标值。必要性来自早期的不稳定:随机初始化下激活值与梯度的尺度尚未稳定,直接用大学习率容易造成初始化阶段的发散或损失尖峰,这一现象在 Transformer 类模型中尤为明显。
衰减在训练后期降低学习率,让迭代在最优点附近收得更紧。余弦退火(
批大小与学习率的缩放。大批量训练减少了梯度噪声,常用做法是按线性缩放规则把学习率与批大小等比放大,同时配合预热。理论依据是噪声尺度与
梯度裁剪处理损失尖峰:对全局梯度范数设上限(
归一化层与条件数
批归一化与层归一化改变的是优化的几何,而不是模型的表达能力。它们的作用可以用 3.3 与 3.5 节的语言表述:归一化把各层的激活尺度固定,等价于对参数做了一种自适应的预条件,损失景观的梯度 Lipschitz 常数与 Hessian 条件数下降(经验上),允许更大的学习率。这一解释比「内部协变量偏移」的原始动机更接近实验证据:归一化带来的主要收益是优化稳定性与对超参数的鲁棒性,而不是统计分布意义上的稳定性。
数值实验:小网络上的优化器对比
下面的实验在一个容易的非凸问题上比较四种配置。数据是两组同心圆(内圆与外环各
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}")输出:
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三组观察。
容易的问题上优化器差别有限。三种稳定配置都在
学习率过大时自适应方法也会失败。
调度的作用。AdamW 加余弦退火在同一学习率下的训练损失(
3.8.6 本节小结
非凸优化的图景与工程对应
本节把非凸优化的内容分成理论图景与实践配置两层。理论上,非凸问题求全局最优是 NP 难的,梯度下降的结论转向梯度范数:固定步长下
非凸优化的讨论到此为止。下一节处理最后一类不在梯度框架内的问题:目标函数没有解析梯度、梯度计算代价极高或根本不可导的场景,那里的工具是无梯度方法与贝叶斯优化。
练习题
第 1 题 概念推导
推导非凸情形下梯度下降的复杂度界:设
参考答案
复杂度界:由下降引理(3.3 节),
代入
对
而
凸情形的差别:凸函数上梯度小直接推出目标值接近最优:
反例:
第 2 题 计算推理
对
参考答案
(a) 稳定点:
(b) 特征分解:
(c) 迭代与逃逸:
在
第 3 题 代码验证
用 3.8.3 节的双下降代码做三组实验并解释:(a) 把噪声标准差从
参考答案
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)))输出:
噪声 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) 噪声增大到
(b) 岭回归(
(c) 重复次数增加会让曲线更光滑,但不会改变形状:峰值位置固定在
第 4 题 综合应用
某团队要训练一个参数量
参考答案
优化器与权重衰减:用 AdamW(解耦权重衰减)。
学习率与调度:峰值学习率取
批大小与梯度累积:单卡放不下大 batch 时用梯度累积模拟目标批大小,按线性缩放规则调整学习率;批大小超过噪声尺度的临界值后线性规则失效,需要按层归一化更新的优化器(LAMB 一类)或改用更小的批。梯度累积与数据并行的组合要注意归一化的分母(按累积的样本数而非微批数)。
梯度裁剪与数值精度:全局范数裁剪(阈值取
必须监控的指标:训练损失与验证损失的滑窗均值(看趋势而不是单点);梯度范数(裁剪触发率、增长趋势);参数更新范数与参数范数之比(更新过大说明学习率过高);激活与注意力的数值范围(异常值检测);以及固定评测集上的阶段性指标。
为什么不能套用凸优化的收敛定理:凸情形的定理给出「目标值与全局最优的差随迭代数下降」这类结论,依赖凸性把梯度小与目标值接近最优联系起来;语言模型的损失景观非凸、目标无下界可实现(每步都在变)、参数与激活的统计随训练演化,
常见错误
错误 1 · 把凸优化的收敛结论直接搬到深度网络
原因:
解决:报告指标与理论结论要对应。非凸训练用梯度范数、参数更新幅度、验证集指标三项监控;理论引用限于「下降到一阶稳定点」这类不依赖凸性的结论(
错误 2 · 用训练损失单点判断训练正常
原因:非凸训练的损失曲线会有平台、尖峰与短期上升,单点的数值无法区分「正常波动」与「开始发散」。本节实验中
解决:监控滑动平均与趋势(近
错误 3 · 把平坦性当作泛化的保证
原因:锐度度量(Hessian 最大特征值、扰动敏感度)不具有重参数化不变性:对相邻两层做权重缩放(
解决:把平坦性作为辅助监控指标而不是结论依据;使用锐度感知最小化这类方法时,把它当作一种有效的正则化手段(会改善泛化)而不是泛化的解释;比较不同模型的平坦性时用与参数化无关的代理(例如函数空间上的扰动敏感度、加入噪声后的损失增量),并同时报告验证集指标。
错误 4 · 学习率、批大小与权重衰减三者独立调参
原因:三者在噪声尺度与有效步长上耦合。批大小决定梯度噪声(噪声方差与批大小成反比),学习率决定噪声的放大(有效噪声尺度与
解决:按耦合关系组织调参顺序:先确定批大小与学习率的比例(线性或平方根缩放),再确定权重衰减与学习率的乘积量级,最后调调度与预热。改变其中一项时按比例调整其余项,并在日志中记录三者的组合(而不是各自独立的数值)。