贝叶斯优化实战(三)(4)

简介: 贝叶斯优化实战(三)

贝叶斯优化实战(三)(3)https://developer.aliyun.com/article/1516480

12.2.3 实现近似模型

我们现在准备在 GPyTorch 中实现一个 VGP。我们的计划是编写一个 VGP 模型类,这类似于我们已经使用的 GP 模型类,并使用小批量梯度下降最小化其 ELBO。我们在表 12.2 中描述的工作流程的不同之处反映在了本小节中的代码中。表 12.3 显示了在 GPyTorch 中实现 GP 与 VGP 时所需的组件。除了均值和协方差模块外,VGP 还需要另外两个组件:

  • 变分分布定义了 VGP 中诱导点的分布。正如我们在上一节中学到的,此分布要进行优化,以使 VGP 类似于在完整数据集上训练的 GP。
  • 变分策略定义了如何从诱导点产生预测。在第 2.2 节中,我们看到多元正态分布可以根据观测结果进行更新。这种变分策略促使对变分分布进行相同的更新。

表 12.3 在 GPyTorch 中实现 GP 与 VGP 时所需的组件。VGP 需要像 GP 一样的均值和协方差模块,但还需要变分分布和变分策略。

组件 GP VGP
均值模块
协方差模块
变分分布
变分策略

考虑到这些组件,我们现在实现了 VGP 模型类,我们将其命名为 ApproximateGPModel我们不再在 __init__() 方法中接受训练数据和似然函数。相反,我们接受一组诱导点,这些点将用于表示整个数据集。 __init__() 方法的其余部分包括声明将用于学习哪组诱导点最佳的学习流程:

  • 变分分布 variational_distribution 变量是 CholeskyVariationalDistribution 类的一个实例,在初始化期间接受诱导点的数量。 变分分布是 VGP 的核心。
  • 变分策略 variational_strategy 变量是 VariationalStrategy 类的一个实例。它接受一组诱导点以及变分分布。我们将 learn_inducing_locations = True 以便在训练过程中学习这些诱导点的最佳位置。如果将此变量设置为 False则传递给 __init__() 的点(存储在 inducing 中)将用作诱导点:
class ApproximateGPModel(gpytorch.models.ApproximateGP):             ❶
  def __init__(self, inducing_points):                               ❷
    variational_distribution =                                       ❸
    ➥gpytorch.variational.CholeskyVariationalDistribution(          ❸
        inducing_points.size(0)                                      ❸
    )                                                                ❸
    variational_strategy = gpytorch.variational.VariationalStrategy( ❸
        self,                                                        ❸
        inducing_points,                                             ❸
        variational_distribution,                                    ❸
        learn_inducing_locations=True,                               ❸
    )                                                                ❸
    super().__init__(variational_strategy)                           ❸
    ...                                                              ❹

我们的 VGP 不是一个 ExactGP 对象,而是一个近似 GP。

接受一组初始诱导点

设置训练所需的变分参数

待续

__init__() 方法的最后一步中,我们声明了 VGP 的均值和协方差函数。它们应该与在数据上训练的普通 GP 中要使用的函数相同。在我们的情况下,我们使用常数均值和具有输出比例的 RBF 核:

class ApproximateGPModel(gpytorch.models.ApproximateGP):
    def __init__(self, inducing_points):
        ...
        self.mean_module = gpytorch.means.ConstantMean()
        self.covar_module = gpytorch.kernels.ScaleKernel(
            gpytorch.kernels.RBFKernel()
        )

我们还以与常规 GP 相同的方式声明 forward() 方法,这里不再展示。现在,让我们用训练集中的前 50 个数据点来初始化这个模型作为引入点:

model = ApproximateGPMod1el(train_x[:50, :])            ❶
likelihood = gpytorch.likelihoods.GaussianLikelihood()

❶ 切片张量 train_x[:50, :] 给出了 train_x 中的前 50 个数据点。

前 50 个数据点没有什么特别之处,它们的值,在 VGP 模型内部存储,将在训练过程中被修改。这种初始化最重要的部分是,我们指定模型应该使用 50 个引入点。如果我们想要使用 100 个,我们可以将 train_x[:100, :] 传递给初始化。

