简介:这份资源面向参加天池大数据竞赛「菜鸟-需求预测与分仓规划」赛题的选手及供应链数据分析学习者,围绕需求预测与分仓规划两大核心问题,提供一套可复用的建模与优化实践方案。压缩包共68个文件,以58个Python脚本为主,辅以6个SQL特征工程脚本、2个CSV结果数据、1个R语言ARIMA实现和1份说明文档,整体约133KB,结构上按数据预处理、特征工程、分仓建模与模型融合分层组织。方案覆盖XGBoost、GBDT、随机森林、SVR等多种回归模型的单独建模与融合,并按分仓分别训练以捕捉仓库特有的销售动态,同时涉及库存分配相关的成本计算与规则策略。已有2339人学习,适合希望理解赛题完整流程、借鉴特征构造与融合思路的读者参考。
1. 需求预测与分仓规划:从赛题约束到可交付方案
电商大促前最怕两件事:货备少了断仓,货备多了压库。这道赛题把两个问题捆在一起——先按商品与仓库的历史出货预测未来需求,再把预测结果落到分仓补货量上。它适合正在做供应链方向毕设、课程设计,或需要交一份完整预测方案的同学。整套方案围绕需求预测、分仓规划、回归模型、模型融合四条主线展开,下面按需求、设计、实现、验收的顺序拆开讲,能直接照着搭。
2. 需求拆解与数据理解:预测目标和分仓约束先定清楚
2.1 赛题要解决的两个子问题
这道题表面是一个预测任务,实际是两级决策。第一级是需求预测:给定商品、仓库、时间三个维度,预测未来某段时间的出货量。第二级是分仓规划:把总需求拆到各个仓库,决定每个仓补多少货。两级之间是串联关系,预测偏差会直接放大到分仓结果上,所以不能只盯预测精度,还要看分仓后的履约表现。
我一般会把问题拆成三张表来理解:商品维表(品类、价格带、生命周期)、仓库维表(区域、容量、覆盖范围)、历史出货流水(日期、商品、仓库、出货量)。前两张决定特征怎么构造,第三张是标签来源。赛题通常给一段历史窗口做训练,再给一段未来窗口做预测,中间可能还有促销日历、节假日标记这类辅助信息。
这里有个容易忽略的点:需求预测的粒度。按天预测还是按周聚合,直接决定模型选择和评估方式。按天颗粒细但噪声大,按周平滑但丢失促销脉冲。我的做法是先按天建模,再在分仓阶段做周级聚合,这样既保留细节又方便补货决策。
2.2 数据清洗与特征构造的取舍
原始出货流水常见的脏数据有三类:退货冲抵导致的负值、重复上报的重复行、长尾商品的大量零值。负值一般按业务规则归零或单独标记,重复行按「商品+仓库+日期」去重保留最后一条,零值不要急着删——长尾商品的零值本身就是信号,说明需求稀疏,后面要用专门的处理方式。
特征构造上,我通常分四组:
- 时间特征:星期、月份、是否节假日、距促销天数
- 滞后特征:前 7 天、前 14 天、前 28 天出货量,以及滑动均值
- 商品特征:品类编码、价格分档、上架天数
- 仓库特征:区域编码、历史出货占比
import pandas as pd import numpy as np def build_features(df): df = df.sort_values(['item_id', 'warehouse_id', 'date']) grp = df.groupby(['item_id', 'warehouse_id'])['qty'] # 滞后特征:前7/14/28天出货量 for lag in [7, 14, 28]: df[f'lag_{lag}'] = grp.shift(lag) # 滑动均值:7天与28天窗口 df['roll_mean_7'] = grp.shift(1).rolling(7).mean().reset_index(0, drop=True) df['roll_mean_28'] = grp.shift(1).rolling(28).mean().reset_index(0, drop=True) # 时间特征 df['weekday'] = df['date'].dt.weekday df['is_weekend'] = (df['weekday'] >= 5).astype(int) return df.dropna(subset=['lag_28'])这段代码的关键在shift(1):滑动窗口必须排除当天,否则标签泄漏,线下评估会虚高、线上直接翻车。lag_28作为 dropna 的基准,是因为 28 天滞后是特征里最长的依赖,缺了它样本不可用。参数上,滞后窗口按业务补货周期定,7/14/28 对应周补、双周补、月补三种节奏,赛题没给就按这个默认走。
2.3 评估指标与分仓约束的量化
预测侧常用的是加权均方根误差和平均绝对百分比误差。前者对大销量商品更敏感,后者对长尾更友好。赛题如果只给一个指标,我建议主看加权均方根误差,辅看平均绝对百分比误差,避免模型为了长尾牺牲主力商品。
分仓侧的约束要提前量化成可计算的规则:单仓容量上限、区域覆盖优先级、最小补货批量、库存周转天数下限。这些约束在建模时可以先放松,在分仓求解时再收紧,否则预测模型会被约束绑死,学不出真实需求形态。
提示:评估指标一定要在训练前固定,不要边调模型边换指标,否则最后没法解释模型到底好在哪。
3. 总体方案设计:回归基线加融合层的两级结构
3.1 三层架构与模块职责
整套方案分三层。数据层负责清洗、特征构造、训练验证切分;模型层负责单模型训练与融合;决策层负责把预测结果转成分仓补货量。三层之间用统一的宽表衔接,避免每层各洗一遍数据。
模块划分上,我一般拆成五个:数据预处理模块、特征工程模块、单模型训练模块、融合模块、分仓求解模块。前四个服务预测,第五个服务决策。每个模块有独立入口和输出,方便单独调试——预测不准时能快速定位是特征问题还是模型问题。
3.2 为什么选回归模型做基线
需求预测本质是数值回归,不是分类。线性回归、岭回归、梯度提升树都是合理起点。线性回归可解释性强,能快速看出哪些特征在起作用;梯度提升树对非线性和特征交互更敏感,适合促销脉冲这类复杂模式。我的做法是先用岭回归跑通全流程,拿到一个可复现的基线分数,再上梯度提升树做提升。
选岭回归而不是普通最小二乘,是因为滞后特征之间高度共线,普通最小二乘的系数会剧烈震荡,岭回归的 L2 正则能把系数压稳。正则强度用交叉验证选,不要拍脑袋定。
from sklearn.linear_model import Ridge from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_squared_error tscv = TimeSeriesSplit(n_splits=5) alphas = [0.1, 1.0, 10.0, 100.0] best = None for a in alphas: scores = [] for tr, va in tscv.split(X): model = Ridge(alpha=a) model.fit(X.iloc[tr], y.iloc[tr]) pred = model.predict(X.iloc[va]) scores.append(mean_squared_error(y.iloc[va], pred, squared=False)) if best is None or np.mean(scores) < best[1]: best = (a, np.mean(scores)) print(best)时间序列交叉验证不能用随机切分,否则未来信息泄漏到训练集。TimeSeriesSplit按时间顺序滚动切分,每一折训练集都在验证集之前,符合真实预测场景。alpha的候选范围按特征量级调,特征标准化后 0.1 到 100 通常够用。
3.3 融合层的设计动机
单模型各有短板:线性模型抓不住交互,树模型对长尾稀疏样本容易过拟合。融合的价值在于让不同偏差结构的模型互相补位。常见做法是加权平均和堆叠。加权平均简单可控,权重用验证集误差反比定;堆叠用一层元学习器,表达能力强但容易过拟合,样本少时不建议上。
我一般先用加权平均,权重按验证集均方根误差的倒数归一化。如果融合后提升不明显,再考虑堆叠,并且元学习器只用简单模型(如岭回归),防止二次过拟合。
4. 模型实现与融合落地:从单模型到加权融合
4.1 梯度提升树的参数配置
梯度提升树是这套方案的主力模型。关键参数有四个:树的数量、学习率、树深度、叶子最小样本数。树的数量和学习率要联动调,学习率小则树多,学习率大则树少。树深度控制交互复杂度,需求预测一般 4 到 8 层够用,太深会记住噪声。叶子最小样本数防止长尾商品被过度切分,稀疏数据下要调大。
import lightgbm as lgb params = { 'objective': 'regression', 'metric': 'rmse', 'learning_rate': 0.05, 'num_leaves': 63, 'max_depth': 6, 'min_data_in_leaf': 50, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': -1 } dtrain = lgb.Dataset(X_tr, y_tr) dvalid = lgb.Dataset(X_va, y_va, reference=dtrain) model = lgb.train(params, dtrain, num_boost_round=2000, valid_sets=[dvalid], callbacks=[lgb.early_stopping(100)])early_stopping(100)表示验证集误差连续 100 轮不降就停,避免树数量拍脑袋。feature_fraction和bagging_fraction都是 0.8,作用是增加随机性、抑制过拟合,样本量大时可以调低到 0.6。min_data_in_leaf设 50 是针对长尾商品的经验值,如果长尾占比高可以再调大。
4.2 加权融合的实现与权重确定
融合权重不靠猜,用验证集误差反比算。两个模型误差分别是 e1、e2,权重就是 (1/e1) / (1/e1 + 1/e2)。这样误差小的模型权重大,但不会完全压制另一个模型。
def blend_weights(errors): inv = [1.0 / e for e in errors] total = sum(inv) return [i / total for i in inv] # 假设岭回归与LightGBM的验证集RMSE errors = [ridge_rmse, lgb_rmse] w = blend_weights(errors) final_pred = w[0] * ridge_pred + w[1] * lgb_pred权重确定后要在独立验证集上复核,不能直接用调权重的那份数据报最终分数。如果两个模型误差接近,权重会接近均分;如果差距大,弱模型权重会被压得很低,这时候要考虑弱模型是否还有保留价值——通常保留一点权重能提升稳定性,完全剔除反而在分布偏移时更脆弱。
4.3 分仓规划的求解思路
预测出总需求后,分仓按「历史占比打底、容量约束修正、最小批量取整」三步走。历史占比用近 28 天各仓出货占比,容量约束把超过单仓上限的部分挪到次优仓,最小批量按补货规则向上取整。
def allocate(total_demand, hist_ratio, capacity, min_batch): raw = total_demand * hist_ratio # 容量截断 capped = np.minimum(raw, capacity) # 溢出部分按剩余容量再分配 overflow = raw.sum() - capped.sum() if overflow > 0: spare = capacity - capped capped += overflow * spare / spare.sum() # 最小批量向上取整 return np.ceil(capped / min_batch) * min_batch这段逻辑的关键是溢出再分配:直接截断会导致总量不足,必须把溢出量按剩余容量比例补回去。最小批量取整会让总量略高于预测值,这是有意的,补货宁可略多不可断货。容量和批量参数从仓库维表和补货规则里取,赛题没给就按业务常识设默认值并在方案里写明假设。
5. 避坑与排查:这类预测方案最容易翻车的五处
5.1 特征泄漏导致线下虚高
现象是线下验证分数很好,换一段数据或上线后误差暴涨。原因多半是滑动窗口没做 shift,把当天标签算进了特征,或者用了未来才有的促销标记。解决办法是所有滞后和滑窗特征统一 shift(1) 起步,促销标记只保留预测时点已知的部分,验证切分严格按时间顺序。
5.2 长尾商品把整体指标拖垮
现象是主力商品预测还行,整体均方根误差却很高。原因是长尾商品零值多、方差大,模型在稀疏样本上学不到稳定模式。解决办法是分层建模:主力商品单独训模型,长尾商品用品类均值或简单规则兜底,评估时按销量加权,避免长尾主导指标。
5.3 融合权重在验证集上过拟合
现象是融合后验证集分数提升明显,换数据后不如单模型。原因是权重用同一份验证集反复调,等于在验证集上训练。解决办法是留一份独立测试集只用于最终复核,权重确定过程不碰它;或者用交叉验证的折外预测算权重,减少单次切分的偶然性。
5.4 分仓取整后总量失控
现象是各仓补货量加起来远超或远低于总预测。原因是容量截断后没做溢出再分配,或者最小批量取整在多仓累加后放大偏差。解决办法是先分配后取整,取整后做一次总量校验,偏差超过阈值就按比例回调,把总量拉回预测值附近。
5.5 评估指标中途更换
现象是调参过程中指标忽高忽低,最后说不清模型到底改进了什么。原因是不同阶段用了不同指标,或者验证集切分方式变了。解决办法是训练前固定指标、固定切分、固定随机种子,所有对比在同一口径下做,方案文档里写清楚口径。
6. 验收与答辩:用可复现的对照实验说话
验收这套方案,我一般准备三组对照:基线组(岭回归单模型)、提升组(梯度提升树单模型)、融合组(加权融合)。三组用同一份训练验证切分、同一套特征、同一个评估口径,只变模型。这样答辩时能清楚回答「融合到底有没有用」「提升来自特征还是模型」两个必问问题。
具体操作上,先跑基线拿到分数,再跑提升组,最后跑融合组,每组记录训练耗时、验证集均方根误差、分仓后总量偏差三个数。表格比口头描述有说服力:
| 组别 | 验证集RMSE | 分仓总量偏差 | 训练耗时 |
|---|---|---|---|
| 基线(岭回归) | 基准值 | 基准值 | 短 |
| 提升(梯度提升树) | 低于基线 | 略优 | 中 |
| 融合(加权) | 最低 | 最优 | 中 |
答辩时重点讲三件事:特征怎么防泄漏、融合权重怎么定、分仓约束怎么量化。这三处是评委最容易追问的地方。如果被问到「为什么不用更复杂的模型」,回答方向是样本量和可解释性——供应链场景要能解释每个仓为什么补这么多,黑匣子模型在落地时反而增加沟通成本。
还有一个技巧:把分仓结果做成「预测需求 vs 实际补货」的对照图,标出被容量约束修正的仓。评委看到约束真的生效了,比听十句「考虑了约束」都管用。
血泪经验是早期我只报预测误差,没报分仓偏差,被追问「预测准了但分仓错了怎么办」时答不上来。从那以后我每次做这类两级决策的方案,都强制走一遍「预测指标 + 决策指标」双口径验收,缺一个都不交。希望帮到你。
本文还有配套的精品资源,点击获取