高斯过程回归与分类:不确定性建模实战

高斯过程是非参数贝叶斯模型,用核函数刻画函数先验,不仅能给出预测值,还天然给出预测的不确定性。本文从贝叶斯线性回归引出高斯过程、讲清均值函数与 RBF/Matern/周期核、后验预测的均值与方差、用边际似然优化超参数、GP 分类的拉普拉斯近似、O(n³) 复杂度与稀疏/诱导点近似,并给出 scikit-learn 实战及其与贝叶斯优化的联系。

引言

大多数回归模型只给你一个数:预测值。但在很多场景里,「这个预测有多可信」比预测值本身更重要——药物实验中哪些剂量组合值得试、工程仿真里哪个参数区域还没探索过、主动学习里下一个该标哪条样本,答案都依赖对不确定性的准确刻画。

高斯过程(Gaussian Process, GP) 是少数能同时给出预测值与预测方差的模型。它属于非参数贝叶斯方法:不假设函数属于某个固定形式,而是给整个函数空间一个先验分布,用数据把先验收窄成后验。本文从贝叶斯线性回归的自然推广讲起,覆盖核函数、后验预测、超参学习、分类与大规模近似。

前置:回归基础与评估指标见 /ml-supervised-regression/;模型评估与校准指标见 /ml-model-evaluation/。

目录

1. 从贝叶斯线性回归到高斯过程

先从熟悉的线性回归出发。贝叶斯线性回归 y = w·φ(x) 给权重 w 一个高斯先验 w ~ N(0, Σ_p),则任意一组输入对应的预测 f(x) = w·φ(x) 服从联合高斯分布——函数值本身是随机变量。

把基函数 φ 的数量推到无穷,就得到高斯过程的定义:

