简介:这是一份面向交通工程、智能交通系统研究人员与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 : t+lag-1) X(t, :) = flow(t : t+lag-1)'; % 输出:预测 flow(t+lag+horizon-1) y(t) = flow(t + lag + horizon - 1); end endlag的选择很关键。交通流量有很强的自相关,通常以15分钟为粒度时,滞后4到8个点,即过去1到2小时,就能覆盖大部分信息。太短抓不住趋势,太长会把噪声也包进来。yc.m里默认lag=4,适合预测未来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或很小都会让指标失真。特征构造阶段的这几个参数,通常可以用下面这张表固定下来。
| 参数名 | 推荐值 | 设定依据 |
|---|---|---|
| lag | 4~8 | 15分钟粒度下对应1~2小时窗口 |
| horizon | 1~4 | 1表示未来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。lr=0.01是一个保守的学习率,避免残差训练时震荡。
ARIMA部分的'Y0'参数很关键,它指定了预测起点的历史数据。必须传入与模型阶数一致长度的最近历史观测,否则forecast会从零开始递推,结果完全不可用。这里的ytr是整个训练集,实际使用时可以只取最后若干项,但传入完整训练集不会影响结果,只是计算量稍大。不同模型的适用场景可以通过下表快速判断。
| 模型 | 参数设置 | 适用场景 |
|---|---|---|
| ARIMA(1,1,1) | 一阶差分,AR和MA各1阶 | 平峰期、线性趋势明显的序列 |
| BP纯预测 | 两层隐层[10,5],trainlm | 包含非线性联动的高峰期数据 |
| ARIMA+BP残差修正 | 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: 最近lag+horizon个观测 % 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折交叉验证已经能获得接近最优的结果。搜索时注意不要使用全量数据,预留一段最近一周的“最后验证期”,防止搜索过拟合。最后用验证期的误差对比,而不是交叉验证的平均误差来决定最终参数,这才是能扛住真实流量的模型。
本文还有配套的精品资源,点击获取