附录:练习的解决方案
第二章 A.1: 高斯过程作为函数分布
在这个练习中,我们对我们在第一章看到的真实数据集进行了 GP 训练。 解决方案包含在 CH02/02 - Exercise.ipynb 笔记本中。 完成以下步骤:
- 创建四维数据集。
首先,我们导入必要的库:PyTorch 用于数组/张量操作,GPyTorch 用于 GP 建模,Matplotlib 用于可视化:
import torch import gpytorch import matplotlib.pyplot as plt
- 然后,我们将表中的数字存储在两个 PyTorch 张量中,
train_x
和train_y
,分别包含数据集的特征和标签:
train_x = torch.tensor( [ [1 / 2, 1 / 2, 0, 0], [1 / 3, 1 / 3, 1 / 3, 0], [0, 1 / 2, 1 / 2, 0], [0, 1 / 3, 1 / 3, 1 / 3], ] ) train_y = torch.tensor([192.08, 258.30, 187.24, 188.54])
- 通过从所有值中减去均值并将结果除以它们的标准差来标准化第五列。
我们按如下方式标准化标签:
# normalize the labels train_y = (train_y - train_y.mean()) / train_y.std()
- 打印出时,
train_y
应该包含以下值:tensor([-0.4183,
1.4974,
-0.5583,
-0.5207])
。 - 将前四列视为特征,第五列为标签。 在这个数据上训练一个 GP。
我们如下重新实现我们的 GP 模型类:
class BaseGPModel(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.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)
- 然后,我们用我们的训练数据初始化这个类的对象:
lengthscale = 1 noise = 1e-4 likelihood = gpytorch.likelihoods.GaussianLikelihood() model = BaseGPModel(train_x, train_y, likelihood) model.covar_module.lengthscale = lengthscale model.likelihood.noise = noise model.eval() likelihood.eval()
- 创建一个包含百分之零锗和锰的组合的测试数据集。
要组装我们的测试数据集,我们首先为第一列和第二列创建一个跨越单位正方形的网格:
grid_x = torch.linspace(0, 1, 101) grid_x1, grid_x2 = torch.meshgrid(grid_x, grid_x, indexing="ij")
- 这前两列存储在
grid_x1
和grid_x2
中。 然后,我们在grid_x1
和grid_x2
中附加两个额外的全零列,完成了具有四列的测试集:
xs = torch.vstack( [ grid_x1.flatten(), ❶ grid_x2.flatten(), ❷ torch.zeros(101 ** 2), ❸ torch.zeros(101 ** 2), ❹ ] ).transpose(-1, -2)
- ❶ 第一列
❷ 第二列
❸ 第三列,包含全零
❹ 第四列,包含全零 - 预测这个测试集的混合温度。
要在这个测试集上进行预测,我们只需在torch.no_grad()
上下文中通过我们的 GP 模型传递xs
:
with torch.no_grad(): predictive_distribution = likelihood(model(xs)) predictive_mean = predictive_distribution.mean predictive_stddev = predictive_distribution.stddev
- 可视化预测。
要可视化这些预测,我们首先创建一个具有两个面板(即,两个 Matplotlib 子图)的图:
fig, ax = plt.subplots(1, 2, figsize=(16, 6))
- 然后,我们使用
plt.imshow()
将均值和标准差向量可视化为热图,确保将这两个向量重塑为方阵:
c = ax[0].imshow( predictive_mean.detach().reshape(101, 101).transpose(-1, -2), origin="lower", extent=[0, 1, 0, 1], ) ❶ c = ax[1].imshow( predictive_stddev.detach().reshape(101, 101).transpose(-1, -2), origin="lower", extent=[0, 1, 0, 1], ) ❷ plt.colorbar(c, ax=ax[1])
- ❶ 预测均值的热图
❷ 预测标准差的热图
这将创建类似于图 A.1 中的图。
图 A.1 GP 在二维空间上的预测
注意 如果您使用不同的 GP 实现,则完全有可能生成与图 A.1 中略有不同的热图。 只要热图的一般趋势相同,您的解决方案就是正确的。
第三章 A.2: 使用均值和协方差函数结合先验知识
该练习提供了使用自动相关性确定(ARD)的 GP 模型的实践。 解决方案包含在 CH03/03 - Exercise.ipynb 中。 完成以下步骤:
- 使用 PyTorch 在 Python 中实现二维函数。
首先,我们导入必要的库——PyTorch 用于数组/张量操作,GPyTorch 用于 GP 建模,Matplotlib 用于可视化:
import torch import gpytorch import matplotlib.pyplot as plt
- 然后使用给定的公式实现目标函数:
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
- 在域[
0,
2
]²上可视化函数。
要可视化函数,我们需要创建一个网格来表示域。我们将这个网格存储在xs
中:
lb = 0 ub = 2 xs = torch.linspace(lb, ub, 101) ❶ x1, x2 = torch.meshgrid(xs, xs) xs = torch.vstack((x1.flatten(), x2.flatten())).transpose(-1, -2) ❷
- ❶ 一维网格
❷ 二维网格
然后我们可以通过将xs
传递给f()
来在这个网格上获取函数值。结果存储在ys
中:
ys = f(xs)
- 我们使用
plt.imshow()
将ys
可视化为热图:
plt.imshow(ys.reshape(101, 101).T, origin="lower", extent=[lb, ub, lb, ub])
- 从域[
0,
2
]²中随机抽取 100 个数据点。这将作为我们的训练数据。
要在域内随机抽取 100 个点,我们使用torch.rand()
从单位正方形中进行抽样,然后将结果乘以 2 以将其缩放到我们的域中:
torch.manual_seed(0) train_x = torch.rand(size=(100, 2)) * 2
- 这些点的函数值可以通过调用
f(train_x)
来获取:
train_y = f(train_x)
- 使用常数均值函数和作为
gpytorch.kernels.ScaleKernel
对象实现输出尺度的 Matérn 5/2 核来实现一个 GP 模型。我们按照如下指定实现我们的 GP 模型:
class GPModel(gpytorch.models.ExactGP): 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=None ❶ ) ) def forward(self, x): mean_x = self.mean_module(x) covar_x = self.covar_module(x) return gpytorch.distributions.MultivariateNormal(mean_x, covar_x)
- ❶ 设置为 None 以禁用 ARD,设置为 2 以启用 ARD。
- 在初始化核对象时不要指定
ard_num_dims
参数,或者将参数设置为None
。
这是在先前的代码中完成的。 - 使用梯度下降训练 GP 模型的超参数,并在训练后检查长度尺度。
我们初始化我们的 GP 并使用梯度下降进行 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() ❶ losses = [] for i in tqdm(range(500)): ❷ optimizer.zero_grad() ❷ output = model(train_x) ❷ loss = -mll(output, train_y) ❷ loss.backward() ❷ losses.append(loss.item()) ❷ optimizer.step() ❷ model.eval() ❸ likelihood.eval() ❸
- ❶ 启用训练模型
❷ 梯度下降来优化 GP 的超参数
❸ 启用预测模型
经过这 500 次迭代,我们通过打印以下数值来检查长度尺度:
>>> model.covar_module.base_kernel.lengthscale tensor([[1.1535]])
- 换句话说,优化的长度尺度大约等于 1.15。
- 重新定义 GP 模型类,这次将
ard_num_dims
设置为2
。
在GPModel
类中设置ard_num_dims=2
,然后重新运行所有代码单元格,我们得到以下长度尺度的数值:
>>> model.covar_module.base_kernel.lengthscale tensor([[1.6960, 0.8739]])
- 在这里,第一维的长度尺度很大(大约 1.70),而第二维的长度尺度很小(大约 0.87)。这对应于目标函数沿第二维变化更多的事实。
A.3 第四章:使用基于改进的策略优化最佳结果
这一章节有两个练习:
- 第一个涵盖了改进概率提高(PoI)策略的方法,使其能够更好地探索搜索空间。
- 第二个将我们学到的两个 BayesOpt 策略应用于一个模拟的真实世界的超参数调整任务。
A.3.1 练习 1:使用概率提高鼓励探索
这个练习在 CH04/02 - Exercise 1.ipynb 笔记本中实现,引导我们如何修改 PoI 以鼓励探索。完成以下步骤:
- 在 CH04/01 - BayesOpt loop 笔记本中重新创建 BayesOpt 循环,该循环使用一维 Forrester 函数作为优化目标。
- 在实现 BayesOpt 的
for
循环之前,声明一个名为epsilon
的变量:
epsilon = 0.1
- 在
for
循环内,像以前一样初始化 PoI 策略,但这次指定由best_f
参数设置的现任阈值是现任值 加上 存储在epsilon
中的值:
policy = botorch.acquisition.analytic.ProbabilityOfImprovement( model, best_f=train_y.max() + epsilon )
- 重新运行笔记本,并观察是否此修改比原始 PoI 策略更好地优化性能,通过鼓励更多探索,如图 A.2 所示。
图 A.2 修改后 PoI 在最后一次迭代中的优化进展。策略已经找到了最优解。
在这里,修改后的 PoI 已经找到了最优解。 - PoI 变得更加探索性取决于存储在
epsilon
中的最小改进阈值的大小。将此变量设置为 0.001 并不足以鼓励探索,策略再次陷入困境。将此变量设置为 0.5 效果很好。 - 实现一个相对最小改进阈值,要求改进达到 110%:
epsilon_pct = 0.1 for i in range(num_queries): ... ❶ policy = botorch.acquisition.analytic.ProbabilityOfImprovement( model, best_f=train_y.max() * (1 + epsilon_pct) ❷ )
贝叶斯优化实战(四)(2)