很难准确地说,多少个引入点足以用于 VGP。我们使用的点越少,模型训练就越快,但是这些引入点在表示整个集合时的效果就越不好。随着点数的增加,VGP 有更多的自由度来展开引入点以覆盖整个集合,但训练会变慢。

一般规则是不超过 1,000 个引入点。正如我们马上要讨论的,50 个点足以让我们以高保真度近似前一小节中训练的 GP。

要设置小批量梯度下降,我们首先需要一个优化器。我们再次使用 Adam 优化器:

optimizer = torch.optim.Adam(
    [
        {"params": model.parameters()},
        {"params": likelihood.parameters()}    ❶
    ],
    lr=0.01
)

❶ 优化似然函数的参数以及 GP 的参数

要优化的参数

之前,我们只需要将 model.parameters() 传递给 Adam。在这里,似然函数没有与 VGP 模型耦合——常规 GP 使用似然函数初始化,而 VGP 则没有。因此,在这种情况下,有必要将 likelihood.parameters() 传递给 Adam。

对于损失函数,我们使用 gpytorch.mlls.VariationalELBO 类,该类实现了我们希望通过 VGP 优化的 ELBO 量。在初始化期间,此类的一个实例接受似然函数、VGP 模型和完整训练集的大小(我们可以通过 train_y.size(0) 访问)。有了这些,我们声明这个对象如下:

mll = gpytorch.mlls.VariationalELBO(
    likelihood,
    model,
    num_data=train_y.size(0)    ❶
)

❶ 训练数据的大小

有了模型、优化器和损失函数设置好了,我们现在需要运行小批量梯度下降。为此,我们将训练数据集分成批次,每个批次包含 100 个点,使用 PyTorch 的 TensorDatasetDataLoader 类:

train_dataset = torch.utils.data.TensorDataset(train_x, train_y)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=100)

这个 train_loader 对象允许我们以干净的方式迭代大小为 100 的数据集的小批量,当运行梯度下降时。损失—即 ELBO—通过以下语法计算:

output = model(x_batch)
loss = -mll(output, y_batch)

这里,x_batchy_batch 是完整训练集的一个给定批次(小子集)。总的来说,梯度下降的实现如下:

model.train()                                 ❶
likelihood.train()                            ❶
for i in tqdm(range(50)):                     ❷
    for x_batch, y_batch in train_loader:     ❸
        optimizer.zero_grad()                 ❹
        output = model(x_batch)               ❹
        loss = -mll(output, y_batch)          ❹
        loss.backward()                       ❹
        optimizer.step()                      ❹
model.eval()                                  ❺
likelihood.eval()                             ❺

❶ 启用训练模式

❷ 迭代整个训练数据集 50 次

❸ 在每次迭代中,迭代 train_loader 中的小批次

❹ 小批量梯度下降,在批次上运行梯度下降

❺ 启用预测模式

在运行这个小批量梯度下降循环时,你会注意到它比使用普通 GP 的循环要快得多。(在同一台 MacBook 上,这个过程只需不到一秒钟,速度大幅提升!)

VGP 的速度

你可能认为我们在普通 GP 中进行的 500 次迭代与 VGP 中进行的 50 次小批量梯度下降的比较不公平。但是请记住,在小批量梯度下降的外部for循环的每次迭代中,我们还要在train_loader中迭代 10 个小批量,所以最终总共进行了 500 次梯度步骤。此外,即使我们运行了 500 次小批量梯度下降的迭代,也只需要不到 1 秒乘以 10,仍然比 45 秒快 4 倍。

因此,通过小批量梯度下降,我们的 VGP 模型可以更高效地训练。但是对于训练质量如何呢?图 12.10 的左侧面板可视化了我们小批量梯度下降运行期间的逐渐 ELBO 损失。与图 12.5 相比,尽管损失并没有在每一步时持续减小(存在锯齿趋势),但损失在整个过程中有效地最小化了。


图 12.10 VGP 在小批量梯度下降期间逐步损失服从的长度尺度和输出尺度的对应关系

这表明,在优化过程中的每一步可能不是最小化损失的最佳方向,但小批量梯度下降确实是有效地减小了损失。这在图 12.11 中更清楚地显示出来。


图 12.11 过程中 VGP 的逐步损失在小批量梯度下降中逐渐减小。尽管存在一定的变异性,但损失有效地最小化了。

