ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高斯过程时间序列预测:小样本场景下比LSTM更稳的Python实现

高斯过程时间序列预测:小样本场景下比LSTM更稳的Python实现 简介这份资源面向计算机、电子信息工程、数学等专业的大学生及算法入门者提供一套可直接运行的高斯过程时间序列预测完整方案适用于课程设计、期末大作业与毕业设计等场景。压缩包共5个文件约57KB包含3个csv与1个xlsx数据文件以及1个Python源码脚本数据与代码配套齐全方便直接复现实验。源码基于Anaconda、PyCharm与TensorFlow环境编写采用参数化编程思路参数可灵活调整代码结构清晰并配有保姆级注释几乎一行一注释便于零基础读者理解高斯过程回归的建模流程与预测逻辑。目前已有177人学习下载。作者为某大厂资深算法工程师具备八年Matlab与Python算法仿真经验擅长智能优化算法、神经网络预测与信号处理等方向。读者可借此掌握时间序列预测的完整实现路径并在此基础上迁移到其他数据集开展实验。1. 高斯过程做时间序列预测小样本场景下为什么它比 LSTM 更值得试手上只有几十到几百个采样点却要预测未来一段时间的走势这是很多工程场景的真实处境。设备传感器刚上线、实验数据只跑了几轮、业务指标按周汇总不到两年这些情况下上 LSTM 往往直接翻车——参数还没训稳验证集就已经过拟合了。高斯过程回归Gaussian Process Regression, GPR在这类小样本仿真数据预测里反而是更稳的选择它不需要海量数据驱动输出自带置信区间超参数少且可解释。这篇笔记就围绕 Python 实现高斯过程时间序列预测这条线把核函数怎么选、超参数怎么调、时间序列怎么构造成监督样本、完整源码怎么落地讲清楚数据也一并给出可复现的构造方式。适合已经会 Python 基础语法、想找一个能直接跑通的小样本预测方案的从业者也适合做仿真数据建模、想把置信区间一起输出的人。2. 高斯过程回归的数学骨架与核函数选型2.1 从贝叶斯线性回归到高斯过程高斯过程的核心思想是不再假设目标函数是某个固定形式比如线性、多项式而是假设函数本身服从一个分布。一个高斯过程由均值函数 m(x) 和协方差函数 k(x, x) 完全确定f(x) ~ GP(m(x), k(x, x))对于回归任务观测值 y f(x) ε其中 ε ~ N(0, σ²_n) 是噪声。给定训练集 X、y 和测试点 X*预测分布仍然是高斯分布均值和协方差有闭式解μ* K(X*, X) [K(X, X) σ²_n I]⁻¹ y Σ* K(X*, X*) - K(X*, X) [K(X, X) σ²_n I]⁻¹ K(X, X*)这个闭式解就是 GPR 不需要迭代训练的根本原因——它本质上是矩阵运算超参数通过最大化对数边际似然来优化。相比 LSTM 那种需要反向传播、调学习率、堆层数的方案GPR 的“训练”过程就是几十次超参数优化迭代几秒钟就能收敛。对小样本仿真数据来说这个特性非常关键。数据量少的时候神经网络的归纳偏置反而成了负担而 GPR 的归纳偏置直接写在核函数里你可以精确控制“我认为这个函数有多光滑、周期性多强”。2.2 核函数怎么选RBF、Matern、周期核的适用边界核函数决定了高斯过程的“性格”。时间序列预测里最常用的几个核函数表达式核心适合的场景注意点RBF (平方指数)exp(-d²/2l²)平滑、无突变的时间序列过度平滑对突变点反应慢Matern ν3/2(1√3d/l)exp(-√3d/l)有一定粗糙度的物理信号比 RBF 更贴近真实传感器数据Matern ν5/2更高阶多项式形式较平滑但保留局部变化计算量略大周期核exp(-2sin²(πd/p)/l²)明显周期性的业务指标需要预估周期 p线性核x·x明显趋势项单独用效果差常与 RBF 相加我一般会先用 RBF 加白噪声核跑一版基线看预测曲线是否过于平滑。如果原始序列有明显的锯齿或突变换成 Matern ν3/2 通常更贴合。如果序列有日周期、周周期就在 RBF 基础上加一个周期核两者相加构成复合核。from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, Matern, WhiteKernel, ConstantKernel, ExpSineSquared # 基线核常数核 * RBF 白噪声 kernel_base ConstantKernel(1.0, (1e-3, 1e3)) * RBF(length_scale1.0, length_scale_bounds(1e-2, 1e2)) \ WhiteKernel(noise_level1e-3, noise_level_bounds(1e-6, 1e-1)) # 带周期性的复合核 kernel_periodic ConstantKernel(1.0, (1e-3, 1e3)) * RBF(length_scale1.0, length_scale_bounds(1e-2, 1e2)) \ ConstantKernel(1.0, (1e-3, 1e3)) * ExpSineSquared(length_scale1.0, periodicity7.0) \ WhiteKernel(noise_level1e-3, noise_level_bounds(1e-6, 1e-1)) # 粗糙信号用 Matern kernel_matern ConstantKernel(1.0, (1e-3, 1e3)) * Matern(length_scale1.0, nu1.5) \ WhiteKernel(noise_level1e-3)上面三段核定义里ConstantKernel控制输出幅度length_scale控制光滑程度越大越平滑WhiteKernel吸收观测噪声。length_scale_bounds和noise_level_bounds是优化边界设得太窄会导致优化器无法找到合适值设得太宽会拖慢收敛。经验做法是先给一个宽边界跑一次看优化后的值落在哪里再适当收窄。注意WhiteKernel的 noise_level 不要设成 0否则矩阵求逆容易数值不稳定预测方差会异常。2.3 超参数优化对数边际似然与优化器设置GPR 的超参数通过最大化对数边际似然来求log p(y|X) -0.5 yᵀ(K σ²_n I)⁻¹ y - 0.5 log|K σ²_n I| - 0.5 n log(2π)sklearn 里通过optimizer参数控制优化器默认是 L-BFGS-Bn_restarts_optimizer控制随机重启次数。小样本场景下我一般设 5 到 10 次重启避免陷入局部最优。gpr GaussianProcessRegressor( kernelkernel_base, n_restarts_optimizer10, normalize_yTrue, alpha1e-6 ) gpr.fit(X_train, y_train) print(优化后的核:, gpr.kernel_) print(对数边际似然:, gpr.log_marginal_likelihood_value_)normalize_yTrue会把 y 标准化这对核函数长度尺度的优化很重要——如果 y 的量级是几千而 x 是 0 到 1不标准化的话优化器很难同时找到合适的幅度和长度尺度。alpha是加到对角线上的一个小量用于数值稳定一般设 1e-10 到 1e-6。3. 把时间序列改造成 GPR 能吃的监督样本3.1 滑动窗口构造与多步预测策略GPR 本身是回归器输入是特征向量输出是标量。时间序列要变成监督学习问题最常见的是滑动窗口用前 k 个时刻的值预测下一个时刻的值。import numpy as np def make_sliding_window(series, window_size, horizon1): series: 一维时间序列 window_size: 输入窗口长度 horizon: 预测步长 返回 X (n_samples, window_size), y (n_samples,) X, y [], [] for i in range(len(series) - window_size - horizon 1): X.append(series[i:i window_size]) y.append(series[i window_size horizon - 1]) return np.array(X), np.array(y) # 示例窗口 10预测下一步 X, y make_sliding_window(series, window_size10, horizon1)window_size的选择取决于序列的自相关长度。可以先画自相关图ACF看相关性衰减到 0.5 以下大概需要多少步。太小会丢失历史信息太大会引入无关噪声并增加计算量。对采样频率较高的传感器数据窗口 20 到 50 比较常见对按天汇总的业务指标窗口 7 到 14 就够。多步预测有两种策略递归预测用预测值当输入继续预测下一步和直接预测每个预测步长单独训一个模型。递归预测误差会累积直接预测需要训多个模型。小样本场景下我一般用递归预测因为数据量不足以支撑多个模型。3.2 特征工程时间特征与滞后特征的取舍纯滑动窗口只用了历史值但时间序列往往还有日历特征。把小时、星期几、是否节假日作为额外特征加进去对有明显周期性的序列提升很大。import pandas as pd def add_time_features(df, time_col): df df.copy() df[hour] df[time_col].dt.hour df[dayofweek] df[time_col].dt.dayofweek df[is_weekend] (df[dayofweek] 5).astype(int) # 周期性编码避免 23 点和 0 点距离被算成 23 df[hour_sin] np.sin(2 * np.pi * df[hour] / 24) df[hour_cos] np.cos(2 * np.pi * df[hour] / 24) return df周期性特征一定要做 sin/cos 编码直接用原始小时数会让模型认为 23 和 0 相差 23而实际上只差 1。这个坑我在早期项目里踩过预测凌晨时段的误差明显偏大换成 sin/cos 编码后降了一半。提示加特征后记得对特征做标准化GPR 的核函数对特征尺度敏感不同量纲的特征混在一起会让长度尺度优化失效。3.3 训练集/验证集划分时间序列不能随机切时间序列的划分必须按时间顺序不能 shuffle。常见做法是前 80% 做训练后 20% 做验证或者用滚动窗口交叉验证。from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_idx, val_idx in tscv.split(X): X_train, X_val X[train_idx], X[val_idx] y_train, y_val y[train_idx], y[val_idx] gpr.fit(X_train, y_train) score gpr.score(X_val, y_val) print(f验证 R²: {score:.4f})TimeSeriesSplit保证每次验证集都在训练集之后模拟真实预测场景。如果随机切分模型会“偷看”未来信息验证分数虚高上线后直接翻车。4. 完整源码从数据生成到预测可视化4.1 仿真数据生成与预处理为了让源码可以直接跑这里用合成数据。真实项目里把generate_series换成读取 CSV 即可。import numpy as np import matplotlib.pyplot as plt np.random.seed(42) def generate_series(n200): 生成带趋势、周期和噪声的仿真时间序列 t np.arange(n) trend 0.05 * t seasonal 2.0 * np.sin(2 * np.pi * t / 24) noise np.random.normal(0, 0.3, n) return trend seasonal noise series generate_series(200) plt.plot(series) plt.title(仿真时间序列) plt.show()这段数据包含线性趋势、周期 24 的正弦项和高斯噪声模拟了常见的传感器或业务指标形态。趋势项让序列非平稳周期项考验核函数是否能捕捉周期性噪声项考验 WhiteKernel 的设置。4.2 训练、预测与置信区间输出from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, WhiteKernel, ConstantKernel, ExpSineSquared from sklearn.preprocessing import StandardScaler # 构造滑动窗口 window 12 X, y make_sliding_window(series, window_sizewindow, horizon1) # 划分 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] # 标准化 scaler_X StandardScaler().fit(X_train) scaler_y StandardScaler().fit(y_train.reshape(-1, 1)) X_train_s scaler_X.transform(X_train) X_test_s scaler_X.transform(X_test) y_train_s scaler_y.transform(y_train.reshape(-1, 1)).ravel() # 核函数RBF 周期 白噪声 kernel ConstantKernel(1.0, (1e-3, 1e3)) * RBF(length_scale1.0, length_scale_bounds(1e-2, 1e2)) \ ConstantKernel(1.0, (1e-3, 1e3)) * ExpSineSquared(length_scale1.0, periodicity24.0) \ WhiteKernel(noise_level1e-2, noise_level_bounds(1e-6, 1e0)) gpr GaussianProcessRegressor(kernelkernel, n_restarts_optimizer10, normalize_yFalse, alpha1e-6, random_state42) gpr.fit(X_train_s, y_train_s) # 预测 y_pred_s, y_std_s gpr.predict(X_test_s, return_stdTrue) y_pred scaler_y.inverse_transform(y_pred_s.reshape(-1, 1)).ravel() y_std y_std_s * scaler_y.scale_[0] # 可视化 plt.figure(figsize(12, 5)) plt.plot(range(len(y_test)), y_test, b-, label真实值) plt.plot(range(len(y_pred)), y_pred, r--, label预测均值) plt.fill_between(range(len(y_pred)), y_pred - 1.96 * y_std, y_pred 1.96 * y_std, alpha0.2, colorred, label95% 置信区间) plt.legend() plt.title(GPR 时间序列预测) plt.show()return_stdTrue是 GPR 相比 LSTM 最大的优势之一——它直接给出每个预测点的标准差乘以 1.96 就是 95% 置信区间。这个区间在业务上非常有用比如库存预测可以直接用上界做安全库存。periodicity24.0对应仿真数据的周期。真实数据里如果不知道周期可以先对序列做 FFT 或自相关分析找到主频对应的周期再填进去。4.3 评估指标与残差检查from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(y_test, y_pred) rmse np.sqrt(mean_squared_error(y_test, y_pred)) r2 r2_score(y_test, y_pred) print(fMAE: {mae:.4f}) print(fRMSE: {rmse:.4f}) print(fR²: {r2:.4f}) # 残差检查 residuals y_test - y_pred plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(residuals) plt.title(残差序列) plt.subplot(1, 2, 2) plt.hist(residuals, bins20) plt.title(残差分布) plt.show()残差应该接近白噪声如果残差还有明显趋势或周期性说明核函数没选对。比如残差呈现周期性波动就要在核里加周期项残差有趋势就要加线性核。5. 避坑与排查GPR 时间序列预测的五个血泪教训5.1 预测曲线过于平滑突变点完全跟不上现象预测曲线像一条被拉直的绳子原始序列里的尖峰和骤降全被抹平。原因RBF 核的 length_scale 被优化得过大模型认为函数非常光滑。或者 WhiteKernel 的 noise_level 太大把真实信号当噪声吸收了。解决换 Matern ν3/2 核它允许函数有一定粗糙度。同时检查优化后的 length_scale如果接近上界说明边界设窄了放宽length_scale_bounds重新训练。5.2 矩阵求逆报错 LinAlgError: not positive definite现象fit 的时候直接抛LinAlgError提示矩阵非正定。原因训练数据里有重复点或者特征之间高度共线导致核矩阵奇异。滑动窗口构造的样本尤其容易出现这个问题——相邻窗口高度重叠。解决在GaussianProcessRegressor里设alpha1e-6或更大给对角线加一个小量。如果还不行检查特征是否有常数列去掉或做 PCA 降维。5.3 验证集 R² 很高上线后预测完全不能用现象离线验证 R² 0.95 以上实际部署后预测误差巨大。原因划分训练集时用了随机切分模型偷看了未来信息。或者标准化时用了全量数据的均值和方差造成信息泄漏。解决严格用TimeSeriesSplit或按时间顺序切分。标准化器只在训练集上 fit然后 transform 验证集和测试集。这个坑几乎每个新手都会踩一次。5.4 多步递归预测误差迅速累积现象预测第一步还行到第五步、第十步误差已经大到没法看。原因递归预测把预测值当真实值喂回模型每一步的误差都会传递并放大。解决如果必须多步预测改用直接预测策略每个步长单独训一个 GPR。或者缩短预测步长只预测一步滚动更新。另外置信区间会随步长增大而变宽这是正常现象可以据此判断预测可信范围。5.5 核函数优化陷入局部最优每次跑结果不一样现象同样的数据不同随机种子跑出来的预测差异明显。原因对数边际似然是非凸函数L-BFGS-B 容易陷入局部最优。解决增大n_restarts_optimizer一般设 10 到 20。同时固定random_state保证可复现。如果数据量允许可以先用网格搜索粗筛 length_scale 和 noise_level 的范围再交给优化器精调。6. 进阶技巧用复合核与在线更新把 GPR 用得更顺手核函数相加是 GPR 最灵活的地方。真实时间序列往往同时包含趋势、周期和噪声单一核搞不定但复合核可以。我一般的做法是先画原始序列肉眼判断有哪几种成分然后每种成分对应一个核相加后一起优化。from sklearn.gaussian_process.kernels import DotProduct # 趋势 周期 局部变化 噪声 kernel_advanced ConstantKernel(1.0) * DotProduct(sigma_01.0) \ ConstantKernel(1.0) * ExpSineSquared(length_scale1.0, periodicity24.0) \ ConstantKernel(1.0) * Matern(length_scale1.0, nu1.5) \ WhiteKernel(noise_level1e-2)DotProduct捕捉线性趋势ExpSineSquared捕捉周期Matern捕捉局部波动WhiteKernel吸收噪声。四个核相加后优化器会自动分配权重。这个复合核在我做过的设备振动预测和业务指标预测里都比单一 RBF 好代价是优化时间从几秒变成几十秒。另一个实用技巧是在线更新。GPR 的闭式解意味着新增一个样本后不需要从头训练可以用 Sherman-Morrison 公式增量更新逆矩阵。sklearn 没直接提供这个接口但可以自己实现def incremental_update(gpr, X_new, y_new): 简化版增量更新实际项目需处理标准化和核参数固定 X_all np.vstack([gpr.X_train_, X_new]) y_all np.concatenate([gpr.y_train_, y_new]) gpr.fit(X_all, y_all) return gpr严格来说这不是真正的增量更新只是重新 fit。真正的增量更新需要固定核参数只更新逆矩阵。如果数据流式到达且量不大重新 fit 也能接受如果每秒都有新数据就得自己实现 Sherman-Morrison 更新。验证 GPR 预测是否可信我习惯看两个东西一是置信区间的宽度是否合理如果 95% 区间宽到没有业务意义说明模型不确定性太大要么加数据要么换核二是残差的自相关用statsmodels的 ACF 图检查如果残差还有显著自相关说明模型没榨干序列里的信息。最后说个我自己的习惯每次换核函数或调完参数我都会把优化后的核表达式打印出来和上一版对比。核表达式就是模型的“黑匣子”说明书length_scale 变大意味着更平滑noise_level 变大意味着模型认为数据更吵。看多了之后光看核表达式就能判断这次预测大概是什么风格。希望帮到你。本文还有配套的精品资源点击获取
返回列表