贝叶斯优化实战(一)(3)https://developer.aliyun.com/article/1516379
3.4.2 使用不同的协方差函数控制平滑度
到目前为止,我们仅仅使用了 RBF 核作为我们的协方差函数。然而,如果 RBF 不合适,完全可以使用不同的核函数用于我们的 GP。在本小节中,我们将学习使用另一种核函数家族,即马特恩核,并看看这种核函数对我们的 GP 会产生什么影响。
注意 通过使用马特恩核,我们正在指定 GP 模型函数的平滑度。这里的 “平滑度” 是一个技术术语,指的是函数的可微性;函数可微性越多次,它就越平滑。我们可以大致将其视为函数值以曲折方式 “跳动” 的程度。
RBF 核模拟的函数具有无限可微性,这是现实世界中很少有的函数特性。与此同时,Matérn 核生成的函数是有限可微的,这些函数可以被微分的次数(即这些函数的平滑度)由可设置的参数控制,我们马上就会讨论到。
要看到 Matérn 核的实际效果,我们首先重新实现我们的 GP 模型类:
class MaternGPModel(gpytorch.models.ExactGP): def __init__(self, train_x, train_y, likelihood, nu): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ZeroMean() self.covar_module = gpytorch.kernels.MaternKernel(nu) def forward(self, x): ... ❶
❶ 省略
在这里,我们的 covar_module
属性被初始化为 gpytorch .kernels.MaternKernel
类的实例。这个初始化接受一个参数 nu
,定义了我们的 GP 将具有的平滑程度,这也是我们 __init__()
方法的一个参数。
重要提示:在撰写本文时,GPyTorch 支持三个 nu
值,1/2、3/2 和 5/2,对应函数分别是不可微分、一次可微分和两次可微分的。换句话说,这个 nu
参数越大,我们的 GP 越平滑。
让我们首先尝试 nu
=
0.5
,通过在初始化 GP 时设置该值来实现:
likelihood = gpytorch.likelihoods.GaussianLikelihood() model = MaternGPModel(train_x, train_y, likelihood, 0.5) ... ❶ visualize_gp_belief(model, likelihood)
❶ 修正超参数并启用预测模式
这段代码生成图 3.13。
图 3.13 中 Matérn 1/2 核的 GP 预测,这表明目标函数不可微分,对应非常粗糙的样本
与我们之前在 RBF 中看到的情况不同,这个 Matérn 核的样本都非常参差不齐。事实上,它们都不可微分。当建模时间序列数据时,比如股票价格,nu
=
0.5
是 Matérn 核的一个好值。
然而,在 BayesOpt 中通常不使用这个值,因为像图 3.13 中那样的参差不齐的函数非常不稳定(它们可以以不可预测的方式上下跳动),通常不是自动优化技术的目标。我们需要目标函数具有一定的平滑度,以便进行优化;否则,有效的优化是不切实际的目标。
Matérn 5/2 核通常是首选。它的预测结果与 Matérn 3/2 生成的结果在图 3.14 中可视化。
图 3.14 中 Matérn 5/2(左)和 Matérn 3/2(右)核的 GP 预测。这里的样本足够平滑,以便 GP 有效地从数据中学习,但也足够参差不齐,以真实地模拟现实生活中的过程。
我们看到这个 5/2 核的样本要平滑得多,这导致 GP 更有效地学习。然而,这些样本也足够粗糙,以至于它们类似于我们在现实世界中可能看到的函数。因此,BayesOpt 中的大多数工作,无论是研究还是应用,都使用这个 Matérn 5/2 核。在未来的章节中,当我们讨论 BayesOpt 的决策时,我们将相应地默认使用这个核。
注意 虽然我们在这里没有包含相应的细节,但 Matérn 核函数有自己的长度尺度和输出尺度,可以像前面的小节一样指定,以进一步定制生成的 GP 的行为。
通过将均值函数与核函数配对,我们可以在 GP 的预测中诱发复杂的行为。就像我们的先验影响了我们朋友圈中每个人在看到有人正确猜出一个秘密数字 100 次后的结论一样,我们对均值函数和核函数的选择决定了 GP 的预测。图 3.15 展示了三个例子,其中每种均值函数和核函数的组合导致了截然不同的行为。
图 3.15 展示了在相同数据集上训练时,均值函数和核函数的三种不同选择以及它们各自后验 GP 的预测,每种选择都导致不同的预测行为。
3.4.3 用多个长度尺度建模不同级别的变异性
因为我们一直只考虑一维目标函数(其输入具有一个特征),所以我们只需要考虑一个长度尺度。然而,我们可以想象到一种情况,在这种情况下,高维目标函数(其输入具有多个特征)在某些维度上具有更多的变异性,在其他维度上较为平滑。也就是说,某些维度具有小的长度尺度,而其他维度具有大的长度尺度。还记得本章开头的启发性例子吗:房屋价格的预测增加一个楼层的幅度比增加一个平方英尺的生活面积更大。在本小节中,我们探讨了如何在 GP 中维护多个长度尺度以对这些函数进行建模。
如果我们只使用一个长度尺度来处理所有维度,那么我们将无法忠实地对目标函数进行建模。这种情况需要 GP 模型为每个维度维护一个单独的长度尺度,以完全捕获它们各自的变异性。在本章的最后一节中,我们学习如何在 GPyTorch 中实现这一点。
为了帮助我们的讨论,我们使用一个具体的二维目标函数,称为 Ackley,它可以修改为在不同维度中具有各种级别的变异性。我们将该函数实现如下:
def ackley(x): # a modification of https:/ /www.sfu.ca/~ssurjano/ackley.xhtml return -20 * torch.exp( -0.2 * torch.sqrt((x[:, 0] ** 2 + x[:, 1] ** 2) / 2) ) ➥ - torch.exp(torch.cos(2 * pi * x[:, 0] / 3) ➥ + torch.cos(2 * pi * x[:, 1]))
我们特别将该函数的定义域限制为两个维度上的方形区域,即 -3 到 3,通常表示为 [-3, 3]²。为了可视化这个目标函数,我们在图 3.16 中使用热图。
图 3.16 作为我们的目标使用的二维 Ackley 函数。在这里,x 轴的变异性比 y 轴小(变化较少),需要不同的长度尺度。
热图中的每个暗斑点都可以被看作是目标函数表面上具有低值的山谷。在这里,沿y轴有更多的山谷,而沿x轴则没有那么多,这表明第二个维度的变异性更大——也就是说,目标函数沿y轴上下波动的次数比沿x轴多得多。
再次强调,这意味着仅使用一个长度尺度来描述两个维度并不是一个好选择。相反,我们应该为每个维度(在本例中为两个)有一个长度尺度。然后,可以使用梯度下降独立地优化每个长度尺度。
使用每个维度的长度尺度的内核称为自动相关确定(ARD)。这个术语表示,在使用梯度下降优化这些长度尺度之后,我们可以推断出目标函数的每个维度与函数值相关的程度。具有较大长度尺度的维度具有较低的变异性,因此,在建模目标函数值时比具有较小长度尺度的维度不太相关。
使用 GPyTorch 实现 ARD 非常容易:我们只需在初始化协方差函数时将 ard_num_dims
参数指定为我们的目标函数具有的维度数即可。像这样使用 RBF 内核:
class ARDGPModel(gpytorch.models.ExactGP): def __init__(self, train_x, train_y, likelihood): super().__init__(train_x, train_y, likelihood) self.mean_module = gpytorch.means.ZeroMean() self.covar_module = gpytorch.kernels.ScaleKernel( gpytorch.kernels.RBFKernel(ard_num_dims=2) ) def forward(self, x): ... ❶
❶ 省略
让我们看看,当在我们的 Ackley 函数上训练时,这个模型是否为两个维度给出了不同的长度尺度。为此,我们首先构造一个包含 100 个点的随机抽样训练数据集:
torch.manual_seed(0) train_x = torch.rand(size=(100, 2)) * 6 - 3 train_y = ackley(train_x)
在使用梯度下降训练模型后,我们可以通过打印出优化后的长度尺度的值来检查它们。
>>> model.covar_module.base_kernel.lengthscale tensor([[0.7175, 0.4117]])
这确实是我们预期的结果:第一个维度的长度尺度较大,函数值的变异性较低,而第二个维度的长度尺度较小,函数值的变异性较高。
更多关于内核的阅读
内核本身已经受到了 ML 社区的极大关注。除了我们迄今所涵盖的内容之外,还有一点需要注意的是,内核还可以编码复杂的结构,如周期性、线性和噪声。对于更全面和技术性的内核讨论,感兴趣的读者可以参考 David Duvenaud 的 内核菜谱 (www.cs.toronto.edu/~duvenaud/cookbook/
)。
这个讨论标志着第三章的结束。在本章中,我们广泛地研究了我们的 GP 模型是如何受到均值和协方差函数的影响,特别是它们的各个参数。我们将这视为将我们对目标函数的了解(即先验信息)融入到我们的 GP 模型中的一种方式。我们还学会了使用梯度下降来估计这些参数的值,以获得最佳解释我们数据的 GP 模型。
这也标志着本书的第一部分的结束,我们将重点放在了 GP 上。从下一章开始,我们开始学习 BayesOpt 框架的第二个组成部分:决策制定。我们从两种最常用的 BayesOpt 策略开始,这两种策略旨在改善已见到的最佳点:概率改进和期望改进。
3.5 练习
这个练习是为了练习使用 ARD 实现 GP 模型。为此,我们创建一个目标函数,沿一个轴变化的程度比沿另一个轴变化的程度更大。然后,我们使用来自该函数的数据点训练一个有或没有 ARD 的 GP 模型,并比较学习到的长度尺度值。解决方案包含在 CH03/03 - Exercise.ipynb 中。
这个过程有多个步骤:
- 使用 PyTorch 在 Python 中实现以下二维函数:
这个函数模拟了一个支持向量机(SVM)模型在超参数调整任务中的准确性曲面。x轴表示惩罚参数c的值,y轴表示 RBF 核参数γ的值。(我们在未来的章节中也将使用该函数作为我们的目标函数。) - 在区间[
0,
2
]²上可视化该函数。热图应该看起来像图 3.17。
图 3.17 SVM 模型在测试数据集上的准确率作为惩罚参数c和 RBF 核参数γ的函数。函数对γ的变化比对c的变化更快。 - 在区间[
0,
2
]²中随机选择 100 个数据点,作为我们的训练数据。 - 使用常数均值函数和 Matérn 5/2 卷积核来实现一个 GP 模型,其中输出规模作为
gpytorch.kernels.ScaleKernel
对象实现。 - 在初始化核对象时不要指定
ard_num_dims
参数,或将该参数设置为None
。这将创建一个没有 ARD 的 GP 模型。 - 使用梯度下降训练 GP 模型的超参数,并在训练后检查长度尺度。
- 重新定义 GP 模型类,这次设置
ard_num_dims
=
2
。使用梯度下降重新训练 GP 模型,并验证两个长度尺度具有显著不同的值。
总结
- 先验知识在贝叶斯模型中起着重要作用,可以极大地影响模型的后验预测结果。
- 使用 GP 模型可以通过均值和协方差函数来指定先验知识。
- 均值函数描述了高斯过程模型的预期行为。在没有数据的情况下,高斯过程的后验均值预测回归到先验均值。
- 高斯过程的均值函数可以采用任何函数形式,包括常数、线性函数和二次函数,这可以通过 GPyTorch 实现。
- 高斯过程的协方差函数控制了高斯过程模型的平滑度。
- 长度尺度指定了输出与函数输入之间的变异性水平。较大的长度尺度导致更加平滑,因此预测的不确定性较小。
- 高斯过程中的每个维度都可以有自己的长度尺度。这被称为自动相关性确定(ARD),用于模拟在不同维度上具有不同变异程度的目标函数。
- 输出尺度指定了函数输出的范围。较大的输出尺度导致更大的输出范围,因此在预测中有更多的不确定性。
- Matérn 核类是 RBF 核类的泛化。通过指定其参数
nu
,我们可以模拟高斯过程预测中的各种平滑程度。 - 高斯过程的超参数可以通过最大化使用梯度下降的数据的边际似然来进行优化。
第二部分:用贝叶斯优化做决策
GP 只是方程式的一部分。为了充分实现 BayesOpt 技术,我们需要方程式的第二部分:决策策略,这些策略规定了如何进行函数评估以尽快优化目标函数。本部分列举了最流行的 BayesOpt 策略,包括它们的动机、数学直觉和实现。虽然不同的策略受到不同目标的驱使,但它们都旨在平衡勘探和开发之间的权衡——这是 BayesOpt 特别是以及不确定性问题下的决策制定的核心挑战。
第四章通过引入获取分数的概念来开始介绍事物,这是一种量化进行函数评估价值的方法。该章还描述了一种试图从迄今为止看到的最佳点改进的启发式方法,这导致了两种流行的 BayesOpt 策略:改进概率和预期改进。
第五章将 BayesOpt 与一个密切相关的问题联系起来:多臂赌博机。我们探索了流行的上置信界限策略,该策略使用乐观主义下的不确定性启发式,以及 Thompson 抽样策略,该策略利用了 GP 的概率性质来辅助决策。
第六章向我们介绍了信息理论,这是数学的一个子领域,在决策问题中有许多应用。利用信息理论的一个核心概念熵,我们设计了一个名为 BayesOpt 策略,该策略旨在获取关于我们搜索目标的最多信息。
在这一部分中,我们了解了解决勘探-开发权衡的不同方法,并建立了多种优化方法的多样化工具集。虽然我们已经学会了在前一部分中使用 GPyTorch 实现 GPs,但 BoTorch,首要的 BayesOpt 库,是我们在这一部分的重点。我们学习如何声明 BayesOpt 策略,使用它们来促进优化循环,并比较它们在各种任务中的性能。到本部分结束时,您将获得关于实施和运行 BayesOpt 策略的实践知识。
第五章:通过基于改进的政策优化最佳结果
本章包括
- BayesOpt 循环
- 在 BayesOpt 政策中开发开发和探索之间的权衡
- 以改进为标准来寻找新数据点。
- 使用改进的 BayesOpt 政策
在本章中,我们首先提醒自己 BayesOpt 的迭代性质:我们在已收集的数据上交替训练高斯过程(GP)并使用 BayesOpt 政策找到下一个要标记的数据点。这形成了一个良性循环,在这个循环中,我们的过去数据指导未来的决策。然后我们谈论我们在 BayesOpt 政策中寻找什么:一个决策算法,它决定标记哪个数据点。一个好的 BayesOpt 政策需要平衡足够探索搜索空间并集中在高性能区域。
最后,我们了解了两种政策,这两种政策旨在改进到目前为止 BayesOpt 循环中见过的最佳数据点:改进概率和最常用的 BayesOpt 政策之一,即期望改进。例如,如果我们有一个超参数调整应用程序,我们想要识别在数据集上提供最高验证准确性的神经网络,并且到目前为止我们见过的最高准确性为 90%,那么我们很可能想要改进这个 90%的阈值。本章中学到的策略试图创建这种改进。在我们在表 1.2 中看到的材料发现任务中,我们想要搜索混合温度低(对应高稳定性)的金属合金,而我们找到的最低温度为 187.24,上述两种政策将寻求找到低于这个 187.24 基准的值。
令人惊讶的是,由于我们对目标函数的信念的高斯性质,我们可以期望从最佳观察点的改进程度可以通过封闭形式计算。也就是说,虽然我们不知道未见位置的目标函数的样子,但是在 GP 下,仍然可以轻松地计算基于改进的数量。到本章结束时,我们深入了解了 BayesOpt 政策需要做什么以及如何使用这两个基于改进的政策来完成此操作。我们还学习了如何集成 BoTorch,即我们从本章到本书结束时使用的 Python 中的 BayesOpt 库(botorch.org/docs/introduction
),以实现 BayesOpt 政策。
4.1 在 BayesOpt 中导航搜索空间
我们如何确保我们正确利用过去的数据来指导未来的决策?我们在 BayesOpt 政策中寻找什么作为自动决策程序?本节回答了这些问题,并为我们清楚地说明了在使用 GP 时 BayesOpt 的工作原理。
具体来说,在下一小节中,我们将重新检查在第 1.2.2 节中简要介绍的贝叶斯优化循环,以了解如何通过贝叶斯优化策略与高斯过程的训练同时进行决策。然后,我们将讨论贝叶斯优化策略需要解决的主要挑战:在具有高不确定性的区域和在搜索空间中利用良好区域之间的平衡。
以图 4.1 为例,该图显示了在 1 和 2 处训练的两个数据点的高斯过程。在这里,贝叶斯优化策略需要决定我们应该在 -5 和 5 之间的哪个点评估下一个目标函数。探索和利用的权衡是明显的:我们需要决定是否在搜索空间的左右极端(大约在 -5 和 5 附近)检查目标函数,在这些位置,我们的预测存在相当大的不确定性,或者留在平均预测值最高的区域周围。探索和利用的权衡将为我们讨论的不同贝叶斯优化策略奠定基础,这些策略分布在本章和后续章节中。
图 4.1 贝叶斯优化中的探索和利用的权衡。每个策略都需要决定是否查询具有高不确定性的区域(探索),还是查询具有高预测平均值的区域(利用)。
4.1.1 贝叶斯优化循环和策略
首先,让我们回顾一下贝叶斯优化循环的外观以及贝叶斯优化策略在此过程中的作用。在本章中,我们还实现了这个循环的框架,我们将在以后的章节中用来检查贝叶斯优化策略。回顾图 4.2,它是图 1.6 的重复,显示了贝叶斯优化在高层次上的工作方式。
图 4.2 贝叶斯优化循环,结合了模拟高斯过程和决策制定策略。此完整工作流现在可以用于优化黑盒函数。
具体来说,贝叶斯优化是通过一个循环完成的,该循环在以下几个方面交替进行:
- 对当前训练集进行高斯过程(GP)训练。我们已经在以前的章节中彻底讨论了如何做到这一点。
- 使用训练有素的高斯过程(GP)对搜索空间中的数据点进行评分,评估它们在帮助我们确定目标函数最优值时的价值(图 4.2 中的步骤 2)。选择最大化此分数的点进行标记,并添加到训练数据中(图 4.2 中的步骤 3)。如何进行此评分由使用的贝叶斯优化策略决定。我们在本章和第 5 和第六章中了解更多有关不同策略的信息。
我们重复此循环,直到达到终止条件,通常是一旦我们已经评估了目标函数达到目标循环迭代次数。这个过程是端到端的,因为我们不仅仅是使用高斯过程进行预测,而是利用这些预测来决定下一步收集哪些数据点,进而推动未来预测的生成。
定义 BayesOpt 循环是模型训练(GP)和数据收集(策略)的良性循环,彼此互相帮助和受益,目标是找到目标函数的最优解。BayesOpt 的良性循环是一个反馈循环,向着具有期望性质的平衡状态迭代;其组件协同作用以实现期望的结果,而不是在导致不良结果的恶性循环中相互恶化。
决定数据点如何根据其在帮助我们实现这一目标的价值方面得分的规则由 BayesOpt 策略决定,因此对于优化性能至关重要。一个好的策略会将高分分配给对优化真正有价值的数据点,从而更快、更高效地指引我们走向目标函数的最优点,而一个设计不良的策略可能会误导我们的实验,并浪费宝贵的资源。
定义 BayesOpt 策略 根据其价值对每个潜在的查询进行评分,从而决定我们应该在下一步查询目标函数的位置(评分最高的地方)。由策略计算的此评分被称为获取分,因为我们将其用作数据获取的方法。
连接到强化学习策略
如果你有强化学习(RL)的经验,你可能会注意到 BayesOpt 策略和 RL 策略之间的联系。在这两种技术中,策略告诉我们在决策问题中应该采取哪些行动。而在 RL 中,策略可能为每个动作分配一个分数,然后我们选择分数最高的动作,或者策略可能只是输出我们应该采取的动作。在 BayesOpt 中,它始终是前者,策略输出一个量化每个可能查询价值的分数,因此我们的工作是确定最大化此分数的查询。
不幸的是,设计一个好的 BayesOpt 策略的问题没有完美的解决方案。也就是说,并没有一种单一的 BayesOpt 策略能够在所有目标函数上始终表现出色。正如我们在本章和后续章节中所见,不同的策略使用不同的焦点和启发式方法。虽然某些启发式方法在某种类型的目标函数上效果良好,但其他启发式方法可能在不同类型的函数上有效。这意味着我们需要接触广泛的 BayesOpt 策略,并了解它们的目的,以便将它们应用于适当的情况——这正是我们将在第 4 至 6 章中所做的。
什么是策略?
每个 BayesOpt 策略都是一个决策规则,根据给定的标准或启发式评分数据点,以确定其在优化中的有用性。不同的标准和启发式导致不同的策略,而没有预先确定的一组 BayesOpt 策略。事实上,BayesOpt 研究人员仍然发布提出新策略的论文。在本书中,我们只讨论实践中最流行和常用的策略。
现在让我们花点时间来实现一个占位的贝叶斯优化循环,从现在开始我们将使用它来检查各种贝叶斯优化策略。这段代码在 CH04/01 - BayesOpt loop.ipynb 中实现。我们需要的第一个组件是一个我们想要使用贝叶斯优化来优化的目标函数。在这里,我们使用熟悉的一维 Forrester 函数作为要最大化的目标函数,它被定义在 -5 到 5 之间。我们还使用 xs
和 ys
作为基本事实,在其定义域 [–5, 5] 内计算 Forrester 函数的值:
def forrester_1d(x): ❶ y = -((x + 1) ** 2) * torch.sin(2 * x + 2) / 5 + 1 ❶ return y.squeeze(-1) ❶ bound = 5 ❷ xs = torch.linspace(-bound, bound, bound * 100 + 1).unsqueeze(1) ❷ ys = forrester_1d(xs)
❶ 目标函数的形式,假设未知
❷ 在 -5 到 5 之间的网格上计算的测试数据
我们需要做的另一件事是修改 GP 模型的实现方式,以便它们可以与 BoTorch 中的贝叶斯优化策略一起使用。实现 GP 构成了我们的贝叶斯优化循环的第一步。
由于 BoTorch 就建立在 GPyTorch 之上,因此只需要进行最小的修改。具体来说,我们使用以下 GP 实现,其中除了我们通常的 gpytorch.models.ExactGP
类之外,我们还继承了 botorch.models.gpytorch.GPyTorchModel
类。此外,我们声明了类特定属性 num_outputs
并将其设置为 1。这些是我们需要进行的最小修改,以便将我们的 GPyTorch 模型与 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.RBFKernel() ) def forward(self, x): mean_x = self.mean_module(x) covar_x = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean_x, covar_x)
❶ 用于 BoTorch 集成的修改
除此之外,我们 GP 实现中的其他一切都保持不变。现在我们编写一个帮助函数,用于在我们的训练数据上训练 GP:
def fit_gp_model(train_x, train_y, num_train_iters=500): noise = 1e-4 ❶ likelihood = gpytorch.likelihoods ➥.GaussianLikelihood() ❷ model = GPModel(train_x, train_y, likelihood) ❷ model.likelihood.noise = noise ❷ optimizer = torch.optim.Adam(model.parameters(), ➥lr=0.01) ❸ mll = gpytorch.mlls.ExactMarginalLogLikelihood ➥(likelihood, model) ❸ model.train() ❸ likelihood.train() ❸ for i in tqdm(range(num_train_iters)): ❸ optimizer.zero_grad() ❸ output = model(train_x) ❸ loss = -mll(output, train_y) ❸ loss.backward() ❸ optimizer.step() ❸ model.eval() ❸ likelihood.eval() ❸ return model, likelihood
❶ 使用梯度下降训练 GP
❷ 声明 GP
❸ 使用梯度下降训练 GP
注意 我们在前几章中使用了所有先前的代码。如果您对某段代码有困难理解,请参考第 3.3.2 节以获取更多细节。
这涵盖了图 4.2 的第 1 步。现在,我们跳过第 2 步,即实现贝叶斯优化策略,并将其留待下一节和未来章节。要实现的下一个组件是可视化到目前为止收集的数据,当前 GP 信念以及一个贝叶斯优化策略如何评分其余数据点。该可视化的目标显示在图 4.3 中,我们在第一章中见过。具体来说,图的顶部面板显示了 GP 模型对真实目标函数的预测,而底部面板显示了由贝叶斯优化策略计算的收购分数。
图 4.3 贝叶斯优化进展的典型可视化。顶部面板显示了 GP 预测和真实的目标函数,而底部面板显示了一个名为期望改进(Expected Improvement)的贝叶斯优化策略所做的收购分数,我们将在第 4.3 节中学习到。
我们已经熟悉如何生成顶部面板,生成底部面板同样简单。这将使用类似于我们在第 3.3 节中使用的辅助函数来完成。该函数接受一个 GP 模型及其似然函数以及两个可选输入:
policy
指的是 BayesOpt 策略对象,可以像调用任何 PyTorch 模块一样调用。在这里,我们将它调用在代表我们搜索空间的网格xs
上,以获取整个空间的获取分数。我们将在下一节中讨论如何使用 BoTorch 实现这些策略对象,但我们现在不需要更多了解这些对象。next_x
是使获取分数最大化的数据点的位置,将其添加到正在运行的训练数据中:
def visualize_gp_belief_and_policy( model, likelihood, policy=None, next_x=None ): with torch.no_grad(): predictive_distribution = likelihood(model(xs)) ❶ predictive_mean = predictive_distribution.mean ❶ predictive_upper, predictive_lower = ❶ ➥predictive_distribution.confidence_region() ❶ if policy is not None: ❷ acquisition_score = policy(xs.unsqueeze(1)) ❷ ... ❸
❶ GP 预测
❷ 获取分数
❸ 省略
在这里,我们从 GP 和测试数据 xs
中生成预测。请注意,如果未传入 policy
,我们不会计算获取分数,在这种情况下,我们也以我们已经熟悉的方式可视化 GP 预测-散点表示训练数据,平均预测的实线,95% CI 的阴影区域:
if policy is None: plt.figure(figsize=(8, 3)) plt.plot(xs, ys, label="objective", c="r") ❶ plt.scatter(train_x, train_y, marker="x", c="k", label="observations") ❷ plt.plot(xs, predictive_mean, label="mean") ❸ plt.fill_between( ❸ xs.flatten(), ❸ predictive_upper, ❸ predictive_lower, ❸ alpha=0.3, ❸ label="95% CI", ❸ ) ❸ plt.legend() plt.show()
❶ 真实值
❷ 训练数据
❸ 平均预测和 95% CI
请参考第 2.4.4 节以了解这个可视化的基础知识。
另一方面,如果传入了策略对象,我们将创建另一个子图以显示搜索空间中的获取分数:
else: fig, ax = plt.subplots( 2, 1, figsize=(8, 6), sharex=True, gridspec_kw={"height_ratios": [2, 1]} ) ... ❶ if next_x is not None: ❷ ax[0].axvline(next_x, linestyle="dotted", c="k") ❷ ax[1].plot(xs, acquisition_score, c="g") ❸ ax[1].fill_between( ❸ xs.flatten(), ❸ acquisition_score, ❸ 0, ❸ color="g", ❸ alpha=0.5 ❸ ) ❸ if next_x is not None: ❷ ax[1].axvline(next_x, linestyle="dotted", c="k") ❷ ax[1].set_ylabel("acquisition score") plt.show()
❶ GP 预测(与以前相同)
❷ 最大化获取分数的点,使用虚线垂直线进行可视化
❸ 获取分数
当传入 policy
和 next_x
时,此函数将创建一个显示根据 BayesOpt 策略的获取分数的较低面板。最后,我们需要实现图 4.2 中 BayesOpt 循环的第 3 步,即(1)找到具有最高获取分数的点,并(2)将其添加到训练数据并更新 GP。对于识别给出最高获取分数的点的第一个任务,虽然在我们的 Forrester 示例中可能在一维搜索空间上进行扫描,但随着目标函数维数的增加,穷举搜索变得越来越昂贵。
请注意,我们可以使用 BoTorch 的辅助函数 botorch.optim.optimize
.optimize_acqf()
,该函数找到最大化任何 BayesOpt 策略得分的点。辅助函数使用 L-BFGS,一种准牛顿优化方法,通常比梯度下降方法更有效。
我们这样做:
policy
是 BayesOpt 策略对象,我们很快就会了解更多。bounds
存储了我们搜索空间的边界,在本例中为-5 和 5。q
=
1
指定我们希望辅助函数返回的点数,这是一个。 (在第七章中,我们学习了允许同时对目标函数进行多次查询的设置。)num_restarts
和raw_samples
分别表示在搜索给出最高获取分数的最佳候选项时 L-BFGS 使用的重复次数和初始数据点数。一般来说,我建议分别使用这些参数的维数的 20 倍和 50 倍。- 返回的值,
next_x
和acq_val
,分别是给出最高获取分数的点的位置和相应的最大化获取分数:
next_x, acq_val = botorch.optim.optimize_acqf( policy, bounds=torch.tensor([[-bound * 1.0], [bound * 1.0]]), q=1, num_restarts=20, raw_samples=50, )
设置重新启动和原始样本的数量
num_restarts
和 raw_samples
的值越高,当搜索最大化获取分数的最佳候选项时,L-BFGS 的穷举程度就越高。这也意味着 L-BFGS 算法运行的时间将更长。如果发现 L-BFGS 在最大化获取分数时失败,或者算法运行时间太长,可以增加这两个数字;反之,可以减少它们。
作为最后一步,我们在贝叶斯优化循环中汇总了我们到目前为止实现的内容。在该循环的每次迭代中,我们执行以下操作:
- 我们首先打印出迄今为止我们看到的最佳值(
train_y.max()
),这显示了优化的进展情况。 - 然后,我们重新在当前训练数据上训练 GP,并重新声明贝叶斯优化策略。
- 使用 BoTorch 中的辅助函数
botorch.optim.optimize_acqf()
,我们确定在搜索空间中最大化获取分数的点。 - 我们调用辅助函数
visualize_gp_belief_and_policy()
,该函数可视化我们当前的 GP 信念和优化进展。 - 最后,我们在确定的点(
next_x
)查询函数值并更新我们观察到的数据。
整个过程总结在图 4.4 中,该图显示了贝叶斯优化循环中的步骤及实现这些步骤的相应代码。每个步骤都由我们的辅助函数或 BoTorch 的模块化代码实现,使得整个过程易于跟踪。
图 4.4 贝叶斯优化循环中的步骤及相应的代码。每个步骤的代码是模块化的,这使得整个循环易于跟踪。
实际的代码实现如下:
num_queries = 10 ❶ 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=torch.tensor([[-bound * 1.0], ❹ ➥[bound * 1.0]]), ❹ q=1, ❹ num_restarts=20, ❹ raw_samples=50, ❹ ) ❹ visualize_gp_belief_and_policy(model, likelihood, policy, next_x=next_x) ❺ next_y = forrester_1d(next_x) ❻ train_x = torch.cat([train_x, next_x]) ❻ train_y = torch.cat([train_y, next_y]) ❻
❶ 可以进行的目标函数评估次数
❷ 更新当前数据上的模型
❸ 初始化贝叶斯优化策略,稍后讨论
❹ 找到给出最高获取分数的点
❺ 可视化当前 GP 模型和获取分数
❻ 在确定的点观察并更新训练数据
有了这一点,我们已经实现了一个 BayesOpt 循环的框架。现在唯一要做的就是用我们想要使用的实际 BayesOpt 策略来填充policy
的初始化,然后笔记本就能在 Forrester 函数上运行 BayesOpt 了。请注意,虽然调用visualize_gp_belief_and_policy()
不是必需的(也就是说,之前的 BayesOpt 循环仍然能够运行而不需要那一行代码),但是这个函数对我们观察 BayesOpt 策略的行为和特性以及诊断任何潜在问题是有用的,正如我们后面在本章中讨论的那样。
BayesOpt 策略最重要的特征之一是探索和利用之间的平衡,这是许多人工智能和机器学习问题中的一个经典权衡。在这里,发现我们目前不知道的高性能区域(探索)的可能性与集中在已知的良好区域(利用)的机会之间进行权衡。我们将在下一小节中更详细地讨论这种权衡。
贝叶斯优化实战(一)(5)https://developer.aliyun.com/article/1516381