现在,让我们可视化这个 VGP 模型产生的预测结果,看看它是否产生了合理的结果。使用visualize_gp_belief()助手函数,我们得到图 12.12,显示我们以较小的时间成本获得了对真实损失进行训练的 GP 的高质量近似。


图 12.12 由 GP 和 VGP 进行的预测。VGP 进行的预测与 GP 的预测大致相同。

结束我们对 VGP 的讨论,让我们可视化 VGP 模型学到的诱导点的位置。我们已经说过这些诱导点应该代表整个数据集并很好地捕捉其趋势。要绘制诱导点,我们可以使用model.variational_strategy.inducing_points.detach()访问它们的位置,并将它们作为散点沿着均值预测进行绘制。当调用这个函数时,我们只需要将variational设置为True

visualize_gp_belief(model, likelihood, variational=True)

这产生了图 12.13,在其中我们看到这些感应点的非常有趣的行为。它们并不均匀分布在我们的训练数据之间;相反,它们聚集在数据的不同部分。这些部分是目标函数上升或下降或呈现某些非平凡行为的地方。通过将感应点分配到这些位置,VGP 能够捕捉嵌入大型训练数据集中最重要的趋势。


图 12.13 VGP 的感应点。这些点被放置在整个数据中,捕捉最重要的趋势。

我们已经学习了如何使用小批量梯度下降来训练 VGP,并且已经看到这有助于以更低的成本近似于不可训练的常规 GP。在接下来的部分,我们将学习另一种梯度下降算法,可以更有效地训练 VGP。

12.3 通过考虑损失表面的几何特性来进行更好的优化

在本节中,我们将学习一种名为自然梯度下降的算法,这是梯度下降的另一种版本,在计算下降步骤时更仔细地推理损失函数的几何结构。正如我们很快会看到的那样,这种谨慎的推理使我们能够快速降低损失函数,最终导致更有效的优化,迭代次数更少(也就是更快的收敛)。

要理解自然梯度下降的动机以及为什么它比我们已经拥有的更好,我们首先区分 VGP 的两种参数类型:

  • 第一种类型是 GP 的常规参数,例如均值常数和协方差函数的长度和输出比例。这些参数取得常规的数值,存在于欧几里得空间中。
  • 第二种类型包括只有 VGP 才有的变分参数。这些与感应点和促使变分分布近似所需的各种组件有关。换句话说,这些参数与概率分布相关,并且具有无法在欧几里得空间内很好表示的值。

注意:这两种参数之间的差异有些相似,尽管不完全类似于欧几里得距离可以衡量该空间中两点之间的距离,但无法衡量两个概率分布之间的差异。

尽管我们在前一节中使用的小批量梯度下降效果已经足够好,但该算法假设所有参数存在于欧几里得空间中。例如,从算法的角度来看,长度尺度从 1 变到 2 的差异与诱导点的均值从 1 变到 2 的差异是相同的。然而,事实并非如此:从长度尺度从 1 变到 2 会对 VGP 模型产生非常不同的影响,而从诱导点的均值从 1 变到 2 的影响也不同。这在图 12.14 的示例中有所体现,其中损失对长度尺度的行为与对诱导均值的行为非常不同。


图 12.14 一个示例,说明了要最小化的损失可能在正常参数和变分参数方面的行为非常不同。这就需要考虑损失的几何形状。

正常参数和 VGP 的变分参数的损失函数的几何形状之间存在这种行为差异是因为。如果小批量梯度下降能够在计算损失的下降方向时考虑到这种几何差异,那么该算法在最小化损失时将更有效。这就是自然梯度下降的作用所在。

自然梯度下降 的定义利用了关于损失函数对于变分参数的几何特性的信息,以计算这些参数更好的下降方向。

通过采用更好的下降方向,自然梯度下降可以更有效地帮助我们优化 VGP 模型,而且更快。最终的结果是我们能够在更少的步骤中收敛到我们的最终模型。继续我们对不同梯度下降算法的二维图示,图 12.15 展示了这种几何推理如何帮助自然梯度下降比小批量梯度下降更快地达到目标。也就是说,自然梯度下降在训练过程中往往需要更少的步骤来达到与小批量梯度下降相同的损失。在我们下山的类比中,使用自然梯度下降时,我们在试图下山时仍然被一块薄布蒙住了眼睛,但现在我们穿着特制的登山鞋,可以更有效地穿越地形。


