☰
基于机器学习的口碑商家客流量预测:SVM实战与避坑指南
2026/10/11 16:48:40 网站建设 项目流程

简介:这份资源面向参加天池IJCAI17口碑商家客流量预测赛题的选手,以及希望用机器学习方法解决真实商业预测问题的开发者与数据科学学习者。内容围绕客流量预测这一典型回归任务,提供从数据预处理、特征工程到模型训练与结果调整的完整实现思路,适合具备一定Python与机器学习基础、想通过完整赛题项目提升实战能力的中级学习者。压缩包共15个文件,约418KB,以11个Python脚本为核心,辅以Java文件、说明文档、文本与压缩数据文件,覆盖用户支付分析、天气数据处理、特征提取、Lasso建模、Stacking融合及规则调整等环节,目录按分析流程分步组织,便于逐模块阅读与复现。目前已有460人学习下载。读者可据此获得一套可直接运行的赛题代码与配套数据,理解口碑商家客流预测的建模链路,掌握特征构造、模型融合与后处理调优的实践方法,并借鉴其排错与调参思路。

1. 从一份能直接跑的客流预测代码说起:它到底解决了什么问题

很多做零售数据或者商业地产运营的朋友都遇到过这种场景:手里攥着某点评平台上一批口碑商家的历史客流记录,老板让你预测下个月哪些店会爆、哪些店会凉,你第一反应是打开 Excel 拉个折线图,结果发现周末和工作日的曲线完全不是一个形状,节假日又跳得离谱,手动拟合根本没法看。这份「基于机器学习的口碑商家客流量预测」完整代码包,就是冲着这个痛点来的——它把数据清洗、特征构造、模型训练、结果评估整条链路都封好了,拿过来改改路径就能跑。适合谁?一是正在做机器学习课程设计、需要一份能讲清楚流程的参考实现的学生;二是刚转行数据岗、想找一个完整项目练手的新人;三是运营侧想快速验证「用历史数据能不能预测未来客流」这个假设的从业者。它不保证你直接拿去就能预测你家楼下奶茶店明天卖多少杯,但它给了一套可复现的骨架,让你知道从原始流水到预测值之间到底要填哪些坑。

2. 拆开代码包:数据流、特征工程与 SVM 的选型逻辑

2.1 原始数据长什么样,以及为什么不能直接喂给模型

拿到这份代码,第一件事不是急着python main.py,而是先看data/目录下的原始文件。通常这类口碑商家客流数据会包含几个核心字段:商家 ID、日期、时间段(比如午市、晚市)、客流计数、以及一些商家属性(品类、人均消费、评分)。原始数据最典型的毛病是时间不连续——有的店周末不营业,有的店某天数据缺失,还有的店因为装修直接断了一个月。如果你直接把这种带缺口的数据丢进 SVM,模型会默认所有样本在时间上是等距的,结果就是预测值整体偏移。

常见做法是先把数据按商家 ID 分组,对每个商家单独做时间序列重采样,把缺失的日期补上,客流值用前后三天的滑动中位数填充,而不是简单填零。这一步在代码里对应preprocess.py中的fill_missing_by_merchant函数。参数上要注意freq='D'是按天补,如果你的数据粒度是小时,就得改成'H',但改完记得检查商家营业时间,否则半夜补出来的零会把模型带偏。

# preprocess.py 片段:按商家分组填充缺失日期 import pandas as pd def fill_missing_by_merchant(df, date_col='date', merchant_col='merchant_id', value_col='flow'): df[date_col] = pd.to_datetime(df[date_col]) filled = [] for mid, group in df.groupby(merchant_col): # 生成该商家从最早到最晚的完整日期范围 full_range = pd.date_range(group[date_col].min(), group[date_col].max(), freq='D') group = group.set_index(date_col).reindex(full_range) # 用前后三天的滑动中位数填充,避免零值干扰 group[value_col] = group[value_col].interpolate(method='linear', limit=3) group[value_col] = group[value_col].fillna(group[value_col].median()) group[merchant_col] = mid filled.append(group.reset_index().rename(columns={'index': date_col})) return pd.concat(filled, ignore_index=True)

这段逻辑的关键在于interpolate的limit=3,意思是连续缺失超过三天就不线性插值了,改用该商家的中位数兜底。为什么是三天?因为客流数据通常有周内规律,超过三天的缺口用线性插值会抹掉周末峰值,反而不如中位数稳。

2.2 特征构造:把日期变成模型能吃的数字

SVM 本身不理解「周六」和「周日」的区别,你得手动构造特征。这份代码里feature_engineer.py做了几件事:把日期拆成星期几、是否周末、是否节假日、月份、以及该商家过去 7 天的滑动平均客流。其中过去 7 天滑动平均是最重要的特征,它相当于给模型一个「近期热度」的锚点。没有这个特征,SVM 只能靠星期几硬猜,精度会掉一大截。

