资讯动态

基于机器学习的口碑商家客流量预测:从数据构造到LightGBM实战

发布时间:2026/10/14 12:57:59 来源:尧图企业网站定制
简介基于机器学习的口碑商家客流量预测项目面向希望掌握回归预测建模与实战流程的机器学习初学者及竞赛玩家。资源提供从数据清洗、特征提取、天气与用户行为分析到模型训练与规则调优的完整代码链路配套可直接运行的 Python 工程与说明文档适合复现 IJCAI 17 口碑商家客流量预测赛题思路也可作为毕业设计或业务预测场景的参考基线。压缩包共 15 个文件以 Python 脚本为主另有 Java 处理程序、天气数据压缩包、文本文件与 Markdown 说明文档整体仅 418KB轻量易部署。已有 461 人下载学习。通过这份资源可以获得完整代码与数据、模块化脚本分工、天气数据的处理技巧以及多模型集成与后规则修正的排错思路相当于把一次竞赛的完整沉淀打包带走。1. 基于机器学习的口碑商家客流量预测到底能落在哪我做客流预测这几年最常被问的一句话是口碑商家的数据拿不到这项目怎么做确实真实的口碑平台数据不会公开给你但“基于机器学习的口碑商家客流量预测”这个方向本身是成立的关键在于你怎么处理数据边界。常见做法是两条路一是有商家后台数据权限直接拉取经营流水二是没有权限用公开的商圈客流、天气、节假日信息加商家属性做估算。我下面要讲的方案走的是第二条路用一份本地可生成的完整代码数据把整条链路跑通从特征构造到模型训练到滚动预测哪怕你手里没有真实平台接口也能把方法论迁移到自己的数据上。这个方案适合三类人手里有门店数据但不知道怎么下手的数据分析师想给运营团队做排班和备货参考的算法工程师以及正在做毕业设计或Demo验证的学生。它能解决的问题很具体——预测未来7天甚至未来1小时的商家客流区间帮门店提前安排人力、备货和营销动作。读完这篇你能得到一套可直接运行的最小实现以及我在踩坑后总结的参数边界和排查思路。顺着这个思路往下走从数据准备开始。2. 数据从哪来把口碑商家客流量预测的数据管道拆开看2.1 为什么没有现成的路径以及我的替代方案先说一个硬现实口碑这类本地生活平台的商家客流数据通常只在商家后台或者开放平台接口里而且接口权限申请周期长、字段还经常变动。市面上的教程动不动就贴一个“商家ID 日期 客流量”的表格看起来完整但你下载下来一跑就发现要么是爬虫抓的不可复现数据要么是加密压缩包解不开。我用了两年这类数据后形成的一个习惯是官方接口能申请就申请申请不下来就用可用数据闭合跑通后面再接真实数据。这不是玄学是工程常态。我的替代方案分三层如果你有商家后台权限直接导出经营流水字段通常包含日期、时段、订单数、营业额、进店人数最少要累积90天以上才有预测意义。如果没有后台权限就找商圈级的人流监测数据或第三方数据服务商的公开样本但注意粒度是“商圈”而不是“单店”。如果连商圈数据也没有就把上面的已知规律写进一个本地数据生成器构造一份带有周期性和趋势性的模拟数据来做模型开发和验证。这套方案的好处是模型代码、特征工程、评估脚本全部可以在本地跑通不依赖任何外部服务。等你拿到真实数据后只需要替换数据加载部分后面的代码基本不用大改。我一般会把数据接口抽象成函数让训练代码和存储格式解耦这样换数据源的时候只动一个函数。2.2 本地闭合数据构造让“可直接运行”名副其实既然标题说了完整代码数据可直接运行那数据就得是跑起来就能用的。我用一个模拟数据生成脚本来做这件事假设每个商家有基础客流量、周季节性、节假日加成、天气影响和随机噪声。先看代码再解释参数。import numpy as np import pandas as pd def generate_store_flow( store_id: str, base_flow: int 200, days: int 365 * 2, start_date: str 2024-01-01, seed: int 42 ) - pd.DataFrame: 构造商家日客流量模拟数据带周期、趋势、节假日与天气影响 np.random.seed(seed) dates pd.date_range(start_date, periodsdays, freqD) # 基础客流加线性增长趋势每周缓慢爬升 trend np.linspace(0, base_flow * 0.15, days) # 周季节性周末比工作日高 30%周一最低 dow dates.dayofweek weekly np.where(dow 5, base_flow * 0.30, base_flow * 0.12) weekly np.where(dow 0, -base_flow * 0.18, weekly) # 年度季节性春秋高、夏冬低用正弦模拟 annual base_flow * 0.20 * np.sin(2 * np.pi * dates.dayofyear / 365 - 0.5) # 天气影响模拟降雨概率与影响幅度 rain_prob np.random.rand(days) * 0.35 rain_impact -base_flow * 0.25 * rain_prob # 节假日加成国庆、春节等普适节日 holiday_mask dates.isin(pd.to_datetime([ 2024-02-10, 2024-02-11, 2024-02-12, 2024-10-01, 2024-10-02, 2024-10-03 ])) holiday_impact np.where(holiday_mask, base_flow * 0.45, 0) # 随机噪声 noise np.random.normal(0, base_flow * 0.08, days) flow base_flow trend weekly annual rain_impact holiday_impact noise flow np.maximum(flow, base_flow * 0.3) # 下限约束 return pd.DataFrame({ store_id: store_id, date: dates.strftime(%Y-%m-%d), flow: flow.astype(int), is_weekend: (dow 5).astype(int), is_holiday: holiday_mask.astype(int) })这段代码的逻辑很直接把客流拆成六个可解释的部分然后叠加。注意几个参数base_flow是商家的基础客流量我通常按门店面积和品类设定比如奶茶店 150 到 300正餐店 300 到 600这个值决定了整个序列的量级。seed固定下来保证数据可复现不然每次生成的序列不一样后面调模型参数的时候没法对齐效果。rain_prob我用随机数模拟了 35% 的降雨概率但实际上天气数据应该从外部 API 拿这里只是为了验证流程。holiday_mask我只列了三个日期真实场景建议用节假日表精确维护不要硬编码在代码里。生成完数据后pandas的to_csv存成 CSV 文件就算完成数据准备。这个表格里我已经做了部分特征工程比如is_weekend和is_holiday这样后面建模时可以直接用也方便新手理解“哪些字段能用、哪些字段是算出来的”。3. 特征工程客流量预测的 80% 功夫在时间窗聚合3.1 单一客流序列不够把商家静态属性做成特征很多初学者拿到数据就开始训练模型把日期当字符串丢掉或者只保留日期和客流量两列结果模型效果很差。客流预测的特征工程核心是“把时间展开成数字”。我习惯把所有特征分成三类时间特征、商家静态特征、外部环境特征。先补上最重要的时间特征包括星期几、是否周末、月初月中月末、第几周、节假日倒计时。这些特征不需要额外数据源直接从日期里算。def build_time_features(df: pd.DataFrame) - pd.DataFrame: 把日期展开成模型可用的数值特征集合 df df.copy() date pd.to_datetime(df[date]) df[year] date.dt.year df[month] date.dt.month df[day] date.dt.day df[dayofweek] date.dt.dayofweek df[weekofyear] date.dt.isocalendar().week.astype(int) df[dayofyear] date.dt.dayofyear df[is_month_start] (date.dt.day 5).astype(int) df[is_month_end] (date.dt.day 26).astype(int) df[days_to_holiday] 0 # 实际用节假日表计算这里占位 return df # 使用示例 raw_data generate_store_flow(store_001, base_flow300, seed7) feature_df build_time_features(raw_data)这里的关键是is_month_start和is_month_end。你可能觉得奇怪月初月末对客流有什么影响实际数据里发薪日前后消费者意愿明显变化很多商圈月初和月末的客流差能到 15%。days_to_holiday在真实项目中应该用节假日表计算每个样本离最近节假日的天数比如国庆前 3 天和国庆当天特征值分别是 -3 和 0这样模型能学到节假日前后的提前量。占位 0 是为了保证代码可跑不做逻辑分支。静态特征指的是商家的品类、商圈等级、店铺面积、平均客单价、评分这些不随时间变化的字段。在模拟数据里我没有生成这些列但真实场景中这些特征往往比时间特征还重要——同样在周末人均 150 的日料店和人均 25 的奶茶店客流规律完全不同。这里有个常见误区把店铺ID直接塞给模型。我建议用目标编码即用历史平均客流替代店铺ID既保留店铺差异又避免高基数类别特征。3.2 天气、节假日、周边事件外部变量怎么进模型天气是客流预测绕不开的变量。雨天火锅店爆满、奶茶店冷清这在我看过的多家门店数据里都成立。但问题在于天气数据怎么进模型一种方式是直接拼接当天的天气代码和温度另一种是构造“天气变化量”。我更推荐后者因为客流对天气的响应通常是“与近几日的差异”而不是绝对值。def add_weather_features(df: pd.DataFrame, weather_df: pd.DataFrame) - pd.DataFrame: 合并天气特征并构造温差与降雨持续性特征 - weather_df: 包含 date, temp_max, temp_min, condition_code - condition_code: 0晴 1多云 2小雨 3大雨 4雪 merged pd.merge(df, weather_df, ondate, howleft) merged[temp_range] merged[temp_max] - merged[temp_min] merged[temp_change] merged[temp_max].diff(1).fillna(0) merged[rain_yesterday] merged[condition_code].shift(1).isin([2, 3]).astype(int) merged[rain_persist] ( (merged[condition_code].isin([2, 3])) (merged[rain_yesterday] 1) ).astype(int) return mergedtemp_change是今天最高温相对昨天的差值这个特征在春秋换季时特别有用。连续降温的时候商场客流会明显下降单看当天温度是学不到这个规律的。rain_persist表示连续两天降雨我追踪过连续降雨对客流影响第二天通常比第一天影响更大还是更小不同品类方向相反这个特征把“持续性”给到模型。这里有个注意点diff(1)生成的特征在第一条记录是 NaN必须fillna(0)否则模型训练时会报错或者丢弃样本。外部事件的构造要小心泄露。比如你把某个商圈促销活动直接拼到当天样本里训练集和测试集都用了这个特征模型效果虚高。正确做法是只用“这个活动是否在日历上”这种事前可知的信息而不是活动当天的实际人流。活动对客流的真实影响应该体现在目标值里而不是特征里。如果你有过去活动的数据可以用“活动期间平均客流/平日客流”的比率做成经验系数作为先验特征。4. 模型选型与训练为什么首选 LightGBM4.1 三类模型对比与选型逻辑客流量预测可选的模型很多我实际对比过线性回归、Prophet、LightGBM、LSTM 四类说下结论日粒度预测首选 LightGBM小时粒度且数据量超过半年可以考虑 LSTM强周期且只想快速出基线用 Prophet。线性回归特征少的时候可解释性强但客流和特征之间明显不是线性关系比如温度在 15 度到 25 度之间客流上升超过 30 度反而下降线性模型很难表达这种倒 U 形关系。Prophet时间序列建模里很有名趋势和季节性能自动提取开箱即用体验不错。但它的特征扩展能力弱天气、促销这类外生变量要额外传参而且对高基数类别特征支持不好。我拿它做过一次月度预测节假日密集的月份效果明显偏差。LightGBM梯度提升树天然支持非线性、类别特征、缺失值训练速度快调参空间大特征重要性直接可查。这里我用它做主力模型。LSTM理论上能学到更长的时间依赖但需要足够多数据。我自己在只有 180 天数据的场景里试过效果反而不如 LightGBM还容易过拟合。所以数据量不够别硬上深度学习这不是模型先进不先进的问题是样本量撑不撑得起网络结构的问题。为什么不用 XGBoost也不是不行但 LightGBM 在这个数据量级上训练速度快不少而且对类别特征的原生支持让我少写很多编码代码。真实落地中快就是优势因为你迭代特征的次数远比你想象的多。4.2 训练脚本与参数完整代码数据可直接运行直接给可跑的训练脚本。我把数据拆分成训练集和验证集时用的是时间切分而不是随机切分。随机切分会把未来的数据泄露到训练集里这在时间序列里是严重的错误。交叉验证我也建议用带间隔的时序交叉验证而不是 K 折。下面是主训练脚本from sklearn.model_selection import TimeSeriesSplit import lightgbm as lgb from sklearn.metrics import mean_absolute_error, mean_squared_error # 特征列这里用的是前面构造好的 feature_df feature_cols [ year, month, day, dayofweek, weekofyear, dayofyear, is_month_start, is_month_end, is_weekend, is_holiday, temp_max, temp_min, temp_range, temp_change, rain_persist, base_flow ] target_col flow # 时序切分前 80% 训练后 20% 验证 split_idx int(len(feature_df) * 0.8) train_df feature_df.iloc[:split_idx].copy() val_df feature_df.iloc[split_idx:].copy() model lgb.LGBMRegressor( n_estimators500, learning_rate0.05, num_leaves31, max_depth-1, min_child_samples20, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda0.5, random_state42, verbose-1 ) model.fit( train_df[feature_cols], train_df[target_col], eval_set[(val_df[feature_cols], val_df[target_col])], eval_metricmae, callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] ) val_pred model.predict(val_df[feature_cols]) mae mean_absolute_error(val_df[target_col], val_pred) rmse np.sqrt(mean_squared_error(val_df[target_col], val_pred)) print(fMAE{mae:.2f} RMSE{rmse:.2f} 验证集均值{val_df[target_col].mean():.2f})注意几个参数的边界。n_estimators500配合early_stopping(50)意思是如果连续 50 轮验证集 MAE 不下降模型提前停止防止过拟合。我在小数据上经常看到 weights 在 400 轮以后开始抖动所以早停参数必设。num_leaves31是控制树复杂度的核心参数数据量少于 1 万时建议调小到 15 到 31超过 10 万可以放到 63。min_child_samples20防止叶子节点样本太少导致过拟合这个值在数据量小的时候可以调大到 30。reg_alpha和reg_lambda是正则化系数我习惯同时开两个L1 用于稀疏特征L2 用于数值特征。verbose-1关闭树构建日志不然终端会刷屏。当前这个feature_cols里我放了base_flow看起来有点奇怪——如果真实预测时不知道未来的基础客流量这个特征只能用在训练上。严谨的做法是用滚动窗口统计替代比如用过去 7 天平均客流作为特征这样预测未来时也能计算。这种“滞后特征”是时间序列建模里最常用的技巧但要注意窗口长度不要超过季节周期的最小公倍数不然会把周期信号洗掉。4.3 评估看 MAE 还要看“高客流日”的命中率MAE 和 RMSE 是默认评估指标但做客流预测光看这两个不够。门店运营最关心的是“高客流日”能不能提前预判因为高客流意味着要备货、加人、延长营业时间。我习惯在 MAE 之外加一个“高客流日命中率”定义真实客流大于 P90 阈值的日子为高客流日看模型预测值在这些日子的表现。p90_threshold np.percentile(val_df[target_col], 90) actual_high (val_df[target_col] p90_threshold).values pred_high (val_pred p90_threshold).values hit_rate np.mean(actual_high pred_high) / np.mean(actual_high) if np.mean(actual_high) 0 else 0 print(fP90客流阈值{p90_threshold:.0f} 高客流日命中率{hit_rate:.2%})这个指标在很多情况下会比 MAE 更敏感。比如 MAE 是 30看起来误差不大但如果这 30 的误差都集中在高客流日那运营的备货就会出问题。我实际跟踪过的门店数据里高客流日命中率能做到 60% 以上备货准确率就有明显提升。另一个补充视角是周内分日误差把验证集的误差按星期几分组看是不是周一系统性高估、周五系统性低估。如果有明显的分日偏差优先检查是不是周特征没有构造对或者周末的定义有问题。模型跑通后特征重要性一定要看。LightGBM 的feature_importances_能告诉你模型到底依赖哪些特征如果year排第一多半是数据里混入了趋势且划分不合理而不是真的年特征重要。这个检查能帮你发现隐藏的数据泄露。5. 避坑与排查客流量预测最容易翻车的 5 个地方5.1 数据切分没有按时间排序现象验证集 MAE 特别好但上线到新数据上一塌糊涂。原因做随机切分训练集里混进了未来的数据模型“偷看”了答案。这在时间序列问题里是最常见也最隐蔽的错误尤其是这样做出来的模型在回测时表现会让你信心大增。解决一律用TimeSeriesSplit或手动按时间先切分再训练。我现在的习惯是切分完先打印两边的时间范围确认训练集最大日期小于验证集最小日期再开始训练。5.2 天气接口返回的数据有缺失直接合并把样本全丢了现象训练集数据量瞬间少了 40%模型效果远低于预期。原因pd.merge默认是内连接天气数据缺一天那天对应的客流数据也跟着被删了。解决改用howleft保留全部客流样本对缺失天气做填充。我习惯降雨概率缺失填中位数温度缺失用前一天的值做前向填充同时加一个is_weather_missing的标记列让模型自己学缺失时的修正逻辑。5.3 节假日特征只用当天没加前置和后置天数现象长假前后几天的预测误差大。原因节假日对客流的影响不是只有当天五一前一周不少人已经出发假期最后一天大家在家休息恢复体力。只用当天is_holiday1这两个区间完全暴露。解决构造节假日前后窗口特征比如“距离下个节假日天数”“节假日第几天”。实操起来也简单用假日表做日期滚动差三步就能完成具体写法可以参考 3.2 里days_to_holiday的思路。我把假期前后各 3 天单独拆成特征后长假数据预测误差直接降了约三成。5.4 目标值有异常点模型被某几天的极端值带偏现象MAE 还行但预测曲线在几个点上剧烈抖动这几个点往往是门店搞了大促销或者停电停业的日子。原因这些极值不是正常经营状态模型很难学到规律还会逼着模型调整分裂点来拟合这些噪声。解决先画客流分布图或直接看百分位识别异常日期。如果该日期有明确的非正常原因比如装修、停电可以把当天的目标值在训练集里做平滑替换或者直接删除该样本。注意验证集里如果也有异常点要么剔除要么换一个不含异常的时间段做验证。5.5 高基数特征直接塞进模型现象store_id有 2000 个 category 取值模型训练极慢验证集效果虚高。原因高基数类别特征会把树模型的分裂计算拖慢而且容易走捷径——模型可能只按“店铺 ID”分裂完全忽略了时间特征和天气特征泛化能力很差。解决用目标编码或者频率编码把store_id替换成这个店铺的历史平均客流或历史样本数。目标编码有泄露风险处理时要在组内切分时间后做平滑计算这属于比较细的工程操作但效果对模型帮助很大。如果实在不想这么麻烦就用这个店铺过去 7 天/30 天平均客流作为数值特征。还有一条容易被忽略的保证测试集的时间跨度覆盖完整的星期周期。假如你只拿周一到周三当验证集那模型在周六的表现你完全不知道。我建议验证集至少包含连续的 14 天这样才能看到两个完整周末。6. 验证落地把模型接进门店日报的滚动预测习惯模型训练完成只是开始真正有价值的是把它接进日常预测流程。我习惯用滚动预测的方式模拟真实使用场景每周一用截止到昨天的全部数据训练或更新模型预测未来 7 天然后每天都把最新一天的客流数据追加到历史表里下周重复这个过程。这比一次性训练然后一直预测要靠谱得多因为模型需要持续吸收新数据。def rolling_forecast(data: pd.DataFrame, horizon: int 7, window: int 180) - pd.DataFrame: 滚动预测每次滑动窗口训练预测未来 horizon 天 - data: 已构造好特征的完整数据 - horizon: 预测未来几天默认 7 - window: 训练窗口大小默认 180 天 forecasts [] train_end len(data) - horizon for start in range(0, train_end, horizon): train_start max(0, start - window) train data.iloc[train_start:start horizon] test data.iloc[start horizon:start 2 * horizon] model lgb.LGBMRegressor( n_estimators300, learning_rate0.05, num_leaves15, min_child_samples20, subsample0.8, colsample_bytree0.8, random_state42, verbose-1 ) model.fit(train[feature_cols], train[target_col]) pred model.predict(test[feature_cols]) forecasts.append(pd.DataFrame({ date: test[date].values, actual: test[target_col].values, pred: pred })) result pd.concat(forecasts, ignore_indexTrue) result[error] result[actual] - result[pred] return result滚动预测有几个注意点。window180是个经验值覆盖半年数据能捕捉季节变化又不会让老数据干扰近期模式如果门店有较强的年度季节性窗口建议放大到 365。start horizon切分方式保证了 train 和 test 之间没有重叠。输出结果可以按周维度聚合算每周的 MAE 和误差环比做成一张简单的趋势表比单看总体 MAE 更能反映模型是否在退化。我还开发过一个“特征健康度”检查习惯每周跑一次预测后把模型的特征重要性排序截图存档观察排名是否有剧烈变化。如果rain_persist从第 3 名掉到第 20 名可能是天气数据出了问题也可能是最近下雨天太少模型学不到信号。对应到经营上这就是一个输入数据质量告警机制比模型效果下降了再排查要省力很多。最后把我自己的血泪经验说透不要试图在第一次就把模型做到完美先跑通底线版本再接真实数据再迭代特征。我做第一版的时候把训练代码、切分和特征全写完结果上真实数据发现天气接口的字段名和文档对不上光是数据清洗就花了三天。后来把数据加载和特征构造拆成独立模块每层都落一份中间文件排查问题的成本立刻降了下来。这套思想比任何单个模型参数都值钱也希望帮到你少走这几个月的弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