尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

共享单车需求预测建模全攻略:从数据处理到LightGBM实战

共享单车需求预测建模全攻略:从数据处理到LightGBM实战 共享单车需求预测这道题在数学建模竞赛里出现的频率非常高国赛、华为杯、美赛都考过类似的背景。它属于典型的时空预测问题看着门槛不高但想拿高分并不容易——很多队伍在“预测”这一步就翻车了导致后面调度、定价、投放策略全是空中楼盘。我当年带队打比赛时也在这个题上踩过不少坑今天专门把“建模篇”这部分摊开讲清楚从问题定义、数据处理、模型选型到实操代码和避坑指南一次性说透。这篇内容适合正在备战国赛、华为杯或美赛的同学也适合想用机器学习做城市级需求预测的从业者。不管你打算用传统时间序列、LightGBM这类表格模型还是想尝试图神经网络这篇文章都能给你一条可以直接落地的路线。1. 先想清楚这道题到底在预测什么1.1 需求预测的三个层级很多队伍拿到“共享单车需求预测”的题第一反应就是“用LSTM跑一跑”。这个想法本身没问题但问题是你得先搞清楚要预测的是哪个粒度的需求。从比赛和实际业务的角度来看需求预测通常分三个层级站点级别预测某个具体站点的借车量或还车量。难度最高数据稀疏受单点因素影响大。网格/区域级别把城市划分成网格预测每个格子里的需求。中等难度是很多城市级调度系统的实际做法。城市总量级别预测全城或者某个大区的总需求。难度最低适合做宏观趋势分析但很难支撑具体调度决策。读题时第一件事就是判断题目要求落在哪个层级。我之前见过有队伍把题目理解错题目要求预测站点级租借量他们直接对全城总量建模最后评委问“你这预测结果怎么指导单站点调度”彻底答不上来。1.2 一个容易忽略的边界问题时间粒度怎么选时间粒度直接决定建模方式。共享单车数据常见的粒度有逐小时24点/天高峰期粒度早晚高峰单独建模逐15分钟高频数据逐小时是大多数比赛的标准粒度。但这里有个隐藏问题你要预测的是一天的某个时段还是未来多天同一时段是滚动预测还是单步预测这决定了你的特征矩阵怎么构建、验证集怎么划分。我的经验是先把预测目标固定下来写成一句话。比如“基于过去14天的逐小时数据预测未来24小时内每个站点的借车数量”。目标越明确后面每一步决策越不容易跑偏。1.3 读题阶段就要画好的“预测闭环”建模不只是建一个模型。完整的预测闭环包含原始数据清洗与对齐特征工程时间、天气、空间、历史统计模型训练与调参预测结果输出与后处理误差分析分站点、分时段、分天气比赛时间紧很多人会把精力全砸在第3步结果特征没做好模型再强也白搭。真正拿高分的队伍往往在前两步和最后一步花了大功夫。我在备赛时习惯先画一张“数据流图”把每一步输入输出写清楚。别小看这个环节它能帮全队对齐思路省掉后期大量返工。2. 建模前的数据工程决定上限的不是算法2.1 共享单车数据集里有哪些“坑”共享单车数据坑很多最典型的几类时间戳不是标准格式有的精确到秒有的精确到分钟需统一对齐。站点经纬度缺失或漂移有的站点位置明显有误。天气数据粒度不一致气象站数据是逐小时的但偶尔有空档。站点新建/拆除导致ID不连续数据分布突变。异常值极少数站点的订单量突然变成0可能是因为设备故障而不是真没人骑。这些坑如果不在建模前处理干净后面所有分析都会失真。我见过有队伍预测出的某个站点全天需求为0就是因为原始数据里这个站点当天有大量空值他们直接删行处理了。2.2 构建特征让模型认识“潮汐”和“天气”共享单车需求最明显的模式是“潮汐现象”早高峰从住宅区流向办公区晚高峰反向流动。模型不知道这个规律但我们可以把规律“翻译”成特征。我用过的特征体系大致分三类第一类是时间特征。小时0-23、星期几、是否周末、是否节假日、距离最近节假日的天数、一年中的第几天。这些看起来简单但对模型区分“工作日早高峰”和“周末下午闲逛”至关重要。第二类是天气特征。温度、体感温度、风速、降水、天气类型晴/雨/雪。天气对骑行影响极大尤其是降雨。我建议把降水做成二值特征是否降水和一个连续特征降水量同时喂给模型效果比单一的降水等级好很多。第三类是历史统计特征。过去7天同一小时的平均需求、过去24小时的累计需求、前一天同一站点同一时段的需求量。这相当于给模型“抄作业”的机会因为周期性是共享单车需求最强的信号。2.3 时空特征怎么落到表格里很多同学一听到“时空数据”就以为必须用张量或者图结构。其实对于比赛来说先把时空特征展平成表格用LightGBM就能跑出不错的结果。具体做法是每一行是一个(站点小时)对列包括该站点的经纬度、该站点历史需求、周边POI数量如果数据提供、该时刻的时间特征和天气特征。如果你拿到的数据里有站点之间的骑行OD起终点那还能构建图结构站点是节点OD流量是边权。这种数据可以喂给GNN模型但对大多数比赛来说不是必须的。2.4 训练集/验证集划分的时序陷阱这是很多新手最容易犯的错误直接用随机划分的方式切分训练集和验证集。但时间序列数据一旦随机打乱模型会“偷看未来”验证集上的表现会虚高真实场景下完全不可复现。正确的做法是按时间顺序划分比如用前21天训练、第22到28天验证、最后几天测试。如果数据量足够还可以做多折时序交叉验证每一折的训练集都严格早于验证集。另外还要注意一个细节如果预测目标是多个站点划分时不能把所有时刻随机划分必须保证同一时刻的所有样本都进同一侧否则会引入信息泄漏。3. 三套主流建模路线对比共享单车需求预测的方法论这些年基本收敛为三套路线下面我逐一拆解它们的适用场景和优劣。3.1 路线一统计基线法——Holt-Winters和SARIMA别觉得统计方法过时它们最大的意义是提供“基线”。在比赛中基线分数决定了你后续模型到底是真有效还是“自我感动”。Holt-Winters适合捕捉趋势和季节性SARIMA适合有明确周期的时间序列。共享单车数据有以“天”为周期的强季节性所以SARIMA的(季节性部分)设置对结果影响很大。这类方法的优点是计算快、可解释性强评委容易看懂。缺点是难以加入天气、节假日等外部变量不是不能加但很麻烦而且对站点级别的稀疏数据非常不友好。我的建议是不管最后用什么高级模型先跑一个SARIMA或者周期性均值填充作为Base。如果LSTM或GBDT连这个Base都打不过说明你的特征或实现有问题及时止损。3.2 路线二机器学习表格流——LightGBM和XGBoost这是我认为比赛性价比最高的路线。LightGBM对表格数据极其友好训练快、不容易过拟合、还能自动处理缺失值。XGBoost效果类似但LightGBM在大数据量下更快。表格流的核心优势是特征工程灵活。你可以轻松加入天气、节假日、站点属性、历史统计等各种特征。我试过在同样的数据上LightGBM加好特征工程之后比单纯LSTM提升10%以上的精度。具体参数方面我个人用得比较顺的LightGBM配置大致是n_estimators 1000左右learning_rate 0.05num_leaves 31max_depth 7subsample 0.9colsample_bytree 0.9。当然具体还是要靠验证集调。3.3 路线三时空图网络——什么时候才值得上如果把站点当节点、骑行流量当边共享单车系统天生就是一个动态图。用GNN图神经网络可以显式建模站点之间的空间依赖这是LightGBM做不到的。但这部分我劝大家谨慎。GNN的调参难度和训练成本都不是比赛阶段能轻松驾驭的。如果数据量不大GNN的优势根本体现不出来如果代码不熟很容易在这上面浪费大量时间。我的建议是如果题目明确给了OD数据或者站点规模超过500个且你们队伍有余力可以考虑把GNN作为一个“亮点模型”写进论文里。否则用LightGBM作为主模型把空间信息通过“周边站点平均需求”这种特征表达出来性价比更高。3.4 三条路线的取舍与组合策略最优的做法不是选一条路线死磕到底而是“多模型融合”。我常用的组合是用SARIMA生成一列预测值作为LightGBM的一个额外特征。用LightGBM跑出主预测结果。如果时间充裕用LSTM或者GNN生成第二个预测值再做加权平均。模型融合的提升往往比单纯调参靠谱得多。但要注意融合的模型之间差异要大才有意义两个结构几乎一样的模型融合纯属浪费算力。4. 从0到1手把手跑通一个LightGBM预测流程4.1 整体流程拆解下面我以一个模拟的逐小时站点级预测任务为例演示完整的建模流程。假设我们有订单表每一行是一次骑行记录包含开始时间、开始站点、结束时间、结束站点。站点表站点ID、经纬度。天气表逐小时温度、降水、风速。我们要预测的是每个站点在每个整点时刻的借车数量未来24小时滚动预测。整体流程分五步数据聚合把订单表按站点和小时聚合成“每小时借车量”。合并特征把时间、天气、历史统计特征拼接到聚合表上。时序切分按时间顺序划分训练集和验证集。LightGBM训练与调参。预测并反推回原格式。4.2 数据聚合与特征构建的代码实现import pandas as pd import numpy as np from datetime import datetime # 1. 读取原始订单数据 orders pd.read_csv(orders.csv, parse_dates[start_time]) stations pd.read_csv(stations.csv) weather pd.read_csv(weather.csv, parse_dates[time]) # 2. 聚合按站点小时统计借车量 orders[hour] orders[start_time].dt.floor(H) rental_demand orders.groupby([station_id, hour]).size().reset_index(namecnt) # 3. 合并站点静态信息 rental_demand rental_demand.merge( stations[[station_id, longitude, latitude]], onstation_id, howleft ) # 4. 提取时间特征 rental_demand[hour_of_day] rental_demand[hour].dt.hour rental_demand[day_of_week] rental_demand[hour].dt.dayofweek rental_demand[is_weekend] (rental_demand[day_of_week] 5).astype(int) # 5. 合并天气特征 weather[hour] weather[time].dt.floor(H) rental_demand rental_demand.merge( weather[[hour, temperature, precipitation]], onhour, howleft ) # 6. 构造历史统计特征过去7天同一小时同站点的需求量 rental_demand[hour_lag] rental_demand[hour] - pd.Timedelta(days7) past_data rental_demand[[station_id, hour_lag, cnt]].rename( columns{hour_lag: hour, cnt: cnt_lag7} ) rental_demand rental_demand.merge(past_data, on[station_id, hour], howleft)这段代码里有几个细节值得说。第一使用floor(H)把时间对齐到整点避免同一个骑行记录前后相差几分钟导致聚合不一致。第二历史特征我用了“过去7天同一小时”这是因为共享单车的周周期性非常强周期特征比原始滞后特征更稳。第三天气数据的时间列也必须做同样的 floor 处理否则 join 的时候会大量掉线。4.3 训练集/验证集切分与模型训练from sklearn.model_selection import TimeSeriesSplit import lightgbm as lgb from sklearn.metrics import mean_absolute_error # 按时间排序并指定特征列 rental_demand rental_demand.sort_values([hour, station_id]).reset_index(dropTrue) feat_cols [hour_of_day, day_of_week, is_weekend, temperature, precipitation, longitude, latitude, cnt_lag7] # 时序切分最后7天作为验证集 cutoff rental_demand[hour].max() - pd.Timedelta(days7) train rental_demand[rental_demand[hour] cutoff].dropna(subsetfeat_cols) valid rental_demand[rental_demand[hour] cutoff].dropna(subsetfeat_cols) # 训练LightGBM model lgb.LGBMRegressor( n_estimators1000, learning_rate0.05, num_leaves31, max_depth7, subsample0.9, colsample_bytree0.9, random_state42 ) model.fit( train[feat_cols], train[cnt], eval_set[(valid[feat_cols], valid[cnt])], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] ) # 验证集评估 pred model.predict(valid[feat_cols]) mae mean_absolute_error(valid[cnt], pred) print(fValidation MAE: {mae:.4f})这里我用了dropna而不是填充。原因是cnt_lag7如果缺失说明过去7天该站点没有历史数据这类样本的特征本身不可靠直接剔除比填0更干净。如果历史数据丢失的站点较多再考虑用所有站点的中位数填充。early_stopping(50)这个参数非常重要它能在验证集指标连续50轮不提升时自动停止训练防止过拟合也帮你省掉手动找最优迭代次数的麻烦。4.4 结果解读与后处理预测结果出来后别急着写论文。先做两件检查第一检查预测值是否有负值。LightGBM输出的回归值理论上可能为负但租车量不可能小于0。直接把负值截断为0是标准做法。第二分时段看误差。比如早晚高峰的误差是否远大于夜间如果高峰误差大说明特征里还缺“站点承载能力”或者“周边竞争站点”的信息。再比如雨天误差大说明天气特征可能需要更细的粒度。我把这几步走完通常在竞赛类数据上能把MAE压到基线的60%左右。这时候再去优化结构或者上深度学习才是合理的节奏。5. 竞赛实战中的常见问题与排查5.1 指标老是上不去先查这三个地方模型预测分数一直提不上去时很多人第一反应是换模型或者调参。但根据我的经验应该按以下顺序排查第一个排查点是特征泄漏。检查验证集划分是否混入未来信息检查历史统计特征是否包含预测时刻之后的数据。特征泄漏会让训练集和验证集上的表现差异产生矛盾——训练集得分异常好验证集却很差。第二个排查点是目标分布。如果某些站点需求极度稀疏比如绝大多数小时都是0模型会倾向于全预测0因为这种预测的平均误差最小。这种情况下可以考虑分站点建模或者用泊松回归设置适合“计数型”目标的损失函数。第三个排查点是特征粒度。共享单车需求的空间关联性很强如果你的特征矩阵里没有“周边站点需求均值”或“最近站点距离”空间信息就完全缺失了。这时可以构造一个“站点热度”特征以该站点为中心半径500米内所有站点在该小时的平均需求效果非常明显。5.2 特征泄漏的典型表现和自查方法特征泄漏是比赛中容易丢分又不容易察觉的问题。我总结几个典型表现用未来的天气数据做预测。题目如果只要求预测未来24小时但天气数据给了未来72小时你可以用但必须说明“假设未来天气可精确预报”。如果不说明评委大概率当你不严谨。用预测时段内的实际需求构造历史统计特征。比如你想预测第28天却用了第28天当天中午的数据来构造早高峰特征这属于铁板钉钉的泄漏。用目标值本身做标准化。有些队伍把整个数据集的均值方差存下来对测试集也用训练集的统计量这本没错但如果不小心用验证集和测试集的数据一起算统计量就是泄漏。自查方法很简单把特征列一个个过一遍问自己“如果在预测时刻这个特征的数值是否已经真实存在”。只要答案是“否”就必须删除或替换。5.3 参数调优心得LightGBM调参不需要狂跑随机搜索。我通常按这个顺序调先调num_leaves和max_depth这两个控制模型复杂度。num_leaves太大容易过拟合尤其在训练数据少的情况下。再调learning_rate和n_estimators它们是一对组合低学习率加早停通常能拿到稳定结果。最后调subsample和colsample_bytree这俩是防过拟合的调节阀。另外分享一个细节如果你发现模型在验证集上存在“高峰时段误差大、夜间误差小”的现象——注意文本中的“高峰”是指峰值时段——不需要强行调参可以针对高峰时段单独训练一个模型然后用加权平均融合效果往往比全局调整好。5.4 优秀论文里的“加分写法”模型跑完只完成了一半工作。比赛是“算法50%论文50%”论文写不好模型再强也可能被埋没。我在几篇获奖论文里看到过几个共性写法值得借鉴第一画图要抓核心矛盾。模型精度用时间序列折线图展示不用全画抽一周数据把真实值和预测值叠在一起突出高峰时段的拟合效果。再画一张站点预测误差热力图让评委一眼看出误差集中在哪里。第二把“为什么选这个模型”写清楚不要只是甩形状。比如“因为共享单车需求具有强周期性而LightGBM可以通过滞后特征有效捕捉这种周期因此选用表格模型而非深度网络”这比“LightGBM效果很好”有说服力得多。第三写清楚你的方法在计算资源上的成本。评委很看重实用性如果你们用了GNN要写明训练时长和推理时长如果用了融合模型要写明各模型的权重及确定过程。这些细节能看出队伍真正想清楚了问题。写在最后的几条实战体会这套流程我自己带队伍用过两届也在复盘其他获奖论文时反复对照过。最大的体会是共享单车需求预测这道题真正的难点不是模型有多深多新而是你能不能在没有明确指导的情况下自己把数据到预测结果的链路走通。建模只是一个环节数据理解了会占到一半以上的工作量。再分享一个小技巧预测结果出来后可以多做一个“可视化巡检”而不是只看指标分数。把某个站点一周的真实需求和预测值画在一张图上肉眼看一遍模型哪些地方学到了、哪些地方偷懒没学立刻一目了然。这一步花不了多少时间但往往能发现指标看不出的问题。如果你比赛时间还宽裕可以在这个思路上再加一步把预测结果作为调度模型的输入做一个简单的“站点调度模拟”验证你的预测值放到实际业务场景里是否合理。这样你的论文就不只是一篇预测报告而是真正讲完了一个“预测-决策”的完整故事。
返回列表