尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

LSTM交通客流预测:从数据预处理到多步预测的完整实践

发布时间:2026/9/29 16:47:21

资讯中心
01
ARTICLE

LSTM交通客流预测:从数据预处理到多步预测的完整实践

LSTM交通客流预测:从数据预处理到多步预测的完整实践
简介针对地铁站点日常客流预测需求这份基于LSTM的交通客流预测压缩包给出了从数据处理到模型训练与可视化的完整方案。项目以2019年某地铁站平日客流量及天气因素为输入按8:2划分训练集与测试集借助numpy、pandas完成预处理再通过sklearn、LSTM神经网络建模预测并利用matplotlib、seaborn输出结果适合机器学习初学者与交通数据分析人员学习参考。压缩包共17个文件含5个csv原始数据、2个h5训练模型、1个py预测脚本还有doc/ppt/xlsx等说明与展示材料整体3.95MB。目前已有1292人浏览学习。通过该资源可系统掌握客流量预测的完整流程获得可直接运行的代码、预训练模型及答辩PPT能够快速复现实验并延伸应用到同类场景。1. 基于 LSTM 的交通客流预测资源包一个能直接跑通的时间序列项目如果你做过客流预测一定见过这种场景早高峰地铁闸机的出站量、节假日前一天的高速收费站车流、商场促销时的进店人数波动大、周期强、突发情况多用传统 ARIMA 或移动平均很难追上突变。这份《基于LSTM交通客流预测.zip》正是一个把 LSTM 应用到短时客流预测的完整资源核心链路是「时序数据 → 滑窗构造样本 → LSTM 训练 → 反归一化评估」不依赖第三方付费数据源用公开的客流序列就能复现。适合三类人刚入门时间序列预测的学生想把 LSTM 落到实际业务但不想从零搭框架的数据工程师以及需要一份基线模型做对比的实验人员。2. 数据预处理把原始客流序列变成模型能吃的时间窗LSTM 吃的是「序列」不是单条记录。原始数据通常是按 15 分钟或 1 小时间隔记录的客流数值比如[2024-01-01 08:00, 352]。这个结构离模型需要的输入形状还差两步构造滑窗、做归一化。顺序不能反先滑窗再归一化是常规做法但更稳的是先归一化再滑窗理由后面第五章节会说。2.1 拿到数据先别急着建模型先做缺失值与异常值客流数据最常见的脏数据有两种。第一种是设备断传导致的整段缺失常见于闸机或摄像头采集链路表现为连续数小时数值为 0 或直接没有记录第二种是瞬时尖峰比如某天早高峰因为列车晚点导致出站量突然翻倍这类点不一定是噪声但对训练影响很大。我的处理习惯是先做探索性可视化把序列画出来用肉眼确认缺失段和异常段的位置再决定填充策略。处理缺失值时客流序列按周和按天都有明显周期性所以用前一周同一天同时段的均值填充比用线性插值更符合业务规律。异常值用滚动中位数检测以 24 个点为一个窗口如果某个点的值超过窗口内中位数的 3 倍或低于其 1/3就标记为异常然后用前后两个正常点的均值替换。注意这里不要用整体均值替换节假日或促销日的高峰值一旦被整体均值拉平模型训练出来会系统性低估峰期客流。import pandas as pd import numpy as np def load_and_clean(df): # df 必须包含两列: timestamp 和 flow df[timestamp] pd.to_datetime(df[timestamp]) df df.set_index(timestamp).sort_index() # 缺失填充: 前一周同一时刻的均值 df[flow] df[flow].replace(0, np.nan) # 0 值当作缺失处理 for idx in df[df[flow].isna()].index: week_ago idx - pd.Timedelta(days7) df.loc[idx, flow] df.loc[week_ago, flow] # 异常值替换: 滚动中位数 3 倍阈值 median df[flow].rolling(24, min_periods1).median() diff (df[flow] - median).abs() threshold median * 3 df.loc[diff threshold, flow] median.loc[diff threshold] return df这段代码两条核心逻辑缺失值用「上周同期」语义填充而不是简单的前向填充这是考虑到了客流数据的周期属性异常值检测用的是滚动中位数而非滚动均值因为中位数对尖峰本身不敏感不会出现「均值被尖峰拉高、结果尖峰反而看起来正常」的自我矛盾。rolling(24)里的 24 对应小时粒度下的一天如果你数据是 15 分钟粒度就填 96。2.2 滑窗构造样本window 大小和步长的取舍逻辑滑窗是把连续序列切成「过去 N 个时间点预测未来 M 个时间点」的样本对。窗口大小是这份资源里最值得调的第一个参数。我的经验法则窗口至少覆盖一个完整的日周期小时粒度至少 2415 分钟粒度至少 96如果数据包含周末和工作日两种模式可以考虑把窗口拉到 48 小时让模型看到跨天规律。但窗口不是越大越好LSTM 对这种长序列的记忆能力有限超长窗口反而会引入大量无关历史信息把训练时间拉长的同时还不见得提升精度。步长step控制样本的重叠程度。步长等于 1 时相邻两个样本只错开一个时间点数据量最大但相邻样本几乎一样训练耗时高步长等于预测长度时样本完全不重叠数据量小但能覆盖更长的时间跨度。我的默认配置是窗口 48、步长 24这样既保留了日周期特征又不会让训练集膨胀到十万级。下面是滑窗构造的核心代码。def create_sequences(data, window48, step24, pred_len1): X, y [], [] # 按时间顺序滑动不能打乱 for i in range(0, len(data) - window - pred_len 1, step): X.append(data[i : i window]) y.append(data[i window : i window pred_len]) return np.array(X), np.array(y) # 归一化必须在滑窗之前且 fit 只能用在训练集上 from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_raw.reshape(-1, 1)) val_scaled scaler.transform(val_raw.reshape(-1, 1)) X_train, y_train create_sequences(train_scaled.flatten(), window48, step24) X_val, y_val create_sequences(val_scaled.flatten(), window48, step24)create_sequences返回的X形状是(样本数, window, 1)第三个维度是特征数单变量预测时就是 1。这里有个新手最容易忽略的点scaler.fit_transform只对训练集做验证集和测试集用transform让验证集的数据分布「模型没见过」评估结果才可信。归一化到 [0,1] 是 LSTM 这类梯度敏感模型的标准做法客流数值动辄几千上万的量级如果直接喂给模型激活函数的梯度会迅速饱和。3. 模型训练搭 LSTM 网络与调参的落地路径数据处理完下一步就是把滑窗样本喂进 PyTorch 的nn.LSTM。这章先说网络结构怎么定再说训练循环怎么写全程按能跑出合理结果的标准来配置不给花活。3.1 网络结构nn.LSTM 四个关键参数的选型nn.LSTM里有四个参数直接决定模型容量input_size、hidden_size、num_layers、batch_first。input_size等于每个时间步的特征维度单变量客流预测就是 1如果你加了天气、节假日特征这里就变成特征总数。hidden_size是隐状态维度客流预测任务 64 到 128 之间是甜点区间太小拟合不了周期性波动太大容易过拟合且训练时间成倍上涨。num_layers一般取 1 或 2客流序列的规律还没复杂到需要三层以上堆叠。batch_firstTrue让输入形状变为(batch, seq_len, input_size)符合直觉代码不容易绕晕。需要注意nn.LSTM的输出是一个元组(output, (h_n, c_n))做单步回归时常规做法是取最后一层在最后一个时间步的隐状态h_n[-1]再过一个全连接层把维度压到预测长度。下面是我在这类任务上常用的一种结构。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, output_size1, dropout0.2): super().__init__() self.lstm nn.LSTM(input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch, window, input_size) out, (h_n, c_n) self.lstm(x) # h_n[-1]: 最后一层最后一个时间步的隐状态 last_hidden h_n[-1] # (batch, hidden_size) pred self.fc(last_hidden) # (batch, output_size) return prednum_layers2时如果dropout传 0.2PyTorch 只会在两个 LSTM 层之间丢不会作用于输出层这个dropout是层间随机失活不是给输入数据加噪声理解错容易误调。last_hidden h_n[-1]这个写法的含义是取最深那一层在所有 batch 样本上的最后时刻隐状态它保留了整个输入序列压缩后的信息是序列到单点回归的标准取法。3.2 训练流程损失函数、优化器与早停客流预测本质是回归任务损失函数用均方误差nn.MSELoss()即可它对大误差值比如峰期预测偏差几百人的惩罚呈平方级放大倒逼模型把峰期拟合得更准。优化器选 Adam学习率默认 0.001这是时间序列任务里最不容易翻车的起手配置。训练循环里比「前向传播」更需要留意的是数据形状要对齐X_train的形状是(样本数, 窗口, 特征数)但到了模型里 PyTorch 自动把第一维当 batch这正好对应设计。早停early stopping建议自己写一个最简版本盯验证集损失连续 20 个 epoch 没有下降就保存历史最优权重并终止训练。客流数据本身噪声大训练集损失降到很低也不代表泛化验证集才是模型真实能力的照妖镜。def train_model(model, X_train, y_train, X_val, y_val, epochs100, lr0.001, patience20): optimizer torch.optim.Adam(model.parameters(), lrlr) criterion nn.MSELoss() best_val_loss float(inf) wait 0 for epoch in range(epochs): model.train() optimizer.zero_grad() pred model(X_train) loss criterion(pred, y_train) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_pred model(X_val) val_loss criterion(val_pred, y_val).item() # 早停: 验证损失连续 patience 轮不降则终止 if val_loss best_val_loss: best_val_loss val_loss wait 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: break model.load_state_dict(torch.load(best_lstm.pt, weights_onlyTrue)) return modeloptimizer.zero_grad()必须在loss.backward()之前漏掉这行梯度就会跨 batch 累积训练损失曲线会呈现不规则的锯齿。每个 epoch 结束切到model.eval()再算验证损失是为了让 dropout 层在验证阶段不生效否则验证指标每次前向传播都带随机性没法做早停判断。torch.save保存的是网络参数训练完用load_state_dict恢复到验证集最优状态避免因最后几轮过拟合把模型带偏。4. 评估验证RMSE、滞后效应与预测结果的可信度训练跑通只是第一步模型到底能不能用要看它在测试集上的表现。这章讲三件事指标怎么算、预测曲线怎么看、以及评估里最容易被忽略的滞后效应。4.1 评估指标RMSE、MAE 与 MAPE 各看什么评估 LSTM 客流预测推荐同时算三个指标各有侧重点只看一个容易自欺欺人。RMSE 对大幅偏差敏感能反映峰期预测失误的严重程度MAE 是平均绝对误差单位与人次一致最直观MAPE 是相对误差但由于客流分母可能接近 0深夜低谷时段会导致 MAPE 虚高所以建议只算白天时段的 MAPE。计算指标前必须先把预测结果做反归一化inverse_transform直接在 [0,1] 尺度上算误差得到的数值没有任何业务含义。常见错误是把验证集的预测值和真实值各自inverse_transform但注意真实值在归一化时是按「整体训练集最大最小值」缩放的反变换时也要用同一个 scaler不能重新 fit。def evaluate(y_true, y_pred): # y_true, y_pred 传入时是原始人次尺度 rmse np.sqrt(np.mean((y_true - y_pred) ** 2)) mae np.mean(np.abs(y_true - y_pred)) mask y_true 0 mape np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100 return {RMSE: round(rmse, 2), MAE: round(mae, 2), MAPE: round(mape, 2)} # 反归一化示例 pred_org scaler.inverse_transform(pred_scaled.reshape(-1, 1)).flatten() true_org scaler.inverse_transform(y_val_scaled.reshape(-1, 1)).flatten() print(evaluate(true_org, pred_org))mask y_true 0这行是为了剔除深夜零客流的时段否则这些时段真实值可能是 5、10预测值即使只差 3 人次MAPE 也会暴涨到 60% 以上直接把整体指标搞失真。RMSE、MAE、MAPE 三个值建议同时看如果 MAE 不大但 RMSE 明显偏高说明存在少数预测极差的时段这些时段大概率集中在早晚高峰需要单独拿出来分析。4.2 滞后效应评估图里最容易被忽视的黑匣子训练完成、指标也打印了但如果你只是埋头看数字很可能漏掉最严重的问题——预测曲线滞后。把预测值和真实值画在同一张图上如果预测曲线整体比真实曲线「晚了一两个时间点」像贴着真实曲线平移了一段距离说明模型学到的是「复制前一个时刻的值」而不是真正预测未来。这种现象在客流预测里极其常见尤其是单一客流序列输入时模型发现最优策略就是把最后的观测值原样输出因为客流本身有强自相关性这么做损失函数已经很低了。判断方法很简单计算预测序列和真实序列的交叉相关性看峰值出现在哪个 lag 上更直接的办法是把预测结果整体向前平移一个时间点再算一次 RMSE。如果平移后误差显著变小基本可以确认模型在偷懒。解决滞后效应有两个方向一是把窗口拉开到 48 甚至 72让模型有更多历史信息去做模态判断而不是贴身复制二是加入多步预测损失让模型同时输出未来 1、2、3 步的预测值从训练信号上逼它学到趋势而非复制。这段属于我自己的血泪经验早先做第一个客流模型时指标好看到不行画图才发现全滞后了一拍。5. 避坑指南客流序列在 LSTM 里的五个翻车点这章集中写我复现这类资源时踩过、以及周围同事踩过的具体坑。每一条都是「现象 → 原因 → 解决」三步走建议在自己机器上跑的时候逐条对照。5.1 数据泄漏归一化 fit 到了全量数据现象验证集指标极好RMSE 低得离谱但一到上线或者换新数据预测就崩盘。原因写代码时图省事先对整个数据集做了MinMaxScaler.fit_transform再接滑窗和划分训练验证。这样验证集的最小最大值已经被模型在归一化阶段「见过」了数据分布的边界信息泄漏到了训练流程里评估结果虚高。解决严格按「先划分训练/验证/测试再在训练子集上fit验证集和测试集只做transform」。我在第 2.2 节的代码里就是这个顺序照抄就不会踩。数据泄漏是这类时序项目里最隐蔽的坑因为指标不会报错只会给一个不真实的好结果。5.2 验证集划分用了随机打乱现象训练损失正常下降验证损失也正常但模型在测试集上一塌糊涂而且没有滞后问题的曲线也看不出明显原因。原因train_test_split默认shuffleTrue把时间序列随机抽成了训练集和验证集。LSTM 训练时验证集里混着训练集时刻前后的样本相当于让模型提前「看了看」未来数据周期规律被完整泄露。解决时序项目的划分强制按时间序训练集取前 70%~80%验证集取再往后的 10%测试集取最后 10%。如果担心季节覆盖不全可以按「滚动划分」做多轮评估但每轮内部依然严格按时间切分不能用随机抽样的思路。5.3 训练损失降了验证 MAPE 却极高现象训练集 RMSE 很小验证集 RMSE 也不大但业务方问「平均误差百分之几」时MAPE 一算 30% 以上直接没法看。原因前面提过夜间低谷时段的真实客流可能只有 20 人次预测值 35 人次绝对误差只有 15RMSE 贡献微乎其微但 MAPE 是 75%。如果按全天 24 小时包含低谷一起算MAPE 被几个深夜点拉爆。解决评估指标按业务时段区分。做交通客流运营预测重点关注早高峰 7:00~9:00、晚高峰 17:00~19:00 和全天非低谷时段分组计算 MAPE。深夜段用 MAE 衡量即可不要混在一个数里。5.4 LSTM 输入形状 shape 报错三维变二维的维度错位现象model(X_train)报错Expected 3D input, got 2D或者到了nn.Linear时报 hidden_size 和输入维度对不上。原因滑窗构造时X_train是(样本数, window)的二维数组而nn.LSTM要求(batch, seq_len, input_size)三维输入。另一种情况是取隐状态时用了h_n而不是h_n[-1]导致 fc 层的输入维度变成了(batch, num_layers, hidden_size)的展平值。解决构造完序列后做一次X_train X_train.reshape(-1, window, 1)确认形状取最后隐状态写h_n[-1]这是 last layer 的 hidden state维度正好是(batch, hidden_size)和前面的nn.Linear(hidden_size, output_size)能对上。5.5 多特征拼接时忘了对齐时间戳现象加了天气温度、是否是节假日这些特征后训练直接崩或者 Loss 变成 NaN。原因特征表和客流表的索引没有对齐比如客流是 15 分钟粒度天气是小时粒度两者直接concat后产生了时间偏移模型读到的「当前时刻温度」其实是 45 分钟前的。解决先统一时间粒度客流 15 分钟粒度时天气特征按小时向前填充forward fill到 15 分钟粒度节假日特征要保证按天对齐不要用未来日期的标签。拼接前打印df.shape和df.isna().sum()确认没有形状错位。6. 多步预测与多特征融合把单步预报扩展成实用方案前面整条链路做的是「输入 48 个小时预测未来 1 个小时」但实际业务里更常见的问题是「现在下午 4 点想预测今晚 6 点到 8 点的出站客流」。这就涉及多步预测有两条路线递归预测和直接多输出。递归预测的策略是把上一步的预测值当作下一步的输入循环预测到目标步数实现简单但误差会逐步累积预测到第 6 步以后曲线会趋于平滑因为模型把不确定性平均化了。直接多输出则是修改模型最后一层把output_size设为待预测步数一次输出多步结果。这两种方案各有场景做未来 1~3 小时的客流调配建议用直接多输出模型一层输出就能拿到完整预测曲线。注意直接多输出时前面滑窗构造的pred_len要相应调大训练标签y的形状变为(样本数, pred_len)。多特征融合是把预测质量往上提一档的关键。单用客流序列训练模型本质上只能学「过去的客流模式在未来重演」遇到节假日、暴雨天气这些外部扰动必然失效。常见做法是加入 4 个特征是否为工作日、是否为节假日、小时序号0~23 的正弦/余弦编码、天气等级0~3 映射。特征拼接的时机是在归一化之后把所有特征与客流序列一起构成input_size5的输入。加入外部特征后滞后效应通常会减轻因为模型不再只依赖历史客流做贴身复制而是能感知「今天是工作日且下雨」这种全局状态。在那之后我跑客流预测养成了一个习惯每换一个数据集或改一个特征都强制先看一眼预测对齐图再算指标。图和数字一起通过才敢把结果拿出去。模型的结构、参数、数据处理全都可以在这一份资源包里直接改希望这份拆解能帮你在自己的客流数据上少走几段弯路。# 直接多输出 多特征的训练入口示意 # 假设 feature_cols 已与客流序列按时间对齐并归一化 X_multi, y_multi create_sequences(multi_feature_data, window48, step24, pred_len4) model LSTMPredictor(input_sizeX_multi.shape[2], hidden_size128, num_layers2, output_size4) model train_model(model, X_multi, y_multi, X_val_multi, y_val_multi, epochs80, lr0.001, patience15)这份资源包里附带的数据集结构、模型初始参数和训练脚本都是以此为基准写的。把input_size、hidden_size、window三项按你的数据量做加减法就能从一个「能跑的 demo」变成一个「业务上敢用的预测模块」。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。