☰
随机森林回归实战:共享单车投放量预测与调度优化
2026/9/26 22:46:40 网站建设 项目流程

1. 背景与目标:投放量预测到底在解决什么问题

共享单车投放量分析与预测,落到实际项目里其实是两件事:一是分析哪些因素在显著影响各站点的借还车需求,二是基于这些因素预测未来一天或一周的最优投放量,用来指导调度和运维。我们这次用随机森林回归完成了这套流程,最终把站点日需求预测的MAE稳定在6辆左右,结果直接进入了早高峰前的预调度环节。

这个项目不是从零开始。团队之前是靠固定规则加人工经验安排投放量,效果勉强但完全是“事后补救”:车骑走了没人补,高峰期站点空桩,潮汐现象突出的地方尤其严重。运营同学每天要花大量时间打电话问站点情况,属于典型的数据有了、系统有了,但最关键的执行判断仍靠经验拍板。我接手以后没有急着建模,而是先梳理业务流程,发现真正要做的不是一次性预测,而是把“预测—调度—复盘”串成闭环。于是项目拆成四块:数据清洗、特征构建、随机森林模型训练、结果输出到投放策略。下面按这个顺序展开,也是我们实际执行时的顺序。

1.1 这个案例的真实业务痛点

先聊痛点,因为建模思路完全是顺着业务痛点来的。共享单车投放最麻烦的是潮汐现象:工作日早高峰,小区门口的车全涌到写字楼下;晚高峰再反向涌回来。如果投放量没有提前预判,调度车永远在堵车的路上,站点要么空桩要么满桩,用户体验和车辆周转率双双被拖累。

数据端的困难也不小。系统里记录的是订单流水,但很多站点的“实际库存”并不等于“投放量减借出量”,因为用户乱停放、车辆维修下线、故障车未及时回收都会让真实库存和系统库存差得很远。我们后来花了不少精力做清洗,比如用站点半小时颗粒度的借还差来近似净流量,过滤掉超过库存阈值的异常记录,否则模型即使拟合得不错,预测目标本身也是错的。

目标口径也要提前定死。我们最终按天粒度预测每个站点的“最优投放量”,业务定义是“当天站点净借出量+安全余量”。净借出量为正说明要补车,为负说明要回流。这个口径敲定之后,后面所有特征和目标值都围绕它来做,避免建模到一半再来回扯皮。

1.2 为什么选随机森林:和决策树、线性回归的对比

选型时我们对比了线性回归、单棵决策树、随机森林和XGBoost。线性回归训练最快,但共享单车需求非线性非常明显:天气的影响不是“温度高就骑得多”,而是“适宜温度骑得多,太热太冷都显著下降”,线性模型很难刻画这种倒U型关系,即使加入多项式项,特征交互也得很费劲地手工设计。

单棵决策树能处理非线性,但方差太大,数据一扰动就换一套划分规则。XGBoost确实是精度上限更高的选择,但调参成本高,当时团队里算法同学还要并行支撑其他项目,我们需要一个稳定、可解释、少调参也能出效果的基线方案。随机森林正好卡在中间:通过Bagging对多棵决策树取平均,降低方差,泛化更稳,而且能输出特征重要性,方便后续和运营解释“为什么模型给出这个投放建议”。

这里补一句随机森林算法原理。它训练多棵决策树,每棵树在训练时用有放回抽样生成一个子数据集,每个节点分裂时又只随机挑选部分特征进行最优划分。这个随机性让每棵树长得都不太一样,平均之后整体方差明显下降,单棵树可能过拟合,森林却不容易。这个特点特别适合共享单车这类噪声大、特征交互复杂的数据,所以即使它已经不算新算法,在业务落地里依然很能打。

2. 数据清洗与特征工程:决定预测上限的地基

2.1 数据字段与清洗要点

我们用的是城市某共享单车平台近12个月的订单流水,关联天气和节假日数据。主要字段包括:订单ID、站点编号、借车时间、还车时间、车辆类型,以及气温、湿度、风速、天气状况,还有是否工作日、是否节假日。

