ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

小波神经网络在交通流量预测中的Matlab实现与调参实践

小波神经网络在交通流量预测中的Matlab实现与调参实践 简介这是一套基于Matlab实现的小波神经网络交通流量时间序列预测源码与配套数据面向计算机、电子信息工程、数学等专业的大学生适用于课程设计、期末大作业、毕业设计等场景中的预测模型复现与参考。资源包体积仅4KB共包含4个文件3个Matlab源文件分别对应小波神经网络主程序、小波基函数及其导数实现1个.mat数据文件提供交通流量实验数据结构精简、运行环境要求低便于快速加载。小波神经网络结合了小波变换的时频局部化特性与神经网络的自学习能力在非线性交通流量预测中具有较好表现资料可直接运行并观察预测效果同时便于读者结合代码理解小波函数选择、梯度推导与网络训练流程为后续改进预测精度或扩展到其他时间序列问题提供参考框架。目前该资源已有644人学习浏览适合具有基础Matlab编程能力的读者下载使用。1. 交通流量预测为什么需要小波神经网络交通流量预测的难点从来不在“跑通一个模型”而在于流量序列同时具有强非线性、周期性和偶发突变。直接用标准BP网络做多步预测收敛慢而且容易把突变点当噪声抹平换成LSTM调参和训练时间又太贵。小波神经网络Wavelet Neural Network, WNN的思路是把传统神经网络隐藏层的Sigmoid替换成Morlet等小波基函数用伸缩因子和平移因子去拟合信号局部特征收敛速度和拟合精度在中小规模时间序列上往往优于同配置BP网络。这套基于Matlab实现的源码包正好把整条链路补齐mymorlet.m负责小波基d_mymorlet.m提供反向传播所需导数wavenn.m串起数据加载、训练与预测traffic_flux.mat给出现成交通流量观测数据。适合做课程设计、期末大作业也适合想快速验证小波神经网络效果的在职人员。2. 小波基函数选型与Morlet小波的Matlab实现2.1 为什么隐藏层不用Sigmoid而用小波基传统BP网络的隐藏层激活函数通常是Sigmoid或Tanh这类函数是全局激活的输入在很大范围内变化时输出都有响应想拟合一个只在某个时间段出现的脉冲型突变往往要堆很多神经元才能把局部特征“凑”出来训练时还容易陷入局部极小。小波神经网络最大的差别在激活函数。它使用小波基函数作为隐藏层激活小波基在时域上是局部化的伸缩因子a控制频率尺度平移因子b控制时间中心。网络学到的不是一组抽象的权重而是“在什么时间位置、以什么尺度、多强的幅度”去响应输入这跟交通流量中“早高峰是平滑上升、事故导致的堵塞是突变尖峰”的混合形态天然匹配。值得区分的是这里说的WNN是把小波基嵌入神经网络内部当作激活函数跟“先用小波分解把信号拆成低频和高频分量再分别建模”是两条技术路线。前者是单模型端到端训练后者是两阶段串联。这套源码走的是前者所以训练过程里a和b是作为可学习参数参与梯度更新的不是事先算好的固定值。2.2 Morlet小波的数学形式与mymorlet.m实现Morlet小波是工程上用得最频繁的复数小波但做神经网络激活时通常取其实部形式psi(x) cos(1.75x) * exp(-x^2 / 2)1.75这个频率参数让基函数在时域和频域之间取得较好折中系数太小则局部性变差太大则振荡过快导致梯度不稳定。资源包里的mymorlet.m就是它的Matlab实现常见写法如下function y mymorlet(x, a, b) % MYMORLET Morlet小波基函数实部形式 % 输入: % x - 神经元的净输入行向量或矩阵 % a - 伸缩因子尺度标量 % b - 平移因子标量 % 输出: % y - 小波激活结果维度与x一致 t (x - b) ./ a; % 将净输入映射到小波基的自变量t y cos(1.75 .* t) .* exp(-0.5 .* t .^ 2); end这里的向量化写法是关键。如果网络一次前向传播要算几百个样本、每个样本又有多个隐藏节点用for循环逐个求值会慢一个数量级用.运算符对整个矩阵统一计算Matlab的底层矩阵运算吞吐量会高很多。实际调用时一般把隐藏层每个神经元的a和b独立存储例如a_vec(k)表示第k个隐藏节点对应的伸缩因子前向传播就按神经元逐个调用这个函数。注意一个容易搞混的点x已经是“净输入”不是原始特征。常见做法是先做线性变换z W * input再把z传入mymorleta和b在网络中被初始化为1附近的值然后靠训练不断修正。如果误把原始特征传进小波基a和b的物理意义就完全错乱了网络也基本学不出周期性。2.3 d_mymorlet.m反向传播的钥匙反向传播要求激活函数对净输入可导。Morlet基函数由余弦和指数相乘构成导数是解析可求的没有必要用数值差分去近似。对psi(x)求导得到dy/dx (-1.75/a) * sin(1.75t) * exp(-0.5t^2) - (t/a) * cos(1.75t) * exp(-0.5t^2)资源包里的d_mymorlet.m做的就是这件事典型实现如下function dy d_mymorlet(x, a, b) % D_MYMORLET Morlet小波基对净输入x的导数 % 输入输出同mymorlet.m用于反向传播时误差信号的回传 t (x - b) ./ a; c exp(-0.5 .* t .^ 2); dy (-1.75 ./ a) .* sin(1.75 .* t) .* c ... - (t ./ a) .* cos(1.75 .* t) .* c; end这个导数的结构值得多说两句。第一项来自余弦项的链式法则第二项来自指数项的链式法则注意a同时出现在两处分母上所以a的初始值不能太小否则梯度会呈指数级放大训练到第二步就出NaN。我一般把a初始化为0.8到1.2之间b初始化为0附近这样小波基的中心落在净输入分布的中央区域。另外d_mymorlet返回的是对净输入x的偏导。如果网络还要对a和b本身做梯度更新就需要额外推导对a和对b的偏导wavenn.m里把a和b当作参数随权重一起迭代时更新量应分别乘上相应的敏感度项。很多初学改动版本只更新W1和W2把a和b锁死这等于退化成带小波形状激活函数的BP网络效果会明显变差。3. wavenn.m主程序结构与训练流程拆解3.1 整体数据流与网络拓扑wavenn.m是整套源码的主控程序建议直接在当前目录下运行依赖同目录的mymorlet.m、d_mymorlet.m和traffic_flux.mat。整个流程分六步加载数据、构造训练样本对、归一化、初始化网络参数、迭代训练、反归一化并绘图数据流可以简化成下面这条链原始交通流量序列 → 滞后窗口切分 → 训练集/验证集划分 → 归一化 → 小波神经网络迭代 → 反归一化 → 多步预测误差评估网络拓扑是典型的单隐藏层结构输入层节点数对应滞后窗口长度n_in隐藏层用H个小波神经元输出层1个节点对应下一时刻流量。输入层到隐藏层的权重矩阵W1维度是(n_in, H)隐藏层到输出层的权重矩阵W2维度是(H, 1)再加上所有隐藏神经元共享的a和b参数向量。整体参数量是(n_in3)*H1对几百上千条交通流量样本来说这个容量刚好够用不会像深层网络那样在小数据集上严重过拟合。3.2 前向传播与损失计算前向传播在Matlab里的写法很紧凑常见的实现段如下% X_train: (N, n_in) 归一化后的输入矩阵 % W1: (n_in, H) 输入到隐藏层权重 % a_vec: (1, H) 每个隐藏神经元的伸缩因子 % b_vec: (1, H) 每个隐藏神经元的平移因子 % W2: (H, 1) 隐藏层到输出权重 hid_in X_train * W1; % (N, H) 各隐藏神经元的净输入 hid_out zeros(size(hid_in)); for k 1:H hid_out(:, k) mymorlet(hid_in(:, k), a_vec(k), b_vec(k)); end y_pred hid_out * W2; % (N, 1) 预测输出这里用for循环遍历隐藏神经元而不是直接一次性调用mymorlet是因为每个神经元的a和b不同必须逐列处理。如果嫌循环慢可以把a_vec和b_vec扩展成与hid_in同维度的矩阵用Matlab的隐式扩展一次性算完但后者可读性差一些调试时不容易看明白问题出在哪个神经元上。损失函数一般就用均方误差MSE对应代码是loss mean((y_pred - y_train).^2);。选择MSE而不是MAE是因为MSE对大误差的惩罚更重能逼迫网络优先拟合流量序列里的峰值段。代价是训练初期如果某个样本预测误差特别大梯度会被拉得过大所以后面要配一个自适应学习率或限制梯度范数。在这套实现里W1和W2都用0均值小方差正态分布初始化常见范围是randn * sqrt(2 / (n_in H))这个缩放比例来自He初始化思路能让前向传播的信号方差在跨层时保持稳定。3.3 反向传播与参数更新反向传播的推导起点是输出层误差delta2 y_pred - y_train然后借助d_mymorlet把误差回传到隐藏层。wavenn.m里典型的更新段长这样% 输出层梯度 delta2 y_pred - y_train; % (N, 1) % 隐藏层梯度误差经W2加权后乘小波导数 delta1 zeros(size(hid_in)); for k 1:H delta1(:, k) (delta2 * W2(k)) .* d_mymorlet(hid_in(:, k), a_vec(k), b_vec(k)); end % 计算各参数梯度 grad_W2 hid_out * delta2 / N; grad_W1 X_train * delta1 / N; grad_a sum(delta1 .* d_psi_da(hid_in, a_vec, b_vec), 1) / N; grad_b sum(delta1 .* d_psi_db(hid_in, a_vec, b_vec), 1) / N; % 带动量的梯度下降更新 W1 W1 - lr * grad_W1 momentum * (W1 - W1_prev); W2 W2 - lr * grad_W2 momentum * (W2 - W2_prev); a_vec a_vec - lr * grad_a momentum * (a_vec - a_vec_prev); b_vec b_vec - lr * grad_b momentum * (b_vec - b_vec_prev);这段代码里最容易写错的是delta1的计算。小波基的链式法则是“输出层误差 × W2分量 × 小波基对净输入的导数”三个因子缺一不可。常见做法是直接调用d_mymorlet但资源包里只有它对x的导数如果需要更新a和bwavenn.m内部还有一组对a和b的偏导函数实际工程里也可以用自动微分或者数值梯度去验证这些梯度算得对不对。参数更新的动量项是这套代码收敛稳定的重要因素动量系数一般取0.85到0.95之间。小波神经网络的损失面比BP网络更崎岖因为a的微小变化会让小波基振荡频率发生整体偏移没有动量缓冲的话梯度方向会高频震荡表现为loss曲线反复横跳就是不下降。下表给出wavenn.m常用超参的建议区间参数作用建议范围调参方向隐藏神经元数H模型容量820样本多、序列复杂取大值学习率lr梯度步长0.0010.05出现NaN就调小动量系数平滑梯度0.850.95loss震荡明显时调大a初始值小波尺度起点0.81.2输入归一化后取1附近b初始值小波中心-0.50.5无特殊先验就取0迭代轮数训练时长5003000看验证集早停提示训练到一半loss变成NaN九成是学习率偏大导致小波基指数项梯度爆炸把学习率按10倍细调重跑即可不要先怀疑代码逻辑。4. traffic_flux.mat数据预处理与超参数调优4.1 先摸清数据维度不要猜拿到traffic_flux.mat之后的第一步不是直接跑wavenn.m而是搞清楚数据内部的结构。交通流量数据常见的组织方式有两种一是单列时间序列每行代表固定时间间隔的流量值二是多列矩阵每列是不同检测点或不同日期的流量。load进去之后先用whos确认变量名和尺寸再用绘图看序列形态load(traffic_flux.mat); whos data traffic_flux; % 实际变量名以whos输出为准 figure; plot(data); xlabel(采样点); ylabel(交通流量); title(原始交通流量序列);如果数据是多列先决定预测目标——是预测某个检测点的流量还是总体流量。我一般先把各列画在同一张图上观察若列间趋势相似取平均或选取波动最明显的一列作为预测目标若列间差异大则分别训练模型对比效果不要一上来就把所有列拼成多维输入那会把输入维度撑到几十维样本量不够时过拟合很严重。4.2 滞后窗口与归一化策略小波神经网络做时间序列预测用的是监督学习范式要把原始序列转换成“用过去m个时刻预测下一时刻”的样本对。滞后窗口m的选择是第一个关键超参它的作用是决定模型能看到多长的历史。窗口太短周期性信息进不来太长输入维度变大样本量被压缩。常见做法是先用自相关函数ACF观察序列的显著滞后阶数再取一个包含完整周期长度的窗口对小时级交通流量数据多取6到12个小时的滞后值。归一化在这套源码里是个隐形陷阱。训练集和验证集要共用训练集的min和max做映射而不是各自独立归一化。独立归一会让两个数据集失去可比性预测误差计算出来会虚假偏小。Matlab的mapminmax可以直接复用训练集的统计量[X_norm, ps] mapminmax(X_train, 0, 1); X_val_norm mapminmax(apply, X_val, ps);ps里保存了训练集的归一化参数验证集必须用同一组参数做变换。训练完成后对预测结果做反变换用的是mapminmax(reverse, y_pred, ps_y)这里的ps_y是从真实流量值y_train上统计出来的不能拿X的ps去还原输出。这个错位问题在代码评审里反复出现默认输出在0到1之间看着正常但反归一化后整条预测曲线整体偏低误差大得离谱。4.3 超参数调优的优先级与常见坑调参最忌讳一上来把所有参数同时乱试。我的经验是固定迭代轮数和动量先扫H隐藏神经元数在8、10、12、16这几个值里跑一遍观察验证集MSE的变化然后固定H再扫学习率最后回到H做细调。这样每轮只有一个变量在动出了问题也能定位到是容量不够还是步长不合理。调参步骤固定参数扫描参数判断标准第1轮lr0.01, momentum0.9H[8,10,12,16]验证集MSE最低点第2轮H取最优, momentum0.9lr[0.005,0.01,0.02,0.05]训练集收敛曲线是否平滑第3轮H、lr取最优momentum[0.85,0.9,0.95]验证集是否继续下降第4轮全部固定迭代轮数可增至3000观察是否过拟合训练时顺手记录每一轮的训练误差和验证误差画成两条曲线。训练误差持续下降但验证误差在第几百轮拐头上升就是典型的过拟合信号解法不是加正则项而是先降低H或提前截止训练。反之如果两条曲线都压在0.05以下但验证误差仍然偏高说明数据本身有噪声或滞后窗口不够属于特征工程层面的问题调参救不回来。交通流量数据自带很强的周期性直接用原始值训练时a的初始值如果偏离1太远小波基振荡频率会和流量周期严重错配网络会花大量迭代把a往正确方向推。所以归一化这一步不是可选项它直接决定a和b初始化为1是否合理。检查手段也很简单第一次前向传播后打印hid_out的均值和方差若均值稳定在0.4到0.7之间、方差不太小说明小波基工作区间是健康的若全部输出都接近0说明小波基被推到了两侧衰减区学习会非常慢。注意不要用测试集调参。测试集只能看最后结果否则你在隐式地根据测试误差改参数最后报告的误差是偏乐观的。课程作业里很多人在这里栽跟头答辩一问测试集参与了几次调参就露馅。5. 预测效果验证与常见坑位的检查方法5.1 MAE、RMSE、MAPE要配合着看预测做完不能只盯着一张拟合图说“效果不错”要算量化指标。计算三个基础误差指标的Matlab代码很短y_true traffic_flux(N_train_m 1:end); y_pred_denorm mapminmax(reverse, y_pred, ps_y); MAE mean(abs(y_true - y_pred_denorm)); RMSE sqrt(mean((y_true - y_pred_denorm).^2)); MAPE mean(abs((y_true - y_pred_denorm) ./ y_true)) * 100; fprintf(MAE%.3f RMSE%.3f MAPE%.2f%%\n, MAE, RMSE, MAPE);这三个指标反映的问题不同。MAE给出平均偏差的绝对量级适合说明“平均差多少辆车”RMSE放大了大误差的惩罚如果它明显大于MAE说明预测在峰值时段出了较大的偏差模型对突发流量拟合差MAPE是相对量纲但注意流量接近0时MAPE会爆炸所以交通流量场景里我更看重RMSE和MAE的对比关系。RMSE/MAE比值接近1.2到1.5是正常范围超过2说明少数极端样本主导了误差。5.2 从三个角度判断模型有没有真的学到流量特征第一看误差是否集中在突变点。把y_true和y_pred_denorm画在同一张图同时把绝对误差画在第二张子图误差尖峰出现的位置如果与早高峰、事故拥堵段重合说明模型捕捉到了平滑周期但没有抓住突变。这时优先调整滞后窗口而不是增加隐藏神经元数量因为突变前的低流量上升趋势往往在更长历史里才有有效信息。第二检查残差的自相关性。调出残差序列res y_true - y_pred_denorm如果残差里还带着明显周期成分说明模型只学到了流量的均值水平没学到动态结构。此时看看是不是归一化方式有问题或者小波基的a值收敛到了某个局部最优可以尝试重新初始化并分多次训练取验证集最优的那一次。第三和Baseline对比才有说服力。常见做法是拿ARIMA或者线性回归做同样滞后窗口的预测再和WNN对比RMSE如果你手头有Python环境可以顺手用LSTM或Transformer时间序列预测做交叉验证。小波神经网络在数据量小、训练时间受限时的表现通常更稳但若数据量上万条、特征维度更高LSTM和Transformer的后劲更大代码里预留出对比接口会方便很多。验证集合的样本数量不要太少至少保留整段序列最后15%以上的数据。把全部历史数据都灌进训练集的做法在课程作业里很常见但这会让你对模型的真实泛化能力一无所知。拿最近500个流量样本单独跑一遍测试记录MAE和RMSE作为基准值后续每次调整参数只有验证集指标低于这个基准才保留改动这是最笨也最有效的防过拟合手段。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进