
简介一份基于深度置信网络DBN的时间序列预测实例主要面向需要利用 DBN 模型处理序列数据的科研人员、研究生或机器学习开发者通过完整示例演示从数据准备、模型训练到预测输出的流程。包内共183个文件压缩包整体约31.11MB核心是123个.m的Matlab源码文件涵盖网络构建、训练与预测函数另有7个.mat数据文件用于加载测试数据7个.fig结果图可直观查看预测效果4个.md说明文档帮助理解工程结构并包含少量C语言辅助文件。目前已有257人浏览学习适合快速复现实验流程。读者可通过该实例掌握DBN在时间序列预测中的建模思路理解参数设置与数据预处理细节并借助携带的完整数据与结果图表进行对照验证便于替换自己的序列数据做进一步拓展实验。1. DBN时间序列预测为什么还要用回深度信念网络即便在lstm时间序列预测python、transformer预测python代码这类教程铺天盖地的今天深度信念网络Deep Belief Network, DBN依然值得专门写一篇。原因不复杂LSTM 和 Transformer 擅长捕捉长程依赖但对小样本、强噪声、周期性明显的结构化序列它们的训练成本和过拟合风险反而更高。DBN 通过逐层无监督预训练先把序列数据转成更稳定的高层特征再做回归或分类这种两阶段训练方式在金融时序预测、银行客户认购产品预测、用户消费预测等场景里往往比端到端深度学习更稳。标题里的test_example_DBN.m更像是一个 MATLAB 版本的示例脚本里面包含DBN.m、序列数据、DBN预测这几个关键元素。这篇文章就把这条路径说透先讲清楚 DBN 在时序任务中的位置再给一套能直接复现的最小代码最后落到工程上的验证技巧和坑。2. 深度信念网络处理序列数据前先把这三件事想清楚2.1 从RBM到DBN无监督预训练给时序特征提取留下了什么DBN 的结构基础是受限玻尔兹曼机Restricted Boltzmann Machine, RBM。单个 RBM 可以看成是一个两层网络可见层接收原始输入隐藏层学习该输入的分布特征。训练时通过对比散度Contrastive Divergence, CD算法逼近对数似然梯度更新权重。DBN 的做法是把多个 RBM 逐层堆叠前一个 RBM 的隐藏层输出作为后一个 RBM 的可见层输入。这个过程不需要标签属于无监督预训练。对时间序列预测来说这个过程的价值在于原始的序列数据往往带有大量局部波动和噪声直接喂给监督模型容易让模型去拟合这些噪声。DBN 的预训练像是先做了一次“软特征提取”把相邻时间步之间的相关性通过 RBM 权重固化下来。预训练结束后再在最后一层接一个回归层或分类层进行有监督微调模型面对的就是一组比原始输入更干净的抽象特征。这里有一个关键差异LSTM 通过门控单元显式建模时间顺序DBN 则不做这件事。DBN 对时序建模的本质是把序列转成特征再用前馈网络完成映射。所以用 DBN 做时序预测输入形态的设计比模型本身更影响效果。这一点很多人第一次跑 DBN 时容易忽略总以为把序列拉平喂进去就行结果预测曲线严重滞后。2.2 把时间序列改造成DBN的输入滑动窗口与归一化DBN 的前馈结构要求输入是固定维度的向量。把一串不等长的历史序列变成固定维度通用做法是滑动窗口sliding window。假设原始序列是s[1], s[2], ..., s[T]设定窗口大小window_size w预测步长horizon h那么每一条训练样本就是X [s[t-w1], ..., s[t]]标签是y s[th]。这里最容易被忽视的是归一化。RBM 的可见层通常假设输入取值在[0, 1]之间尤其是使用二进制单元时更是如此。对于股票价格、气温、用电量这类数值型序列必须先做 min-max 归一化x_scaled (x - min) / (max - min)在sklearn里直接用MinMaxScaler即可。注意MinMaxScaler必须用训练集的min和max去变换验证集和测试集不能用整个数据集的统计量否则会造成信息泄漏。下面的代码演示了这一流程。import numpy as np from sklearn.preprocessing import MinMaxScaler def create_dataset(series, window_size12, horizon1): 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) # 原始序列 raw np.array([...]) # 你的原始序列 scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(raw.reshape(-1, 1)).ravel() # 划分训练/测试再滑窗 train_len int(len(scaled) * 0.8) train_scaled scaled[:train_len] test_scaled scaled[train_len - 12:] # 留出12个时间步做窗口 X_train, y_train create_dataset(train_scaled, window_size12, horizon1)这段代码把训练集的前12个时间步作为第一个窗口标签是第13个时间步的值。test_scaled取train_len - 12开始是为了保证测试集第一个样本的窗口来自训练集末端符合真实预测场景。参数horizon1表示单步预测想预测未来三步就把horizon改成3同时要在创建数据集时调整索引范围。2.3 DBN与LSTM在时间依赖上的本质差别很多文章把 DBN 和 LSTM 放在一起对比但两者的建模哲学完全不同。LSTM 在循环结构中通过遗忘门、输入门和输出门逐时间步传播状态天然适合捕捉序列内部的先后依赖DBN 则是一个静态映射给定一个窗口内的向量映射到一个目标值。窗口里各时间步之间的相对位置关系DBN 只能靠输入特征的排列顺序来隐式学习没有任何跨时间步的参数共享。这就导致一个常见现象用 DBN 做单步预测时结果往往比 LSTM 更平滑但不太“跟手”峰值的幅值经常被低估。原因在于预训练阶段 RBM 学到的特征更偏全局分布对局部的剧烈变化不敏感。如果数据里存在明显的季节项或趋势项建议在进入 DBN 之前先做差分或季节性拆分。后面第五章会给出具体的差分处理代码。简单地说DBN 适合那些整体规律性强、允许一定滞后、但特征维度高的序列不适合频谱成分极复杂的高频交易数据。3. 从test_example_DBN.m出发DBN预测的最小可运行实例3.1 用Python还原一个可跑的DBN时序预测流程虽然标题里给的是.m文件但今天在 IT 环境里复现 DBN更通用的方式是 Python。核心思路不变先用sklearn的BernoulliRBM做逐层特征提取再在特征基础上训练一个回归层。严格意义上多 RBM 堆叠才算 DBN但为了演示预训练思想这里用一个 RBM 加回归层的最小版本跑通之后再堆多层。from sklearn.neural_network import BernoulliRBM from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline from sklearn.metrics import mean_squared_error def train_dbn_predictor(X_train, y_train, X_test, hidden_dim64, learning_rate0.01, n_iter20, batch_size32): # 1. 无监督预训练RBM学习原始特征分布 rbm BernoulliRBM( n_componentshidden_dim, learning_ratelearning_rate, n_itern_iter, batch_sizebatch_size, random_state42 ) # 2. 监督微调回归器直接作用在RBM的隐藏特征上 regressor LinearRegression() pipeline Pipeline([(rbm, rbm), (regression, regressor)]) pipeline.fit(X_train, y_train) y_pred pipeline.predict(X_test) return y_pred X_train, y_train create_dataset(train_scaled, window_size12, horizon1) X_test, y_test create_dataset(test_scaled, window_size12, horizon1) y_pred train_dbn_predictor( X_train, y_train, X_test, hidden_dim64, learning_rate0.01, n_iter20, batch_size32 ) print(RMSE:, mean_squared_error(y_test, y_pred, squaredFalse))这段代码里BernoulliRBM先把输入窗口映射成64维的隐藏特征这一层是无监督训练目标是最小化重建误差接着LinearRegression在隐藏特征上拟合目标值。Pipeline保证 RBM 的输出自动传给回归器避免手工写中间转换。hidden_dim决定特征表达的容量太小会丢失信息太大会让预训练变得不稳定learning_rate控制 CD 算法每次更新的步长n_iter是 CD 迭代轮数batch_size影响梯度估计的噪声水平。这几个参数是这类模型最重要的调节对象下一节给出具体的调整思路。3.2 DBN预测模型的四个必调参数速查参数设置范围对预测结果的影响调参建议hidden_dim16128隐藏层节点数越多特征表达越丰富但过大会导致预训练收敛慢先从小开始观察验证集 RMSE 变化如果欠拟合再加learning_rate0.0010.1学习率过大RBM 权重震荡过小预训练迭代速度慢用对数刻度搜索优先试 0.01n_iter1050迭代次数不足特征未充分收敛过多则可能过拟合固定其他参数绘制损失曲线在曲线变平处停止batch_size1664小批量能让训练更稳定但太小会使梯度噪声大样本少时用 16样本多时用 64这四个参数之间不是完全独立的。hidden_dim 变大时通常需要适当增大n_iter让更多特征被充分训练learning_rate也应该随之略微下调。实际操作中我习惯先把hidden_dim固定为64其余三个参数用三次随机搜索每次只跑 20 轮以节省时间。如果测试集 RMSE 明显下降再扩大hidden_dim重试。3.3 读懂test_example_DBN.m里常见的MATLAB写法在test_example_DBN.m这类示例脚本里MATLAB 的常见组织方式是先加载序列数据再调用一个DBN类或函数完成预训练与微调。典型代码结构如下% 读取序列数据 data load(series_data.mat); series data.series; % 归一化 series_min min(series); series_max max(series); series_norm (series - series_min) / (series_max - series_min); % 调用DBN训练函数 dbn DBN([10, 50, 20]); % 输入层10隐藏层50输出层20 dbn dbn.train(series_norm, opts); % opts含学习率、迭代次数 pred dbn.predict(series_norm);这里的DBN([10, 50, 20])表示网络结构为输入层 10 个节点、隐藏层 50 个节点、输出层 20 个节点。结合前面的滑窗概念10对应窗口大小20是回归层维度。如果你只拿到这样一个 MATLAB 文件迁移到 Python 时只需要抓住三点网络结构数组、训练参数学习率、迭代次数、数据归一化方式。其他诸如绘图、误差计算等代码都是外围辅助不影响模型主体。4. 用DBN做序列预测的三个大坑数据泄漏、滞后与不稳定4.1 因果滑窗是数据泄漏的重灾区用train_test_split随机划分时间序列是初学者最常犯的错误。DBN 的每个样本包含一个窗口和它对应的未来值如果训练集和测试集来自重叠的时间区间模型相当于提前见过测试窗口的信息。正确做法是把连续的时间范围切开训练集取前 80% 时间步测试集取后 20% 时间步。# 错误做法随机打乱 # from sklearn.model_selection import train_test_split # X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2) # 正确做法按时间顺序切分 split_idx int(len(X) * 0.8) X_train_seq, X_test_seq X[:split_idx], X[split_idx:] y_train_seq, y_test_seq y[:split_idx], y[split_idx:]注意创建X和y时用的是整个序列所以split_idx必须同时作用于X和y先切分再训练。如果先滑窗生成样本再随机切分会造成测试样本的时间戳早于训练样本预测没有实际意义。4.2 预测曲线滞后一个节拍根因与缓解几乎所有序列预测模型都会遇到滞后问题DBN 更明显。原因是模型在训练时倾向于输出靠近窗口末端的均值因为这样可以最小化均方误差。给定窗口[v(t-11), ..., v(t)]模型学到的最优预测往往是v(t)附近的值而不是真正的v(th)。这导致预测曲线看起来像是把真值整体右移了一步。缓解滞后有两个有效手段。第一是增加预测步长不要预测t1而是预测t3或t5虽然误差会变大但滞后感会减轻。第二是对目标值做差分让模型预测增量而不是绝对值diff_series np.diff(scaled_series) X_diff, y_diff create_dataset(diff_series, window_size12, horizon1)这里的np.diff把原始序列转成相邻时间步之差。模型预测的是下一步的变化量预测完成后用pred last_value diff_pred恢复原始尺度。由于差分了原始序列模型的输出不再是窗口末端的近似值滞后现象会显著缓解。4.3 多次运行验证稳定性DBN的随机性比你想象的大BernoulliRBM有随机初始化即使随机种子固定不同批次数据的顺序和采样也会带来结果差异。我在实际项目中观察到同一套参数跑五次测试集 RMSE 的变异系数可能达到 5%10%。如果没有多次运行很难判断一次结果好坏是模型能力还是运气。建议至少跑 5 次并在每次训练前分别设置不同的random_state然后记录 RMSE 的均值和标准差。results [] for seed in range(5): pred_seed, _ train_dbn_predictor( X_train, y_train, X_test, hidden_dim64, learning_rate0.01, n_iter20, batch_size32 ) rmse mean_squared_error(y_test, pred_seed, squaredFalse) results.append(rmse) print(RMSE mean: %.4f, std: %.4f % (np.mean(results), np.std(results)))如果标准差大于均值的一半说明当前参数下模型极不稳定优先调低learning_rate或增大n_iter。如果依然发散请检查数据是否包含异常尖峰RBM 对极端值非常敏感预训练阶段可能被个别样本拉偏。5. 滚动预测与误差指标DBN预测模型上线前的验证组合拳5.1 用滚动预测模拟真实的上线状态一次性预测整个测试集的做法相当于假设未来数据可以同时获得所有历史窗口实际上线时做不到。更严谨的办法是滚动预测每次只用当前时间点之前的数据预测下一个点然后把真实观测值并入历史窗口继续预测再下一个点。这样能真实检验模型的在线表现也更容易暴露滞后和累积误差。def rolling_forecast(model_pipeline, X_initial, y_test, history_len): history list(X_initial) predictions [] for t in range(len(y_test)): current_window np.array(history[-history_len:]).reshape(1, -1) pred model_pipeline.predict(current_window)[0] predictions.append(pred) # 把真实值加入历史保持窗口长度 history.append(np.append(current_window[0, 1:], y_test[t])) return np.array(predictions)这里X_initial是测试期开始前的最后一个窗口。每预测完一个时间步就把该时间步的真实值拼接进历史形成新的窗口从而让模型始终基于截止到当前时刻的信息做预测。对比一次性预测和滚动预测的结果差异如果滚动误差远大于一次性误差说明模型对远距离依赖较弱更适合短窗口。5.2 三个误差指标别只盯RMSERMSE 对极端值敏感在金融时序和消费预测中会被几个异常样本拉高。我至少会再算两个指标指标公式适用场景SMAPEmean(2*abs(y-ŷ)/(abs(y)abs(ŷ))) * 100数值跨多个量级时的稳健比较MASEmean(abs(y-ŷ)) / mean(abs(y[t]-y[t-1]))与朴素预测上一期值比较改善程度MDAmean(sign(y[t]-y[t-1]) sign(ŷ[t]-y[t-1]))分类正确率更高适合方向性预测def mase(y_true, y_pred, y_history): n len(y_true) naive_errors np.mean(np.abs(np.diff(y_history[-n-1:]))) return np.mean(np.abs(y_true - y_pred)) / naive_errorsMASE 小于 1 表示模型优于“用上一期值预测当前值”的朴素方法这是 DBN 预测能否上线的关键门槛。MDA 则在股票预测、银行客户认购这类方向比幅度更重要的任务里有参考价值。5.3 非平稳序列的快速预处理技巧如果数据有明显趋势或季节周期直接做归一化后进入 DBN 会让预训练阶段学到的特征以趋势为主忽略局部模式。建议先用numpy做差分和一阶季节性提取def seasonal_adjust(series, period24): # 计算每个季节位置的均值做季节性分解 season_avg np.array([ series[i::period].mean() for i in range(period) ]) season_pattern np.tile(season_avg, int(np.ceil(len(series) / period)))[:len(series)] adjusted series - season_pattern return adjusted, season_patternseasonal_adjust返回去除季节性的序列和季节模式。模型只对adjusted部分训练预测结果再加上对应的season_pattern分量回归真实值。对于周期为 24 的小时级数据、12 的月度数据这个处理能让 DBN 的预训练集中在更微观的序列变化上实际效果往往比直接建模原始序列稳定得多。本文还有配套的精品资源点击获取