简介:Python实现高斯过程时间序列预测的完整源码与配套数据集,面向计算机、电子信息工程、数学等专业学生,适合用于课程设计、期末大作业和毕业设计。源码采用参数化编程,核心参数均可方便调整,代码思路清晰,并配有近乎逐行的保姆级注释,对刚接触Python和时间序列建模的初学者十分友好。资源压缩包共5个文件,包含1个Python主脚本、3个CSV数据文件与1个XLSX数据文件,整体仅57KB,轻量便捷;Python脚本完整实现高斯过程回归预测流程,CSV与XLSX提供焦作等多组实验数据,可直接替换数据验证不同场景效果。已有178人学习下载。作者为资深算法工程师,从事Python、Matlab算法仿真多年,代码经实际项目打磨,结构规范、便于二次开发,也适合作为毕业设计或算法竞赛的参考实现。
1. 高斯过程时间序列预测:为什么预测值要带一个区间
大多数预测模型只给你一个点:明天销量 873 件。高斯过程时间序列预测给的是“873 ± 51”,并且告诉你这个区间在哪里变宽、在哪里收窄。它是基于高斯过程回归(GPR)对观测序列建模的方案:用核函数表达趋势、周期和噪声,在几百到几千个观测点的中小数据集上,比 LSTM 更好训、比 ARIMA 更灵活。适合做销量预测、传感器趋势、容量预估,以及任何“既想要预测值、又想要可信度”的 Python 工程。下文按实战顺序展开:GP 与核函数的选型、完整可复现源码、参数调优、常见翻车点和一套验证方法。
2. 高斯过程为什么适合时序:核函数才是预测的“模型”
2.1 GP 回归在做什么:预测值不是点,是分布
高斯过程把观测序列 y(t) 看成一个连续函数 f(t) 的带噪采样:y(t) = f(t) + ε,ε 服从均值为 0 的正态分布。f 的先验由均值函数和协方差核 k(t, t') 决定。给入训练数据后,条件后验仍然是高斯过程,所以预测输出不仅有均值,还有解析的方差。这个方差不是统计软件顺手附赠的,而是贝叶斯推断直接给出的不确定性。
实际效果可以这么理解:给定训练点之间的协方差结构 K,测试点 x* 的预测均值是训练观测值的加权组合,权重由核函数计算;预测方差等于先验方差减去被训练数据“解释掉”的部分。离训练点越远,方差恢复得越大;附近数据越多、越密,方差越小。这个特性对时间序列非常自然——历史数据覆盖充分的区间,模型有底气;进入远期外推,区间自然张开。
和常用方法对比一下。ARIMA 对平稳性和线性关系有较强假设,遇到趋势、季节、噪声混合的实测序列,要花不少时间做差分和定阶。LSTM 这类深度模型在几千条数据上很难喂饱,而且训练过程像个黑匣子,区间估计要自己做。GP 在这两种场景之间找到一个平衡点:不要求平稳,不需要海量数据,预测区间直接由模型给出。我一般在一开始就提醒同事:GP 适合样本量几百到几千的中小数据,数据超过三千条就要考虑稀疏近似或降采样,硬上全量数据会慢到怀疑人生。
还有一点容易被忽略:GP 学习的是“这个序列在时间轴上如何相关”,而不是“下一时刻由哪几个历史时刻决定”。这意味着它外推远期时不会像自回归模型那样逐点累积误差,而是直接给出一个远期区间。后面第 4 章和第 5 章会回到这个区别。
2.2 RBF 与 Matern:长度尺度在控制什么
核函数是 GP 的全部“模型结构”。最常用的起点是 RBF 核:k(t, t') = σ² · exp(-‖t-t'‖² / 2l²)。这里 l 就是长度尺度,控制两个输入点的相关性衰减速度。l 小,模型认为短时间内相关性就消失,预测曲线会剧烈波动;l 大,模型认为历史信息能影响很远的未来,预测曲线更平滑。在时间序列里,可以把 l 粗略理解为“相关性的记忆长度”。
如果数据有明显的毛刺或周期性不完全规整,RBF 会过于光滑。这时候可以考虑 Matern 核,它通过参数 ν 控制平滑程度,ν=3/2 和 ν=5/2 是最常用的两个档位。Matern 对“有一定趋势但不是光滑函数”的实测信号更稳。
时间序列几乎必然有周期成分,比如日周期、周周期、年度周期。sklearn 里的 ExpSineSquared 核就是为此设计的:它是一个周期核,真正需要调的是 periodicity 参数,也就是你估计的周期长度。注意它内部还套了一个长度尺度,控制周期波形的“一致性”——同一点每周期的形状是否稳定。如果序列同时有长期趋势和周期成分,一个 RBF 往往不够,必须组合核。
2.3 时间序列核组合:趋势 + 周期 + 噪声
实测序列一般可以分解成四部分:信号幅度、慢变趋势、周期成分、观测噪声。对应的核组合写成这样:
from sklearn.gaussian_process.kernels import ( RBF, WhiteKernel, ExpSineSquared, ConstantKernel as C) kernel = (C(1.0, (0.1, 10.0)) * RBF(20.0, (5.0, 80.0)) + ExpSineSquared(50.0, (30.0, 100.0)) + WhiteKernel(0.5, (0.01, 10.0)))这段代码的语义很直白:ConstantKernel 负责整体信号幅度,RBF 负责慢变趋势,ExpSineSquared 负责周期成分,WhiteKernel 负责噪声。加号代表各成分独立贡献协方差。核组合有玄学成分,但结构选对以后,剩下的就是超参数初始化与优化的问题。
这里还要做一个建模选择:直接用时间 t 作为输入,还是构造滞后特征窗口。直接回归只把时间戳作为特征,适合做外推和趋势判断,不假设自回归阶数;滞后特征窗口则把 y_{t-1}, y_{t-2}, …, y_{t-p} 作为输入,适合做逐点滚动预测,但特征维度增加后 GP 的训练开销明显上升。我自己的默认做法是:先用时间索引加组合核跑通基线,只有在需要利用近期波动做递归预测时才考虑滞后特征。不要一上来就堆特征。
3. 最小可复现源码:从模拟数据到 GP 预测与置信区间
3.1 数据准备:模拟序列与真实 CSV 替换
为了让代码可以直接跑,我用一个可复现的模拟序列:线性的慢变趋势加上周期 50 的正弦季节项,再叠一点高斯噪声。这个结构贴近生产环境里常见的销量、流量、容量监控数据。数据量取 300 个点,前 250 个做训练,后 50 个做测试——既够 GP 发挥,又不会让读者等待训练太久。
import numpy as np rng = np.random.default_rng(42) t = np.arange(0, 300) trend = 0.03 * t season = 4.0 * np.sin(2 * np.pi * t / 50) y = trend + season + rng.normal(0, 0.8, size=t.shape) X_train, X_test = t[:250].reshape(-1, 1), t[250:].reshape(-1, 1) y_train, y_test = y[:250], y[250:]reshape(-1, 1) 是 sklearn 的硬性要求,所有回归器都要求 X 是二维数组,哪怕你只有一个特征维度。时间戳是连续整数,GP 内部会对它做归一化吗?不会,所以如果时间跨度很大,比如从 0 到 100000,需要先除以跨度或者做标准化,否则核函数的长度尺度会很难优化。
换成自己的真实数据时,用 pandas 读进来再转成 numpy 数组即可:
import pandas as pd df = pd.read_csv("series.csv") # 至少两列:time, value t = df["time"].to_numpy(dtype=float) y = df["value"].to_numpy(dtype=float)真实数据里如果时间戳是日期,先用 pd.to_datetime 转成 datetime,再取数值序号;缺失值直接删掉或前向填充都可以。这里不要做滑动平均之类的手脚,GP 自己对噪声有建模能力,过度平滑反而会把噪声信息抹掉。
3.2 核心训练脚本:核定义、fit 与 predict
下面的代码是完整可跑的训练流程。核的选择和上一节一致:RBF 捕获趋势,ExpSineSquared 捕获周期 50 的季节项,WhiteKernel 吸收噪声。模拟数据的周期已知是 50,所以 periodicity 初值直接给 50,边界放宽到 30~100。
from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import ( RBF, WhiteKernel, ExpSineSquared, ConstantKernel as C) kernel = (C(1.0, (0.1, 10.0)) * RBF(20.0, (5.0, 80.0)) + ExpSineSquared(50.0, (30.0, 100.0)) + WhiteKernel(0.5, (0.01, 10.0))) gp = GaussianProcessRegressor( kernel=kernel, n_restarts_optimizer=5, normalize_y=True, alpha=1e-6, ) gp.fit(X_train, y_train) mean, std = gp.predict(X_test, return_std=True)几个参数说清楚。n_restarts_optimizer=5 表示优化器会从 5 个随机起点重新搜索超参数,避免一次优化卡在局部最优。代价是训练时间大约是 6 倍,数据量不大时完全值得。normalize_y=True 会在内部对 y 做标准化,让模型不依赖目标的绝对数值,这能显著减少优化器来回找尺度的折磨。alpha=1e-6 是加到协方差矩阵对角线的数值稳定项,防止矩阵求逆时数值爆炸;它不等于噪声,噪声由 WhiteKernel 负责,这里千万不要把 alpha 设成 0.1 之类的“经验噪声值”,否则会把整个模型的方差吞掉。
预测时 return_std=True 返回每个测试点的标准差,这是 GP 相比普通回归最大的区别。拿到 mean 和 std,就可以画区间了。
3.3 可视化:把置信区间画出来
预测结果不画出来等于没做。用 matplotlib 把训练点、真实测试点、预测均值和 95% 置信带画在同一张图上:
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(10, 5)) ax.plot(t[:250], y_train, "k.", alpha=0.4, label="train") ax.plot(t[250:], y_test, "r.", alpha=0.4, label="true") ax.plot(t[250:], mean, "b-", label="GP mean") ax.fill_between(t[250:], mean - 1.96 * std, mean + 1.96 * std, color="blue", alpha=0.2, label="95% band") ax.legend() ax.set_xlabel("time") ax.set_ylabel("y") plt.show()正常结果应该是:蓝色均值线贴着真实红色点走,置信带在刚离开训练数据时有一段自然张开,但不会宽到失去意义。如果置信带在训练数据内部就窄成一条线,在测试段突然爆宽,说明核函数的长度尺度没有匹配真实趋势的尺度——大概率是 RBF 的 length_scale 初值给大了,模型把长距离相关性看得过高。反过来如果训练段预测本身就剧烈震荡,置信带毛糙,那是 length_scale 初始值太小,模型把邻近点都当成几乎不相关。
4. 参数调优:核函数的五个关键开关与滚动重训策略
4.1 核参数与优化器参数总览
GP 不像神经网络那样有几十个超参数,真正需要关注的开关其实就五个。把这些参数的含义和初值规则一次说清。
| 参数 | 我常用的初值 | 常用边界 | 影响 |
|---|---|---|---|
| RBF 的 length_scale | 数据时间跨度的 1/10 | (跨度/100, 跨度/2) | 趋势变化速度,决定曲线平滑度 |
| ExpSineSquared 的 periodicity | 业务周期经验值 | 经验值 ±30% | 周期是否匹配真实季节项 |
| WhiteKernel 的 noise_level | 数据方差的 1/5 | (方差/100, 方差) | 噪声容忍度与过拟合 |
| n_restarts_optimizer | 5 | 0~10 | 避免超参数优化落入局部最优 |
| normalize_y | True | True/False | 消除目标量纲与均值偏移的影响 |
这里的关键认知是:GP 的超参数不是“调得越细越好”,边界范围往往比初值更重要。优化器会在边界内部跑 L-BFGS 搜索,如果边界给得太宽,搜索空间里有大片平坦区域,优化器容易停在原地;边界太窄又可能把真实最优解挡在外面。我的血泪经验是:初始化给得好,比盲目堆核函数管用得多。
4.2 初始化与边界:先看 gp.kernel_ 打印结果
fit 之后第一件事不是看预测图,而是打印训练完的核函数参数:print(gp.kernel_)。这一步很多人跳过,导致模型出了问题只能靠猜。
正常收敛后,kernel_ 中每个子核的参数都应该落在你设定的边界内部,而且数值看起来“合理”:RBF 的 length_scale 不会等于边界值,ExpSineSquared 的 periodicity 不会贴到下界 30 或上界 100。如果某个参数恰好卡在边界上,说明真实结构超出了你的先验范围,这时候再改边界重训,而不是接受这个结果。
还有一种典型情况:优化后 length_scale 变得非常大,RBF 几乎变成常数核,预测曲线接近一条直线。原因通常有两个:一是数据本身的趋势很弱,模型判断“长期相关性远远覆盖整个区间”,二是优化器没有找到更好的局部最优。解决方法是把 RBF 的 length_scale 上界收紧,并把 n_restarts_optimizer 提到 5 以上。如果数据确实只有微弱趋势,接受水平线预测也是一种合理结果,但要能解释给业务听。
4.3 多步预测策略:一次外推还是滚动重训
时间序列预测通常需要预测未来多个点。GP 最简单的做法是一次性把未来一段区间全部喂给 predict,前面第 3 章的代码就是这个思路。它的问题是你只用了训练数据学到的结构,未来每个点的预测都依赖同一组超参数,而真实系统可能在预测窗口内发生漂移。
更稳的做法是滚动重训:把预测窗口切小,每走几步就把真实观测并入历史,重新 fit 一次。以 10 步为一个窗口:
horizon = 10 history_t = list(X_train.flatten()) history_y = list(y_train) for start in range(0, len(X_test), horizon): gp.fit(np.array(history_t).reshape(-1, 1), np.array(history_y)) idx = np.arange(start, min(start + horizon, len(X_test))) mean, std = gp.predict(X_test[idx], return_std=True) # 真实值到达后并入历史,继续下一轮 # history_t.extend(X_test[idx].flatten()) # history_y.extend(y_test[idx])这段代码每 10 步重新训练一次,代价是训练次数变多。数据量在 500 点以内时完全能接受,超过 1000 点以后,每次 fit 都要处理 1000×1000 的协方差矩阵,速度和内存都会吃紧。实际工程里的折中方案是:固定历史窗口,比如只保留最近 300 个点做滚动训练,既能跟上数据漂移,又不会让训练时间无限膨胀。
5. 避坑现场:GP 时序预测最常见的五个翻车点
5.1 预测曲线拉成一条水平线
现象:训练 fit 很顺利,但预测段均值是一条水平线,置信带还特别宽。
原因:RBF 的 length_scale 初始值给得过大,模型认为所有点之间都高度相关,趋势项被压成常数;或者优化器陷入平坦区域,没有找到有区分度的超参数组合。
解决:把 RBF 的 length_scale 初始值下调到数据时间跨度的 1/10 左右,同时收紧上界到跨度的一半;n_restarts_optimizer 从 0 改成 5。改完重训后打印 gp.kernel_ 对比 length_scale 是否还在边界边缘。
5.2 置信区间窄到失真,预测还剧烈震荡
现象:训练段置信带细得像一条线,测试段预测值上下乱跳,95% 区间远远盖不住真实点。
原因:alpha 参数被当成噪声设置成了 0.1 甚至更大的值,与 WhiteKernel 同时争抢噪声解释权;或者 WhiteKernel 的 noise_level 上界太小,噪声方差被压缩到接近零,模型对训练点过拟合。
解决:alpha 永远保持 1e-6 这个量级,它只做数值稳定,不做噪声建模;WhiteKernel 的 noise_level 初值设为数据方差的 1/5,边界上限放宽到数据方差的量级,让优化器有机会找到噪声项的真实尺度。
5.3 数据过一千之后又慢又卡
现象:数据量到 1500 个点时,一次 fit 耗时从秒级跳到分钟级,内存也涨得吓人。
原因:GP 的标准实现需要对 n×n 协方差矩阵做分解,复杂度是 O(n³)。这不是代码写得差,是算法本身的边界。
解决:三个选项。第一,降采样,每 3 个点取 1 个,损失高频细节但保留趋势与周期;第二,改用固定窗口滑动训练,比如只用最近 500 个点;第三,换用稀疏 GP 近似或支持核近似的库。作为从 0 到 1 的工程方案,前两个足够。
5.4 递归多步外推越走越偏
现象:不用上面的滚动重训,而是像自回归那样“先用前 250 点训练,预测第 251 点,再把第 251 点的预测值当真实值喂回去预测第 252 点”,结果预测曲线越走越偏,最后直接飞出合理区间。
原因:预测值本身有不确定性,把它当真实观测喂给模型等于把误差当成事实累积起来。GP 的均值线虽然平滑,但滚动自回归会把每次的偏差不断放大。
解决:不要做逐点递归。要么一次外推整个预测窗口,要么按 4.3 的做法等真实值到达后再滚动重训。如果必须逐点预测,用 GP 预测分布去采样多个轨迹,再取均值与分位数,不要只拿 mean 去喂下一步。
5.5 优化器报错或拉出极端参数
现象:fit 过程打印奇异矩阵警告,或者 kernel_ 里某个参数直接等于 1e-6、1e6 这样的边界值。
原因:核函数边界设置不规范,比如把 length_scale 的下界设成了 0,优化器在数值上会撞到无穷;或者周期核的 periodicity 初值与真实周期差太远,搜索过程长期处于不可导区域。
解决:所有核参数下界统一切到 1e-3 以上,不要用 0;periodicity 初值尽量贴近业务经验。出现警告时先重置 kernel 对象再重训,不要在同一对象上调参数二进宫,优化器状态会被上次的失败路径带偏。
6. 验证技巧:置信区间覆盖率与对数似然
GP 预测的真正价值在区间。所以我的验证习惯是:先看区间准不准,再谈均值准不准。两个指标可以直接计算:95% 区间覆盖率和对数边际似然。
from scipy.stats import norm # 95% 置信区间覆盖率,经验上应接近 0.95 coverage = np.mean((mean - 1.96 * std <= y_test) & (y_test <= mean + 1.96 * std)) # 对数似然:越大说明分布预测越好,可用来在不同核之间比 loglik = norm.logpdf(y_test, loc=mean, scale=std).sum() print(f"coverage={coverage:.2f}, log_lik={loglik:.2f}")覆盖率低于 0.9,说明区间过窄,模型过度自信,优先检查 WhiteKernel 与 alpha 的设置。覆盖率高于 0.97,区间过宽,预测缺乏鉴别力,说明核函数可能把真实周期性当成噪声吸收掉了。对数似然则是模型之间的对比工具:换核函数结构调整核参数后,同样的测试集上对数似然更大,就说明新模型对分布预测更好。
现在的做法是:所有 GP 时序方案上线前必须跑一遍这两个指标,而且数据一更新就重算。有一回同事调完参数信心满满,结果覆盖率只有 0.82,一查发现确实把 alpha 当噪声设大了——这类翻车只要看过一次,就会把验证指标当习惯。希望帮到你。
本文还有配套的精品资源,点击获取