数据清洗这一步我踩了不少坑。流水数据里最典型的是“幽灵订单”:借车和还车在同一秒完成,骑行距离为0,明显是开锁失败或故障单,但系统照样记流水。这批数据如果不剔除,会把需求量虚高不少。我们按“骑行时长小于1分钟”和“骑行距离为0”两个条件筛掉了一部分。运营看数据时可能觉得几十单不算什么,但预测模型的误差会被这种噪声逐步放大。

站点维度同样有脏数据。维护中的站点会被系统暂时锁定,但历史流水还在,如果直接用当日流水预测该站点的投放量,预测目标会凭空多出一块。后来我们把“当日有效服务时长少于8小时”的站点单独标记出来,在训练和预测阶段都用掩码过滤,而不是简单删行。这样既保留了站点历史信息,又不会让异常状态污染模型。

2.2 特征构造、编码与业务语义

业务上的关键特征我分成三类:时间特征、气象特征、站点自身特征。

时间特征做了小时、星期、是否工作日、是否节假日、月份。小时和星期是常规操作,但注意不能直接把“星期几”当成无序数值丢给模型,尤其随机森林是基于阈值切分的,虽然理论上能自己找出切分点,但把工作日/非工作日、早高峰(7-10点)/晚高峰(17-20点)/平峰构造成布尔特征后,解释起来更顺,模型划分也更稳定。

气象特征里温度和体感温度相关性很强,只保留一个;风速、降水等级直接作为数值特征。这里有个经验:用降水等级而不是连续降水量,因为业务上更关心“是否下雨、下多大”,而不是精确到毫米。连续值会让随机森林的划分更碎,反而不利于泛化。温度则保留连续值,因为不同站点对温度敏感性不一样,树模型能找到自己的切分点。

站点自身特征包括站点所在区域、桩位数、历史平均需求、近7天平均需求。其中“近7天平均需求”这种滚动统计对预测非常有效,但必须小心:只能用截至当前时刻的历史数据,不能用未来信息,否则就是典型的特征泄漏。我们是用groupby加shift生成的,确保每个时间点的滚动均值不包含当天及未来数据。

2.3 时间序列验证:防止未来数据泄露

这里单独提醒一个高频错误:很多人做时间序列预测时,习惯直接train_test_split随机划分训练集和测试集,这在带滚动统计特征时会严重失真。随机森林本身不太关心样本顺序,但特征里的滚动均值一旦包含了未来窗口的数据,模型在验证集上的分数会虚高,上线后立刻变差。

我们最终采用按时间排序的滚动切分:前10个月训练,后2个月验证。训练集内部再做时间块交叉验证,而不是打乱所有样本,保住时间语义。网格搜索在这套验证方式下选出的参数,和正式上线时的表现几乎一致,这点非常重要,因为很多人调参评估很好看,上线后却被业务方质疑模型不准,根源往往就在这里。

如果你只是想快速验证随机森林在某个特征空间内是否可用,那随机划分没问题,但结果只能当作算法可行性参考,不要把它当作真实预测能力的度量。这一点写进我们团队的经验手册之后,后续好几个项目都少踩了坑。

3. 随机森林建模与调参实操

3.1 基线模型与核心代码

建模流程尽量简单:读取清洗后的DataFrame,定义特征列表,按时间切分训练测试集,用随机森林回归拟合。下面是一段可复用的核心代码,特征名字我做了简化,但结构就是项目实际使用的方案。

import pandas as pd from sklearn.ensemble import RandomForestRegressor # 清洗后的数据为 df,按时间升序排列 features = [ 'hour', 'is_weekday', 'is_holiday', 'is_rush_hour', 'temperature', 'rain_level', 'wind_level', 'station_region', 'dock_count', 'demand_mean_7d' ] X = df[features] y = df['net_demand'] # 按时间排序后做滚动切分,前83%训练,后17%验证 split_point = int(len(df) * 0.83) X_train, X_test = X.iloc[:split_point], X.iloc[split_point:] y_train, y_test = y.iloc[:split_point], y.iloc[split_point:] model = RandomForestRegressor( n_estimators=300, max_depth=12, min_samples_leaf=5, random_state=42, n_jobs=-1 ) model.fit(X_train, y_train) pred = model.predict(X_test)

