高斯过程回归在金融预测中的应用:不仅给点预测,还给你一张可信度地图
线性回归只给一个点估计,深度学习连不确定性都给不清。高斯过程(GP)回归的卖点是「均值 + 协方差」一体输出:预测的同时,直接吐出每个点的预测方差。本文用纯 numpy 实现(RBF 核 + 边缘似然超参优化 + Cholesky 求解),在「滞后波动率→次日波动率、且藏了一段概念漂移」的合成数据上,给它画出分布内/分布外的可信带,并诚实拆穿它最核心也最危险的陷阱:平稳核在分布外会过度自信。附完整代码与五类真实陷阱(中阶)。
你训练一个波动率预测模型,喂进去「过去 5 日已实现波动率」,让它预测「明天波动率」。线性回归给你一个数,深度学习给你一个数——但你不知道这个数靠不靠谱:在样本密集区它或许准,在从没见过的极端区间它可能差出天际,却还是一脸自信。
高斯过程(Gaussian Process, GP)回归不一样。它输出的是一个完整的后验分布:每个 x 上都有均值 μ(x) 和方差 σ²(x)。于是你拿到的不是「明天波动率 2.3%」,而是「明天波动率 2.3% ± 0.8%,而且在这个区间我特别没把握、σ 飙到了 2.1%」。这张可信度地图,是 GP 在金融风控里最值钱的东西。
结论先放这:GP 在分布内(训练覆盖范围)确实给了诚实的不确定性——名义 95% 区间实际覆盖率 0.953,几乎完美;但一旦越过训练分布的边界(分布外 / OOD),它的可信带反而「假自信」:名义 95% 区间实际只覆盖了 0.070,预测标准差才 0.51,真实误差却有 1.68——差了三倍。这正是一个平稳 RBF 核的根本局限,必须在落地时处理,否则它会用一条窄窄的带子骗你。

1. GP 到底是什么:把「函数」当成一个随机变量#
线性回归假设「参数是随机的、数据是确定的」;GP 反过来——它假设整个函数 f(x) 是一个高斯过程,即在任意有限个点上采样,得到的函数值都服从联合高斯分布。
一个 GP 由两个东西完全决定:
- 均值函数 m(x):通常取 0(不损失一般性,把信号留给协方差);
- 协方差函数(核)k(x, x’):它决定了「两个点靠得近时,函数值多相似」。
最常用的是 RBF 核(平方指数核):
其中 是长度尺度(多近算「近」), 是信号方差。这两个加上噪声水平 ,就是 GP 的超参数——它们不是模型权重,而是「你对世界平滑度的先验信念」。
给定训练数据 和测试点 ,GP 的后验是闭式的:
注意:后验方差 只取决于核和训练点位置,与 y 的具体取值无关。这就是 GP 的妙处——不确定性是「几何属性」,不用额外训练。
2. 纯 numpy 实现:RBF 核 + Cholesky 求解#
下面是从零的 GP 回归。核心只有三件事:组装核矩阵、Cholesky 分解求逆(数值稳)、算均值与方差。
import numpy as np
class GP1D:
def __init__(self, ls=1.0, sig=1.0, noise=0.1):
self.ls, self.sig, self.noise = ls, sig, noise
def _K(self, X, X2=None):
if X2 is None:
X2 = X
X, X2 = np.atleast_2d(X).T, np.atleast_2d(X2).T
d = X - X2.T
return self.sig ** 2 * np.exp(-0.5 * d ** 2 / self.ls ** 2)
def fit(self, X, y):
self.X = np.asarray(X, float)
self.y = np.asarray(y, float)
# 加噪声项到对角,再 Cholesky 分解(比直接求逆稳得多)
K = self._K(self.X) + (self.noise ** 2 + 1e-8) * np.eye(len(self.X))
self.L = np.linalg.cholesky(K)
self.alpha = np.linalg.solve(self.L.T, np.linalg.solve(self.L, self.y))
def predict(self, Xs):
Xs = np.asarray(Xs, float)
Ks = self._K(Xs, self.X)
mean = Ks @ self.alpha
v = np.linalg.solve(self.L, Ks.T)
var = self.sig ** 2 - np.sum(v ** 2, axis=0) # 后验方差(仅几何,与 y 无关)
return mean, np.sqrt(np.clip(var, 0, None))python关键点:fit 里把噪声 加到核矩阵对角线上,然后做 Cholesky。预测时 用的是 (测试点自身核)减去一项——测试点离训练点越远,减得越少,方差越大,可信带自然张开。这就是「没见过的区间该更不自信」在数学上自动成立。
3. 超参数怎么定:最大化边缘似然#
超参数 不能拍脑袋。GP 给了个漂亮的自助餐——边缘似然(marginal likelihood),可以直接优化:
第一项惩罚「预测残差」,第二项惩罚「模型复杂度(核矩阵行列式)」。两者自动平衡:既想拟合好,又不想把核调得巨复杂过拟合。我们用粗网格 + 坐标下降精修:
def nll(self, X, y):
n = len(X)
K = self._K(X) + (self.noise ** 2 + 1e-8) * np.eye(n)
L = np.linalg.cholesky(K)
alpha = np.linalg.solve(L.T, np.linalg.solve(L, y))
return 0.5 * y @ alpha + np.sum(np.log(np.diag(L))) + 0.5 * n * np.log(2 * np.pi)
# (粗网格扫描 ls/sig/noise,再用坐标下降在三个方向上微调,取 nll 最小者)python在我们的合成数据(见下)上,边缘似然把长度尺度稳定在 、信号方差 附近——平滑但不至于把噪声当结构。
4. 合成数据:滞后波动率→次日波动率,藏一段概念漂移#
我们造一条「诚实但带坑」的数据,用来检验 GP 的边界:
- 特征 = 个股滞后 5 日已实现波动率(年化 %);
- 目标 = 次日已实现波动率;
- 真实关系在两区制间突变:当 (区制 1,训练用)是温和线性 + 轻微正弦;当 (区制 2)斜率骤变——这就是金融里常见的概念漂移(比如波动率从「常态」跳进「危机」)。
- 只在区制 1 上训练,测试覆盖两段——这样能干净地暴露分布外行为。
def true_fn(x):
f1 = 0.62 * x + 0.15 * np.sin(x)
f2 = (0.62 * 3.5 + 0.15 * np.sin(3.5)) + 1.25 * (x - 3.5)
return np.where(x < 3.5, f1, f2)
x_all = np.linspace(0.5, 5.0, 600)
y_all = true_fn(x_all) + rng.normal(0, 0.12, len(x_all))
train_mask = x_all < 3.5
gp = GP1D(ls=1.0, sig=1.5, noise=0.12)
gp.fit(x_all[train_mask], y_all[train_mask])python5. 校准检验:分布内诚实、分布外假自信#
光看带子「长得对」不够,要测覆盖率。这里有个极易踩的坑——95% 区间必须用观测带 = ,否则你只覆盖了「潜在函数」而没覆盖「带噪声的观测」。
我们同时画了两种:正确的观测带 vs 只看函数方差的「错误带」。

