2026/10/11 3:13:56

小波神经网络预测代码详解:小波分解+ELMAN时间序列预测实战

小波神经网络预测代码详解:小波分解+ELMAN时间序列预测实战 简介一份聚焦小波神经网络WNN的毕业设计预测代码面向信号处理、交通流预测等方向的学生和研究人员系统展示小波变换多分辨率分析与神经网络自学习能力融合的完整流程。压缩包共6个文件包含5个.m脚本与1个.mat数据文件整体仅5KB结构精简整洁主程序负责整合小波层与神经网络层配套小波基函数、导数计算、层析特征分析和网络训练等脚本并提供一组交通流量实测数据用于验证预测效果精简体积也方便快速下载、分模块阅读与调试。当前已有98人次学习适合具备机器学习基础、希望借助真实数据集快速复现WNN预测流程的读者。通过这套代码可完整观察从小波基构造、层析分析到神经网络训练与预测的整体链路也便于后续将思路迁移至Python/TensorFlow等更现代的深度学习环境是兼顾课程设计与科研复现的轻量参考实现。1. 小波神经网络预测代码打开rar前先搞懂导师的“两层疑问”刚接手“毕业设计-小波神经网络预测代码.rar”时很多人以为解压后就是一个独立的神经网络模型跑通就能拿数据出图。实际打开后你会发现它多半是“小波分解 ELMAN网络”的组合包先用小波变换把非平稳序列拆成低频趋势和若干高频细节再对每个子序列分别训练网络预测最后重构相加。这套方案对负荷、水位、股价这类非平稳时间序列很友好答辩时也容易讲清楚。但也正因为是组合方案代码里藏着大量“黑匣子”分解层数怎么定、数据和网络如何喂、预测结果怎么拼。这篇笔记从原理到参数再到踩坑按我自己的复现顺序写一遍希望能帮你少折腾几个通宵。2. 小波分解神经网络到底哪部分在“预测”三种实现范式与选型理由2.1 小波分解先拆信号近似分量与细节分量各司其职小波变换和傅里叶变换最大的区别在于它能同时看频率和位置。傅里叶告诉你信号里有哪些频率成分但不知道这些成分出现在什么时候小波变换用一组可以缩放和平移的基函数去匹配信号从而把一段非平稳序列拆成时间对齐的几个分量。对时间序列预测来说这一步的意义在于你不需要让一个神经网络同时去拟合“稳定的趋势”和“突变的细节”而是把它们拆开、分别建模最后再叠加回完整预测值。在Python里最常见的工具就是PyWavelets。用一个简单的调用就能完成分解import pywt import numpy as np # 生成一段非平稳演示序列趋势 周期 随机冲击 t np.arange(300) data 0.02 * t np.sin(t / 5.0) np.random.normal(0, 2, sizet.shape) # db4小波3层分解 coeffs pywt.wavedec(data, db4, level3) cA3, cD3, cD2, cD1 coeffs这里pywt.wavedec返回一个列表第一个元素是最后一次分解的近似系数cA3后面依次是第三层到第一层的细节系数cD3, cD2, cD1。近似系数代表信号的主要趋势细节系数则保存了不同尺度下的波动和噪声。level3意味着把原始信号做了三次二抽取每一个子序列的长度约为上一层的一半所以cA3长度只有原始数据的约八分之一。这是后面最容易踩坑的地方重构时不能直接相加必须用pywt.waverec。为什么选db4小波这是毕设代码里最常用的“默认配置”。db系列小波属于Daubechies小波族性质是紧支撑、正交且具备较好的平滑性。db4是一个折中阶数太低则小波太简单无法捕获细节阶数太高则计算量变大、边界效应明显。如果你的数据是电力负荷、交通流量这类带有明显周期性和趋势性的信号一般就用db4不建议在这个参数上花太多时间。2.2 小波神经网络不是指定网络三范式对比“小波神经网络”这个词在课程论文和毕业设计里被用得非常泛。常见做法是把“小波”和“神经网络”拼在一起但到底谁在前谁在后代码差别很大。我见过三类范式整理成表范式组合方式预测逻辑典型场景A分解网络先小波分解每个分量分别用BP/ELMAN预测重构相加各分量独立建模再求和实现简单、可解释负荷、水位、流量预测B小波基作激活函数使用小波基函数替代隐层激活函数经典WNN尺度/平移因子作为可学习参数单个网络参数较多训练不稳定故障诊断、小型分类C小波变换作特征输入将小波系数与原始序列合并为特征喂入LSTM/ELMAN一个网络需要特征对齐多变量预测毕业设计里最常见的是范式A因为它的“分工”非常清晰小波分解负责提取不同频段的子序列神经网络负责对每个子序列做时间依赖建模。而“小波ELMAN神经网络”这个热词就是指用ELMAN网络作为范式A中的预测器。ELMAN网络本身就是一个带反馈环的循环网络它的隐层输出会沿时间步回传天然适合时间序列。和LSTM相比ELMAN没有门控参数少很多训练速度快在CPU上就能跑动非常适合毕设这种“必须能看到自己代码每个细节”的场景。2.3 为什么毕设偏爱小波ELMAN网络可解释性、能跑动、有说头我从实际带毕设的学长那里听到最多的理由是导师要看“结合点”。单独用BP网络预测太普通单独用LSTM又显得像套包。小波ELMAN网络天然自带两个技术名词小波分解负责多尺度特征提取ELMAN负责时间递归建模。答辩时可以说“小波分解解决了非平稳性问题ELMAN解决了时序记忆问题”这个说法在讲台上站得住。另外ELMAN的训练成本远低于LSTM。一个3000点的序列做3层小波分解后每个子序列长度只有几百到一千多训练一个隐层16节点的ELMAN几乎几十秒就能完成。迭代快调参也快这是很多预测类毕设最终选择它的直接原因。不过要注意ELMAN在Keras里对应的就是SimpleRNN层不要在答辩时把LSTM也混进去讲有些老师会细抠。3. 用Python复现“小波分解ELMAN”预测从wavedec到滚动训练3.1 数据预处理与db4三层小波分解核心代码与参数先粘贴一个完整的流程骨架。假设你手头有一列历史观测值data长度N现在要预测未来H步。最常用的做法不是一次性预测H步而是每次预测1步然后滚动更新。这样每个分量模型都只做单步预测稳定很多。import numpy as np import pywt from sklearn.preprocessing import MinMaxScaler # 原始序列 data load_your_series() # 形状 (N,) # 1. 小波分解 wavelet db4 level 3 coeffs pywt.wavedec(data, wavelet, levellevel) # 2. 对每个分量分别归一化 scalers [] scaled_coeffs [] for c in coeffs: scaler MinMaxScaler(feature_range(-1, 1)) c c.reshape(-1, 1) scaler.fit(c) scaled_coeffs.append(scaler.transform(c).flatten()) scalers.append(scaler)说明由于原始序列长度不一定是2的整数倍小波分解后各分量长度不一致这是正常的。PyWavelets的wavedec默认采用symmetric扩展得到的每个分量长度是上一层的一半向上取整。后续构建滑窗时你只需要按每个分量的实际长度各自制作样本即可不需要强行对齐。归一化放(-1, 1)是ELMAN网络的习惯它比(0,1)收敛更快因为tanh激活函数的输出范围本身就是(-1, 1)和输入范围对齐会减少早期震荡。为什么要分别训练、分别归一化因为不同分量的量纲不同cA3反映的是信号的大幅趋势可能数值很大而高频细节cD1往往在0附近上下波动数值很小。如果混在一起归一化高频分量的有效变化会被压缩没。这也是很多“翻车”代码的源头。3.2 为每个分量构建滑窗样本并训练SimpleRNN接下来是对每个分量构造监督学习样本。设滑窗长度look_back 12用过去12个点预测下1个点。一个分量的样本构造如下def create_samples(series, look_back12): X, y [], [] for i in range(len(series) - look_back): X.append(series[i:ilook_back]) y.append(series[ilook_back]) return np.array(X), np.array(y) look_back 12 models [] histories [] for idx, s in enumerate(scaled_coeffs): X, y create_samples(s, look_back) # 按时间顺序划分训练集、测试集不能乱序打散 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]这里最关键的一点是样本不能随机打散。时间序列预测评估的是未来如果把样本随机混洗等于让模型偷看了未来的信息。毕设代码里最常见的“效果奇好”往往就是这里写错了。然后搭一个简单的ELMAN网络。用Keras的SimpleRNN来等价实现from tensorflow.keras.models import Sequential from tensorflow.keras.layers import SimpleRNN, Dense def build_elman(look_back12, hidden16): model Sequential() model.add(SimpleRNN(hidden, activationtanh, input_shape(look_back, 1), return_sequencesFalse)) model.add(Dense(1)) model.compile(optimizeradam, lossmse) return model model build_elman(look_back, hidden16) # 训练轮次先给30早停可以后用 history model.fit(X_train, y_train, epochs30, batch_size16, validation_data(X_test, y_test), verbose0)SimpleRNN的隐层反馈结构就是ELMAN的核心每个时刻隐层状态会保存下来并和下一时刻输入一起参与计算。return_sequencesFalse表示只取最后一个时间步的输出这样正好做单步预测。隐层节点数hidden16是从小往大调的起点后面可以按验证误差增减。3.3 重构预测序列注意waverec的系数顺序与长度对齐每个分量模型只预测测试集的“下一步”但如果要做多步预测需要滚动用预测出的值接在序列末尾继续预测下下步。但要注意小波分解后的每个分量长度各不相同滚动时每个分量独立进行。def recursive_predict(model, scaler, series, n_predict): scaled scaler.transform(series.reshape(-1, 1)).flatten() result [] window scaled[-look_back:] for _ in range(n_predict): x window.reshape(1, look_back, 1) pred_scaled model.predict(x, verbose0)[0, 0] result.append(pred_scaled) window np.append(window[1:], pred_scaled) preds scaler.inverse_transform(np.array(result).reshape(-1, 1)).flatten() return preds注意这里每次滚动预测用的是“经过小波分解后的分量序列”而不是原始序列。当你要得到最终的原始量纲预测时需要把所有分量的滚动预测结果放到对应的系数位置上再用pywt.waverec重构pred_coeffs [] for idx, model in enumerate(models): preds recursive_predict(model, scalers[idx], coeffs[idx], n_predict) pred_coeffs.append(preds) # 重构前对齐长度waverec要求每个系数长度与分解时一致 # 常见做法预测长度n_predict可能小于某个细节分量长度需要截断或填充 min_len min([len(c) for c in pred_coeffs]) aligned [c[:min_len] for c in pred_coeffs] final_pred pywt.waverec(aligned, db4)这个重构过程往往让新手抓狂。pywt.waverec不会自动识别“预测出来的系数”和“分解时的系数长度差”如果你预测的长度与原始测试集长度不一致重构出来的序列长度会不等于你预期。常见做法是把所有分量预测结果对齐到同一最小长度或者把预测步长设计成与分解后最短分量长度一致避免截断导致边界畸变。这一段的详细坑放在第5章展开。4. 预测代码的5个必调参数与效果验证RMSE不是唯一标准4.1 参数速查表把参数调对往往比换模型更有效。下面这张表是我做小波预测时会优先核对的一批参数直接照表检查能省很多试错时间。参数常见取值影响调整思路小波基db4, db3, sym5分解出的分量形态不同一般用db4高频噪声多时试sym5分解层数3 ~ 4层数越多低频越平滑但细节分量越短观察重构误差误差大就降层look_back12 / 24决定记忆长度小则欠拟合大则过拟合先按周期长度设置再看验证曲线隐层节点8 / 16 / 32容量和训练速度从小往上调验证集loss不再降就停学习率0.001 / 0.0005影响收敛和平稳loss震荡就降到0.0005训练集比例0.8太大会导致测试集样本过少保持时间顺序按比例切分分解层数是最玄学的一个参数。你可能会发现level3和level4结果相差20%以上。我一般是这样判断先看分解后各分量重构回去的误差如果重构误差在1e-10量级说明分解本身没问题如果误差很大大概率是level选得和小波基不匹配导致边界效应被放大。4.2 验证指标RMSE、MAPE与“趋势命中率”很多毕设只放RMSE但老师往往会追问“你预测的趋势对不对”。所以我会多算两个指标MAPE和趋势命中率。MAPE能直观反映百分比误差趋势命中率则看预测涨跌方向和真实涨跌方向是否一致。from sklearn.metrics import mean_squared_error def rmse(y_true, y_pred): return float(np.sqrt(mean_squared_error(y_true, y_pred))) def mape(y_true, y_pred): y_true np.asarray(y_true).flatten() y_pred np.asarray(y_pred).flatten() mask y_true ! 0 return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 def trend_hit(y_true, y_pred): diff_true np.diff(y_true) diff_pred np.diff(y_pred) return np.mean((diff_true * diff_pred) 0) * 100注意如果序列存在接近0的值MAPE会爆炸所以还要结合RMSE一起看。趋势命中率特别适合小波预测这种多步滚动场景因为它能暴露“滞后”问题——如果预测曲线总是跟着真实曲线走滞后一个步长RMSE可能不大但趋势命中率会明显低于50%这时候就要回看是不是预测策略有误。4.3 对比实验怎么设计让导师一眼看到小波分解的价值要证明“小波分解ELMAN”不是花架子最稳妥的做法是做一个“消融实验”相同的数据、相同的网络结构一组走小波分解流程一组直接喂原始序列给ELMAN。这样对比的是“有没有小波分解”的差异而不是“换了个网络”。# 伪代码骨架同一份数据和同样的训练设置 results {} # 对照组直接ELMAN X, y create_samples(scaled_data, look_back) model_direct build_elman(look_back, hidden16) model_direct.fit(X_train, y_train, epochs30, batch_size16, verbose0) pred_direct recursive_predict(model_direct, scaler, data, n_predict) results[direct] (rmse(ytest, pred_direct), mape(ytest, pred_direct)) # 实验组小波分解ELMAN前面3.3已经得到final_pred results[wavelet] (rmse(ytest, final_pred), mape(ytest, final_pred)) # 输出对比注意测试集必须完全一致 print(results)做对比实验时最容易犯的错是对照组用了完整归一化实验组用了分量分别归一化但两者测试集的“起止时间”没有对齐。一定要保证n_predict对应的真实段落在两条路径中完全相同否则对比没意义。如果实验组RMSE明显低于对照组说明小波分解确实有帮助如果两者接近也别急着否定可以试着调整分解层数和小波基再跑一次对比。5. 小波神经网络预测避坑最容易翻车的5个地方现象、原因、解决5.1 预测曲线比真实值滞后一个周期现象预测形状和真实曲线几乎一样但整体右移了一个步长看着“拟合得很好就是慢半拍”。原因最常见的是训练样本构造时用了连续滑窗但测试时“用真实值作输入”预测下一步然后拿去画图。这样预测的其实是“已知下一点的序列”所以显示滞后。具体来说如果你在测试阶段没有滚动更新输入窗口而是每预测一个点就把窗口换成包含真实值的窗口你得到的只是“拿着真相拼出来的短时预测”必然滞后。解决严格区分“一步预测”和“多步滚动预测”。验证时一步预测肯定会滞后要用多步滚动预测的输出去对比真实值。代码中用第3章的recursive_predict()生成连续多步预测再计算指标。画图时也要把前look_back个点留出来不要从第一个点就开始对比。5.2 全序列分解后再划分训练集导致的未来信息泄漏现象验证集误差极低甚至近乎零导师一换数据就崩。原因对整个原始序列做小波分解然后再切训练/测试。小波分解本质上是全局运算某一个点的近似/细节系数会受到前后邻域点的影响。测试集的系数已经包含了来自训练集的信息相当于在测试时模型“见过”未来趋势。解决对训练集和测试集分别做小波分解或者采用滚动预测模式。在数据准备阶段就要把分解放在划分之后顺序是“切分数据 - 用训练集分解 - 训练 - 用测试集分解 - 预测”。不过测试集分解时长度不足边界效应需要处理。更好的做法是用滑动窗口在线分解每次只用历史窗口做小波分解预测未来一步窗口移动后重新分解。这样既避免泄漏也符合实际部署场景。5.3 ELMAN网络loss震荡不收敛现象训练过程中loss一会儿降一会儿升预测结果全是一个常数或线性拉直。原因学习率不合适、数据未归一化、隐层节点太多。ELMAN的隐层反馈会让梯度在时间维度传播学习率过大时非常容易震荡。另一个原因是每个分量数据量少训练轮次太多导致过拟合loss在验证集上飘。解决先检查归一化是否在(-1,1)把学习率调到0.0005或0.0001隐层节点从8起步不要一上来就64。还可以给SimpleRNN加一点kernel_regularizerl2(1e-5)。另外每轮训练用EarlyStopping监控验证集losspatience10不要等300轮跑完。5.4 重构预测值量纲不对现象预测结果比真实值大几十倍或者出现明显负值。原因waverec要求传入的系数长度和分解时一致并且顺序必须是[cA_k, cD_k, ..., cD_1]。很多人预测时把cA和cD长度搞混或者把顺序倒过来重构就会出错。另外每个分量是分别归一化的重构前必须用各自scaler做inverse_transform如果忘记还原量纲会乱。解决写一个调试函数把分解后的系数直接用waverec重构回原始序列对比和原始数据的差异。如果重构误差不是0说明系数顺序或者长度处理有问题。先保证“分解-重构”无损再谈预测。5.5 导师质疑“你这不算小波神经网络”现象答辩时导师说“你这就是BP加了个预处理不是小波神经网络”。原因很多人做的是范式A小波分解普通网络但论文标题写“小波神经网络”严格意义上经典WNN是“小波基函数替换激活函数”的网络。如果导师是信号处理方向出身会揪这个细节。解决提前在论文和PPT里写清楚本设计采用的是“小波分解与ELMAN网络结合的预测模型”属于小波神经网络的组合变体并说明选择理由——分解使非平稳序列平稳化ELMAN捕捉时间依赖。千万不要把两个范式混称。如果导师偏向经典WNN可以在代码里加一个对比实验用Morlet小波基作为激活函数训练一个单隐层BP但效果通常不如范式A这样反而能从“为什么不用”的角度讲出深度。“小波ELMAN神经网络”在现在毕业论文里就是指这种组合你答辩时主动讲比被动解释要好。6. 把“能跑”变成“能答辩”滚动预测模式解决数据泄漏和边界效应最后一个技巧是把我自己的毕设从“被导师挑刺”拉回“导师点头”的关键。前面5.2提到的数据泄漏问题最彻底的解法不是“先划分再分解”而是直接用滚动预测模式。滚动预测的核心理念是每次预测时只使用当前时刻之前的一段历史数据做小波分解然后预测下一个点窗口整体右移一步。这样不存在“未来信息”进入分解过程符合真实预测场景。具体实现时每次窗口长度可以固定为窗口长度2^level的整数倍配合PyWavelets的周期扩展模式能明显减少边界畸变。代码如下def wavelet_predict_once(history, model, scaler, waveletdb4, level3): # history长度至少为 2^level coeffs pywt.wavedec(history, wavelet, levellevel, modeperiodization) scaled scaler.transform(history.reshape(-1, 1)).flatten() # 注意这里用原始history做分解但网络预测用的是系数简化演示只说明滚动思路 window scaled[-look_back:].reshape(1, look_back, 1) pred_scaled model.predict(window, verbose0)[0, 0] pred scaler.inverse_transform(pred_scaled.reshape(-1, 1)).flatten()[0] return pred这个片段我刻意简化了实际使用时需要把每个小波分量各自预测再重构但思路是核心每次预测前都基于当前可见的历史重新分解。这样你测试集每个点的分解都只用到它之前的数据不会泄漏。虽然计算量比一次性分解大但对毕设数据量来说完全无压力。另外滚动模式下别忘了用modeperiodization。默认的symmetric模式在窗口较短时边界会出现轻微畸变预测值可能不稳定。我当年就是在窗口长度只有64、level3时反复调参无果最后换成周期模式后预测曲线立刻平滑了。这是个很小的改动但能让结果稳定不少。如果你还想再进一步可以给滚动预测加上“重归一化”每当窗口移动就重新用窗口内的数据计算scaler的上下界。这能让模型适应不同时间段的不同幅值范围尤其适合非平稳性强的数据。我第一次做水电站水位预测时就是靠这一套滚动预测周期小波模式把测试集MAPE从15%压到了接近8%。这也让我养成了一个习惯任何小波预测项目先写一个“滚动预测基准版本”再去调分解层数和网络结构否则后面全是玄学。希望这个经历能帮到你。本文还有配套的精品资源点击获取