图 12.15 展示了在损失“谷底”中进行梯度下降、小批量梯度下降和自然梯度下降的插图,在谷底的中心给出了最低的损失。通过考虑损失函数的几何特性,自然梯度下降比小批量梯度下降更快地达到了损失最小值。

自然梯度下降的补充材料

对于更加数学化的自然梯度下降解释,我推荐阅读 Agustinus Kristiadi 的优秀博文“自然梯度下降”:mng.bz/EQAj

注意 需要注意的是,自然梯度下降算法仅优化 VGP 的变分参数。常规参数,例如长度和输出尺度,仍然可以通过常规小批量梯度下降算法优化。在接下来实现新的训练过程时,我们将看到这一点。

因此,让我们使用自然梯度下降来训练我们的 VGP 模型。与之前部分的相同一维目标函数一样,我们实现了一个能够与自然梯度下降一起工作的 VGP 模型。这种情况下的模型类似于我们在上一节为小批量梯度下降实现的ApproximateGPModel,它

  • 仍然扩展gpytorch.models.ApproximateGP
  • 需要一个变分策略来管理学习过程
  • 具有类似常规 GP 模型的均值函数、协方差函数和forward()方法

这里唯一的区别是,当训练模型时,变分分布需要是gpytorch.variational.NaturalVariationalDistribution的一个实例,以便我们在使用自然梯度下降时进行训练。整个模型类实现如下:

class NaturalGradientGPModel(gpytorch.models.ApproximateGP):
  def __init__(self, inducing_points):
    variational_distribution =                         ❶
      gpytorch.variational.                            ❶
      ➥NaturalVariationalDistribution(                ❶
        inducing_points.size(0)                        ❶
    )                                                  ❶
    variational_strategy = gpytorch.variational.
    ➥VariationalStrategy(                             ❷
        self,                                          ❷
        inducing_points,                               ❷
        variational_distribution,                      ❷
        learn_inducing_locations=True,                 ❷
    )                                                  ❷
    super().__init__(variational_strategy)             ❷
    self.mean_module = gpytorch.means.ConstantMean()   ❷
    self.covar_module = gpytorch.kernels.ScaleKernel(  ❷
        gpytorch.kernels.RBFKernel()                   ❷
    )                                                  ❷
  def forward(self, x):
        ...                                            ❸

❶ 变分分布需要是自然的才能与自然梯度下降一起工作。

❷ 声明剩余的变分策略与以前相同。

forward()方法与以前相同。

我们再次使用 50 个引导点初始化此 VGP 模型:

model = NaturalGradientGPModel(train_x[:50, :])         ❶
likelihood = gpytorch.likelihoods.GaussianLikelihood()

❶ 50 个引导点

现在是重要的部分,我们为训练声明优化器。请记住,我们使用自然梯度下降算法来优化模型的变分参数。然而,其他参数,例如长度和输出尺度,仍然必须由 Adam 优化器优化。因此,我们使用以下代码:

ngd_optimizer = gpytorch.optim.NGD(                  ❶
  model.variational_parameters(), num_data=train_y.  ❶
  ➥size(0), lr=0.1                                  ❶
)                                                    ❶
hyperparam_optimizer = torch.optim.Adam(             ❷
  [{"params": model.parameters()}, {"params":        ❷
  ➥likelihood.parameters()}],                       ❷
  lr=0.01                                            ❷
)                                                    ❷
mll = gpytorch.mlls.VariationalELBO(
  likelihood, model, num_data=train_y.size(0)
)

❶ 自然梯度下降接受 VGP 的变分参数,model.variational_parameters()

❷ Adam 接受 VGP 的其他参数,model.parameters()likelihood.parameters()

现在,在训练期间,我们仍然使用以下方式计算损失

output = model(x_batch)
loss = -mll(output, y_batch)

在计算损失时,我们循环遍历我们训练数据的小批量(x_batchy_batch)。然而,现在我们有两个优化器同时运行,我们需要通过在每次训练迭代时调用zero_grad()(清除前一步的梯度)和step()(执行一步下降)来管理它们:

model.train()                              ❶
likelihood.train()                         
for i in tqdm(range(50)):
    for x_batch, y_batch in train_loader:
        ngd_optimizer.zero_grad()          ❷
        hyperparam_optimizer.zero_grad()   ❷
        output = model(x_batch)
        loss = -mll(output, y_batch)
        loss.backward()
        ngd_optimizer.step()               ❸
        hyperparam_optimizer.step()        ❸