实测(观测带):
| 区段 | 名义覆盖率 | 实际覆盖率 | 预测 std 均值 | 真实误差均值 |
|---|---|---|---|---|
| 分布内(区制1,已训练) | 95% | 0.953 | 0.54 | — |
| 分布外 OOD(区制2,漂移段) | 95% | 0.070 | 0.51 | 1.68 |
而用「只看函数方差」的错误带,覆盖率更是只有 0.163 / 0.020——连分布内都崩。
这个对照本身就是一条金科玉律:GP 后验方差是「潜在函数」的不确定性,不含观测噪声。如果你的 95% 区间要包住带噪声的 y,必须显式把 加回去。很多论文和库默认给的是函数带,直接用会系统性低估风险。
更刺眼的是 OOD 段:预测 std 才 0.51,真实误差却有 1.68。GP 在没见过的区间,带子是收住的、窄的、一脸「我很懂」——但它完全错了。原因在下一节。
6. 计算代价:O(n³) 是把双刃剑#
GP 每次预测要解一个 线性系统,复杂度 O(n³)。我们把 Cholesky 求解单独拎出来计时:

大样本段 log-log 斜率≈1.93(理论 3.0,小样本段被常数开销摊平)。这意味着:样本量翻 10 倍,耗时翻约 倍。几千个样本还能跑,几万、几十万就别想朴素 GP 了——要么上稀疏 GP(Sparse GP / 诱导点)、要么老老实实换树模型。这是 GP 在「大 A 股全市场面板」上落不了地的硬约束。
7. 五类真实陷阱(不拆穿就是自欺)#
陷阱 1:分布外过度自信(最致命)。 如上,平稳 RBF 核在训练边界外会错误地收缩方差。金融里「极端波动率 / 危机区间」恰恰是最该被宽待的区间,GP 却最自信。对策:用 GP 的 σ 当异常检测器——σ 突然变小却预测偏离,就是「模型在瞎猜」的红旗;或换有界核 / 加 OOD 正则。
陷阱 2:观测带 vs 函数带(覆盖率踩坑)。 直接拿后验方差画 95% 带,覆盖率会塌到 0.16。记住:包 y 要加噪声项。本文所有图都用的观测带。
陷阱 3:超参数对似然敏感,网格要够密。
训练点太密时核矩阵近奇异,边缘似然会把长度尺度推到极端值骗分。对策:训练集适当子采样 + 加 (noise²+1e-8) 对角项 + 网格扫描。
陷阱 4:O(n³) 撑不住大样本。 几千样本是舒适区,几十万就爆。落地选稀疏 GP、随机特征近似、或只在「小样本高价值」场景用(如单个标的的波动率曲面点、少数宏观状态的过渡概率)。
陷阱 5:平稳核假设「近者相像」——但金融不总是。 RBF 假设相似输入给相似输出。可波动率有跳跃、机制切换:x 只差一点,y 可能天差地别。这时 GP 的均值会「被平滑」掉尖峰。对策:加跳跃项、用非平稳核、或对 y 先做变换(如 log)。
8. 小结:GP 该用在哪#
GP 回归不是「又一个预测器」,它是少数能端到端输出可信度地图的方法。它最适合:
- 小样本 + 你需要知道「我有多不确信」:期权隐含波动曲面插值、少数宏观状态转移、单标的波动率预测;
- 主动的不确定性量化:用 σ 当风控开关、当异常检测、当「该不该相信这个预测」的旋钮。
它最不适合:
- 大样本全市场面板(O(n³) 直接劝退);
- 分布外区间决策(平稳核会假自信,必须配 OOD 处理);
- 指望它预测金融里的尖峰跳跃(平滑核天生削峰)。
一句话:GP 给你一张地图,但地图上的「未知区」它画得比已知区还自信——你得自己记得,没标的地方才是最危险的。
代码与数据完全可复现:本文所有图由
gen_gp_finance.py生成(纯 numpy,无 sklearn/torch 依赖)。分布内覆盖率 0.953、OOD 覆盖率 0.070、OOD 真实误差 1.68 vs 预测 std 0.51、复杂度斜率≈1.93,均直接打印在脚本输出里。95% 带统一用观测带(函数方差 + 噪声方差)。