代码里有两个设置值得展开。n_jobs=-1是把所有CPU都用上,300棵树在几十万行数据上不会等太久;min_samples_leaf=5是让每个叶子节点至少包含5个样本,防止树在个别异常站点上钻牛角尖。设成1反而会在早高峰噪声大的站点上预测出剧烈抖动。

预测完成后的工作其实不在模型内部:我们要按站点分组汇总预测结果,再叠加“安全余量”生成投放建议。这一步决定了预测是否能被业务直接使用,所以我建议建模一开始就把结果导出的接口定义好,避免模型做出来了却迟迟落不了地。

3.2 超参数如何选:网格搜索结果记录

随机森林需要重点关心的核心参数大概是四个:n_estimators、max_depth、min_samples_leaf、max_features。我把调参分成两轮。

第一轮固定max_depth和min_samples_leaf,扫描n_estimators。实测下来,n_estimators从100涨到500时,验证集MAE开始下降明显,200棵树以后基本稳定,再往上收益很小但耗时成倍增加。所以线上版本没有刻意追求1000棵树,只选了300棵,兼顾效果和推理速度。

第二轮固定n_estimators,用网格搜索扫max_depth和min_samples_leaf。下面是我当时记录的一组对照数据:

max_depthmin_samples_leaf验证集MAE
826.8
1226.5
1256.4
1656.4
16106.7

最终选定max_depth=12,min_samples_leaf=5。max_depth太浅表达不了温度、时段之间的复杂交互,太深又会盯着训练集里的噪声。min_samples_leaf调大一点能让预测更平滑,尤其对平时需求为0、偶尔爆量的站点很有效,因为小叶节点容易被单天异常值带偏。

讲一个高频问题:max_features有没有必要调?我的习惯是回归任务默认取特征总数的三分之一,即max_features='sqrt',效果稳定。如果你发现站点之间差异很大,可以稍微调低一点,增加树之间的多样性,一般不会坏事,但不要使用全部特征,否则森林里树的相关性太高,Bagging降低方差的效果会减弱。

3.3 大训练集的提速与落地细节

数据量大时,随机森林训练其实很吃内存。我们的数据是小时粒度,全量几十万行,内存还好,但如果你把站点、小时、天全展开,单机也可能冲到几亿样本。这时候有两个办法:一是把明显不活跃的站点分到另一个模型去预测,二是按区域分别建模。我们最终按城市区域训练了3个模型,预测精度和训练速度都比一个全量模型更好。

另外,一定要把模型和特征列表一起保存。我们上线时因为特征列顺序写错,预测结果全部错位,后来用model.feature_names_in_对了一下列名才发现。这个错误很低级但代价很大,建议在每个模型交付时都附带一个特征清单JSON,加载模型后先校验列名再跑预测。

4. 评估指标与特征重要性解读

4.1 为什么同时看MAE、R²和分时段误差

评估模型时,R²很容易给人错觉。我们的验证集R²大约0.72,听起来不错,但画出来可以发现,它主要是靠早高峰大需求量拉高了相关性,低需求站点的误差其实不小。所以项目里我们同时盯MAE和分时段误差,目标口径是“站点日净需求预测MAE不超过8辆”。

为什么用MAE而不用RMSE?因为调度业务更关心平均偏差,不希望个别站的极端值把指标放大。RMSE会惩罚大偏差,但如果某天某个站点因为交通事故导致需求暴跌,这个偏差是模型无法预知的,过分优化RMSE会让模型过度保守,反而影响正常日的调度效率。

评估时我们还做了分时段切片:早高峰、晚高峰、平峰各算一次MAE。最终结果大约是早高峰6.8辆、晚高峰7.1辆、平峰4.5辆。这个拆解比整体指标有用得多,能直接告诉运营:哪些时段预测置信度高可以放心执行,哪个时段需要人工兜底。

