高斯过程#

GP 基础知识#

有时,模型中未知的参数或变量不是标量值或固定长度的向量,而是一个函数。高斯过程 (GP) 可以用作先验概率分布,其支撑集是连续函数空间。函数 \(f(x)\) 的 GP 先验通常写为:

\[f(x) \sim \mathcal{GP}(m(x), \, k(x, x')) \,.\]

函数值被建模为从多元正态分布中抽取的样本,该分布由均值函数 \(m(x)\) 和协方差函数 \(k(x, x')\) 参数化。由于多元正态分布的边缘化和条件化属性,高斯过程是作为函数先验的便捷选择。通常,边缘分布在 \(f(x)\) 上进行评估,在推理步骤中。然后,条件分布用于预测新点 \(x_*\) 处的函数值 \(f(x_*)\)

\(f(x)\)\(f(x_*)\) 的联合分布是多元正态分布:

\[\begin{split}\begin{bmatrix} f(x) \\ f(x_*) \\ \end{bmatrix} \sim \text{N}\left( \begin{bmatrix} m(x) \\ m(x_*) \\ \end{bmatrix} \,, \begin{bmatrix} k(x,x') & k(x_*, x) \\ k(x_*, x) & k(x_*, x_*') \\ \end{bmatrix} \right) \,.\end{split}\]

从联合分布开始,得到 \(f(x)\) 的边缘分布,为 \(\text{N}(m(x),\, k(x, x'))\)。条件分布为:

\[f(x_*) \mid f(x) \sim \text{N}\left( k(x_*, x) k(x, x)^{-1} [f(x) - m(x)] + m(x_*) ,\, k(x_*, x_*) - k(x, x_*) k(x, x)^{-1} k(x, x_*) \right) \,.\]

注意

有关 GP 的更多信息,请查看 Rasmussen & Williams 的书籍 Gaussian Processes for Machine Learning,或 D. Mackay 的 这篇介绍

PyMC 是一个非常适合使用完全贝叶斯高斯过程模型的环境。PyMC 中的 GP 具有清晰的语法和高度可组合性,并且包含许多预定义的协方差函数(或核函数)、均值函数和多种 GP 实现。GP 被视为可以在更大或分层模型中使用的分布,而不仅仅是作为独立的回归模型。

均值和协方差函数#

使用过 GPy 或 GPflow Python 包的用户会发现构造均值和协方差函数的语法有些熟悉。首次实例化时,均值和协方差函数会被参数化,但尚未给出其输入。协方差函数还必须提供输入矩阵的维度数量,以及索引这些维度中哪些维度是它们要操作的列表。这种设计的原因是为了可以构造协方差函数,这些函数是其他协方差函数的组合。

例如,要构造一个指数平方协方差函数,该函数对表示三个预测变量的三列矩阵的第二列和第三列进行操作:

ls = [2, 5] # the lengthscales
cov_func = pm.gp.cov.ExpQuad(input_dim=3, ls=ls, active_dims=[1, 2])

这里 ls 或长度尺度参数是二维的,允许第二维和第三维具有不同的长度尺度。我们必须指定 input_dimX 的总列数)和 active_dims(协方差函数将作用于的列或维度)的原因是,cov_func 实际上尚未看到输入数据。active_dims 参数是可选的,默认值为输入矩阵的所有列。

PyMC 中的协方差函数密切遵循核函数的代数规则,这允许用户将协方差函数组合成新的函数,例如:

  • 两个协方差函数的和也是协方差函数

    cov_func = pm.gp.cov.ExpQuad(...) + pm.gp.cov.ExpQuad(...)
    
  • 两个协方差函数的积也是协方差函数

    cov_func = pm.gp.cov.ExpQuad(...) * pm.gp.cov.Periodic(...)
    
  • 协方差函数与标量的积(或和)也是协方差函数

    cov_func = eta**2 * pm.gp.cov.Matern32(...)
    

定义协方差函数后,它现在是一个可以通过调用 cov_func(x, x)(或 mean_func(x))来评估的函数。由于 PyMC 构建在 PyTensor 之上,因此定义和试验非标准协方差和均值函数相对容易。有关更多信息,请查看关于协方差函数的 教程

GP 实现#

PyMC 包含多种 GP 实现,包括边缘模型和潜在变量模型,以及一些快速近似。它们的使用都遵循类似的模式:首先,使用均值函数和协方差函数实例化 GP。然后,可以将 GP 对象加在一起,从而可以仔细地建模和分离函数特征。最后,在 GP 对象上调用 priormarginal_likelihoodconditional 方法之一,以实际构造表示函数先验的 PyMC 随机变量。

gp.Latent 为例,首先指定 GP 的语法是:

gp = pm.gp.Latent(mean_func, cov_func)

第一个参数是均值函数,第二个参数是协方差函数。我们已经创建了 GP 对象,但我们尚未明确它将作为哪个函数的先验,输入是什么,或者它将以哪些参数为条件。

注意

gp.Marginal 类和类似的类没有 prior 方法。相反,它们有一个 marginal_likelihood 方法,其使用方式类似,但具有额外的必需参数,例如观测数据、噪声或其他,具体取决于实现。请参阅笔记本示例。conditional 方法的工作方式类似。

调用 prior 方法将创建一个 PyMC 随机变量,该变量表示潜在函数 \(f(x) = \mathbf{f}\)

f = gp.prior("f", X)

f 是一个随机变量,可以在 PyMC 模型中像任何其他类型的随机变量一样使用。第一个参数是表示我们放置先验的函数的随机变量的名称。第二个参数是函数的输入,先验是关于该函数的,X。输入通常是已知的并且存在于数据中,但它们也可以是 PyMC 随机变量。如果输入是 PyTensor 张量或 PyMC 随机变量,则需要给出 shape

通常在此时,对模型执行推理。conditional 方法创建任意 \(x_*\) 输入点 \(f(x_*)\) 处潜在函数的条件分布或预测分布。要构造条件分布,我们写:

f_star = gp.conditional("f_star", X_star)

加性 GP#

PyMC 中的 GP 实现的构造方式使其易于定义加性 GP 并从各个 GP 组件中采样。我们可以写:

gp1 = pm.gp.Marginal(mean_func1, cov_func1)
gp2 = pm.gp.Marginal(mean_func2, cov_func2)
gp3 = gp1 + gp2

GP 对象必须具有相同的类型,gp.Marginal 不能添加到 gp.Latent

考虑两个独立的 GP 分布函数,\(f_1(x) \sim \mathcal{GP}\left(m_1(x),\, k_1(x, x')\right)\)\(f_2(x) \sim \mathcal{GP}\left( m_2(x),\, k_2(x, x')\right)\)\(f_1,\, f_1^*,\, f_2,\, f_2^*,\, f_1 + f_2\)\(f_1^* + f_2^*\) 的联合分布为:

\[\begin{split}\begin{bmatrix} f_1 \\ f_1^* \\ f_2 \\ f_2^* \\ f_1 + f_2 \\ f_1^* + f_2^* \end{bmatrix} \sim \text{N}\left( \begin{bmatrix} m_1 \\ m_1^* \\ m_2 \\ m_2^* \\ m_1 + m_2 \\ m_1^* + m_2^* \\ \end{bmatrix} \,,\, \begin{bmatrix} K_1 & K_1^* & 0 & 0 & K_1 & K_1^* \\ K_1^{*^T} & K_1^{**} & 0 & 0 & K_1^* & K_1^{**} \\ 0 & 0 & K_2 & K_2^* & K_2 & K_2^{*} \\ 0 & 0 & K_2^{*^T} & K_2^{**} & K_2^{*} & K_2^{**} \\ K_1 & K_1^{*} & K_2 & K_2^{*} & K_1 + K_2 & K_1^{*} + K_2^{*} \\ K_1^{*^T} & K_1^{**} & K_2^{*^T} & K_2^{**} & K_1^{*^T}+K_2^{*^T} & K_1^{**}+K_2^{**} \end{bmatrix} \right) \,.\end{split}\]

使用联合分布来获得 \(f_1^*\) 的条件分布,并从中分解出 \(f_1 + f_2\) 的贡献,我们得到:

\[f_1^* \mid f_1 + f_2 \sim \text{N}\left( m_1^* + K_1^{*^T}(K_1 + K_2)^{-1}\left[f_1 + f_2 - m_1 - m_2\right] \,,\, K_1^{**} - K_1^{*^T}(K_1 + K_2)^{-1}K_1^* \right) \,.\]

这些方程显示了如何将 GP 模型分解为各个组件,以了解每个组件如何贡献于数据。有关更多信息,请查看 David Duvenaud 的博士论文

PyMC 中的 GP 对象会自动跟踪这些边缘分布。以下代码草图显示了如何定义 \(f_2^*\) 的条件分布。我们在示例中使用 gp.Marginal,但同样的方法适用于其他实现。第一个代码块拟合 GP 先验。为了简洁起见,我们将 \(f_1 + f_2\) 表示为 \(f\)

with pm.Model() as model:
    gp1 = pm.gp.Marginal(mean_func1, cov_func1)
    gp2 = pm.gp.Marginal(mean_func2, cov_func2)

    # gp represents f1 + f2.
    gp = gp1 + gp2

    f = gp.marginal_likelihood("f", X, y, sigma)

    idata = pm.sample(1000)

要构造 gp1gp2 的条件分布,我们还需要包含额外的参数 Xysigma

with model:
    # conditional distributions of f1 and f2
    f1_star = gp1.conditional("f1_star", X_star,
                              given={"X": X, "y": y, "sigma": sigma, "gp": gp})
    f2_star = gp2.conditional("f2_star", X_star,
                              given={"X": X, "y": y, "sigma": sigma, "gp": gp})

    # conditional of f1 + f2, `given` not required
    f_star = gp.conditional("f_star", X_star)

第二个代码块生成条件分布。请注意,\(f1\)\(f2\) 的条件分布需要额外的参数,但 \(f\) 则不需要。这是因为当在 gp 上调用 .marginal_likelihood 时,这些参数会被缓存。

注意

在构造条件分布时,必须将额外的参数 Xysigmagp 作为名为 given 的字典提供!

由于未调用 gp1gp2 的边缘似然方法,因此它们的条件分布需要提供所需的输入。与先验相同,f_starf1_starf2_star 是随机变量,现在可以像 PyMC 中的任何其他随机变量一样使用。

请查看 笔记本,以获取 PyMC 中 GP 功能用法的详细演示。