深入理解高斯过程回归 (GPR):从理论到 Python 实战

在机器学习的广阔天地中,回归分析无疑是我们手中最强大的武器之一。然而,你是否曾经遇到过这样的困境:数据集非常小,或者你需要模型不仅能给出预测值,还能告诉你这个预测有多“确定”?传统的参数化模型(如线性回归或神经网络)在小数据集上容易过拟合,且往往难以直观地量化不确定性。这时候,高斯过程回归(Gaussian Process Regression, GPR)就像一道光,照亮了这些复杂问题的解决之路。

GPR 是一种基于贝叶斯推断的非参数模型。与普通回归不同,GPR 并不学习固定的参数 $w$,而是定义了所有可能函数的概率分布。在本文中,我们将深入探讨 GPR 的核心原理,拆解其数学直觉,并通过 Python 代码实战,看看如何利用它来解决现实世界中的预测难题。让我们开始这段探索之旅吧!

高斯过程回归 (GPR) 核心概念

要掌握 GPR,我们首先需要建立一种全新的思维方式。与其问“这条直线的斜率和截距是多少?”,不如问“哪些函数与我的数据相吻合?”。这种思维方式的转变,正是理解高斯过程的关键。

什么是高斯过程?

简单来说,高斯过程是正态分布在无限维度上的推广。在标准正态分布中,我们描述的是随机变量;而在高斯过程中,我们描述的是函数。

想象一下,你有一组随机变量,其中任意有限个变量的联合分布都服从多元高斯分布。这就是高斯过程的本质。我们可以将其形式化地表示为:

$$ f(x) \sim GP(m(x), k(x, x‘)) $$

这里有两个核心组件:

  • 均值函数 $m(x)$:代表函数在各点的平均取值(通常假设为 0)。
  • 协方差函数 $k(x, x‘)$(也称为核函数):定义了不同点之间函数值的相似程度。

为什么它被称为“非参数”模型?

你可能会好奇,既然没有参数,模型怎么工作呢?其实,“非参数”意味着参数的数量不是固定的,而是随着数据量的增加而增加。对于 GPR,每一个数据点都可以看作是一个参数。这种灵活性使得 GPR 能够极其灵活地适应各种复杂的数据模式,而无需像神经网络那样担心网络结构的设计。

核函数:灵魂所在

核函数决定了模型的“假设”或“先验”。如果你选择了一个线性核,模型就会假设数据是线性关系的;如果你选择了 RBF 核(径向基函数),模型就会假设函数是平滑且连续的。

#### 1. RBF 核

这是最常用的核函数,也叫平方指数核。它假设函数是无限可微的(非常平滑)。其公式如下:

$$ k(xi, xj) = \sigmaf^2 \exp\left(-\frac{\

xi – x_j\

^2}{2l^2}\right) $$

这里有两个重要的超参数:

  • 长度尺度 $l$ (Length Scale):控制函数的“摆动”程度。$l$ 越大,函数变化越平缓;$l$ 越小,函数变化越剧烈。
  • 信号方差 $\sigma_f^2$:控制函数在垂直方向上的变化幅度。

#### 2. Matérn 核

虽然 RBF 核非常平滑,但在现实世界中,数据往往并不像 RBF 假设的那样完美。Matérn 核允许我们控制函数的平滑程度,通常在处理物理或地理空间数据时表现更好。

高斯过程回归的数学原理

让我们来看看 GPR 是如何进行预测的。这也是贝叶斯推断的魅力所在:我们将“先验分布”结合“观测数据”,更新为“后验分布”。

假设我们有一个训练集 $X$ 和对应的观测值 $y$。我们的目标是预测新的测试点 $X$ 对应的 $f = f(X_*)$。

联合概率分布可以写为:

$$ \begin{bmatrix} y \\ f \end{bmatrix} \sim \mathcal{N}\left(0, \begin{bmatrix} K(X, X) + \sigman^2I & K(X, X) \\ K(X, X) & K(X, X*) \end{bmatrix}\right) $$

