引言
大多数回归模型只给你一个数:预测值。但在很多场景里,「这个预测有多可信」比预测值本身更重要——药物实验中哪些剂量组合值得试、工程仿真里哪个参数区域还没探索过、主动学习里下一个该标哪条样本,答案都依赖对不确定性的准确刻画。
高斯过程(Gaussian Process, GP) 是少数能同时给出预测值与预测方差的模型。它属于非参数贝叶斯方法:不假设函数属于某个固定形式,而是给整个函数空间一个先验分布,用数据把先验收窄成后验。本文从贝叶斯线性回归的自然推广讲起,覆盖核函数、后验预测、超参学习、分类与大规模近似。
前置:回归基础与评估指标见 /ml-supervised-regression/;模型评估与校准指标见 /ml-model-evaluation/。
目录
- 1. 从贝叶斯线性回归到高斯过程
- 2. 均值函数与协方差核
- 3. 常用核函数
- 4. GP 回归:后验预测
- 5. 超参数学习
- 6. GP 分类
- 7. 复杂度与稀疏近似
- 8. scikit-learn 实战
- 9. 与贝叶斯优化的联系
- 10. 常见坑与选型
- 11. 总结
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 核的两个性质
合法的核必须满足:
- 对称性:
k(x, x') = k(x', x)。 - 半正定(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 Quadratic | RBF 的多尺度混合 | 多尺度 | 复杂数据 |
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 专题 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。