f(x) ~ GP(m(x), k(x, x'))

含义是:任意有限个输入 x_1, ..., x_n 对应的函数值 f(x_1), ..., f(x_n) 服从一个 n 维联合高斯分布:

f = [f(x_1), ..., f(x_n)]ᵀ ~ N(μ, K)
μ_i = m(x_i)
K_ij = k(x_i, x_j)

关键洞察:GP 完全由两个函数决定——均值函数 m(x) 和协方差函数(核)k(x, x')。核决定了「函数有多光滑、点与点之间如何相关」,这正是 GP 表达力的来源。

import numpy as np

def sample_gp_prior(n_points=200, n_samples=5, length_scale=0.3, seed=0):
    """从 GP 先验采样:无需数据,纯由核决定函数形状"""
    rng = np.random.default_rng(seed)
    X = np.linspace(0, 1, n_points)[:, None]
    sq = (X - X.T) ** 2
    K = np.exp(-0.5 * sq / length_scale ** 2)      # RBF 核
    K += 1e-8 * np.eye(n_points)                   # 抖动保证数值稳定
    L = np.linalg.cholesky(K)
    return X.ravel(), (L @ rng.standard_normal((n_points, n_samples)))

X, samples = sample_gp_prior()
print("先验样本形状:", samples.shape)   # (200, 5),5 条光滑随机函数

length_scale 越小,采样出的函数越「毛躁」;越大越平滑。这就是核超参数对函数先验的直观影响。

2. 均值函数与协方差核

2.1 均值函数

m(x) 表达函数在没有数据时的默认走向。绝大多数场景直接取 m(x) = 0,让数据决定一切。若已知趋势(如周期性、线性趋势),可用参数函数作为均值,但需谨慎——均值设错会带来系统性偏差。

def zero_mean(X):
    return np.zeros(len(X))

def linear_mean(X):
    return 2.0 * X.ravel()      # 假设有上升趋势

2.2 核的两个性质

合法的核必须满足:

  1. 对称性:k(x, x') = k(x', x)。
  2. 半正定(PSD):任意输入集对应的 Gram 矩阵半正定。这是 Mercer 定理的要求,保证协方差矩阵合法。

核还编码了先验假设:平稳核(如 RBF)假设函数统计性质不随位置变化;非平稳核允许不同区域不同行为。

3. 常用核函数

核公式性质适用
RBF / 平方指数σ² exp(-‖x-x'‖²/(2ℓ²))无穷次可微、极光滑通用默认
Matern ν=3/2(1+√3d/ℓ)exp(-√3d/ℓ)一次可微物理实验、稍粗糙
Matern ν=5/2二次可微更光滑通用备选
周期核exp(-2 sin²(π‖x-x'‖/p)/ℓ²)周期性季节数据
线性核σ² x·x'线性趋势外推
Rational QuadraticRBF 的多尺度混合多尺度复杂数据
def rbf(X1, X2, length_scale=1.0, variance=1.0):
    sq = (X1[:, None, :] - X2[None, :, :]) ** 2
    return variance * np.exp(-0.5 * sq.sum(-1) / length_scale ** 2)

def matern32(X1, X2, length_scale=1.0, variance=1.0):
    d = np.sqrt(((X1[:, None, :] - X2[None, :, :]) ** 2).sum(-1))
    r = np.sqrt(3) * d / length_scale
    return variance * (1 + r) * np.exp(-r)

X = np.array([[0.0], [0.5], [1.0]])
print("RBF 核矩阵:\n", rbf(X, X).round(3))
print("Matern 3/2 核矩阵:\n", matern32(X, X).round(3))

RBF 太光滑是常见陷阱:真实物理过程往往有局部粗糙性,此时 Matern ν=3/2 或 5/2 更贴合。Matern 的 ν 控制可微次数,ν→∞ 退化为 RBF。

核可以组合:加法(叠加不同尺度)、乘法(调制)、加法加噪声项,都是合法核。

def combined_kernel(X1, X2, ls1=1.0, ls2=0.1):
    return rbf(X1, X2, ls1) + 0.5 * rbf(X1, X2, ls2)   # 长短两个尺度

4. GP 回归:后验预测

给定训练数据 (X, y),假设观测含高斯噪声 y = f(x) + ε, ε ~ N(0, σ_n²)。对新输入 x*,后验预测分布仍是高斯,有闭式解:

μ* = k*ᵀ (K + σ_n²I)⁻¹ y
σ*² = k(x*, x*) - k*ᵀ (K + σ_n²I)⁻¹ k*

其中 k* = [k(x*, x_1), ..., k(x*, x_n)]ᵀ。

  • 均值 μ*:预测值,是训练目标的核加权组合。
  • 方差 σ*²:预测不确定性。远离训练数据的点方差大,训练点附近方差小——这正是 GP 的核心价值。
def gp_predict(X_train, y_train, X_test, length_scale=0.3, noise=0.05):
    K = rbf(X_train, X_train, length_scale) + noise ** 2 * np.eye(len(X_train))
    Ks = rbf(X_train, X_test, length_scale)
    Kss = rbf(X_test, X_test, length_scale)
    L = np.linalg.cholesky(K)
    alpha = np.linalg.solve(L.T, np.linalg.solve(L, y_train))
    mu = Ks.T @ alpha
    v = np.linalg.solve(L, Ks)
    cov = Kss - v.T @ v
    return mu, np.sqrt(np.clip(np.diag(cov), 0, None))

rng = np.random.default_rng(0)
X_tr = rng.uniform(0, 1, 15)[:, None]
y_tr = np.sin(2 * np.pi * X_tr.ravel()) + rng.normal(0, 0.1, 15)
X_te = np.linspace(-0.2, 1.2, 100)[:, None]
mu, std = gp_predict(X_tr, y_tr, X_te)
print("测试点预测前 3 个:", mu[:3].round(3))
print("对应标准差:", std[:3].round(3))

注意 X_te 在 [0,1] 之外的区域标准差显著增大——模型诚实地表达「这里我没见过数据」。

5. 超参数学习

核超参数(length_scale、variance、noise)不靠人工指定,而是最大化边际似然(Marginal Likelihood)。边际似然是把函数值 f 积分掉后的数据似然:

log p(y | X, θ) = -½ yᵀ (K_θ + σ_n²I)⁻¹ y
                  - ½ log|K_θ + σ_n²I|
                  - n/2 log(2π)

三项各有含义:

项名称作用
-½ yᵀ K⁻¹ y数据拟合项鼓励模型拟合数据
-½ log|K|复杂度惩罚项惩罚过于复杂的核
-n/2 log 2π归一化常数与参数无关

边际似然自动平衡拟合与复杂度,这就是贝叶斯奥卡姆剃刀——无需额外正则项,也不会像最大似然那样过拟合。

from scipy.optimize import minimize

def neg_log_marginal_likelihood(log_params, X, y):
    ls, var, noise = np.exp(log_params)          # 在对数空间优化,保证正
    K = var * rbf(X, X, ls) + noise ** 2 * np.eye(len(X))
    L = np.linalg.cholesky(K)
    alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
    return 0.5 * y @ alpha + np.log(np.diag(L)).sum() + 0.5 * len(X) * np.log(2 * np.pi)

res = minimize(neg_log_marginal_likelihood, np.log([0.3, 1.0, 0.1]),
               args=(X_tr, y_tr), method="L-BFGS-B")
print("最优超参数 (ls, var, noise):", np.exp(res.x).round(3))

优化用 L-BFGS-B(梯度可解析计算),通常几十次迭代即收敛。务必在对数空间优化,否则长度尺度可能被优化成负数导致核非法。

6. GP 分类

分类任务的目标是预测类别概率,但高斯过程本质是回归。做法是:对隐函数 f(x) 用 GP 先验,再通过链接函数(如 sigmoid)映射到概率 p(y=1) = σ(f(x))。问题在于后验不再有闭式解(sigmoid 破坏了高斯共轭),需要近似:

6.1 拉普拉斯近似(Laplace Approximation)

在隐函数后验的众数(MAP)处做二阶泰勒展开,用一个高斯分布近似真实后验:

1. 用牛顿法找后验众数 f̂
2. 在 f̂ 处算 Hessian,得到近似高斯 N(f̂, H⁻¹)
3. 对新点用该高斯做预测积分

6.2 期望传播(EP)

用一组高斯因子逼近真实后验,通常比拉普拉斯更准,但更复杂。scikit-learn 的 GaussianProcessClassifier 默认用拉普拉斯。

from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.gaussian_process.kernels import RBF
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(n_samples=200, n_features=2, n_redundant=0,
                           n_informative=2, random_state=0)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=0)
gpc = GaussianProcessClassifier(kernel=RBF(length_scale=1.0), random_state=0)
gpc.fit(X_tr, y_tr)
print("测试精度:", round(gpc.score(X_te, y_te), 4))

分类任务的输出是概率,可用预测概率的熵来量化不确定性——熵高的区域就是模型「拿不准」的区域。

7. 复杂度与稀疏近似

7.1 O(n³) 的天花板

GP 需要对 n × n 的核矩阵求逆/分解,复杂度 O(n³),存储 O(n²)。n 超过几千就非常吃力,n 上万几乎不可行。这是 GP 最大的工程限制。

样本量可行性
< 1,000直接求解,秒级
1,000 ~ 10,000直接求解,分钟级,需谨慎
> 10,000必须用稀疏/近似方法

7.2 稀疏 GP 与诱导点

诱导点(Inducing Points) 方法引入 m << n 个伪输入,把复杂度降到 O(n m²):

  • FITC:用诱导点近似协方差,简单但可能不一致。
  • VFE / SGPR:变分下界,理论更优。
  • 随机特征:用随机傅里叶特征逼近平稳核,转成贝叶斯线性回归,复杂度 O(nm²)。
# 概念示意:用诱导点近似核矩阵的低秩结构
import numpy as np

def inducing_approx(X, Z, length_scale=0.3):
    """Z: (m, d) 诱导点,返回近似核矩阵"""
    Kmm = rbf(Z, Z, length_scale)
    Knm = rbf(X, Z, length_scale)
    Kmm_inv = np.linalg.pinv(Kmm)
    return Knm @ Kmm_inv @ Knm.T      # 低秩近似,秩 <= m

Z = np.linspace(0, 1, 8)[:, None]     # 8 个诱导点
print("近似核矩阵形状:", inducing_approx(X_tr, Z).shape)

7.3 深度学习与 GP 的结合

深度核学习(Deep Kernel Learning) 用神经网络先把输入映射到特征空间,再在特征上做 GP——兼具深度学习的表示能力与 GP 的不确定性。这是当前 GP 研究的热点方向。

8. scikit-learn 实战

完整流程:标准化、核选择、超参优化、预测区间。

import numpy as np
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, WhiteKernel, ConstantKernel, Matern
from sklearn.preprocessing import StandardScaler

rng = np.random.default_rng(42)
X = rng.uniform(0, 5, 60)[:, None]
y = np.sin(X.ravel()) + 0.1 * X.ravel() + rng.normal(0, 0.15, 60)

scaler = StandardScaler().fit(X)
X_s = scaler.transform(X)

# 核 = 常数尺度 × RBF + 白噪声(学习噪声水平)
kernel = ConstantKernel(1.0) * RBF(length_scale=1.0) + WhiteKernel(noise_level=0.1)
gp = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=10,
                              normalize_y=True, random_state=0)
gp.fit(X_s, y)
print("学到的核:", gp.kernel_)

X_test = scaler.transform(np.linspace(-1, 6, 200)[:, None])
mu, std = gp.predict(X_test, return_std=True)
lower, upper = mu - 1.96 * std, mu + 1.96 * std
print("预测区间宽度(端点):", round((upper - lower)[0], 3))
print("预测区间宽度(中部):", round((upper - lower)[100], 3))

端点区间宽、中部区间窄——这正是 GP 的价值:它知道自己在数据外没有把握。n_restarts_optimizer 多次随机重启优化超参,避免陷入局部最优。

8.1 核选择经验

# 从简单到复杂依次尝试
kernels = {
    "RBF": RBF(length_scale=1.0),
    "Matern32": Matern(length_scale=1.0, nu=1.5),
    "Matern52": Matern(length_scale=1.0, nu=2.5),
    "RBF+noise": RBF(length_scale=1.0) + WhiteKernel(noise_level=0.1),
}
for name, k in kernels.items():
    g = GaussianProcessRegressor(kernel=k, normalize_y=True, random_state=0)
    g.fit(X_s, y)
    print(f"{name}: log-marginal-likelihood = {g.log_marginal_likelihood_value_:.2f}")

比较 log_marginal_likelihood_value_,数值越大说明该核越适合数据。

9. 与贝叶斯优化的联系

GP 最经典的应用是贝叶斯优化(Bayesian Optimization):优化一个评估代价高昂的黑盒函数(如模型超参、仿真参数)。流程是:

1. 用 GP 代理目标函数(给出均值与方差)
2. 用采集函数(EI / UCB / PI)决定下一个评估点
   - 均值高 → 利用(exploit)
   - 方差大 → 探索(explore)
3. 评估该点,更新 GP,重复

GP 的预测方差正是采集函数的核心输入——方差大代表「还没探索」,值得一试。这就是 GP 在超参搜索中不可替代的原因,具体实现与 Optuna 的集成见 /ml-automl-hpo/。

import numpy as np
from scipy.stats import norm

def expected_improvement(mu, std, best, xi=0.01):
    """EI 采集函数:平衡利用与探索"""
    z = (mu - best - xi) / (std + 1e-9)
    return (mu - best - xi) * norm.cdf(z) + std * norm.pdf(z)

mu = np.array([1.0, 1.5, 0.5])
std = np.array([0.1, 0.3, 0.8])
print("EI:", expected_improvement(mu, std, best=1.2).round(4))

第三点均值低但方差大,EI 可能反而最高——它代表「不确定,值得探索」。

10. 常见坑与选型

现象根因处理
训练极慢或内存爆样本量过大用诱导点/随机特征/换模型
核矩阵非正定报错无抖动或核非法加 1e-8 * I,检查核 PSD
预测区间过窄忽略观测噪声加 WhiteKernel 项
超参优化发散未在对数空间对数空间 + L-BFGS-B
外推预测离谱GP 外推回到均值限制使用范围,或加线性核
分类概率极端拉普拉斯近似偏差试 EP 或校准

10.1 什么时候用 GP

样本量 < 1 万 且 需要不确定性?
  ├── 是 → GP(RBF/Matern + WhiteKernel)
  └── 否
        ├── 只需预测值 → 梯度提升树 / 神经网络
        ├── 超参搜索 → GP + 贝叶斯优化
        ├── 时序预测 → GP + 周期核(或 ARIMA)
        └── 大数据回归 → 稀疏 GP / 深度核 / 换模型

GP 的独特价值在于小数据 + 不确定性量化 + 平滑插值。当数据量大到几万以上,或者只关心点预测时,GP 往往不是最优选择。

11. 总结

11.1 核心要点

  • 高斯过程是非参数贝叶斯模型,由均值函数与协方差核完全定义。
  • 核决定函数的光滑性与相关性;RBF 极光滑,Matern 更适合真实物理过程。
  • 后验预测给出均值与方差,方差在无数据处自然增大——这是 GP 的核心价值。
  • 超参数靠最大化边际似然学习,自动平衡拟合与复杂度(贝叶斯奥卡姆剃刀)。
  • GP 分类需近似(拉普拉斯 / EP),因为 sigmoid 破坏共轭。
  • 复杂度 O(n³) 是硬限制,大数据要用诱导点或随机特征。
  • GP 是贝叶斯优化的引擎,预测方差驱动探索与利用的平衡。

11.2 与相邻方法的衔接

GP 的回归建模见 /ml-supervised-regression/,在超参搜索中的落地见 /ml-automl-hpo/,若用周期核处理季节数据可结合 /ml-time-series/。想深入算法原理与大规模工程实现,可延伸阅读 AI/ML 专题 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「ml」更多文章

  1. MLOps 全生命周期实践:从实验到生产闭环
  2. 公平性与偏差缓解:从度量到去偏实战
  3. 数据标注与质量治理:从标注规范到一致性度量