其中 $K$ 是通过核函数计算出的协方差矩阵,$\sigma_n^2$ 是观测噪声的方差。

根据高斯分布的条件概率公式,我们可以直接计算出后验预测分布 $f | X, y, X$,它也是一个高斯分布,由均值 $\bar{f}$ 和方差 $\text{var}(f)$ 定义:

$$ \bar{f} = K(X, X)[K(X, X) + \sigma_n^2I]^{-1}y $$

$$ \text{var}(f) = K(X, X) – K(X, X)[K(X, X) + \sigman^2I]^{-1}K(X, X*) $$

通俗解读
预测均值:基本上是训练集标签 $y$ 的加权和。权重取决于新点 $X_$ 与训练点 $X$ 的相似度(由核函数决定)。

  • 预测方差:由两部分组成。第一部分是先验的方差,第二部分则是我们从数据中获得的信息。注意,这是 GPR 的杀手锏——我们不仅知道预测值是多少,还知道这个预测值的不确定性范围(置信区间)。

Python 实现:从零开始到实战

理论讲完了,现在让我们卷起袖子写代码。我们将使用 scikit-learn 库,它提供了非常便捷的 API。

示例 1:基础高斯过程回归

首先,我们生成一个带噪声的正弦波数据集,看看 GPR 如何拟合它。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as C

# 1. 生成模拟数据
# 我们假设真实函数是一个带噪声的正弦波
np.random.seed(42)
X = np.linspace(start=0, stop=10, num=1_000).reshape(-1, 1)
y = np.sin(X).ravel() + np.random.normal(0, 0.1, X.shape[0])

# 为了演示,我们只选取其中的 10 个点作为训练数据
training_indices = np.random.randint(0, 800, size=10)
X_train, y_train = X[training_indices], y[training_indices]

print(f"训练数据量: {X_train.shape[0]}")

# 2. 定义核函数
# 这里我们使用 RBF 核 (平方指数核)
# alpha 参数代表噪声水平,对应我们刚才数学公式中的 sigma_n^2
kernel = C(1.0, (1e-3, 1e3)) * RBF(1.0, (1e-2, 1e2))

# 3. 创建并拟合 GPR 模型
gp = GaussianProcessRegressor(kernel=kernel, alpha=0.1, 
                              n_restarts_optimizer=10, normalize_y=True)

gp.fit(X_train, y_train)

# 4. 进行预测
# GPR 返回预测均值 和 标准差
y_pred, sigma = gp.predict(X, return_std=True)

# 5. 可视化结果
plt.figure(figsize=(10, 5))
plt.plot(X, np.sin(X), ‘r:‘, label=u‘$f(x) = \sin(x)$ (真实函数)‘)
plt.errorbar(X_train.ravel(), y_train, fmt=‘r.‘, markersize=10, label=u‘观测数据 (训练集)‘)
plt.plot(X, y_pred, ‘b-‘, label=u‘GPR 预测 (均值)‘)
plt.fill_between(X.ravel(), y_pred - 1.96*sigma, y_pred + 1.96*sigma,
                 alpha=0.2, color=‘k‘, label=u‘95% 置信区间‘)
plt.xlabel(‘$x$‘)
plt.ylabel(‘$f(x)$‘)
plt.title(‘高斯过程回归演示 (10个训练点)‘)
plt.legend(loc=‘upper right‘)
plt.show()

代码详解

  • normalize_y=True:这是一个非常实用的技巧。它建议我们在训练前将目标值 $y$ 归一化为零均值和单位方差。这有助于数值优化算法更好地工作。
  • n_restarts_optimizer:对数边际似然函数可能有多个局部最优解。设置这个参数可以让优化器从不同的随机起点开始多次搜索,从而找到更好的超参数。
  • return_std=True:这是 GPR 的魔法开关。开启后,模型不仅返回预测值,还返回标准差,我们可以用它来绘制置信区间。

