贝叶斯优化实战(四)(1)https://developer.aliyun.com/article/1516484
A.3.2 练习 2:超参数调优的 BayesOpt
此练习在 CH04/03 - Exercise 2.ipynb 中实现,将 BayesOpt 应用于模拟支持向量机模型在超参数调优任务中的准确度曲面的目标函数。完成以下步骤:
- 在 CH04/01 - BayesOpt loop.ipynb 中重新创建 BayesOpt 循环。我们的目标函数实现为
def f(x): return ( torch.sin(5 * x[..., 0] / 2 - 2.5) * torch.cos(2.5 - 5 * x[..., 1]) + (5 * x[..., 1] / 2 + 0.5) ** 2 / 10 ) / 5 + 0.2
- 使用
xs
声明相应的测试数据,表示域的二维网格和xs
的函数值的ys
:
lb = 0 ub = 2 num_queries = 20 bounds = torch.tensor([[lb, lb], [ub, ub]], dtype=torch.float) xs = torch.linspace(lb, ub, 101) x1, x2 = torch.meshgrid(xs, xs) xs = torch.vstack((x1.flatten(), x2.flatten())).transpose(-1, -2) ys = f(xs)
- 修改可视化优化进展的辅助函数。我们将此函数声明为
visualize_progress_and_policy()
,该函数只需要一个策略对象和next_x
作为要查询的下一个点。首先,函数计算测试数据xs
的获取分数:
def visualize_progress_and_policy(policy, next_x=None): with torch.no_grad(): acquisition_score = policy(xs.unsqueeze(1)) ... ❶
- ❶ 待续
接下来,我们声明两个 Matplotlib 子图,并且对于第一个子图,绘制存储在ys
中的真实情况:
c = ax[0].imshow( ❶ ys.reshape(101, 101).T, origin="lower", extent=[lb, ub, lb, ub] ❶ ) ❶ ax[0].set_xlabel(r"$C$", fontsize=20) ax[0].set_ylabel(r"$\gamma$", fontsize=20) plt.colorbar(c, ax=ax[0]) ax[0].scatter(train_x[..., 0], train_x[..., 1], marker="x", c="k") ❷
- ❶ 显示真实情况的热图
❷ 显示标记数据的散点图
最后,我们在第二个子图中绘制另一个热图,显示获取分数:
c = ax[1].imshow( ❶ acquisition_score.reshape(101, 101).T, ❶ origin="lower", ❶ extent=[lb, ub, lb, ub] ❶ ) ❶ ax[1].set_xlabel(r"$C$", fontsize=20) plt.colorbar(c, ax=ax[1])
- ❶ 显示获取分数的热图
我们可以选择显示next_x
:
if next_x is not None: ax[1].scatter( next_x[..., 0], next_x[..., 1], c="r", marker="*", s=500, label="next query" )
- 从第三章的练习中复制 GP 类,该类实现了具有 ARD 的 Matérn 2.5 核。进一步修改此类以使其与 BoTorch 集成:
class GPModel(gpytorch.models.ExactGP, ❶ botorch.models.gpytorch.GPyTorchModel): ❶ _num_outputs = 1 ❶ def __init__(self, train_x, train_y, likelihood): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ConstantMean() self.covar_module = gpytorch.kernels. ❷ ➥ScaleKernel( ❷ gpytorch.kernels.MaternKernel( ❷ nu=2.5, ❷ ard_num_dims=2 ❷ ) ❷ ) ❷ def forward(self, x): ... ❸
- ❶ BoTorch 相关修改
❷ 具有 ARD 的 Matérn 2.5 核
❸ 省略 - 重用辅助函数
fit_gp_model()
和实现 BayesOpt 的for
循环。我们复制fit_gp_model()
并声明初始数据集:
train_x = torch.tensor([ [1., 1.], ]) train_y = f(train_x)
- 然后我们声明 BayesOpt 循环:
num_queries = 20 for i in range(num_queries): print("iteration", i) print("incumbent", train_x[train_y.argmax()], train_y.max()) model, likelihood = fit_gp_model(train_x, train_y) policy = ... ❶ next_x, acq_val = botorch.optim.optimize_acqf( policy, bounds=bounds, q=1, num_restarts=40, ❷ raw_samples=100, ❷ ) visualize_progress_and_policy(policy, ➥next_x=next_x) ❸ next_y = f(next_x) train_x = torch.cat([train_x, next_x]) train_y = torch.cat([train_y, next_y])
- ❶ 策略初始化的占位符
❷ 使搜索更加穷举
❸ 调用新的可视化辅助函数 - 在这个目标函数上运行 PoI 策略。观察到该策略再次陷入局部最优。将初始化 BayesOpt 策略的行替换为
policy = botorch.acquisition.analytic.ProbabilityOfImprovement( model, best_f=train_y.max() )
- 运行整个笔记本,显示策略再次陷入局部最优,如图 A.3 所示。
图 A.3 显示了 PoI 在最后一次迭代中的优化进度。该策略被困在了一个局部最优解中。 - 运行修改后的 PoI 版本,其中最小改进阈值设置为 0.1。将初始化 BayesOpt 策略的行替换为:
policy = botorch.acquisition.analytic.ProbabilityOfImprovement( model, best_f=train_y.max() + 0.1 )
- 该策略更具探索性,表现优于常规 PoI。图 A.4 显示了该策略在第 17 次迭代时的进展,其中它首次达到至少 90%的准确率。
图 A.4 显示了修改版 PoI 在第 17 次迭代中的优化进度,在该次迭代中,该策略首次达到至少 90%的准确率。
在这里,C = 1.6770,γ = 1.9039 是提供此准确度的参数。 - 在此目标函数上运行 Expected Improvement(EI)策略。用初始化 BayesOpt 策略的行替换:
policy = botorch.acquisition.analytic.ExpectedImprovement( model, best_f=train_y.max() )
- 该策略在我们的目标函数上表现良好,如图 A.5 所示,在第 15 次迭代中找到了至少 90%的准确率。
图 A.5 显示了 EI 在第 4 次迭代中的优化进度,在该次迭代中,该策略首次达到至少 90%的准确率。
在这里,C = 1.6331,γ = 1.8749 是提供此准确度的参数。 - 实施重复实验,并可视化 10 个实验中的平均 incumbent 值和误差条。我们首先将实现 BayesOpt 循环的代码放入一个外部循环中,该循环迭代多个实验。我们在
incumbents
中的每一步跨实验存储每次最好的值:
num_repeats = 10 incumbents = torch.zeros((num_repeats, num_queries)) for trial in range(num_repeats): print("trial", trial) torch.manual_seed(trial) ❶ train_x = bounds[0] + (bounds[1] - bounds[0]) ❶ ➥* torch.rand(1, 2) ❶ train_y = f(train_x) ❶ for i in tqdm(range(num_queries)): incumbents[trial, i] = train_y.max() ❷ ... ❸ torch.save(incumbents, [path to file]) ❹
- ❶在搜索空间中均匀采样一个数据点作为起始点
❷跟踪最佳值
❸省略的代码与之前相同。
❹将结果保存到文件中,以便稍后可视化。
然后我们实现一个帮助函数,绘制平均 incumbent 值和误差条。该函数读取保存在path
中的 PyTorch tensor,该 tensor 应是前一步中incumbents
的保存版本:
def show_agg_progress(path, name): def ci(y): ❶ return 2 * y.std(axis=0) / np.sqrt(num_repeats) ❶ incumbents = torch.load(path) ❷ avg_incumbent = incumbents.mean(axis=0) ❸ ci_incumbent = ci(incumbents) ❸ plt.plot(avg_incumbent, label=name) ❹ plt.fill_between( ❹ np.arange(num_queries), ❹ avg_incumbent + ci_incumbent, ❹ avg_incumbent - ci_incumbent, ❹ alpha=0.3, ❹ ) ❹
- ❶计算误差条的辅助子函数
❷加载保存的优化结果
❸计算结果的平均值和误差条
❹可视化结果平均值和误差条
然后我们可以运行我们在前面代码中拥有的策略,并比较它们的表现:
plt.figure(figsize=(8, 6)) show_agg_progress([path to EI data], "EI") show_agg_progress([path to PoI data], "PoI") show_agg_progress([path to modified PoI data], "PoI" + r"$(\epsilon = 0.1)$") plt.xlabel("# queries") plt.ylabel("accuracy") plt.legend() plt.show()
- 这生成了图 A.6,显示了 PoI、修改版 PoI 和 EI 的优化性能。
图 A.6 显示了 10 个重复实验中各种策略的优化进度。
我们发现图 A.6 比单次运行中的策略更具见解。在这里,PoI 的表现不如其他两个策略,而且其性能也不太稳健,从大的误差条中可以看出来。修改版 PoI 和 EI 表现相当,很难判断哪个更好,因为它们的误差条重叠。
A.4 第五章:使用赌博工具风格的策略探索搜索空间
本章中有两个练习:
- 第一个练习探索了一种为 UCB 策略设置权衡参数的潜在方法,该方法考虑了我们在优化中的进展情况。
- 第二个练习将本章学到的两种策略应用于以前章节中看到的超参数调整问题。
A.4.1 练习 1:为上置信界限设置探索计划
这个练习实现在 CH05/02 - Exercise 1.ipynb 中,讨论了自适应设置 UCB 策略权衡参数β值的策略。完成以下步骤:
- 在 CH04/02 - Exercise 1.ipynb 中重新创建 BayesOpt 循环,该循环将一维 Forrester 函数作为优化目标。
由于 BayesOpt 循环中有 10 次迭代,β乘以倍数m 10 次,从 1 到 10。也就是说,1 × m10 = 10。解决这个方程得到了倍数的代码:
num_queries = 10 start_beta = 1 end_beta = 10 multiplier = (end_beta / start_beta) ** (1 / num_queries)
- 实现这个调度逻辑,并观察结果的优化性能。
我们对 BayesOpt 循环进行如下修改:
num_queries = 10 start_beta = 1 end_beta = 10 multiplier = (end_beta / start_beta) ** (1 / num_queries) beta = start_beta for i in range(num_queries): ... ❶ policy = botorch.acquisition.analytic.UpperConfidenceBound( model, beta=beta ) ... ❷ beta *= multiplier
- ❶ 获得训练好的 GP
❷ 找到最大化获取分数的点,查询目标函数,并更新训练数据
此代码生成图 A.7。
图 A.7 自适应 UCB 策略的进展。该策略能够摆脱局部最优解,并接近全局最优解。
我们看到该策略在第五次迭代时检查了局部最优解,但最终能够逃脱并在最后接近全局最优解。
A.4.2 练习 2:用于超参数调整的 BayesOpt
此练习实现在 CH05/03 - Exercise 2.ipynb 中,将 BayesOpt 应用于模拟超参数调整任务中支持向量机模型准确率表面的目标函数。完成以下步骤:
- 在 CH04/03 - Exercise 2.ipynb 中重新创建 BayesOpt 循环,包括实施重复实验的外部循环。
- 运行 UCB 策略,将权衡参数的值设置为β ∈ { 1, 3, 10, 30 },并观察值的聚合性能。
可以在初始化策略对象时设置权衡参数的值:
policy = botorch.acquisition.analytic.UpperConfidenceBound( model, beta=[some value] )
- 图 A.8 显示了四个版本 UCB 的优化性能。我们看到当β = 1 时,策略过于探索,性能最差。
图 A.8 不同 UCB 策略的进展
随着权衡参数值的增加,性能也增加,但当 β = 30 时,过度探索导致 UCB 在定位 90%准确度时变慢。总体而言,β = 10 达到了最佳性能。 - 运行 UCB 的自适应版本(见练习 1)。
我们对 BayesOpt 循环进行如下修改:
num_repeats = 10 start_beta = 3 end_beta = 10 multiplier = (end_beta / start_beta) ** (1 / num_queries) incumbents = torch.zeros((num_repeats, num_queries)) for trial in range(num_repeats): ... ❶ beta = start_beta for i in tqdm(range(num_queries)): ... ❷ policy = botorch.acquisition.analytic.UpperConfidenceBound( model, beta=beta ) ... ❸ beta *= multiplier
- ❶ 随机生成初始训练数据
❷ 记录现有值并重新训练模型
❸ 找到最大化获取分数的点,查询目标函数,并更新训练数据
图 A.9 展示了两种自适应版本相对于最佳性能固定值 β = 10 的优化性能。
图 A.9 两种 UCB 策略的自适应版本的进展。该策略对交易参数的结束值具有鲁棒性。
这些版本是可比较的,将结束值从 10 更改为 30 并不会对优化性能产生太大影响。 - 运行 Thompson 抽样(TS)策略,并观察其综合性能。
我们按照以下方式实现 TS:
num_candidates = 2000 num_repeats = 10 incumbents = torch.zeros((num_repeats, num_queries)) for trial in range(num_repeats): ... ❶ for i in tqdm(range(num_queries)): ... ❷ sobol = torch.quasirandom.SobolEngine(1, scramble=True) candidate_x = sobol.draw(num_candidates) candidate_x = bounds[0] + (bounds[1] - bounds[0]) * candidate_x ts = botorch.generation.MaxPosteriorSampling(model, ➥replacement=False) next_x = ts(candidate_x, num_samples=1) ... ❸
- ❶ 随机生成初始训练数据
❷ 记录现任价值并重新训练模型
❸ 找到最大化收益分数的点,查询目标函数并更新训练数据
图 A.10 展示了 TS 的优化性能。我们看到该策略在开始时取得了显著进展,并且与第六章的 EI 相当,并且略逊于 UCB 的最佳版本。
图 A.10 TS 的进展。该策略与 EI 相当,并略逊于 UCB 的最佳版本。
贝叶斯优化实战(四)(3)