model.eval()                               ❹
likelihood.eval()                          ❹

❶ 启用训练模式

❷ 清除前一步的梯度

❸ 使用每个优化器执行下降步骤

❹ 启用预测模式

注意 像往常一样,在梯度下降之前,我们需要调用model.train()likelihood.train(),在训练完成后需要调用model.eval()likelihood.eval()

请注意,我们在自然梯度下降优化器和 Adam 上都调用了 zero_grad()step(),以优化 VGP 模型的相应参数。训练循环再次只需很短的时间即可完成,并且训练得到的 VGP 产生的预测结果如图 12.16 所示。我们看到的预测结果与图 12.4 中的普通 GP 和使用小批量梯度下降训练的 VGP 在图 12.13 中的预测结果非常相似。


图 12.16 由 VGP 和通过自然梯度下降训练的感应点所做出的预测。这些预测的质量很高。

我们可以进一步检查训练过程中 ELBO 损失的逐步进展。其进展在图 12.17 的左侧面板中可视化。


图 12.17 VGP 在自然梯度下降期间的逐步损失。经过少数迭代后,损失得到了有效最小化。

令人惊讶的是,在训练过程中,我们的 ELBO 损失几乎立即降至一个较低的值,这表明自然梯度下降能够帮助我们快速收敛到一个良好的模型。这说明了当训练 VGP 时,这种梯度下降算法的变体的好处。

我们现在已经到达第十二章的结尾。在本章中,我们学习了如何通过使用感应点将 GP 模型扩展到大型数据集,感应点是一组代表性点,旨在捕获大型训练集所展现的趋势。由此产生的模型称为 VGP,它适用于小批量梯度下降,因此可以在不计算所有数据点的模型损失的情况下进行训练。我们还研究了自然梯度下降作为小批量算法的更有效版本,使我们能够更有效地优化。在第十三章中,我们将涵盖 GP 的另一种高级用法,将其与神经网络结合以建模复杂的结构化数据。

12.4 练习

此练习演示了在加利福尼亚房价的真实数据集上,从普通 GP 模型转换为 VGP 模型时效率的提高。我们的目标是观察 VGP 在实际环境中的计算优势。

完成以下步骤:

  1. 使用 Pandas 库中的 read_csv() 函数读取存储在名为 data/housing.csv 的电子表格中的数据集,该数据集来自 Kaggle 的加利福尼亚房价数据集(mng.bz/N2Q7),使用的是创意共用公共领域许可证。一旦读取,Pandas dataframe 应该看起来类似于图 12.18 中的输出。

    图 12.18 作为 Pandas dataframe 显示的房价数据集。这是本练习的训练集。
  2. 在散点图中可视化median_house_value列,这是我们的预测目标,其x-和y-轴对应于longitudelatitude列。一个点的位置对应于一个房子的位置,点的颜色对应于价格。可视化效果应该类似于图 12.19。

    图 12.19 房价数据集的散点图表示
  3. 提取除最后一列(median_house_value)以外的所有列,并将它们存储为 PyTorch 张量。这将用作我们的训练特征,train_x
  4. 提取median_house_value列,并将其对数变换存储为另一个 PyTorch 张量。这是我们的训练目标,train_y
  5. 通过减去均值并除以标准差来归一化训练标签train_y。这将使训练更加稳定。
  6. 使用具有恒定均值函数和具有自动相关性确定(ARD)的 Matérn 5/2 核实现常规 GP 模型。关于 Matérn 核和 ARD 的详细内容,请参阅第 3.4.2 和第 3.4.3 节。
  7. 使用以下代码创建一个噪声至少为 0.1 的似然性:
likelihood = gpytorch.likelihoods.GaussianLikelihood(
    noise_constraint=gpytorch.constraints.GreaterThan(1e-1)  ❶
)
  1. ❶该约束强制噪声至少为 0.1。
    此约束有助于通过提高噪声容限来平滑训练标签。
  2. 初始化之前实现的 GP 模型,并使用梯度下降对其进行 10 次迭代训练。观察总训练时间。
  3. 实现具有与 GP 相同的均值和协方差函数的变分 GP 模型。这个模型看起来类似于我们在本章中实现的ApproximateGPModel类,只是现在我们需要 Matérn 5/2 核和 ARD。
  4. 使用类似初始化的似然性和 100 个诱导点对此 VGP 进行训练,使用自然梯度下降进行 10 次迭代。对于小批量梯度下降,您可以将训练集分成大小为 100 的批次。
  5. 验证训练 VGP 所需的时间是否少于训练 GP 的时间。对于计时功能,您可以使用time.time()来记录每个模型训练的开始和结束时间,或者您可以使用tqdm库来跟踪训练持续时间,就像我们一直在使用的代码一样。