从图中你会发现,在有数据点的地方,置信区间(阴影部分)非常窄(不确定性低);而在远离训练点的地方,模型会回归到先验分布,置信区间变宽(不确定性高)。这正是我们想要的安全感!

示例 2:探索不同的核函数

如果不小心选错了核函数会发生什么?让我们对比一下 RBF 核 和 RationalQuadratic (RQ) 核。

from sklearn.gaussian_process.kernels import RationalQuadratic

# 创建稍微复杂一点的数据
X = np.linspace(0, 10, 100)
y = np.sin(X) + 0.5 * np.sin(3 * X) + np.random.normal(0, 0.1, len(X))
X = X.reshape(-1, 1)

# 只取 20 个点
idx = np.random.choice(len(X), 20, replace=False)
X_train, y_train = X[idx], y[idx]

# 场景 A: 使用 RBF 核
kernel_rbf = RBF(length_scale=1.0)
gpr_rbf = GaussianProcessRegressor(kernel=kernel_rbf, alpha=0.1).fit(X_train, y_train)

# 场景 B: 使用 RationalQuadratic 核
# RQ 核可以看作是许多不同长度尺度的 RBF 核的和,适用于多尺度变化的数据
kernel_rq = RationalQuadratic(length_scale=1.0, alpha=0.1)
gpr_rq = GaussianProcessRegressor(kernel=kernel_rq, alpha=0.1).fit(X_train, y_train)

# 预测
y_rbf, sigma_rbf = gpr_rbf.predict(X, return_std=True)
y_rq, sigma_rq = gpr_rq.predict(X, return_std=True)

# 绘图对比
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.scatter(X_train, y_train, c=‘red‘, zorder=10, label=‘Data‘)
plt.plot(X, y_rbf, ‘b-‘, label=‘RBF Prediction‘)
plt.fill_between(X.ravel(), y_rbf - 1.96*sigma_rbf, y_rbf + 1.96*sigma_rbf, alpha=0.2)
plt.title("RBF Kernel (单一尺度)")

plt.subplot(1, 2, 2)
plt.scatter(X_train, y_train, c=‘red‘, zorder=10, label=‘Data‘)
plt.plot(X, y_rq, ‘g-‘, label=‘RQ Prediction‘)
plt.fill_between(X.ravel(), y_rq - 1.96*sigma_rq, y_rq + 1.96*sigma_rq, alpha=0.2)
plt.title("RationalQuadratic Kernel (多尺度)")

plt.show()

实用见解

RBF 核假设函数在整个定义域内具有相同的平滑度(即同一个长度尺度 $l$)。而 RationalQuadratic 核引入了一个 $\alpha$ 参数,使得模型可以对不同频率的变化进行建模。如果你的数据同时包含长周期的趋势和短周期的波动,RQ 核或 Matérn 核往往比单纯的 RBF 核表现更好。

示例 3:处理噪声

现实数据总是充满噪声的。GPR 提供了一个参数 INLINECODE0cbf5a28 来显式地处理噪声。如果 INLINECODE3576854f 设置得很小,模型会强行拟合每一个点(可能导致过拟合,在训练点处抖动);如果 alpha 设置得很大,模型会认为噪声很大,从而进行更强的平滑处理。

此外,如果你使用的是 WhiteKernel,GPR 甚至可以帮你自动估计数据中的噪声水平。

from sklearn.gaussian_process.kernels import WhiteKernel

# 生成高噪声数据
X = np.linspace(0, 5, 20)
y = np.sin(X) + np.random.randn(len(X)) * 0.5 # 增加噪声
X = X.reshape(-1, 1)

# 定义包含白噪声的核
# WhiteKernel 会自动学习噪声的方差
kernel = C(1.0) * RBF(1.0) + WhiteKernel(noise_level=1)