# feature_engineer.py 片段:构造时间特征与滑动窗口特征 import pandas as pd import numpy as np def build_features(df, date_col='date', value_col='flow'): df = df.sort_values(date_col) df['dayofweek'] = df[date_col].dt.dayofweek df['is_weekend'] = df['dayofweek'].isin([5, 6]).astype(int) df['month'] = df[date_col].dt.month # 过去7天滑动平均,min_periods=1 保证首日也有值 df['rolling_mean_7'] = df[value_col].rolling(window=7, min_periods=1).mean() # 过去7天滑动标准差,反映客流波动 df['rolling_std_7'] = df[value_col].rolling(window=7, min_periods=1).std().fillna(0) return df

参数上,window=7对应一周周期,如果你的数据有明显月度周期,可以再加一个rolling_mean_30。但注意特征不是越多越好,SVM 对冗余特征敏感,加太多滑动窗口会导致维度爆炸,训练时间翻倍而精度不升。我一般会先用rolling_mean_7和rolling_std_7两个,跑一遍看 MAE,再决定要不要加。

2.3 为什么选 SVM 而不是线性回归或决策树

这份代码用 SVM 做回归(SVR),核函数默认是 RBF。选它的理由很实际:客流数据往往不是线性可分的,周末和工作日的分界线在二维平面上是一条曲线,线性回归拟合不了;而决策树虽然能拟合非线性,但单棵树容易过拟合,尤其当某个商家只有几十条记录时,树会直接把训练集背下来。SVR 的 RBF 核相当于把数据映射到高维空间找一条平滑的回归面,对中小规模数据(几千到几万条)比较稳。

但 SVM 有个硬伤:对参数极度敏感。C和gamma稍微变一点,结果能差出 20% 的 MAE。代码里默认C=1.0, gamma='scale',这是保守值。如果你发现预测曲线过于平滑、跟不上真实波动,先把C调到 10 或 100;如果发现预测值跳动太大、像在拟合噪声,就把gamma从'scale'改成0.01或0.001。调参这块没有银弹,只能网格搜索。

# train.py 片段:SVR 训练与网格搜索 from sklearn.svm import SVR from sklearn.model_selection import GridSearchCV, TimeSeriesSplit from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline def train_svr(X_train, y_train): # 必须做标准化,SVM 对量纲敏感 pipe = Pipeline([ ('scaler', StandardScaler()), ('svr', SVR(kernel='rbf')) ]) param_grid = { 'svr__C': [0.1, 1, 10, 100], 'svr__gamma': ['scale', 0.01, 0.001], 'svr__epsilon': [0.1, 0.5, 1.0] } # 时间序列交叉验证,不能用随机 KFold tscv = TimeSeriesSplit(n_splits=5) grid = GridSearchCV(pipe, param_grid, cv=tscv, scoring='neg_mean_absolute_error', n_jobs=-1) grid.fit(X_train, y_train) return grid.best_estimator_, grid.best_params_

这里有个容易翻车的地方:交叉验证必须用TimeSeriesSplit。如果你用默认的KFold,它会随机打乱时间顺序,导致模型在训练时「看到未来」,评估分数虚高,上线后直接崩。代码里已经改成了TimeSeriesSplit,但很多人抄代码时会把这一行漏掉,这是血泪经验。

3. 跑通全流程:从环境配置到预测结果输出

3.1 环境依赖与版本对齐

这份代码包没有提供requirements.txt,但根据 import 语句可以反推出依赖:pandas、numpy、scikit-learn、matplotlib(画图用)。版本上,scikit-learn建议用 1.0 以上,因为SVR的gamma='scale'在旧版本里行为不一致。我一般会先建一个干净虚拟环境,避免和系统里的包打架。

# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install pandas numpy scikit-learn matplotlib

装完后先跑python -c "import sklearn; print(sklearn.__version__)"确认版本。如果低于 1.0,gamma='scale'会按1 / n_features算,而新版本按1 / (n_features * X.var())算,结果不同。这不是玄学,是实打实的版本坑。

3.2 数据路径与配置文件修改

代码里数据路径通常写死在config.py或main.py顶部,比如DATA_PATH = './data/raw_flow.csv'。你拿到自己的数据后,第一件事是改这个路径,第二件事是检查列名是否匹配。代码默认列名是merchant_id, date, flow,如果你的数据里叫shop_id, dt, count,要么改代码里的列名参数,要么在读取后统一重命名。我习惯在main.py开头加一段列名映射,这样不用动核心逻辑。

# main.py 片段:列名映射与数据加载 import pandas as pd from preprocess import fill_missing_by_merchant from feature_engineer import build_features COLUMN_MAP = { 'shop_id': 'merchant_id', 'dt': 'date', 'count': 'flow' } def load_and_prepare(path): df = pd.read_csv(path) df = df.rename(columns=COLUMN_MAP) df = fill_missing_by_merchant(df) df = build_features(df) return df

改完路径后,先跑python main.py --dry_run(如果代码支持)或者手动执行到build_features后打印df.head(),确认特征列都生成了。这一步花五分钟,能省掉后面半小时的报错排查。

3.3 训练、评估与结果解读

