ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Python的轨道交通客流预测系统:从数据清洗到XGBoost/LSTM模型实战

基于Python的轨道交通客流预测系统:从数据清洗到XGBoost/LSTM模型实战 简介这份资源是面向高校计算机相关专业学生的轨道交通客流预测系统完整源码包适用于毕业设计、期末大作业与课程设计等场景难度适中评审分达到98分源码均经本地编译验证可运行。项目以Python为核心实现客流预测逻辑同时包含前端可视化与数据交互模块可帮助读者理解从数据预处理、模型构建到结果展示的完整链路。压缩包共1707个文件约37.25MB其中ts与js文件占主体用于前端页面与交互逻辑py文件承载预测算法与后端服务json、xml、sqlite3与pkl等文件负责配置、数据存储与模型持久化另有少量vue、html、md等辅助文件目录结构清晰便于按模块查阅。目前已有360人学习关注适合需要快速获取可运行项目、对照实现思路并完成论文或答辩准备的同学参考使用。1. 从一份「毕业设计-基于Python的轨道交通客流预测系统源码」说起它到底能跑出什么每年毕业季计算机和轨道交通相关专业的同学都会在选题上卡壳。有人想做点「看起来有技术含量、答辩时老师问不倒、简历上还能写一行」的东西于是「基于 Python 的轨道交通客流预测系统」就成了一个高频选项。但真正动手时问题立刻冒出来数据从哪来预测模型选哪个系统是做成网页还是桌面程序源码拿到手之后为什么跑不起来这份标题里的关键词其实已经把范围框死了Python 是技术栈轨道交通是业务场景客流预测是核心算法任务系统说明它不只是个脚本而是一个能演示、能交互、能答辩的完整工程。它解决的不是「预测明天全城地铁有多少人」这种宏大命题而是一个更务实的落地问题——给定某个站点或某条线路的历史刷卡/闸机数据预测未来一段时间通常是小时级或日级的进出站客流并把结果用图表和界面呈现出来。适合读这篇的人有三类正在做类似毕设、需要一套能跑通的最小闭环的同学已经拿到源码但被环境、数据格式、模型参数卡住的开发者以及想把这个方向往实际工程项目上靠、需要知道边界和坑在哪的工程师。下面我不谈空泛的「智慧交通愿景」只讲这套东西怎么从零搭起来、每一步的参数怎么定、哪些地方最容易翻车。2. 客流预测系统的技术选型为什么是 Python 时序模型 Web 演示2.1 业务问题先拆清楚预测粒度决定了后面所有选型轨道交通客流预测不是一个单一任务。按时间粒度分有短时预测15 分钟、30 分钟、1 小时和长时预测日、周、月按空间粒度分有单站预测、线路预测、全网预测按输入特征分有纯历史客流序列也有融合天气、节假日、周边 POI 的多变量输入。毕设场景下最常见也最容易做出效果的是「单站或少量站点的短时进出站客流预测」粒度取 1 小时预测未来 1 到 6 个时间步。为什么推荐这个粒度因为再细到 15 分钟数据噪声会急剧放大闸机数据在非高峰时段经常出现大量零值模型很难学到稳定模式再粗到日级样本量又太少一个站点一年也就 365 条记录训练集撑不起来。1 小时粒度在两者之间既有足够的样本量一年 8760 条又能体现早晚高峰的规律性答辩时画出来的曲线也好看。确定粒度之后特征工程的方向就清楚了时间特征小时、星期、是否节假日、滞后特征前 1 小时、前 2 小时、前 24 小时的客流、滑动窗口统计过去 3 小时均值、过去 24 小时最大值。这些特征不需要外部数据源从原始刷卡记录里就能算出来对毕设来说是最稳妥的路线。2.2 模型选型从 ARIMA 到 LSTM毕设到底该用哪个这是被问得最多的问题。我的建议是不要一上来就上深度学习先用统计模型把 baseline 跑出来再用机器学习模型对比最后视时间和算力决定要不要上 LSTM 或 GRU。ARIMA 的优势是解释性强、训练快、对小样本友好缺点是只能处理线性关系遇到早晚高峰这种强非线性模式容易欠拟合。实际做的时候用statsmodels库的ARIMA或SARIMAX把order参数设为(1,1,1)季节项seasonal_order设为(1,1,1,24)因为客流有明显的 24 小时周期。这个配置不是拍脑袋来的(p,d,q)里的d1表示做一阶差分让序列平稳p和q从 1 开始试用 AIC 准则选最优。随机森林和 XGBoost 这类树模型优势是能自动捕捉非线性关系和特征交互对缺失值和异常值也更鲁棒。用sklearn的RandomForestRegressorn_estimators设 100 到 200max_depth控制在 8 到 12 之间防止过拟合。树模型在毕设里的性价比很高训练几分钟就能出结果特征重要性还能直接画图放进论文。LSTM 的优势是能建模长序列依赖适合捕捉「昨天同一时段客流对今天的影响」这类模式。但它的坑也最多需要把数据归一化到 0 到 1 之间需要把时间序列切成滑动窗口样本需要调sequence_length、hidden_size、learning_rate等一堆参数。如果毕设时间紧张我一般会建议用 LSTM 做对比实验但主力模型用 XGBoost这样既有深度学习的「亮点」又有稳定的结果兜底。模型训练速度数据量要求调参难度毕设推荐度ARIMA/SARIMAX快低中作为 baseline随机森林中中低主力可选XGBoost中中中主力推荐LSTM/GRU慢高高作为对比亮点2.3 系统架构Flask ECharts 是毕设演示的最短路径预测模型跑通之后需要一个界面来展示结果。毕设答辩时老师不会看你终端里打印的 RMSE他们要看到一个能点、能切换、能出图的系统。最省事的方案是 Flask 做后端前端用 ECharts 画图数据库用 SQLite 存历史数据和预测结果。Flask 的路由设计很简单一个/路由返回主页一个/api/predict接口接收站点 ID 和预测步数调用模型返回 JSON 格式的预测结果前端用 ECharts 的折线图把历史客流和预测客流画在一起。SQLite 不需要额外安装服务一个.db文件就能搞定适合毕设这种单机演示场景。如果你想让系统看起来更「完整」可以加一个数据管理页面支持上传 CSV 文件、查看原始数据表格、导出预测结果。这些功能用 Flask 的render_template和pandas的to_csv就能实现不需要引入前端框架。记住一个原则毕设系统的核心是「能演示预测流程」不是「做一个生产级平台」把精力花在模型效果和图表呈现上比花在页面美化上划算得多。3. 从原始刷卡数据到模型输入数据清洗与特征工程实操3.1 拿到一份客流 CSV 之后先做这四步清洗假设你手里的原始数据是某站点一段时间的进出站刷卡记录字段可能是card_id、timestamp、station_id、type进/出。第一步是按小时聚合把每一条刷卡记录归到对应的小时区间统计每个小时的进站量和出站量。第二步是处理缺失小时有些时段可能因为设备故障或数据导出问题没有记录需要用前向填充或线性插值补上不能直接删掉否则时间序列就断了。第三步是异常值处理。客流数据里常见的异常是「某小时突然出现一个极大值」可能是系统重复计数或特殊事件导致。我一般用 3 倍标准差法标记异常点然后把它替换为前后两小时的平均值。第四步是检查时间连续性确保聚合后的数据是按小时连续排列的中间没有跳变。import pandas as pd import numpy as np # 读取原始刷卡记录 df pd.read_csv(raw_card_data.csv, parse_dates[timestamp]) # 按小时聚合进出站客流 df[hour] df[timestamp].dt.floor(H) flow df.groupby([hour, type]).size().unstack(fill_value0) flow.columns [in_flow, out_flow] # 补齐缺失小时并插值 full_range pd.date_range(flow.index.min(), flow.index.max(), freqH) flow flow.reindex(full_range) flow flow.interpolate(methodlinear) # 3倍标准差法处理异常值 for col in [in_flow, out_flow]: mean, std flow[col].mean(), flow[col].std() upper, lower mean 3 * std, mean - 3 * std flow[col] np.where((flow[col] upper) | (flow[col] lower), flow[col].rolling(3, centerTrue, min_periods1).mean(), flow[col]) flow.to_csv(hourly_flow.csv)这段代码的逻辑是先聚合再补全再清洗顺序不能乱。dt.floor(H)把时间戳向下取整到小时unstack把进站和出站拆成两列reindex保证时间轴连续interpolate用线性插值填补空缺。异常值处理用rolling均值替换centerTrue表示取前后各一小时参与计算min_periods1保证边界小时也能算。参数方面freqH是小时频率如果你的数据是 15 分钟粒度就改成15T。3 倍标准差是个经验值数据波动大的线路可以放宽到 4 倍波动小的可以收紧到 2.5 倍。清洗完的数据存成hourly_flow.csv后面所有模型都从这个文件读。3.2 构造时间特征和滞后特征让模型「看见」早晚高峰原始客流序列只有一列数值模型从中能学到的信息有限。把时间信息拆成特征之后模型才能区分「周一早上 8 点」和「周日早上 8 点」的差异。具体要构造的特征包括小时0 到 23、星期0 到 6、是否周末0/1、是否节假日0/1。如果有条件拿到节假日表直接 merge 进去没有的话至少把周末标出来。滞后特征是客流预测里最有效的一类特征。lag_1表示前一小时的客流lag_24表示前一天同一小时的客流lag_168表示上周同一小时的客流。这三个滞后项基本能覆盖短期、日周期、周周期三种模式。滑动窗口统计也很有用比如过去 3 小时的均值和标准差能反映近期趋势和波动程度。def build_features(flow_df): df flow_df.copy() df[hour] df.index.hour df[weekday] df.index.weekday df[is_weekend] (df[weekday] 5).astype(int) # 滞后特征 for lag in [1, 2, 3, 24, 168]: df[flag_{lag}] df[in_flow].shift(lag) # 滑动窗口统计 df[rolling_mean_3] df[in_flow].shift(1).rolling(3).mean() df[rolling_std_3] df[in_flow].shift(1).rolling(3).std() df[rolling_max_24] df[in_flow].shift(1).rolling(24).max() df df.dropna() return df feature_df build_features(flow) print(feature_df.shape)这里有个关键细节所有滞后和滑动窗口特征都要用shift(1)先错开一位否则当前小时的客流会「泄露」到特征里模型在训练集上表现很好一到测试集就崩。这是时序预测里最经典的翻车点很多人第一次做都会踩。dropna()会删掉因为 shift 产生的空值行通常前 168 行会被删掉这是正常的。特征构造完之后用train_test_split切分数据集时不能随机打乱必须按时间顺序切。一般取前 80% 做训练后 20% 做测试。如果用 XGBoost直接把特征矩阵和标签传进去就行如果用 LSTM还需要额外做归一化和滑动窗口切分这部分在下一章展开。3.3 训练集和测试集怎么切时间序列不能随机打乱很多人习惯用sklearn的train_test_split默认参数随机抽 20% 做测试集。这在时间序列任务里是错的。随机打乱会让模型「看到未来」测试集里的某些时间点可能在训练集之后但模型已经通过相邻样本间接学到了未来的信息导致评估结果虚高。正确的做法是按时间切假设有 8760 条小时数据取前 7000 条做训练后 1760 条做测试。如果要做交叉验证用TimeSeriesSplit它保证每一折的训练集都在测试集之前。评估指标用 MAE、RMSE 和 MAPE其中 MAPE 要注意分母为零的情况客流为零的小时会导致除零错误实际计算时加一个极小值或者过滤掉零值样本。from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_squared_error X feature_df.drop(columns[in_flow, out_flow]) y feature_df[in_flow] split_idx int(len(X) * 0.8) X_train, X_test X.iloc[:split_idx], X.iloc[split_idx:] y_train, y_test y.iloc[:split_idx], y.iloc[split_idx:] tscv TimeSeriesSplit(n_splits5) for train_idx, val_idx in tscv.split(X_train): print(fTrain: {len(train_idx)}, Val: {len(val_idx)})TimeSeriesSplit的n_splits5表示做 5 折每一折的训练集大小递增。这个类在sklearn里直接可用不需要额外安装。切分完之后训练模型时把X_train和y_train传进去预测时用X_test算出来的 MAE 和 RMSE 才是可信的。如果 MAE 在测试集上比训练集高很多说明过拟合了需要降低模型复杂度或增加正则化。4. 模型训练与预测XGBoost 和 LSTM 两条路的具体参数4.1 XGBoost 版本十分钟跑完训练和评估XGBoost 是我在毕设场景下最推荐的模型原因是训练快、调参少、效果稳定。用xgboost库的XGBRegressor核心参数有五个n_estimators控制树的数量max_depth控制每棵树的深度learning_rate控制每棵树的贡献权重subsample控制每棵树用的样本比例colsample_bytree控制每棵树用的特征比例。import xgboost as xgb from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np model xgb.XGBRegressor( n_estimators300, max_depth6, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42 ) model.fit(X_train, y_train, eval_set[(X_test, y_test)], verboseFalse) preds model.predict(X_test) mae mean_absolute_error(y_test, preds) rmse np.sqrt(mean_squared_error(y_test, preds)) print(fMAE: {mae:.2f}, RMSE: {rmse:.2f})参数解释n_estimators300配合learning_rate0.05是一个比较稳的组合树多但每棵树学得慢不容易过拟合。max_depth6是经验值再深容易记住训练集噪声再浅可能欠拟合。subsample0.8和colsample_bytree0.8是行采样和列采样相当于给模型加了两层随机性能提升泛化能力。eval_set传测试集进去是为了在训练过程中观察验证误差如果开了early_stopping_rounds可以在验证误差不再下降时提前停止。训练完之后用model.feature_importances_看特征重要性通常lag_1、lag_24、hour这三个特征排在最前面这符合客流预测的直觉最近一小时的客流、昨天同一小时的客流、当前是几点这三个信息对预测贡献最大。把特征重要性画成条形图放进论文是一个很好的加分项。4.2 LSTM 版本归一化、滑动窗口和三个必调参数LSTM 的流程比 XGBoost 多三步归一化、滑动窗口切分、维度变换。归一化用MinMaxScaler把客流缩放到 0 到 1 之间因为 LSTM 的激活函数对输入范围敏感不归一化会导致梯度爆炸或消失。滑动窗口是把连续的时间序列切成固定长度的样本比如用过去 24 小时预测下一小时sequence_length就设为 24。import torch import torch.nn as nn from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler() scaled scaler.fit_transform(feature_df[[in_flow]]) def create_sequences(data, seq_len): xs, ys [], [] for i in range(len(data) - seq_len): xs.append(data[i:iseq_len]) ys.append(data[iseq_len]) return np.array(xs), np.array(ys) SEQ_LEN 24 X_seq, y_seq create_sequences(scaled, SEQ_LEN) class LSTMModel(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :]) model LSTMModel(input_size1, hidden_size64, num_layers2) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr0.001)三个必调参数hidden_size控制 LSTM 内部状态的维度64 是一个常用起点数据量大可以加到 128数据量小就降到 32。num_layers2表示两层 LSTM 堆叠层数越多表达能力越强但越容易过拟合毕设场景下 1 到 2 层足够。lr0.001是 Adam 优化器的经典学习率如果训练损失震荡就降到 0.0005如果下降太慢就升到 0.002。训练循环里要注意把数据转成torch.Tensor并且用DataLoader分批送进去。每个 epoch 结束后在验证集上算一次损失如果连续 10 个 epoch 验证损失不下降就提前停止。训练完之后预测结果要用scaler.inverse_transform反归一化回原始量级否则画出来的图和实际客流对不上。4.3 预测结果怎么评估MAE、RMSE 和 MAPE 各看什么MAE 是平均绝对误差单位和人流量一致解释起来最直观MAE 等于 50 就是说平均每个小时的预测偏差是 50 人。RMSE 是均方根误差对大误差更敏感如果 RMSE 比 MAE 大很多说明存在少数预测偏差特别大的样本需要去检查那些时间点是不是有异常事件。MAPE 是平均绝对百分比误差适合比较不同站点的预测效果因为大站和小站的绝对误差不可比但百分比误差可以。计算 MAPE 时要过滤掉真实值为零的样本或者给分母加一个平滑项。毕设论文里通常三个指标都报MAE 和 RMSE 放主表MAPE 作为补充。指标公式含义适用场景注意事项MAE绝对误差的平均值同站点不同模型对比单位与客流一致RMSE误差平方均值的开方关注大误差场景受异常值影响大MAPE百分比误差的平均值不同站点横向对比需处理零值如果 XGBoost 的 MAE 比 LSTM 低不要觉得 LSTM「白做了」。在毕设里两个模型对比本身就是一种贡献说明你做了实验、有数据支撑、能分析原因。通常 XGBoost 在短时预测上不输 LSTM因为树模型对特征工程的质量更敏感而滞后特征已经捕捉了大部分时序模式。5. 避坑与排查这套系统最容易翻车的五个地方5.1 现象模型在训练集上 MAE 很低测试集上翻倍原因几乎总是数据泄露。最常见的是构造滞后特征时忘了shift当前小时的客流被当成特征喂进去了。另一个可能是归一化时用了全量数据的均值和方差而不是只用训练集的统计量。解决方法是检查特征矩阵里有没有和标签高度相关的列归一化时先fit训练集再transform测试集。5.2 现象Flask 接口返回的预测结果全是同一个值这通常是模型加载失败或者输入特征维度不对。检查model.predict的输入 DataFrame 列顺序是否和训练时一致XGBoost 对列顺序敏感列顺序错了不会报错但结果会乱。另一个可能是前端传过来的站点 ID 没有匹配到对应的模型文件导致用了默认模型。在接口里加一行print(X.columns.tolist())对比训练时的列名能快速定位。5.3 现象ECharts 折线图画出来是断的或者时间轴错乱原因是传给前端的 JSON 里时间字段格式不统一。pandas的Timestamp直接转 JSON 会变成时间戳数字ECharts 需要的是2024-01-01 08:00:00这种字符串。解决方法是导出前用dt.strftime(%Y-%m-%d %H:%M:%S)格式化并且确保历史数据和预测数据的时间轴是连续的预测部分的起始时间要接在历史数据最后一个时间点之后。5.4 现象LSTM 训练损失不下降一直卡在某个值先检查归一化有没有做没归一化的客流数据范围可能在几百到几万之间LSTM 根本学不动。如果归一化做了还是不降检查sequence_length是不是太长24 小时窗口对某些站点可能太长改成 12 试试。再检查学习率0.001不奏效就试0.0001和0.01学习率对 LSTM 的影响比 XGBoost 大得多。5.5 现象换一台电脑跑源码就报错依赖装不上毕设源码最常见的环境问题是 Python 版本和库版本不匹配。requirements.txt里如果写的是pandas而不带版本号在新环境里可能装到不兼容的新版本。建议在源码里固定主要库的版本比如pandas2.0.3、xgboost2.0.0、torch2.0.1。另外Windows 和 Linux 下torch的安装命令不同源码里最好分别注明或者用pip install torch --index-url指定 CPU 版本避免下载几个 G 的 CUDA 包。提示如果拿到源码后第一步就报ModuleNotFoundError先看requirements.txt是否存在不存在就根据import语句手动装。不要一上来就升级所有库到最新版版本兼容性在毕设环境里比新特性重要得多。6. 让预测结果更可信残差分析和答辩时的一个小技巧模型跑通、指标算完很多人就停在这里了。但答辩时老师最容易追问的是「你怎么知道模型学到的是真实规律而不是碰巧拟合了数据」这时候残差分析就是你的后悔药。把测试集的预测值和真实值相减得到残差序列画成折线图如果残差在零附近随机波动、没有明显趋势说明模型没有系统性偏差如果残差在某些时段持续为正或为负说明模型在那个时段有系统性高估或低估需要针对性调整。具体操作用residuals y_test - preds算出残差然后用matplotlib画residuals.plot()再加一条plt.axhline(0, colorred, linestyle--)的零线。如果发现残差在早晚高峰时段偏大说明模型对高峰的捕捉不够可以尝试增加高峰时段的样本权重或者在特征里加入「是否高峰时段」的标记。这个分析过程写进论文比单纯报一个 MAE 数字有说服力得多。另一个答辩时很实用的小技巧准备一个「极端场景」的演示。比如选一个节假日或者暴雨天展示模型在那一天的预测曲线和真实曲线的对比。如果预测偏差明显变大不要藏起来主动分析原因——节假日客流模式和平日不同模型训练时这类样本少所以预测不准。这种「知道模型边界在哪」的态度比声称「我的模型准确率 95%」更能打动答辩老师。我自己做这类项目最大的教训是不要等到系统全部做完才开始写论文。每跑通一个模块就把对应的代码片段、参数配置、结果图表整理成文档。否则到最后一周你会发现模型调参的记录找不到了特征重要性的图忘了保存只能重新跑一遍。把「可复现」当成第一优先级比追求模型精度多涨一个点重要得多。希望帮到你。本文还有配套的精品资源点击获取
返回列表