gpr_noise = GaussianProcessRegressor(kernel=kernel, 
                                     alpha=0.0, # alpha 设为 0,让 WhiteKernel 处理噪声
                                     normalize_y=True).fit(X, y)

print(f"学习到的核函数参数: {gpr_noise.kernel_}")

X_test = np.linspace(0, 5, 100).reshape(-1, 1)
y_pred, sigma = gpr_noise.predict(X_test, return_std=True)

plt.scatter(X, y, c=‘k‘, label=‘Noisy Data‘)
plt.plot(X_test, y_pred, ‘b-‘, label=‘Prediction‘)
plt.fill_between(X_test.ravel(), y_pred - sigma, y_pred + sigma, alpha=0.2)
plt.title("GPR with WhiteKernel (自动噪声估计)")
plt.legend()
plt.show()

常见问题与最佳实践

1. 计算复杂度与大数据集

GPR 虽好,但有一个显著的弱点:计算复杂度。计算逆矩阵 $[K(X, X)]^{-1}$ 的复杂度是 $O(N^3)$,其中 $N$ 是样本数量。这意味着如果你有超过几千个数据点,标准的 GPR 就会变得非常慢。

解决方案

  • 使用稀疏近似:例如 INLINECODE922376cb 配合 INLINECODE31d6f4c5 或使用 inducing points(诱导点),只使用部分数据来代表整体。
  • 数据分块
  • 降维:先对 $X$ 进行 PCA 处理,减少特征维度。

2. 数值不稳定

有时候,协方差矩阵 $K$ 可能不是正定的,这会导致求逆计算报错。这通常是因为两个数据点完全相同(距离为 0),或者计算过程中的浮点数误差。

解决方案

  • 确保在核函数上添加一个微小的“抖动”项,也就是 INLINECODE2f6dd76e 参数,即使数据没有噪声,也可以设置一个极小的值(如 INLINECODE4046b507)。

3. 模型过拟合或欠拟合

观察预测图的置信区间可以帮助你判断。

  • 过拟合:如果模型在训练点之间的波动极其剧烈,且置信区间在某些点异常收缩,可能是核函数的长度尺度 $l$ 设置得太小了。
  • 欠拟合:如果模型是一条直线,完全忽略了数据的趋势,可能是长度尺度 $l$ 设置得太大,或者核函数选择不当(例如用线性核去拟合正弦波)。

建议:总是将 INLINECODE4df4be05 设置为 INLINECODEd1487c94。这可以让优化器的任务轻松很多,因为它只需要在统一的尺度上寻找长度尺度,而不需要同时考虑数值的大小。

总结

在这篇文章中,我们一起领略了高斯过程回归的独特魅力。不同于传统的机器学习算法,GPR 提供了一种概率式的视角。它不仅能预测“未来”是什么样(均值),还能告诉我们对这一预测的“把握”有多大(方差)。

关键要点回顾:

  • 非参数模型:GPR 非常灵活,能适应复杂的非线性关系,无需手动指定多项式阶数。
  • 核函数是关键:你需要根据对数据的理解(如是否平滑、是否周期性)来选择合适的核函数(RBF, Matérn 等)。
  • 不确定性量化:这是 GPR 的核心竞争力,特别适用于风险敏感的领域(如机器人导航、金融预测)。
  • 计算成本:注意其 $O(N^3)$ 的复杂度,对于大数据集需要使用近似方法。

虽然 GPR 在计算上相对昂贵,但在样本量有限且对解释性要求极高的场景下,它依然是不可替代的利器。希望你在接下来的项目中,能够尝试运用 GPR,挖掘出数据背后更深层次的故事!

如果你想继续深入,可以尝试探索那些利用 GPR 进行超参数优化的高级库,比如 Scikit-Optimize,看看 GPR 是如何指导我们找到最佳参数的。

祝你在机器学习的道路上越走越远!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/32400.html
点赞
0.00 平均评分 (0% 分数) - 0