全流程跑通后,代码会在output/目录下生成预测结果 CSV 和一张对比图。评估指标通常是 MAE 和 RMSE。MAE 告诉你平均预测偏差多少人次,RMSE 对大误差更敏感。如果 MAE 是 15,意味着平均每个时间段预测值偏离真实值 15 人;如果 RMSE 是 40,说明存在个别时间段偏差极大,可能是节假日没处理好。

看结果时别只看数字,要把预测曲线和真实曲线叠在一起看。常见现象是:模型在平稳期跟得很紧,但一到节假日就塌方。这不是模型不行,是特征里没有节假日标记。代码里is_holiday特征默认是 0,你需要自己填一份节假日日期表进去。填完后重新训练,节假日的预测偏差通常能缩小一半。

# 评估片段:计算 MAE 和 RMSE from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def evaluate(y_true, y_pred): mae = mean_absolute_error(y_true, y_pred) rmse = np.sqrt(mean_squared_error(y_true, y_pred)) print(f"MAE: {mae:.2f}, RMSE: {rmse:.2f}") return mae, rmse

如果 MAE 低于 10,这份模型基本可用;如果在 20 以上,先别调参,回去检查数据填充和特征构造,八成是原始数据没对齐。

4. 避坑与排查:那些让预测结果离谱的常见问题

4.1 现象:预测值全是同一个数,曲线一条直线

原因:SVM 的epsilon参数设得太大,导致模型把所有样本都当成「在容忍范围内」,直接输出均值。或者C太小,模型欠拟合。解决:先把epsilon从默认的 0.1 降到 0.01,再把C从 1 调到 10。如果还是直线,检查特征是否做了标准化——SVM 没标准化时,大数值特征会主导距离计算,模型直接摆烂。

4.2 现象:训练集 MAE 很低,测试集 MAE 爆炸

原因:过拟合。常见于gamma设得太大(比如手动改成 1),或者特征里包含了未来信息(比如用了全量数据的滑动平均而不是滚动窗口)。解决:把gamma降回'scale'或 0.001,检查rolling_mean_7是不是用了center=True,如果是,改成默认的center=False。另外确认TimeSeriesSplit没被换成KFold。

4.3 现象:某些商家预测正常,某些商家完全不准

原因:不同商家的客流模式差异太大,用一个全局模型硬套。比如奶茶店和正餐店的周末效应完全相反。解决:按商家品类分组训练多个模型,或者在特征里加入merchant_category的 one-hot 编码。代码里默认没加品类特征,你需要自己从商家属性表里 merge 进来。

4.4 现象:运行报错ValueError: Input contains NaN

原因:填充缺失值时,某个商家全部数据都是 NaN,interpolate和median都救不回来。解决:在fill_missing_by_merchant里加一行判断,如果某商家填充后仍有 NaN,直接丢弃该商家并在日志里打印 ID。别试图用全局均值填,那会引入虚假模式。

4.5 现象:预测结果比真实值整体偏高或偏低

原因:训练集和测试集的客流分布不一致,常见于用历史数据预测未来时,整体客流趋势在上涨或下跌。解决:对目标值做差分,让模型预测「变化量」而不是「绝对值」。代码里没做差分,你可以在build_features里加一列flow_diff = flow.diff(),然后用flow_diff当标签,预测完再累加回去。

5. 进阶技巧:用残差修正和滚动预测把 MAE 再压一压

跑通基础版之后,如果你想让预测精度再上一个台阶,可以试试两个技巧。第一个是残差修正:先用 SVR 跑一遍,拿到预测值,然后计算残差residual = y_true - y_pred,把残差当成新标签,用同一个特征集再训练一个 SVR。最终预测值 = 第一次预测 + 第二次预测。这相当于让第二个模型专门学第一个模型犯的错。我实测在客流数据上能把 MAE 从 12 降到 9 左右,代价是训练时间翻倍。

# 残差修正示例 from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler def residual_correction(X_train, y_train, X_test, y_test): scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test) # 第一层 SVR svr1 = SVR(kernel='rbf', C=10, gamma='scale', epsilon=0.1) svr1.fit(X_train_s, y_train) pred1_train = svr1.predict(X_train_s) pred1_test = svr1.predict(X_test_s) # 第二层 SVR 学残差 residual_train = y_train - pred1_train svr2 = SVR(kernel='rbf', C=1, gamma='scale', epsilon=0.1) svr2.fit(X_train_s, residual_train) pred2_test = svr2.predict(X_test_s) final_pred = pred1_test + pred2_test return final_pred

第二个技巧是滚动预测。不要一次性预测未来 30 天,而是每天预测下一天,然后把预测值填回历史窗口,再预测下一天。这样做的好处是模型能利用最新的滑动平均特征,缺点是误差会累积。我一般会限制滚动步长不超过 7 天,超过 7 天就重新训练一次模型。代码里main.py的predict_future函数默认是一次性预测,你可以改成循环调用。

最后说一个我自己的习惯:每次改完特征或参数,一定先把random_state固定住,然后跑三次取 MAE 的平均值。SVM 本身没有随机性,但数据划分和网格搜索有,不固定种子的话,你根本分不清精度变化是调参带来的还是随机波动。从那以后我每次跑实验都强制走一遍「固定种子 → 跑三次 → 取均值」的流程,希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询