解决方案包含在CH11/02 - Exercise.ipynb笔记本中。

摘要

  • GP 的计算成本与训练数据集的大小呈立方比例。因此,随着数据集的大小增长,训练模型变得不可行。
  • 在大量数据点上计算 ML 模型的损失在数值上是不稳定的。以不稳定的方式计算损失可能会误导梯度下降过程中的优化,导致预测性能不佳。
  • VGP 通过仅对一小组诱导点进行训练来扩展到大型数据集。这些诱导点需要代表数据集,以便训练的模型尽可能地与在整个数据集上训练的 GP 相似。
  • 为了产生一个尽可能接近在所有训练数据上训练的模型的近似模型,Kullback-Leibler 散度,它度量两个概率分布之间的差异,被用于 VGP 的制定中。
  • 当训练一个 VGP 时,证据下界(ELBO)充当真实损失的代理。更具体地说,ELBO 限制了模型的边际对数似然,这是我们的优化目标。通过优化 ELBO,我们间接优化了边际对数似然。
  • 训练一个 VGP 可以通过小批量进行,从而实现更稳定的损失计算。在该过程中使用的梯度下降算法是小批量梯度下降。
  • 尽管小批量梯度下降的每一步都不能保证完全最小化损失,但是当运行大量迭代时,该算法可以有效降低损失。这是因为许多小批量梯度下降的步骤在聚合时可以指向最小化损失的正确方向。
  • 自然梯度下降考虑了相对于 VGP 的变分参数的损失函数的几何性质。这种几何推理使得算法能够更新训练模型的变分参数并更有效地最小化损失,从而实现更快的收敛。
  • 自然梯度下降优化了 VGP 的变分参数。诸如长度和输出比例等常规参数由小批量梯度下降进行优化。

第十四章:将高斯过程与神经网络结合

本章涵盖

  • 使用常见协方差函数处理复杂结构化数据的困难
  • 使用神经网络处理复杂结构化数据
  • 将神经网络与 GP 结合

在第二章中,我们学到了高斯过程(GP)的均值和协方差函数作为我们希望在模型中融入的先验信息,当进行预测时。因此,这些函数的选择极大地影响了训练后的 GP 的行为。因此,如果均值和协方差函数被错误地指定或不适用于手头的任务,那么得到的预测就不会有用。

例如,记住covariance function,或kernel,表示两个点之间的相关性——即相似性。两个点越相似,它们的标签值可能越相似,我们试图预测的标签值。在我们的房价预测示例中,相似的房子可能会有类似的价格。

核到底如何计算两个给定房子之间的相似性?我们考虑两种情况。在第一种情况中,核函数仅考虑前门的颜色,并对于任何具有相同门颜色的两个房子输出 1,否则输出 0。换句话说,如果两个房子的前门颜色相同,这个核函数认为它们相似。

正如图 13.1 所示,这个核函数对于房价预测模型来说是一个糟糕的选择。核函数认为左边的房子和中间的房子应该有相似的价格,而右边的房子和中间的房子应该有不同的价格。这是不合适的,因为左边的房子比其他两个大得多,而其他两个房子的大小相似。误差发生的原因是核函数错误地判断了房子的哪个特征是房子成本的良好预测特征。


图 13.1 由不合适的核函数计算的房屋之间的协方差。因为它只关注前门的颜色,所以这个核函数无法产生合适的协方差。

另一个核函数更加复杂,并考虑了相关因素,比如位置和居住面积。这个核函数更加合适,因为它可以更合理地描述两个房子之间的价格相似性。拥有合适的核函数——即正确的相似度度量——对于 GP 来说至关重要。如果核函数能够正确地描述给定数据点之间的相似性或差异性,那么使用协方差的 GP 将能够产生良好校准的预测。否则,预测将具有较低的质量。

