3.9 无梯度优化与贝叶斯优化
前面八节的方法都要求能计算梯度或曲率:梯度下降、动量法、牛顿法与拟牛顿法、近端梯度、ADMM,全都建立在
这一类问题的求解方法统称无梯度优化(derivative-free optimization)或黑箱优化。目标函数只提供输入到输出的映射,算法只能利用函数值序列推测下一步的试探点。取舍与前面各节不同:梯度信息的丢失使收敛速度大幅下降(维度越高越严重),换来的是对问题的假设最少。
本节按方法的取样机制组织。3.9.1 给出问题设定与两个基线(网格搜索与随机搜索),它们的表现决定了其他方法值不值得用;3.9.2 讨论直接搜索(Nelder-Mead、模式搜索、Powell 方法);3.9.3 讨论群体方法与进化策略,重点是 CMA-ES;3.9.4 讨论贝叶斯优化,用代理模型与采集函数把每次求值的信息利用到极致;3.9.5 把这些方法落到超参数调优这一主要应用场景。
3.9.1 黑箱问题与两个基线
问题设定
无梯度优化处理的问题形如
维度。无梯度方法的代价随维度上升极快。这是没有梯度信息时必须付出的代价:梯度给出
预算。求值次数有硬上限(实验次数、仿真时长、标注成本)。贝叶斯优化正是为预算极小而单次求值极贵的场景设计的;进化策略适合预算中等(数千到数百万次)的场景。
噪声。求值含随机性时(随机种子、测量误差),直接搜索与模式搜索的步长逻辑会失效(比较两个点的函数值时无法区分真实差异与噪声),需要重复求值与统计检验,或改用天然抗噪的 CMA-ES 与贝叶斯优化。
网格搜索与随机搜索
网格搜索在每一维上取固定数量的水平,枚举所有组合。它的代价是水平的维度次方:
随机搜索在搜索空间内独立均匀采样,预算用尽即停。它看起来更粗糙,但在高维问题上的表现通常严格优于网格搜索,原因是有效维度往往远低于名义维度:许多超参数对目标的实际影响很小,网格搜索把预算均匀铺在名义维度上,只有少数坐标轴的取值被真正区分开;随机搜索的每个样本都是新的坐标组合,在少数重要的维度上得到更多不同的取值。下面的实验直接量化这一差别。
import numpy as np
def f_hp(x):
# 6 维搜索空间,只有前两维影响目标
return (x[0] - 0.3) ** 2 + (x[1] - 0.7) ** 2
d = 6
budget = 64
reps = 200
grid_best, rand_best = [], []
for r in range(reps):
rng = np.random.default_rng(r)
vals = np.linspace(0, 1, 2)
best = np.inf
for code in range(budget):
idx = [(code >> j) & 1 for j in range(d)]
x = np.array([vals[i] for i in idx])
best = min(best, f_hp(x))
grid_best.append(best)
X = rng.random((budget, d))
rand_best.append(min(f_hp(x) for x in X))
grid_best = np.array(grid_best); rand_best = np.array(rand_best)
print(f"预算 {budget} 次求值,{reps} 次重复:")
print(f" 网格搜索: 最好值均值 = {grid_best.mean():.4f}, 中位数 = {np.median(grid_best):.4f}")
print(f" 随机搜索: 最好值均值 = {rand_best.mean():.4f}, 中位数 = {np.median(rand_best):.4f}")
print(f" 随机搜索优于网格搜索的比例 = {np.mean(rand_best < grid_best):.2%}")输出:
预算 64 次求值,200 次重复:
网格搜索: 最好值均值 = 0.1800, 中位数 = 0.1800
随机搜索: 最好值均值 = 0.0050, 中位数 = 0.0037
随机搜索优于网格搜索的比例 = 100.00%网格搜索的结果可以精确算出:
这个实验的结论有明确的适用边界:当所有维度都同等重要且维度不高(不超过
3.9.2 直接搜索方法
Nelder-Mead 单纯形法
Nelder-Mead 方法(简称 NM,与线性规划的单纯形法同名但无关)维护
设
反射:
扩张:反射点比当前最优点还好时,尝试更远的一点
收缩:反射点比次差点还差时,在质心与最差点之间尝试
整体收缩:收缩也失败时,把所有点向最优点方向收缩一半:
NM 在低维光滑问题上表现很好(本例二维 Rosenbrock 中
模式搜索与收敛性
模式搜索(pattern search,也称坐标搜索、compass search)的每一步固定:在当前点附近沿各坐标方向以步长
模式搜索的代价是收敛慢。在强耦合或各维尺度悬殊的问题上,坐标方向的试探效率低:步长主要由最优方向上的进展决定,其他方向只能等待步长慢慢衰减。下面的对照实验把三种方法放在两个问题上比较。
import numpy as np
def nelder_mead(fun, x0, budget=2000, tol=1e-12):
n = len(x0)
sim = [np.array(x0, dtype=float)]
for i in range(n):
p = np.array(x0, dtype=float); p[i] += 0.05
sim.append(p)
sim = np.array(sim)
vals = np.array([fun(p) for p in sim])
neval = n + 1
while neval < budget:
order = np.argsort(vals)
sim, vals = sim[order], vals[order]
if abs(vals[-1] - vals[0]) < tol * (abs(vals[0]) + tol):
break
centroid = sim[:-1].mean(axis=0)
xr = centroid + (centroid - sim[-1]); fr = fun(xr); neval += 1
if fr < vals[0]:
xe = centroid + 2 * (centroid - sim[-1]); fe = fun(xe); neval += 1
sim[-1], vals[-1] = (xe, fe) if fe < fr else (xr, fr)
elif fr < vals[-2]:
sim[-1], vals[-1] = xr, fr
else:
xc = centroid + 0.5 * (sim[-1] - centroid); fc = fun(xc); neval += 1
if fc < vals[-1]:
sim[-1], vals[-1] = xc, fc
else:
for i in range(1, n + 1):
sim[i] = sim[0] + 0.5 * (sim[i] - sim[0])
vals[i] = fun(sim[i]); neval += 2
best = int(np.argmin(vals))
return sim[best], vals[best], neval
def compass_search(fun, x0, budget=2000, step0=1.0):
x = np.array(x0, dtype=float)
fx = fun(x); neval = 1
step = step0
while neval < budget:
improved = False
for i in range(len(x)):
for sgn in (1, -1):
cand = x.copy(); cand[i] += sgn * step
fc = fun(cand); neval += 1
if fc < fx:
x, fx = cand, fc
improved = True
break
if not improved:
step *= 0.5
if step < 1e-12:
break
return x, fx, neval
def random_search(fun, d, budget=2000, lo=-2.0, hi=2.0, seed=0):
rng = np.random.default_rng(seed)
best = np.inf
for _ in range(budget):
x = lo + (hi - lo) * rng.random(d)
best = min(best, fun(x))
return best, budget
rosen = lambda x: (1 - x[0]) ** 2 + 100 * (x[1] - x[0] ** 2) ** 2
print("Rosenbrock,起点 (-1.2, 1.0),预算 2000:")
x, f, n = nelder_mead(rosen, np.array([-1.2, 1.0]), budget=2000)
print(f" Nelder-Mead: {n} 次求值, f = {f:.3e}, 解 = ({x[0]:.6f}, {x[1]:.6f})")
x, f, n = compass_search(rosen, np.array([-1.2, 1.0]), budget=2000)
print(f" 模式搜索: {n} 次求值, f = {f:.3e}, 解 = ({x[0]:.6f}, {x[1]:.6f})")
f, n = random_search(rosen, 2, budget=2000)
print(f" 随机搜索: {n} 次求值, f = {f:.3e}")
rng = np.random.default_rng(7)
d10 = 10
Aq = rng.normal(size=(d10, d10)); Q, _ = np.linalg.qr(Aq)
M100 = Q @ np.diag(np.logspace(0, 2, d10)) @ Q.T # 条件数 100 的旋转椭球
f100 = lambda x: float(x @ (M100 @ x))
print("10 维病态椭球(条件数 100),起点全 1,预算 2000:")
x, f, n = compass_search(f100, np.ones(d10), budget=2000)
print(f" 模式搜索: {n} 次求值, f = {f:.3e}")
f, n = random_search(f100, d10, budget=2000)
print(f" 随机搜索: {n} 次求值, f = {f:.3e}")
x, f, n = nelder_mead(f100, np.ones(d10), budget=2000)
print(f" Nelder-Mead: {n} 次求值, f = {f:.3e}")输出:
Rosenbrock,起点 (-1.2, 1.0),预算 2000:
Nelder-Mead: 305 次求值, f = 8.600e-25, 解 = (1.000000, 1.000000)
模式搜索: 2000 次求值, f = 8.634e-02, 解 = (0.706250, 0.498047)
随机搜索: 2000 次求值, f = 1.191e-02
10 维病态椭球(条件数 100),起点全 1,预算 2000:
模式搜索: 2002 次求值, f = 8.763e-12
随机搜索: 2000 次求值, f = 1.997e+01
Nelder-Mead: 2000 次求值, f = 1.267e-01三组结果各说明一件事。
NM 在低维光滑问题上效率最高。Rosenbrock 的弯曲窄谷使坐标方向几乎无效,NM 靠单纯形的几何变形(沿谷底方向拉长)在
模式搜索的优势在可分离或近可分离的问题上。10 维椭球虽然病态(条件数
随机搜索在两个问题上都不具竞争力:Rosenbrock(
3.9.3 进化策略与 CMA-ES
进化策略的基本形式
进化策略(evolution strategy)维护一个参数化的搜索分布(通常取多元正态分布
两种选择策略:(μ, λ)-ES 只用当前代的 λ 个子代中最好的 μ 个更新分布(父代不进入选择),(μ + λ)-ES 把父代与子代放在一起选。前者抗噪、避免停滞,后者收敛更快。
1/5 成功规则是最早的步长自适应方法:统计最近若干代的改进比例,超过
CMA-ES
CMA-ES(covariance matrix adaptation evolution strategy)把如何调整分布的形状做成完整的算法,是中等维度(
采样。
选择与重组。按函数值排序,取最好的
步长自适应。维护演化路径
协方差自适应。两个更新叠加。秩一更新用演化路径
所有学习率(
CMA-ES 的两个性质使它难以被替代:不变性(对搜索空间的正交变换与平移不变,即问题被旋转不改变算法行为;对单调尺度变换的处理由步长自适应负责)与对病态问题的稳健性(协方差矩阵自动学习问题的尺度方向)。默认群体大小为
数值实验:病态椭球上的对比
取
import numpy as np
def cma_es(fun, x0, sigma0, budget=4000, seed=0):
n = len(x0)
lam = 4 + int(3 * np.log(n))
mu = lam // 2
w = np.log(mu + 0.5) - np.log(np.arange(1, mu + 1))
w = w / w.sum()
mueff = 1.0 / np.sum(w ** 2)
cc = (4 + mueff / n) / (n + 4 + 2 * mueff / n)
cs = (mueff + 2) / (n + mueff + 5)
ds = 1 + 2 * max(0, np.sqrt((mueff - 1) / (n + 1)) - 1) + cs
c1 = 2 / ((n + 1.3) ** 2 + mueff)
cmu = min(1 - c1, 2 * (mueff - 2 + 1 / mueff) / ((n + 2) ** 2 + mueff))
chiN = np.sqrt(n) * (1 - 1 / (4 * n) + 1 / (21 * n ** 2))
rng = np.random.default_rng(seed)
xmean = np.array(x0, dtype=float)
C = np.eye(n); sigma = sigma0
pc = np.zeros(n); ps = np.zeros(n)
best_x, best_f = xmean.copy(), fun(xmean)
neval = 1
gen = 0
while neval < budget:
gen += 1
D, B = np.linalg.eigh(C)
D = np.sqrt(np.maximum(D, 1e-30))
X = xmean + sigma * (rng.normal(size=(lam, n)) @ np.diag(D) @ B.T)
fvals = np.array([fun(x) for x in X]); neval += lam
idx = np.argsort(fvals)
if fvals[idx[0]] < best_f:
best_f = fvals[idx[0]]; best_x = X[idx[0]].copy()
xold = xmean.copy()
xmean = np.sum(w[:, None] * X[idx[:mu]], axis=0)
ymean = (xmean - xold) / sigma
ps = (1 - cs) * ps + np.sqrt(cs * (2 - cs) * mueff) * (B @ ((D ** -1) * (B.T @ ymean)))
hsig = (np.linalg.norm(ps) / np.sqrt(1 - (1 - cs) ** (2 * gen)) / chiN
< 1.4 + 2 / (n + 1))
pc = (1 - cc) * pc + hsig * np.sqrt(cc * (2 - cc) * mueff) * ymean
artmp = (X[idx[:mu]] - xold) / sigma
C = ((1 - c1 - cmu) * C
+ c1 * (np.outer(pc, pc) + (1 - hsig) * cc * (2 - cc) * C)
+ cmu * (artmp.T @ (w[:, None] * artmp)))
sigma = sigma * np.exp((cs / ds) * (np.linalg.norm(ps) / chiN - 1))
return best_x, best_f, neval
f_sphere = lambda x: float(x @ x)
x, f, n = cma_es(f_sphere, np.ones(10), 1.0, budget=2000)
print(f"10 维球函数:CMA-ES {n} 次求值, f = {f:.3e}")
rng = np.random.default_rng(7)
d10 = 10
Aq = rng.normal(size=(d10, d10)); Q, _ = np.linalg.qr(Aq)
M1e4 = Q @ np.diag(np.logspace(0, 4, d10)) @ Q.T
f1e4 = lambda x: float(x @ (M1e4 @ x))
print("10 维旋转椭球(条件数 1e4),起点全 1,预算 4000:")
x, f, n = cma_es(f1e4, np.ones(d10), 1.0, budget=4000)
print(f" CMA-ES: {n} 次求值, f = {f:.3e}")
x, f, n = compass_search(f1e4, np.ones(d10), budget=4000) # 取 3.9.2 节的实现
print(f" 模式搜索: {n} 次求值, f = {f:.3e}")
x, f, n = nelder_mead(f1e4, np.ones(d10), budget=4000) # 取 3.9.2 节的实现
print(f" Nelder-Mead: {n} 次求值, f = {f:.3e}")输出:
10 维球函数:CMA-ES 2001 次求值, f = 1.128e-12
10 维旋转椭球(条件数 1e4),起点全 1,预算 4000:
CMA-ES: 4001 次求值, f = 6.010e-12
模式搜索: 4004 次求值, f = 6.450e-01
Nelder-Mead: 4000 次求值, f = 2.350e+00球函数上的对照说明算法本身正常工作:
CMA-ES 的代价与适用边界
CMA-ES 每代需要一次
3.9.4 贝叶斯优化
代理模型
贝叶斯优化(Bayesian optimization)针对的是求值次数只有几十次的情形。它的做法是用一个代理模型(surrogate model)拟合已知的求值结果,用模型的不确定性指导下一步在哪里取样,使每次求值的信息利用率最大化。
最常用的代理模型是高斯过程回归(Gaussian process regression)。给定已有的观测
其中
长度尺度不是预先知道的,标准做法是最大化对数边际似然(log marginal likelihood):
三项分别对应拟合优度、模型的复杂度惩罚与常数。在若干候选长度尺度上比较这一值(或做梯度优化),选出的
采集函数
有了后验均值与方差,下一步取样点的选择由采集函数(acquisition function)决定。它把预测值好与不确定性大两个诉求编码成一个标量,最大化它得到下一个求值点。
期望改进(Expected Improvement,EI)。设当前最小观测值为
其中
置信界(UCB 与 LCB)。取
熵搜索与知识梯度。它们把减少最优解位置的不确定性直接作为目标(信息增益的期望),性能通常更好但计算代价高(需要估计后验分布中最小点位置的信息熵)。汤普森采样是另一种简单有效的替代:从后验分布中采样一个函数实现,最大化它。它的实现代价低,天然支持批量与并行(每次采样一个函数实现)。
数值实验:与随机搜索的对比
在一维函数上做对照。目标函数有两个高斯型凹陷:宽而浅的局部极小在
import numpy as np
import math
def f_bo(x):
x = float(np.asarray(x).ravel()[0])
return -(np.exp(-((x - 0.2) / 0.1) ** 2) + 1.2 * np.exp(-((x - 0.8) / 0.05) ** 2))
def kern(A, B, Ls):
d2 = (A[:, None, :] - B[None, :, :]) ** 2
return np.exp(-0.5 * np.sum(d2, axis=-1) / Ls ** 2)
def gp_posterior(X, y, Xs, Ls, noise=1e-8):
K = kern(X, X, Ls) + noise * np.eye(len(X))
L = np.linalg.cholesky(K)
alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
Ks = kern(X, Xs, Ls)
mu = Ks.T @ alpha
v = np.linalg.solve(L, Ks)
return mu, np.sqrt(np.maximum(1 - np.sum(v ** 2, axis=0), 1e-12))
def margin_ll(X, y, Ls):
K = kern(X, X, Ls) + 1e-8 * np.eye(len(X))
L = np.linalg.cholesky(K)
alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
return float(-0.5 * y @ alpha - np.sum(np.log(np.diag(L))))
def ei(mu, sd, fbest, xi=0.01):
z = (fbest - mu - xi) / sd
Phi = 0.5 * (1 + np.vectorize(math.erf)(z / np.sqrt(2)))
phi = np.exp(-0.5 * z ** 2) / np.sqrt(2 * np.pi)
return (fbest - mu - xi) * Phi + sd * phi
def run_bo(seed, n_total=33, n_init=3, grid=2001):
rng = np.random.default_rng(seed)
xs = np.linspace(0, 1, grid).reshape(-1, 1)
X = rng.random((n_init, 1))
y = np.array([f_bo(x) for x in X])
hist = {}
pts = [float(v) for v in X.ravel()]
for k in range(n_total - n_init):
best_ls, best_ll = None, -np.inf
for Ls in [0.02, 0.03, 0.05, 0.1, 0.2, 0.4]:
ll = margin_ll(X, y, Ls)
if ll > best_ll:
best_ll, best_ls = ll, Ls
mu, sd = gp_posterior(X, y, xs, best_ls)
vals = ei(mu, sd, y.min()) + 1e-6 * rng.random(len(xs)) # 抖动打破平局
x_new = xs[int(np.argmax(vals))]
y = np.append(y, f_bo(x_new))
X = np.vstack([X, x_new])
pts.append(float(x_new.ravel()[0]))
hist[n_init + k + 1] = y.min()
return hist, pts
def run_random(seed, n_total=33):
rng = np.random.default_rng(seed)
y = np.inf
hist = {}
for k in range(1, n_total + 1):
y = min(y, f_bo(rng.random()))
hist[k] = y
return hist
bo_res = [run_bo(s) for s in range(10)]
rnd_res = [run_random(s) for s in range(10)]
print("一维函数(全局最小值约 -1.2 在 x = 0.8),10 次重复,每次 33 次求值:")
for k in (5, 10, 20, 33):
print(f" 求值 {k:>2} 次: 贝叶斯优化 = {np.mean([h[k] for h, _ in bo_res]):.4f}, "
f"随机搜索 = {np.mean([h[k] for h in rnd_res]):.4f}")
target = -1.19
bh = [next((k for k in sorted(h) if h[k] <= target), None) for h, _ in bo_res]
rh = [next((k for k in sorted(h) if h[k] <= target), None) for h in rnd_res]
print(f" 达到 {target} 以内:贝叶斯优化 {np.mean([x is not None for x in bh]):.0%}"
f"(成功时平均 {np.mean([x for x in bh if x]):.1f} 次), "
f"随机搜索 {np.mean([x is not None for x in rh]):.0%}"
f"(成功时平均 {np.mean([x for x in rh if x]):.1f} 次)")
print("一次运行的采样点序列(前 3 个为初始点):")
print(" " + " ".join(f"{p:.3f}" for p in bo_res[0][1]))输出:
一维函数(全局最小值约 -1.2 在 x = 0.8),10 次重复,每次 33 次求值:
求值 5 次: 贝叶斯优化 = -0.8869, 随机搜索 = -0.8781
求值 10 次: 贝叶斯优化 = -1.0985, 随机搜索 = -1.0561
求值 20 次: 贝叶斯优化 = -1.1980, 随机搜索 = -1.1474
求值 33 次: 贝叶斯优化 = -1.1990, 随机搜索 = -1.1474
达到 -1.19 以内:贝叶斯优化 100%(成功时平均 13.8 次), 随机搜索 30%(成功时平均 6.3 次)
一次运行的采样点序列(前 3 个为初始点):
0.637 0.270 0.041 1.000 0.400 0.206 0.819 0.871 0.777 0.152 0.800 0.520 0.331 0.101 0.942 0.579 0.460 0.000 0.696 0.233 0.704 0.183 0.921 0.942 0.580 0.826 0.951 0.146 0.854 0.146 0.391 0.334 0.322三点观察。
样本效率的差别。
采样序列的结构。一次运行的采样点序列呈现三段模式:前三个随机初始点(
随机搜索的成功率并不为零。
采集函数的两个实现细节
其一是平局与数值塌缩:当所有候选点的 EI 都接近零(模型认为没有改进可能)时,
3.9.5 超参数调优实践
搜索空间的设计
超参数调优是黑箱优化的主要应用。搜索空间的设计对结果的影响往往大于算法的选择。
尺度。学习率、正则系数、噪声强度这类参数应当在对数尺度上搜索:
类型。连续参数(学习率、权重衰减、动量)、整数参数(层数、注意力头数)、类别参数(优化器、激活函数、初始化方式)混合存在。类别参数用均匀采样,整数参数在合理的范围内采样后取整,且注意尺度(层数用线性尺度,批量大小用对数尺度或按
条件参数。某些参数只在另一些参数取特定值时有意义(用了某个优化器才有对应的参数),处理方式是条件搜索空间:先采样上层参数,再按条件采样下层参数。忽略条件会让大量试点的下层参数落在无效组合上。
预算分配:连续减半与 Hyperband
给定固定的总预算(GPU 小时数),两种使用方式:一是给每个配置完整的训练预算(
连续减半的流程:先均匀采样
Hyperband 修正了连续减半的一个缺陷(当最小预算
代理模型的选择
TPE(tree-structured Parzen estimator)与 SMAC 使用非高斯过程代理:TPE 用两个核密度估计分别建模表现好与表现差的配置分布,用它们的比值作为采集函数;SMAC 用随机森林回归,适合含大量类别与条件参数的空间。选择依据是空间的类型组成:连续、低维、求值预算几十次时高斯过程加 EI 最合适;含大量离散与条件参数、观测数上百时树模型更稳健;维度继续升高(几十维以上)时,全局代理模型都会失效,需要回到随机搜索、CMA-ES 或局部贝叶斯优化(在信任域内维护局部 GP)。
与内层训练的分工
超参数调优的调用结构是两级嵌套:内层用 3.4 到 3.8 节的方法训练模型参数,外层用本节的算法搜索超参数。两层的时间尺度相差几个数量级(内层数千到数百万步梯度更新,外层几十到几百次试验),因此优化方法的取舍完全不同:内层依赖梯度的效率,外层依赖样本效率与并行性。
外层实验还需要注意验证集过拟合:反复用同一验证集选择配置,最终的报告指标会系统性偏高。做法是保留一个从未参与选择的测试集用于最终报告,或者用嵌套交叉验证估计选择过程本身的方差。另外,超参数搜索的结果常常是一片平坦区域而非单点最优:报告最优配置附近的若干近优配置比报告单点更稳健,也更有参考价值。
什么时候不必用贝叶斯优化
贝叶斯优化的价值来自每次求值昂贵 + 预算极小。三种情形下它并不划算:预算充足(数百次以上)时随机搜索或 CMA-ES 的期望表现相当且无需维护代理模型;维度很高时全局 GP 失效,需要局部方法或直接换用进化策略;需要大规模并行时异步贝叶斯优化实现复杂,而随机搜索与 CMA-ES 天然并行。判断标准是单位求值成本乘以预算:总成本低时用简单方法,把复杂度留给真正昂贵的场景。
3.9.6 本节小结
无梯度优化的方法地图
本节按取样机制组织了四类方法。基线与判决标准:随机搜索在低维问题上有可观的运气成分,在
第三章到此结束。九节内容从问题的语言与分类开始,经过凸性工具、无约束算法、约束与对偶、非光滑方法,到非凸优化与无梯度方法,覆盖了优化的主要分支与它们在机器学习中的对应物。下一章进入信息论:那里讨论的是另一类目标函数(交叉熵、互信息、散度),它们的凸性与光滑性、以及作为优化目标时的性质,都与本章的工具直接相关;信息论中的最大熵原理、KL 散度与信息几何,也可以看作在概率单纯形这一特殊可行域上的优化问题。
练习题
第 1 题 概念推导
在最小化问题中,设新点的函数值服从后验分布
参考答案
推导:设
代入
第一个积分为
于是
两项的含义:第一项
两个极限:
第 2 题 计算推理
(a) 在 3.9.1 节的
参考答案
(a) 网格搜索结果可以精确计算。每维
与实验输出
(b)
(c) 默认群体大小
权重
与程序输出
第 3 题 代码验证
用本节代码做三组实验并解释:(a) 把 3.9.2 节模式搜索的初始步长从
参考答案
import numpy as np
def rosen(x):
return (1 - x[0]) ** 2 + 100 * (x[1] - x[0] ** 2) ** 2
def compass_search(fun, x0, budget=2000, step0=1.0):
x = np.array(x0, dtype=float)
fx = fun(x); neval = 1
step = step0
while neval < budget:
improved = False
for i in range(len(x)):
for sgn in (1, -1):
cand = x.copy(); cand[i] += sgn * step
fc = fun(cand); neval += 1
if fc < fx:
x, fx = cand, fc
improved = True
break
if not improved:
step *= 0.5
if step < 1e-12:
break
return fx, neval
print("模式搜索的初始步长(Rosenbrock,预算 2000):")
for s0 in [0.1, 1.0, 3.0]:
fv, ne = compass_search(rosen, np.array([-1.2, 1.0]), step0=s0)
print(f" 初始步长 {s0}: f = {fv:.3e}, 求值 {ne} 次")
# (b) 下置信界的探索强度
def f_bo(x):
x = float(np.asarray(x).ravel()[0])
return -(np.exp(-((x - 0.2) / 0.1) ** 2) + 1.2 * np.exp(-((x - 0.8) / 0.05) ** 2))
def kern(A, B, Ls):
d2 = (A[:, None, :] - B[None, :, :]) ** 2
return np.exp(-0.5 * np.sum(d2, axis=-1) / Ls ** 2)
def gp_posterior(X, y, Xs, Ls, noise=1e-8):
K = kern(X, X, Ls) + noise * np.eye(len(X))
L = np.linalg.cholesky(K)
alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
Ks = kern(X, Xs, Ls)
mu = Ks.T @ alpha
v = np.linalg.solve(L, Ks)
return mu, np.sqrt(np.maximum(1 - np.sum(v ** 2, axis=0), 1e-12))
def margin_ll(X, y, Ls):
K = kern(X, X, Ls) + 1e-8 * np.eye(len(X))
L = np.linalg.cholesky(K)
alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
return float(-0.5 * y @ alpha - np.sum(np.log(np.diag(L))))
def run_lcb(seed, kappa, n_total=33, n_init=3, grid=2001):
rng = np.random.default_rng(seed)
xs = np.linspace(0, 1, grid).reshape(-1, 1)
X = rng.random((n_init, 1))
y = np.array([f_bo(x) for x in X])
for _ in range(n_total - n_init):
best_ls, best_ll = None, -np.inf
for Ls in [0.02, 0.03, 0.05, 0.1, 0.2, 0.4]:
ll = margin_ll(X, y, Ls)
if ll > best_ll:
best_ll, best_ls = ll, Ls
mu, sd = gp_posterior(X, y, xs, best_ls)
x_new = xs[int(np.argmin(mu - kappa * sd))]
y = np.append(y, f_bo(x_new))
X = np.vstack([X, x_new])
return y.min()
print("下置信界 kappa 的影响(10 次重复,33 次求值):")
for kappa in [0.0, 0.5, 2.0, 4.0]:
vals = [run_lcb(s, kappa) for s in range(10)]
print(f" kappa = {kappa}: 最优值均值 = {np.mean(vals):.4f}")输出:
模式搜索的初始步长(Rosenbrock,预算 2000):
初始步长 0.1: f = 1.536e-02, 求值 2001 次
初始步长 1.0: f = 8.634e-02, 求值 2000 次
初始步长 3.0: f = 8.299e-01, 求值 2000 次
下置信界 kappa 的影响(10 次重复,33 次求值):
kappa = 0.0: 最优值均值 = -1.1000
kappa = 0.5: 最优值均值 = -1.1000
kappa = 2.0: 最优值均值 = -1.1200
kappa = 4.0: 最优值均值 = -1.1400结论:(a) 初始步长决定模式搜索的起跑效率:
(b) 探索强度越大,
(c)
第 4 题 综合应用
某团队要调优一个深度模型的训练配置,可调的项包括学习率、批大小、权重衰减、网络深度、注意力头数、优化器类型与三种数据增强的强度。单次完整训练需要 12 小时,团队有 8 张加速卡与两周时间。请给出完整的调优方案:搜索空间的设计(各参数的尺度与类型)、预算分配方式、代理模型的选择、并行策略、以及报告结论时的注意事项。并说明什么情况下应当放弃贝叶斯优化改用更简单的方法。
参考答案
搜索空间:学习率、权重衰减、数据增强强度用对数尺度(例如
预算分配:总预算
代理模型:空间以连续参数为主,观测数在几十到几百之间,用高斯过程加 EI(或 TPE)都合适。若类别参数占比大、条件关系复杂,改用 TPE 或随机森林代理更稳。长度尺度或核参数通过最大化对数边际似然拟合,过程中记录拟合的
并行策略:12 小时的训练时长使串行贝叶斯优化的吞吐过低。用异步并行:维护
报告注意事项:保留最终测试集(从不参与选择),避免验证集过拟合;报告最优配置附近的若干近优配置而不是单点(超参数的重要性排序往往比最优值更稳定);报告学习率与批大小的联合选择(两者的耦合关系比各自的绝对值更重要);说明多保真带来的偏差(短预算选出的配置在长预算下可能不再最优)。
放弃贝叶斯优化的情形:预算充足(数百次完整训练)时随机搜索或 CMA-ES 的期望表现相当,而维护 GP 与采集函数的工程成本不低;维度超过约
常见错误
错误 1 · 在高维空间直接使用全局贝叶斯优化
原因:高斯过程的样本效率优势建立在低维与平滑假设上。维度升高后,为覆盖搜索空间所需的观测数指数增长,后验方差在绝大多数区域保持接近先验水平,采集函数退化为近似随机采样;GP 拟合的
解决:高维时改用信任域局部贝叶斯优化(在局部区域内拟合 GP,随进展移动区域)、随机嵌入(先降到低维子空间再优化)、或换成 CMA-ES 与随机搜索。另一个方向是降低维度本身:固定或分阶段优化参数(先调学习率与批大小,再调结构参数),或按参数重要性排序只优化前几个。
错误 2 · 把 Nelder-Mead 当作有收敛保证的方法
原因:NM 的更新规则是启发式的,没有单调下降的保证;存在二维反例(McKinnon 函数)使它收敛到非稳定点,高维与噪声环境下失败模式更多(单纯形退化到低维子空间、步长无法适应各维尺度差异,本节实验中
解决:把 NM 用于低维(不超过
错误 3 · 采集函数实现中不处理平局与重复点
原因:EI 在所有候选点都没有改进可能时会整体塌缩到接近零,
解决:对采集函数值加微小随机抖动或改用 log-EI;噪声项取一个与观测精度匹配的正数(
错误 4 · 超参数搜索的尺度与验证设计错误
原因:两类高频错误。其一是搜索空间的尺度:学习率、正则系数这类跨数量级的参数若用线性采样,绝大部分预算落在区间的大值侧,最优点附近的采样密度过低。其二是验证集过拟合:反复用同一验证集筛选配置(几十次到上百次选择),最终报告的最优指标会系统性偏高几倍于单次评估的标准误。
解决:跨数量级参数一律在对数尺度采样(log-uniform);离散结构参数按语义选择尺度(层数线性、批量按