4.2 特征重要性:向业务方解释的底气

随机森林有个天然优势,训练完直接可以输出feature_importances_。但注意:这个值基于不纯度减少,会偏向取值更多的连续特征,比如温度。它反映的是“该特征在模型划分中贡献的预测信息量”,不是严格因果率。所以给运营汇报时,我还会结合SHAP值再看一遍,两者互相印证。

我们项目里特征重要性排序大致是:近7天平均需求 > 小时 = 是否早高峰 > 站点区域 > 温度 > 是否工作日 > 降水等级。这个排序和业务直觉高度一致:历史需求是惯性最强的指标,时间坡峰决定周期性,天气带来短期波动。

运营同事看到特征排序后,很快提出了一个可落地的方案:温度在25度附近时号召运维多发车,低于5度或高于35度时减量但不断供。这类可解释性,正是树模型能直接落地的红利,也是随机森林在业务场景中比很多“黑盒”模型更受欢迎的原因。

5. 落地部署与常见问题排查

5.1 从预测结果到每日投放建议

模型训练只是手段,投放策略才是终点。我们生成的每日投放建议包含三个数字:预测净需求、建议投放量、置信度修正值。实现时把模型预测结果和站点余桩数做差,再加一个安全余量系数。

举个例子:某站点傍晚预测净需求为+15辆,当前余桩还有6个,那么建议投放量就是15-6+安全余量5,约14辆。这个公式是业务同学和我们一起定义的,本质是把模型输出映射成调度单。如果站点是重点潮汐站,安全余量会提高到8-10辆,宁可多运两辆也不要让早高峰用户找不到车。

上线形式是每天凌晨跑一次全量预测,生成Excel和API接口双份结果。调度员早上打开APP就直接看到当天的预调度任务,不再需要自己对着后台数库存。这里也提醒一句:预测结果要留版本记录,方便后续复盘,否则调度员调整了投放量之后,你根本不知道是模型不准还是执行偏差。

5.2 建模到上线常见的问题排查清单

我整理了一份排查清单,项目里遇到问题可以对着查:

  • 特征顺序错位导致预测全乱:保存模型时把特征列表一起保存,加载后核对feature_names_in_。
  • 测试集泄漏未来信息:滚动统计只能用截至当前时刻的数据,注意shift和groupby的执行顺序,少写一个shift都会让结果失真。
  • 站点临时维护导致目标异常:训练前先过滤掉当日有效服务时长不足的站点,别让异常状态成为常态。
  • 天气数据缺失:用前一天同一时段的天气做填充,不要用全局均值,否则会抹掉天气波动带来的影响。
  • 模型上线后性能下降:先看特征分布有没有漂移,比如运营策略调整后投放量基数变了,历史需求分布也会变,需要尽快重新训练。
  • 预测值出现负数:站点净需求为负很正常,但要设置下限为0,否则调度单会出现“建议回收负数辆车”这种尴尬提示。

5.3 落地阶段最容易忽视的一个优化点

训练完成后,记得把预测误差按站点和时段做成看板。我们发现误差最大的站点集中在景区和商场周边,这类站点的需求受大型活动、临时管制影响强烈,属于随机森林也救不了的“不可预测部分”。针对它们,我们干脆不做预测,改为提前预留人力,活动开始时人工干预。这样做之后,全局MAE反而下降了0.3辆,因为不再强迫模型去拟合那些本就不该预测的特殊事件。

我在做这个项目时最深的体会是:随机森林只是稳定输出的组件,真正的成本花在数据清洗、业务口径定义和结果解释上。这套方法后续还可以继续扩展,比如在区域粒度换成LightGBM对比效果,或者把天气预测字段接入模型,提前做“雨天投放策略”。但当下,一个边界清晰、可解释、能快速上线的随机森林方案,已经足够把共享单车投放从“拍脑袋”变成“看数据说话”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询