你可能会认为一个只考虑门颜色的房屋核函数是不合适的,并且在 ML 中没有合理的核函数会表现出这种行为。然而,正如我们在本章中所展示的,到目前为止我们使用的一些常见核函数(例如,RBF 和 Matérn)在处理结构化输入数据(例如图像)时会出现相同的问题。具体来说,它们未能充分描述两个图像之间的相似性,这给在这些结构化数据类型上训练 GPs 带来了挑战。我们采取的方法是使用神经网络。神经网络是灵活的模型,可以在有足够数据的情况下很好地逼近任何函数。我们学会使用神经网络来转换 GP 的核函数无法很好地处理的输入数据。通过这样做,我们既得到了神经网络的灵活建模,从 GP 中获得了不确定性校准的预测。

在本章中,我们展示了我们通常的 RBF 核函数不能很好地捕捉常见数据集的结构,从而导致 GP 的预测不佳。然后我们将一个神经网络模型与此 GP 结合起来,看到新的核函数可以成功地推理相似性。到本章结束时,我们获得了一个框架,帮助 GP 处理结构化数据类型并提高预测性能。

13.1 包含结构的数据

在本节中,我们解释了结构化数据的确切含义。与我们在之前章节中用来训练 GPs 的数据类型不同,在那些数据类型中,数据集中的每个特征(列)可以取得连续范围内的值,而在许多应用中,数据具有更复杂性。比如说:

  • 房子的楼层数只能是正整数。
  • 在计算机视觉任务中,图像中的像素值是 0 到 255 之间的整数。
  • 在分子 ML 中,分子通常被表示为图形。

那就是,这些应用中的数据点中嵌入了结构,或者需要数据点遵循的要求:没有房子可以有负数的楼层;像素不能以分数作为其值;表示分子的图形将具有表示化学物质和结合物的节点和边缘。我们称这些类型的数据为结构化数据。在本章中,我们将使用流行的 MNIST 手写数字数据集(见huggingface.co/datasets/mnist)作为我们讨论的案例研究。

定义 修改后的美国国家标准与技术研究所(MNIST)数据集包含手写数字的图像。每个图像是一个 28×28 的整数矩阵,取值范围在 0 到 255 之间。

这个数据集中的一个示例数据点如图 13.2 所示,其中像素的阴影对应于其值;0 对应于白色像素,255 对应于黑色像素。我们看到这个数据点是数字五的图像。


图 13.2 来自 MNIST 数据集的数据点,这是一个由 28 行和 28 列像素组成的图像,表示为一个 PyTorch 张量

注意 虽然这个手写数字识别任务在技术上是一个分类问题,但我们使用它来模拟一个回归问题(这是我们在 BayesOpt 中要解决的问题类型)。由于每个标签都是一个数字(一个数字),我们假装这些标签存在于一个连续的范围内,并直接将它们用作我们的预测目标。

我们的任务是在一个图像标签数据集上训练一个 GP(每个标签都是对应图像中写的数字的值),然后在一个测试集上进行预测。这个区别在图 13.3 中有所体现,它显示与分类不同,在分类中,我们选择一个类作为每个数据点的预测,而在回归任务中,这里的每个预测是一个连续范围内的数字。


图 13.3 在 MNIST 数据的上下文中的分类与回归。每个预测是一个分类任务,对应于其中的一个类别;在回归中的每个预测是一个连续范围内的数字。

有许多现实世界的应用遵循这种结构化数据的回归问题的形式:

  • 在产品推荐中,我们希望预测某人点击自定义广告的概率。广告是可以自定义的图片,是结构化数据,点击概率是预测目标。这个概率可以是 0 到 1 之间的任何数字。
  • 在材料科学中,科学家可能希望在实验室中合成某种分子组合时预测其能量水平。每种分子组合都可以表示为具有节点和边的特定结构的图,并且能量水平可以是理论最小和最大能量水平之间的任何数字,一个组合可能表现出的。
  • 在药物发现中,我们希望预测可能产生的药物的有效性。如图 13.4 所示,每种药物对应于一种化合物,它也可以表示为一个图。其有效性可以是一个实数,介于某个范围内(比如从 0 到 10)。


图 13.4 药物发现作为一个结构化回归问题的例子。每个化合物都表示为一个结构化图,并且我们的目标是预测这种化合物在治疗某种疾病方面的有效性,其范围从 0 到 10。

