拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于LSTM的地铁AFC客流量预测:数据预处理与特征工程全解析

简介时间序列预测是数据科学中极具挑战性的任务之一其核心在于从历史数据中捕捉周期性、趋势性等规律。在轨道交通场景中AFC自动售检票系统每天产生海量刷卡流水为客流预测提供了真实、高密度的数据基础。然而原始流水无法直接训练模型数据清洗、时间聚合与特征工程往往决定最终效果。通过构建滞后特征、滑动统计特征并结合LSTM网络可有效建模客流的长短期依赖实现对未来时段进出站人数的精准预估。该技术广泛应用于运力调度、站点预警及商业决策等场景。本文以地铁AFC客流量预测为例完整呈现从原始数据到LSTM模型落地的全过程并重点剖析数据预处理的常见陷阱与工程实践细节。 先说结论这个项目我做完了代码和数据都整理好了。我直接说地铁站AFC客流量预测这件事真正难的不是模型而是数据预处理和特征工程。我从原始刷卡流水一路做到LSTM预测中间的坑踩了不少这篇就把完整思路、代码逻辑和处理细节全部摊开来讲。1. 项目背景AFC数据到底能做什么我为什么要选它1.1 AFC刷卡数据是什么长什么样AFCAutomatic Fare Collection就是地铁自动售检票系统。乘客每次进出站刷的交通卡、二维码都会在后台产生一条交易流水。这个数据最大的特点是真实、量大、带时间戳和空间信息。我用的这份数据是某城市地铁一个枢纽站的连续90天刷卡流水字段主要包括交易卡号已脱敏处理交易时间精确到秒交易类型进站/出站闸机编号票卡类型普通卡/老年卡/学生卡等别看字段少这里面的信息密度远比想象中高。比如同一时刻的进站和出站量差异直接反映了这个站的通勤属性还是商业属性节假日和非节假日的客流曲线形态完全不同。注意AFC数据涉及个人出行隐私做研究或项目演示时务必先脱敏。卡号、手机号、支付账户这类字段一律要做哈希或掩码处理这也是合规的基本要求。1.2 为什么客流量预测值得做实操对地铁运营方来说客流预测直接服务于三个场景运力调度知道早高峰几点到峰顶就能提前安排加开列车。站点客流预警预测到某时段客流超阈值提前启动限流措施。商业与运营决策广告投放、商铺招商、便利店备货都要看客流。对做数据项目的人来说客流预测是个非常好的练手题材它具备时间序列预测的所有经典难点周期性、趋势性、节假日效应、突发事件的扰动。做完这个项目你再去看其他时序预测问题电商销量、流量预测、能耗预测思路基本是相通的。2. 数据预处理90%的坑都埋在这一步2.1 原始数据清洗的四个关键动作拿到原始数据后别急着建模先把数据质量搞定。我总结为四步去重、去异常、补缺失、对齐时间。去重。AFC系统偶尔会有重复上送的情况同一张卡同一秒同一闸机出现两条记录。这个直接用drop_duplicates按全字段去重就行。去异常。我遇到过几种典型脏数据交易时间为日志服务器时间但个别闸机的系统时间没同步出现未来时间或乱序。进出站类型字段出现空值或未知值。单条记录的进出站时长异常比如进站和出站间隔超过12小时这种情况在通勤场景基本不可能。import pandas as pd df pd.read_csv(afc_data_raw.csv, parse_dates[trade_time]) df df.drop_duplicates() # 过滤掉明显异常的时间记录 df df[(df[trade_time] 2024-01-01) (df[trade_time] 2024-03-31)] # 交易类型只保留进站/出站 df df[df[trans_type].isin([entry, exit])]补缺失。AFC数据一般不会整行缺失但会出现某个时间窗口内某类交易完全为空的情况比如闸机故障、网络抖动。这种我没做插补而是直接标记出来在特征里加一个“是否断站”的辅助特征。因为盲目插补会引入不存在的曲线形态反而干扰模型学习。对齐时间。原始流水是秒级的不能直接拿来训练。需要先聚合。我一般按15分钟粒度重采样理由后面会细说。2.2 为什么不直接用逐笔流水建模这是很多新手最容易犯的错拿逐笔交易记录直接进模型。我劝你直接用聚合数据。原因很简单逐笔数据是事件流不是规整的时间序列LSTM这类模型需要固定时间间隔的序列输入。客流预测的业务口径从来都是“某段时间内有多少人进站/出站”而不是“某个人几点刷卡”。逐笔数据的样本量虽然大但噪声也大训练效率低。聚合操作用resample一行搞定# 按15分钟聚合进站、出站客流 df[time_bucket] df[trade_time].dt.floor(15min) agg df.groupby([time_bucket, trans_type]).size().unstack(fill_value0) agg.columns [entry_count, exit_count]2.3 特征工程模型效果的分水岭模型再强特征不行也白搭。我在这个项目里用的特征分三类时间特征。小时、星期几、是否周末、是否节假日。这些是客流预测的基本盘。其中星期几尤其重要周一的早高峰和周六的早高峰完全是两个量级。滞后特征。前一天同时段的客流、上周同时段的客流。地铁客流有非常强的“周相似性”今天上午9点的客流量和上周二上午9点的客流量在正常情况下非常接近。滑动统计特征。过去3小时、6小时的滑动均值、滑动标准差。这些特征能反映当前客流的趋势和波动状态比如某站附近有大型活动散场滑动均值会迅速拉高模型就能捕捉到这个“异常抬升”。经验分享做滞后特征时一定要注意时间对齐严格用历史数据生成特征禁止用到未来信息。比如预测9:00-9:15的客流滞后特征只能用9:00之前的数据不能用9:15之后的数据这是时序预测的红线踩了就是数据泄露。3. 模型设计选LSTM不选ARIMA我是怎么考虑的3.1 为什么用深度学习模型说实话纯统计方法ARIMA、指数平滑在这个项目里也能跑而且解释性更好。但它有个硬伤对多变量特征的融合能力弱。我想把天气、节假日、附近活动、站点属性这些外部信息都塞进模型ARIMA就不太方便了。LSTM的优势在于能自动学习长期依赖关系地铁路径的“周周期性”可以学到。支持多变量输入时间特征、滞后特征可以一起喂进去。推理速度快训练好之后单次预测毫秒级完全满足运营实时预测的要求。我做了一个对比实验用同样的训练数据ARIMA的MAPE在20%左右LSTM能压到12%以下。考虑到LSTM还能继续加特征优化我最终选择了LSTM。3.2 网络结构的核心设计我用的LSTM结构不算复杂但有几个细节值得说输入序列长度取48步即过去12小时预测未来4步即未来1小时。两层LSTM每层64个隐藏单元加Dropout防过拟合。输出层接一个全连接层输出4个值分别对应未来4个15分钟窗口的客流。用专业库的DataLoader做批量训练序列用滑动窗口切分。关键代码如下import torch import torch.nn as nn class FlowLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, output_size4): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropout0.3 ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): out, _ self.lstm(x) # out: [batch, seq_len, hidden] out out[:, -1, :] # 取最后一个时间步的输出 out self.fc(out) return out这里有个很容易犯的错取输出时有人会取所有时间步再做平均但对于“用过去预测未来”的任务最后一个时间步的隐藏状态已经包含了整个序列的编码信息取它就够了。3.3 数据划分和训练参数这些细节决定成败时间序列的数据划分和普通分类任务完全不一样千万不能随机打乱。我是按时间顺序划分的训练集前70天数据验证集中间10天数据测试集最后10天数据这样划分的核心原因是模拟真实场景。模型在训练时永远看不到未来的数据验证集和测试集的预测难度才真实。训练参数我试了好几组最终稳定在这个组合优化器Adam初始学习率0.001学习率调整训练15轮后衰减为0.0005Batch size64训练轮数50轮损失函数Huber Loss平滑平均绝对误差它对离群点的敏感度比MSE低对客流这种偶尔出现突发事件的数据更友好一个小技巧我用了早停Early Stopping验证损失连续10轮不降就停。省时间还能防止过拟合。4. 评估与结果分析不要只看一个指标4.1 我用了哪几个评价指标为什么客流预测常用的指标有MAE、RMSE、MAPE。我三个都算了但重点看MAPE。MAE平均绝对误差直观单位是人次方便理解预测偏差。RMSE均方根误差对大误差更敏感如果预测出现“偶尔特别离谱”的情况RMSE会很大。MAPE平均绝对百分比误差无量纲方便不同站点、不同时段之间对比。我测试集上的最终结果时段MAPEMAE人次/15分钟RMSE全天11.8%15.222.7早高峰7:00-9:009.6%35.648.3平峰10:00-16:0013.4%8.412.9可以看到一个有意思的规律早高峰客流量大绝对误差大MAE约36人次但相对误差反而低MAPE不到10%。原因很简单早高峰的客流模式非常规律模型容易学到。平峰期呢客流绝对值小稍微来几个不规律的人误差占比就上去了。这也是客流预测普遍的现象越是低流量时段MAPE越难看。4.2 预测结果的可视化怎么看我画了三张图第一张是测试集10天的真实值和预测值对比曲线。整体曲线重合度很高模型基本抓住了每天的双峰形态早高峰、晚高峰。第二张是某个工作日的逐15分钟对比散点图。大部分点落在45度线附近说明预测和真实值的一致性很好。少数偏离较远的点集中在晚高峰之后的回落段。第三张是误差分布直方图。误差基本呈正态分布中心在0附近说明模型没有系统性偏差既没有整体高估也没有整体低估。实操提示一定要看误差分布直方图而不仅仅看指标。如果误差分布出现明显的双峰说明模型在某些场景下比如周末有系统性问题需要单独优化。5. 完整代码与数据使用说明5.1 项目文件结构和运行环境我把代码和数据整理成了标准的项目结构. ├── data/ │ ├── raw/ # 原始AFC脱敏数据 │ └── processed/ # 预处理后的聚合数据 ├── src/ │ ├── preprocess.py # 数据清洗和特征工程 │ ├── train.py # 模型训练脚本 │ ├── evaluate.py # 评估与可视化 │ └── utils.py # 公共工具函数 ├── models/ # 训练好的模型权重 ├── requirements.txt └── README.md运行环境是Python 3.9核心依赖pandas 2.0numpy 1.24torch 2.0CPU版也能跑不过训练会慢一些scikit-learn 1.3matplotlib 3.7requirements.txt里都列好了pip install -r requirements.txt一键装完。5.2 三个核心脚本怎么用第一步跑预处理python src/preprocess.py这步会读取data/raw/下的原始流水输出两个文件一个是聚合好的15分钟客流序列processed/flow_15min.csv另一个是带全部特征的特征矩阵processed/features.csv。第二步训练模型python src/train.py --epochs 50 --batch_size 64训练完成后模型权重保存在models/目录下同时会在models/下输出一个训练过程的loss曲线图。第三步评估python src/evaluate.py --model_path models/flow_lstm.pt这会输出测试集上全天、早高峰、晚高峰、平峰四个时段的指标表格并生成预测对比图。5.3 核心训练代码的完整逻辑下面是train.py的核心逻辑我把注释写详细些方便你直接改参数复现import pandas as pd import numpy as np import torch from torch.utils.data import DataLoader, TensorDataset from sklearn.preprocessing import StandardScaler # 1. 加载特征数据 df pd.read_csv(data/processed/features.csv, parse_dates[time]) feature_cols [ entry_count, exit_count, hour, dayofweek, is_weekend, lag_24h, lag_168h, rolling_mean_3h, rolling_std_3h ] data df[feature_cols].values # 2. 标准化注意用训练集的均值和标准差做标准化不要用全量数据 train_size int(len(data) * 0.8) scaler StandardScaler() train_data scaler.fit_transform(data[:train_size]) test_data scaler.transform(data[train_size:]) # 3. 构造滑动窗口样本 SEQ_LEN 48 PRED_LEN 4 def make_samples(arr): x, y [], [] for i in range(len(arr) - SEQ_LEN - PRED_LEN 1): x.append(arr[i:i SEQ_LEN]) y.append(arr[i SEQ_LEN:i SEQ_LEN PRED_LEN, 0]) # 预测进站量 return np.array(x), np.array(y) x_train, y_train make_samples(train_data) x_test, y_test make_samples(test_data) # 4. 转成PyTorch张量并创建DataLoader train_dataset TensorDataset( torch.tensor(x_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.float32) ) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue)注意第2步的注释标准化只用训练集的统计量。如果拿全量数据算均值和方差相当于测试集信息提前泄露到了训练过程测试指标会虚高。5.4 数据说明脱敏后的原始数据一共有约180万条交易记录覆盖90天。我还额外提供了一份处理好的15分钟聚合数据如果你不想跑预处理全流程可以直接从这个文件开始做特征和建模。数据文件都是CSV格式UTF-8编码pandas直接读就行不用额外转换。6. 常见问题与避坑指南6.1 预测值出现“延迟效应”怎么办我第一次跑LSTM的时候预测曲线总是比真实曲线“慢半拍”尤其是早晚高峰的拐点位置预测值还在爬坡真实值已经开始掉头了。这个问题的根源是模型过度依赖滞后特征相当于在用“昨天此时段的值”做预测没有真正学到“拐点前的信号”。我的解决办法把序列长度从48缩短到32减少过长的历史依赖。增加“邻近时段变化量”特征比如“过去30分钟客流增量”让模型更关注趋势变化而不只是绝对值。用BiLSTM替代单向LSTM在时间步内做双向信息融合拐点预测效果会好一些。6.2 节假日和突发大客流怎么处理地铁客流的节假日效应非常明显节假日和普通工作日的客流曲线差异大到像是两个不同站点的数据。模型在这类日期的预测误差会急剧升高。我的处理思路是把节假日作为一个强特征传入模型同时单独判断“节假日的前一天晚上”这种特殊场景。还有一个更实用的方式是训练一个“事件修正器”先用基模型预测再用一个轻量级模型学习“基模型误差与节假日/活动的相关性”做二次修正。6.3 不同站点之间的模型能否通用有的站点是纯通勤站早高峰进站多晚高峰出站多有的是商业区站周末客流反而更高客流模式差异很大。我用单站数据训练出的模型直接迁移到另一个站点MAPE直接从11%飙到24%。这很正常。如果你想做多站泛化有两个方案把站点编号作为分类特征传入模型训练一个多站共享模型。用Transfer Learning的思路在大站上预训练在小站上微调。方案一更简单适合站点数量不多的情况。方案二效果更好但工程实现复杂度高一些。6.4 训练速度慢怎么快速迭代LSTM在这个项目里其实不大CPU上训练50轮大概需要20多分钟。但如果你要频繁调参时间成本还是高。我常用的方法是先用小规模数据跑通流程比如只用30天数据、训练10轮确认代码没问题后再全量训练。这能省掉大量“调bug时全量训练”的等待时间。另外一个提速技巧是把原始序列的采样粒度从15分钟改成30分钟序列长度减半训练速度接近翻倍。精度损失很小但迭代速度大大提升。6.5 数据泄露你以为没泄露但实际泄露了这是时序预测最常见的隐藏问题。我举一个真实踩过的坑我给模型加了“当天截至当前时刻的累计进站量”作为特征。看起来没问题但我发现测试集效果异常好好到让我怀疑。排查后发现我在构造特征时没有区分训练集和测试集的累计起点直接把整个测试期的数据也算进去了。正确的做法是累计特征必须从当天0点开始且只用截止到预测时刻的数据绝不能看到未来的数据。数据泄露在时间序列里往往不报错但它会让你的模型“看起来很好”部署到线上就原形毕露。7. 这个项目做完后还能往哪些方向扩展我做完之后回头复盘这个项目其实只用了AFC数据里最基础的那部分——进出站流水。往深处挖还有很多值得做的方向。第一个方向是OD分析。AFC数据里同时有进站站点和出站站点组合起来就是完整的OD流。预测OD矩阵比预测单站客流量复杂得多但对运营决策的价值也高得多。比如知道“从A站到B站的高峰期有多少人”就能精确规划区间车的停靠方案。第二个方向是突发事件的检测与预警。当某个站点的实际客流显著偏离模型预测时大概率是有异常事件发生。这可以做成一个实时的异常检测系统把模型的预测值和实际值之间的残差作为判断依据。第三个方向是把空间信息加进来。一个站点的客流往往受到周边站点客流的影响尤其是换乘站。用Graph Neural Network把整个线网建模成一张图每个节点是一个站点边是站点之间的关联度理论上比单站模型能学到更多空间维度上的信息。我自己目前在做的是第一个方向OD矩阵的预测。工作量比单站预测大得多但这也是AFC数据真正值钱的地方。最后说一点体会数据项目做到后面决定上限的往往不是模型而是对业务的理解深度。AFC数据里每个字段背后都有真实的运营逻辑多和地铁运营人员聊比多调几个参数收获更大。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门