MATLAB实战短时交通流量预测:ARIMA+BP组合模型全解析
简介这是一份面向交通工程、智能交通系统研究人员与MATLAB初学者的短时交通流量预测源码资源。压缩包内为单个MATLAB脚本文件大小仅1KB代码轻量紧凑聚焦交通流量预测这一核心场景。该脚本围绕数据预处理、特征工程、模型构建、训练优化、预测评估与可视化等完整流程展开适合用于学习ARIMA、支持向量机、神经网络等常见流量预测模型的MATLAB实现思路也可作为课程设计或论文实验的基线参考。已有339人学习该资源说明其在同类资料中具备一定参考价值。通过研读这份源代码可快速掌握短时交通流量预测从数据清洗到结果评估的完整建模链路为后续进一步开展智能交通算法研究打下基础。1. 短时交通流量预测的难点yc.m如何拆解5分钟后的路况在交通管理中真正让人头疼的不是“今天堵不堵”而是“15分钟后这个路口会不会堵”。长期预测可以靠POI和路网结构短时预测则完全依赖对历史流量序列的实时建模。yc.rar里的yc.m正是这样一个MATLAB脚本它用一套经典的数据清洗-特征构造-模型训练-误差评估流程把短时交通流量预测做成了可以复现的代码。对交通工程研究人员来说这是一个可修改的基线对数据分析师来说它示范了如何用MATLAB快速验证时序预测思路而且能直接在真实流量数据上跑通。2. yc.m的数据预处理与特征构造从原始线圈数据到模型输入2.1 原始交通数据的清洗异常值剔除与缺失填充交通流量数据通常来自地磁线圈或者视频检测器这两种设备都会产生两类典型问题掉线导致的缺失值以及车辆变道、设备抖动产生的异常尖峰。如果你的原始序列里混入一个2000辆/小时的数据而相邻时刻只有400辆那后面所有模型都会被这个点带偏。yc.m里第一步用yc_clean函数处理核心是“先剔异常再补缺失”顺序不能反——如果先插值异常值会被当成正常信息扩散。% yc_clean.m - 数据清洗与缺失值填充 function data yc_clean(raw) % raw 必须是 Nx1 向量单位辆/15分钟 data raw; % 1. 用3倍标准差定位离群点置为NaN mu mean(data, omitnan); sigma std(data, omitnan); outlier_idx abs(data - mu) 3 * sigma; data(outlier_idx) NaN; % 2. 用线性插值填补所有NaN idx 1:length(data); data fillmissing(data, linear); end这段代码的逻辑说明omitnan选项让均值和标准差计算时忽略已有缺失值3倍标准差是一个经验窗口适合流量这种近似正态分布的指标。如果数据呈现强周期性比如休息日和工作日方差差异很大我会把阈值改为按周内相同时间段分别计算避免把节假日的高流量误删。fillmissing的linear方法对短时间缺失1到2个时刻足够稳定但连续缺失超过6个时刻时建议换成spline或采用相邻日同时刻的平均值。2.2 滞后特征与时间窗构造短时交通流量预测本质上是一个时序回归问题用过去(p)个时刻的流量预测未来(h)个时刻的流量。这里的(p)就是滞后阶数(h)是预测步长。yc.m里build_lags函数把原始一维序列转换成监督学习所需的输入矩阵X和输出向量y。这个转换是整个预测流程的地基特征构造的方式直接决定模型能捕捉到多少信息。function [X, y] build_lags(flow, lag, horizon) % flow - 清洗后的流量列向量 (N×1) % lag - 滞后阶数使用过去lag个连续点 % horizon - 预测步长预测lag之后第horizon个点 n length(flow); start lag horizon; % 第一个可预测点的位置 m n - start 1; X zeros(m, lag); y zeros(m, 1); for t 1:m % 输入第 t 行是 flow(t : tlag-1) X(t, :) flow(t : tlag-1); % 输出预测 flow(tlaghorizon-1) y(t) flow(t lag horizon - 1); end endlag的选择很关键。交通流量有很强的自相关通常以15分钟为粒度时滞后4到8个点即过去1到2小时就能覆盖大部分信息。太短抓不住趋势太长会把噪声也包进来。yc.m里默认lag4适合预测未来15分钟如果要预测未来半小时或一小时horizon要相应增大同时lag建议同步增加到6或8因为预测跨度越大需要的上下文越长。这里的horizon直接控制了预测前移的步数取值1到4分别对应未来15分钟到1小时。2.3 归一化与样本组织流量数据的取值范围可能在0到几千之间如果不归一化BP神经网络这种基于梯度的模型会训练得很慢甚至不收敛。yc.m里在构造完特征后会做一次归一化将数据压缩到统一尺度。需要注意归一化参数必须在训练集上计算测试集直接用同一套参数变换避免用到未来信息。% 归一化参数只从训练集获取 [Xtr_norm, muX, sigmaX] zscore(Xtr); ytr_norm (ytr - min(ytr)) / (max(ytr) - min(ytr)); % 测试集使用训练集的统计量 Xte_norm (Xte - muX) ./ sigmaX; yte_norm (yte - min(ytr)) / (max(ytr) - min(ytr));注意min(ytr)和max(ytr)必须保存下来后续对实时数据做预测时要使用同一个值反归一化不能每次重新计算否则预测结果会漂移。这里输入特征用了zscore标准化输出则采用最大最小归一化。原因在于输入特征来自不同滞后时刻分布差异不大标准化能加速梯度下降输出如果标准化在反归一化时容易出现数值偏移用最大最小归一化可以保证预测值落在原流量范围。如果你后续要算MAPE输出必须反归一化回真实流量否则分母为0或很小都会让指标失真。特征构造阶段的这几个参数通常可以用下面这张表固定下来。参数名推荐值设定依据lag4~815分钟粒度下对应1~2小时窗口horizon1~41表示未来15分钟4表示未来1小时归一化方式输入zscore输出minmax加速梯度下降保证输出可解释3. 核心预测模型ARIMA与BP神经网络的“接力”3.1 为什么选ARIMA作为基线短时交通流量带有明显的线性和平稳成分尤其是凌晨和午间平峰期流量序列几乎可以近似为带趋势的随机过程。ARIMA自回归积分滑动平均模型是这类问题最成熟的经典基线。它的优势在于训练快、参数解释明确而且MATLAB的econometrics工具箱有现成的arima函数。yc.m里选择ARIMA(1,1,1)作为默认配置因为一阶差分能消除大部分非平稳性而AR项和MA项各一个参数在短序列上不容易过拟合。但ARIMA的短板也很明显它对非线性特征比如突发事故导致的路段联动效应基本无感。所以单纯用ARIMA预测均方根误差RMSE在平峰期可能不错到了早晚高峰就会系统性偏低。这正是我们在同一个脚本里引入BP神经网络的原因。与其让ARIMA去硬拟合非线性成分不如把它留给BP去处理。3.2 BP神经网络在短时预测中的角色这里我把BP设计成两个角色。第一个是独立预测器直接输入滞后特征输出未来流量第二个是残差修正器学习ARIMA在训练集上的预测残差然后加到ARIMA的测试集预测上。这种“线性基线非线性残差”的组合方式比单纯堆两个模型更稳健也更容易定位问题。如果组合后的误差反而比单独ARIMA更大那至少能判断出是哪一部分残差没学好。以yc.m里的实现为例主脚本先用estimate拟合ARIMA再通过infer得到训练残差然后用一个小型BP网络去拟合“特征到残差”的映射。测试时BP的残差预测补上ARIMA的线性误差最终预测值就是ARIMA输出加BP修正。这个结构在交通流预测中很常见相当于用一个线性模型抓住主干再用非线性模型去补细节。3.3 模型训练与参数配置% yc.m 主脚本核心段落 % 假设 Xte_norm, Xtr_norm, ytr, yte 已由前一阶段准备好 % 训练ARIMA(1,1,1) arimaModel arima(1,1,1); fitArima estimate(arimaModel, ytr, Display, off); % 获取训练集上的残差 [resid, ~] infer(fitArima, ytr); % 训练BP残差修正网络 netRes feedforwardnet(5, trainlm); netRes.trainParam.epochs 200; netRes.trainParam.lr 0.01; netRes train(netRes, Xtr_norm, resid); % 预测 [yhatArima, ~] forecast(fitArima, length(yte), Y0, ytr); resHat netRes(Xte_norm); yhatFinal yhatArima resHat; % 同时也训练一个纯BP网络作为对比 netPure feedforwardnet([10, 5], trainlm); netPure.trainParam.epochs 300; netPure.trainParam.lr 0.01; netPure train(netPure, Xtr_norm, ytr); yhatPure netPure(Xte_norm);这段代码需要注意三个参数。feedforwardnet的第一个参数是隐层结构[10, 5]代表两层隐层分别有10个和5个神经元对于残差修正网络由于残差信号的复杂度远低于原始流量我只用了一层5个神经元。trainlm是Levenberg-Marquardt训练算法适合中小规模数据收敛速度快但内存占用偏高如果样本量超过几万建议换成trainscg。lr0.01是一个保守的学习率避免残差训练时震荡。ARIMA部分的Y0参数很关键它指定了预测起点的历史数据。必须传入与模型阶数一致长度的最近历史观测否则forecast会从零开始递推结果完全不可用。这里的ytr是整个训练集实际使用时可以只取最后若干项但传入完整训练集不会影响结果只是计算量稍大。不同模型的适用场景可以通过下表快速判断。模型参数设置适用场景ARIMA(1,1,1)一阶差分AR和MA各1阶平峰期、线性趋势明显的序列BP纯预测两层隐层[10,5]trainlm包含非线性联动的高峰期数据ARIMABP残差修正ARIMA单层5节点BP希望同时保留线性解释力和非线性修正组合模型之所以通常更稳是因为ARIMA负责把握总体的自相关结构BP负责捕捉ARIMA残差里残余的非线性模式。如果只优化ARIMA高峰期的误差会停留在“稳定偏高”的状态如果只用BP平峰期的预测会因为没有平稳性约束而出现无规律抖动。组合模型相当于在两者之间做了任务分工这也是它适合作为yc.m默认方案的原因。4. 预测误差评估与可视化如何判断模型真的能用4.1 误差指标MSE、MAE、MAPE 的适用边界代码中最后用fprintf输出RMSE但只靠RMSE不够。RMSE对大误差敏感适合用来比较模型优劣MAE直接反映平均偏差物理意义直观MAPE则能消除量纲影响。不过在交通流量场景中MAPE有个陷阱当真实流量接近0比如夜间低谷即使绝对误差很小MAPE也会暴涨。所以评估时一般以RMSE为主MAE为辅MAPE只用于白天时段。yc.m里给出了计算三种指标的脚本。为了不做成一次性代码我用匿名函数封装了它们方便在几个模型之间重复调用。% 计算评估指标 rmse (pred, real) sqrt(mean((pred - real).^2)); mae (pred, real) mean(abs(pred - real)); mape (pred, real) mean(abs((pred - real)) ./ (real 1e-6)) * 100; fprintf(ARIMA RMSE%.2f MAE%.2f MAPE%.2f%%\n, ... rmse(yhatArima, yte), mae(yhatArima, yte), mape(yhatArima, yte)); fprintf(BP RMSE%.2f MAE%.2f MAPE%.2f%%\n, ... rmse(yhatPure, yte), mae(yhatPure, yte), mape(yhatPure, yte)); fprintf(组合 RMSE%.2f MAE%.2f MAPE%.2f%%\n, ... rmse(yhatFinal, yte), mae(yhatFinal, yte), mape(yhatFinal, yte));注意real 1e-6这个细节它是为了防止真实流量为0时除零报错。但加上之后夜间低位数据的MAPE会被这个小值拉到异常大所以我会在计算前先过滤掉real 1的样本valid yte 1; mape_filtered mean(abs(yhatFinal(valid) - yte(valid)) ./ yte(valid)) * 100;这样才算出了有意义的平均绝对百分比误差。实际项目中给决策者看的时候建议报过滤后的MAPE否则夜里那一段会吓坏不懂细节的人。流量预测不是追求一个漂亮数字而是要让误差分布可解释。4.2 训练集/测试集划分与滚动预测yc.m默认按时间顺序切分前80%做训练、后20%做测试这比随机划分更严格。随机划分会把未来数据混进训练集造成“前视偏差”这在短时交通预测里是致命的。你随便挑一个模型用随机划分都能把RMSE做得很低但部署到线上就原形毕露。更好的做法是滚动预测训练集仍然从历史开始但测试时每次只用最新观测更新输入窗口预测下一个点然后把预测值合并到历史序列中继续递推。这样更贴近真实运行时的状态。比如你有一个小时的历史窗口每15分钟来一个新数据就用滑动窗口重算特征并调用forecast而不是等测试集全部备好再一次性预测。4.3 结果可视化从对比图到误差分布可视化部分除了画实际值和预测值的折线我还会叠加一张误差直方图。折线图能看出趋势跟随能力但看不出系统偏差方向。误差直方图如果明显偏左或偏右说明模型存在一致性低估或高估需要检查是否有周期性因素没建模。figure; subplot(2,1,1); plot(yte, k-, LineWidth, 1.5); hold on; plot(yhatFinal, g--, LineWidth, 1.2); legend(实际流量, 组合预测, Location, NorthWest); title(短时交通流量预测对比); subplot(2,1,2); histogram(yte - yhatFinal, 30); xlabel(绝对误差 (辆/15分钟)); ylabel(频数); title(误差分布直方图);这里的histogram默认分30个桶如果误差分布接近以0为中心的正态分布说明模型没有明显系统偏差。如果右拖尾长说明模型在某些突发高流量时段预测不足。结合误差分布和时间戳就能定位到具体是早高峰还是晚高峰出的问题。可视化不仅是给论文或PPT用更是自己在调试模型时判断下一步该改哪里的第一手依据。5. 把yc.m改造成实时短时预测服务的三个技巧5.1 用函数封装代替脚本式执行yc.m如果作为脚本逐段运行每次演示OK但接到数据流上就会很痛苦。我会把清洗、特征构造、模型更新、预测分别封装成独立函数然后用一个主函数串联。注意不要在每次预测时重新train而是在定时任务里只调用forecast把训练放到后台单独触发。function yhat yc_predict(history, modelInfo) % history: 最近laghorizon个观测 % modelInfo: 训练好的ARIMA模型和BP网络 [X, ~] build_lags(history, modelInfo.lag, modelInfo.horizon); X (X - modelInfo.muX) ./ modelInfo.sigmaX; yhat forecast(modelInfo.arima, size(X,1), Y0, history); yhat yhat modelInfo.net(X); end这样每次预测只需要处理最新窗口不用重新加载整个训练集。modelInfo里保存了归一化参数、训练好的ARIMA对象和BP网络预测逻辑被压缩到几行后续接定时任务或HTTP接口都很方便。5.2 滑动窗口在线更新模型不能一直用旧参数。我一般每小时用过去7天的数据重新训练一次。更轻量的做法是只更新归一化参数和残差网络ARIMA保持不动因为ARIMA参数在一天内变化不大。如果流量数据有明显的周周期性把训练窗口扩展到两周并且按照“周几”分组训练多个专用模型会比一个万能模型更准。5.3 参数自动搜索lag、隐层神经元、学习率这些超参数用网格搜索即可。MATLAB的bayesopt当然也行但交通流量数据往往足够大网格搜索加5折交叉验证已经能获得接近最优的结果。搜索时注意不要使用全量数据预留一段最近一周的“最后验证期”防止搜索过拟合。最后用验证期的误差对比而不是交叉验证的平均误差来决定最终参数这才是能扛住真实流量的模型。本文还有配套的精品资源点击获取