在所有这些应用中,我们想要进行预测的输入数据是结构化的,我们的预测目标是一个实数值。简而言之,它们是结构化数据的回归问题。使用 MNIST 数据集,我们模拟了这样一个问题。

13.2 捕捉结构化数据内的相似性

在本节中,我们将探讨常见内核(如径向基函数内核)如何无法描述结构化数据中的相似性。量化两个输入的协方差的内核输出,x[1] 和 x[2] 的输出定义如下:


这个输出是两个变量之间的协方差,是两个输入之间差异的负指数除以一个标度。输出始终在 0 和 1 之间,而且越大的差异,输出越小。

这在许多情况下是有道理的,因为如果两个输入具有类似的值,因此差异很小,则它们的协方差将很高;而如果它们具有不同的值,则协方差将很低。两栋面积大致相等的房屋可能会有类似的价格,也就是说,它们的价格具有高的协方差;另一方面,非常大和非常小的房子的价格可能会具有较低的协方差。

13.2.1 使用 GPyTorch 的内核

让我们用代码验证一下。当我们创建 GP 模型时,通常会初始化一个 RBFKernel 对象。这里,我们直接使用这个内核对象进行工作。为此,我们首先使用 GPyTorch 创建一个 RBF 内核对象:

import gpytorch
rbf_kernel = gpytorch.kernels.RBFKernel()

请注意,作为 Python 中实现 GP 相关对象的首选库,我们一如既往地使用 GPyTorch。有关如何在 GPyTorch 中使用内核对象的详细信息,请参见第 2.4 节。

要计算两个输入之间的协方差,我们只需将它们传递给该内核对象即可。例如,让我们计算 0 和 0.1 之间的协方差:

>>> rbf_kernel(torch.tensor([0.]), torch.tensor([0.1])).evaluate().item()
0.9896470904350281

这两个数字在实数线上非常接近(也就是说,它们是相似的),因此它们的协方差非常高,几乎为 1。现在让我们计算 0 和 10 之间的协方差,这是两个不同的数字:

>>> rbf_kernel(torch.tensor([0.]), torch.tensor([10.])).evaluate().item()
0.0

这次,由于两个数字之间的差异要大得多,它们的协方差降为 0。这种对比是合理的行为,并且通过图 13.5 进行说明。


图 13.5 各种数字之间的协方差。两个数字之间的差异较小时,协方差增加;差异较大时,协方差降低。

当两个输入之间的值差异不能捕捉到数据结构差异时,问题就出现了。这通常是对结构化数据(如图像)的情况。接下来,我们将看到像径向基函数(RBF)这样的常见内核如何无法描述结构化数据中的相似性。

贝叶斯优化实战(三)(5)

相关文章
|
6月前
|
数据可视化 算法 PyTorch
贝叶斯优化实战(一)(3)
贝叶斯优化实战(一)
130 2
|
6月前
|
存储 机器学习/深度学习 数据可视化
贝叶斯优化实战(一)(5)
贝叶斯优化实战(一)
123 1
|
6月前
|
存储 数据可视化 PyTorch
贝叶斯优化实战(四)(1)
贝叶斯优化实战(四)
53 1
|
6月前
|
机器学习/深度学习 人工智能 算法
贝叶斯优化实战(一)(1)
贝叶斯优化实战(一)
271 0
贝叶斯优化实战(一)(1)
|
6月前
|
机器学习/深度学习 存储 移动开发
贝叶斯优化实战(四)(2)
贝叶斯优化实战(四)
45 0
|
6月前
|
机器学习/深度学习 算法 数据可视化
贝叶斯优化实战(三)(3)
贝叶斯优化实战(三)
57 0
|
6月前
|
存储 数据可视化 大数据
贝叶斯优化实战(四)(5)
贝叶斯优化实战(四)
49 0
|
6月前
|
存储 机器学习/深度学习 数据可视化
贝叶斯优化实战(三)(1)
贝叶斯优化实战(三)
40 0
|
6月前
|
机器学习/深度学习 移动开发 测试技术
贝叶斯优化实战(四)(3)
贝叶斯优化实战(四)
48 0
|
6月前
|
存储 移动开发 数据可视化
贝叶斯优化实战(二)(5)
贝叶斯优化实战(二)
112 0