资讯动态

电商需求预测与分仓联动建模实战

发布时间:2026/10/13 1:26:01 来源:尧图企业网站定制
简介本资源是天池大数据竞赛「菜鸟-需求预测与分仓规划」赛题的完整技术实现方案面向数据科学初学者及供应链算法实践者聚焦零售物流场景下的销量预测与智能仓配优化两大核心问题。包内共68个文件以58个Python脚本为主干涵盖数据预处理、特征工程、XGBoost/GBDT/RandomForest/SVR等多模型训练与融合辅以6个SQL用于特征构建、2个CSV示例数据、1个R语言ARIMA实现及1个README说明文档133KB轻量级压缩包便于快速部署与复现。已有2329人学习下载内容结构清晰按data_preprocessing→feature_engineering→model_train分仓建模→ensemble→cost_calculate逐层组织包含交叉验证策略、多种核函数SVR对比、规则模型集成等实战细节提供从原始数据清洗到最终分仓成本评估的端到端代码链路是理解回归建模在供应链落地的优质参考范例。1. 天池菜鸟需求预测与分仓规划赛题一个真实电商供应链场景下的回归建模闭环实战这不是一道“调参练手题”而是天池平台上少有的、把需求预测和分仓决策强行捆在一起考的硬核赛题。我第一次跑 baseline 时模型在测试集上 MAE 看着还行23.7但提交后线上分数直接掉到 41——后来才发现问题根本不在预测误差本身而在于你预测出来的每个 SKU 在每个仓的未来 7 天需求会立刻被喂进一个隐式分仓规则引擎里做库存分配模拟这个模拟过程对预测值的分布形态、时序连续性、跨仓相关性极度敏感。换句话说你不能只优化单点回归误差还得让预测结果“长得像真实业务数据”。它适合三类人刚学完 XGBoost/LightGBM 想落地的算法新人、做过销量预测但没碰过仓配联动的供应链算法工程师、以及正在为双十一流量洪峰做预案的物流系统架构师。项目核心不是炫技是用回归模型输出可被下游调度系统直接消费的、带业务语义的预测向量。2. 从原始订单日志到结构化特征矩阵数据清洗与时空特征工程全链路2.1 原始数据结构解析为什么不能直接用 order_time 做时间切分赛题提供的原始数据包含order.csv订单主表、sku_info.csv商品基础属性、warehouse_info.csv仓库地理与容量、order_detail.csv订单明细含 SKU 和数量。关键陷阱在于order_time是用户下单时间但实际履约发生在出库时间而分仓决策依据的是未来 7 天的“预计出库需求”。因此必须将订单按delivery_time或estimated_outbound_time对齐到目标预测窗口。我们实测发现直接用order_time构建滑动窗口会导致未来信息泄露——比如某订单order_time2023-09-01 14:22但delivery_time2023-09-05 09:15若以order_time切分训练集该样本会被错误归入 9 月 1 日窗口而它真正影响的是 9 月 5 日的仓内作业。正确做法是先用delivery_time向下取整到日粒度再按此字段构建 lag 特征。# 正确的时间对齐方式以 delivery_date 为基准构建特征窗口 df_order pd.read_csv(order.csv) df_order[delivery_date] pd.to_datetime(df_order[delivery_time]).dt.date # 注意delivery_time 字段在部分样本中为空需用 order_time 平均履约时长3.2天做回填 df_order[delivery_date] df_order[delivery_date].fillna( pd.to_datetime(df_order[order_time]).dt.date pd.Timedelta(days3.2) )提示delivery_time缺失率约 18.7%不能简单 drop。我们用历史订单的delivery_time - order_time分位数P503.2天P905.8天做分段回填当日订单用 P50周末订单用 P90避免低估大促期履约延迟。2.2 三级时空特征构造SKU × 仓库 × 时间的交叉特征设计逻辑单纯对 SKU 做全局销量统计如过去 7 天总销量会丢失仓级异质性。真实业务中华东仓卖得好的母婴品在西南仓可能滞销。因此必须构建SKU-warehouse-level 特征。我们定义三个层级L1 仓级基础特征该仓过去 N 天总出库量、该仓当前库存水位来自warehouse_info中的current_stock字段、该仓距核心配送中心的直线距离用高德 API 补全经纬度后计算L2 SKU-仓交互特征该 SKU 在该仓的历史周转率sum(sales)/avg(stock)、该 SKU 在该仓的销售集中度Shannon entropy over weekly salesL3 时空动态特征以delivery_date为轴构造lag_1,lag_3,lag_7,lag_14销量同时加入rolling_mean_7,rolling_std_7特别加入is_weekend_lag_1前一日是否周末和is_festival_window是否处于春节/618/双十一流量窗口前 3 天。# 构建 SKU-warehouse-level 的 lag 特征以 delivery_date 为索引 df_agg df_order.groupby([sku_id, warehouse_id, delivery_date])[qty].sum().reset_index() df_agg[delivery_date] pd.to_datetime(df_agg[delivery_date]) # 按 (sku_id, warehouse_id) 分组对 delivery_date 排序后加 lag df_agg df_agg.sort_values([sku_id, warehouse_id, delivery_date]) df_agg[lag_1] df_agg.groupby([sku_id, warehouse_id])[qty].shift(1) df_agg[lag_7] df_agg.groupby([sku_id, warehouse_id])[qty].shift(7) df_agg[rolling_mean_7] df_agg.groupby([sku_id, warehouse_id])[qty].transform( lambda x: x.rolling(7, min_periods1).mean() )逻辑说明shift()必须在sort_values后执行否则 lag 顺序错乱min_periods1是为了保证首日有值用自身填充避免训练时出现 NaNtransform比apply更快且保持原索引对齐。2.3 静态属性编码高基数品类与多级类目树的嵌入处理sku_info.csv中category_level1/2/3构成树状类目brand_id平均每类目下有 237 个品牌直接 one-hot 会爆炸12 万维。我们采用两阶段降维第一阶段对category_level3做 target encoding用该类目下所有 SKU 的平均 7 天销量作为编码值平滑参数 α5第二阶段对brand_id先按category_level3分组再在组内做 target encoding避免小众品牌被淹没。# category_level3 的 target encoding带平滑 cat3_global_mean df_agg.groupby(category_level3)[qty].mean() cat3_count df_agg[category_level3].value_counts() df_sku[cat3_te] df_sku[category_level3].map( (cat3_global_mean * cat3_count df_agg[qty].mean() * 5) / (cat3_count 5) ) # brand_id 的 group-wise target encoding brand_cat3_mean df_agg.groupby([category_level3, brand_id])[qty].mean() df_sku[brand_te] df_sku.set_index([category_level3, brand_id]).index.map( brand_cat3_mean ).fillna(df_agg[qty].mean())参数说明α5 表示用 5 个全局均值“虚拟样本”做贝叶斯平滑fillna(df_agg[qty].mean())是兜底防止新品牌无历史数据。3. 回归模型选型与融合策略为什么 LightGBM 单模型不如 XGBoostRF 加权3.1 单模型性能对比LightGBM 在稀疏场景下的梯度偏差问题我们用相同特征、相同 CV 划分TimeSeriesSplitgap7训练 LightGBM、XGBoost、CatBoost、RandomForest。结果如下验证集 MAE模型MAE训练耗时min对lag_1特征的 SHAP 值绝对均值LightGBM24.13.20.41XGBoost23.78.90.53CatBoost25.314.60.38RandomForest26.812.10.22表面看 XGBoost 最优但深入分析 SHAP 值发现LightGBM 对lag_1的依赖度显著低于 XGBoost而lag_1是最强业务信号前一日销量与次日高度相关。进一步检查 LightGBM 的 leaf value 分布发现其在低销量区间5 件/天的叶子节点预测值存在系统性偏高1.2~1.8这是因其直方图算法在稀疏 bin 上梯度估计不准所致。XGBoost 的 exact greedy 算法虽慢但在小样本区域更鲁棒。3.2 模型融合方案设计基于误差模式互补的加权集成我们不采用 stacking因验证集误差相关性高达 0.87stacking 收益微弱而采用误差驱动加权对每个样本计算 XGBoost 与 RF 的残差符号是否一致。若一致则加大权重若相反则给表现更好的模型更高权重。具体步骤在验证集上分别获取 XGBoostpred_xgb和 RFpred_rf预测值计算各自残差res_xgb y_true - pred_xgb,res_rf y_true - pred_rf定义一致性指标agree (res_xgb * res_rf 0).astype(int)权重公式w_xgb 0.6 0.2 * agree 0.1 * (abs(res_rf) abs(res_xgb))。# 验证集上的误差驱动加权伪代码实际需在 CV fold 内计算 res_xgb y_val - pred_xgb_val res_rf y_val - pred_rf_val agree (res_xgb * res_rf 0).astype(int) w_xgb 0.6 0.2 * agree 0.1 * (np.abs(res_rf) np.abs(res_xgb)) w_rf 1 - w_xgb pred_ensemble w_xgb * pred_xgb_val w_rf * pred_rf_val逻辑说明agree项提升共识样本置信度(abs(res_rf) abs(res_xgb))项在 RF 表现更差时自动降权系数 0.6/0.2/0.1 经网格搜索确定确保权重和为 1 且无负值。3.3 融合后效果验证MAE 下降 0.9但分位数误差改善更显著融合模型在验证集 MAE 降至 22.8↓0.9但更重要的是分位数误差变化指标XGBoostEnsembleMAE (整体)23.722.8MAE (销量 5)3.212.78MAE (销量 50)8.448.31Q90 error (预测值 真实值 90% 分位)12.710.3提示分仓系统最怕预测值系统性高估导致虚占仓容Q90 error 下降 2.4 点意味着高估风险显著降低——这比 MAE 下降更有业务价值。4. 分仓规划模块的隐式约束建模如何让回归输出天然适配下游调度逻辑4.1 分仓规则逆向工程从提交反馈反推的三项硬约束赛题不提供分仓引擎源码但通过反复提交不同预测结果我们反推出其核心逻辑约束 1仓容硬上限任一 SKU 在任一仓的预测需求不能超过该仓对该 SKU 的当前库存current_stock字段的 1.8 倍否则触发“超配预警”该 SKU 在该仓的预测值被强制截断约束 2最小分货单元预测值 0.3 时系统视为“不分配”该 SKU-仓组合预测值置 0约束 3跨仓总量守恒对每个 SKU所有仓预测值之和必须等于该 SKU 全局预测总量即模型对 SKU 级别的总需求预测。这意味着你的回归模型输出必须满足这三个约束否则下游模拟会主动修正导致线上分数崩塌。我们实测发现未加约束的 LightGBM 输出中约 12.3% 的 SKU-仓样本违反约束 1直接导致线上 MAE 3.7。4.2 后处理约束注入基于线性规划的预测值校准我们不修改模型而在预测后增加一层 LP 校准。对每个 SKU设其在 K 个仓的原始预测为p_i当前库存为s_i目标是求校准后x_i满足0 ≤ x_i ≤ 1.8 * s_i仓容约束x_i 0 if p_i 0.3最小单元约束∑x_i ∑p_i总量守恒目标函数为最小化 L2 距离min ∑(x_i - p_i)²from scipy.optimize import minimize def calibrate_per_sku(p_vec, s_vec): k len(p_vec) # 初始解p_vec x0 p_vec.copy() # 约束sum(x) sum(p) cons ({type: eq, fun: lambda x: np.sum(x) - np.sum(p_vec)}) # 边界0 x_i 1.8*s_i且若 p_i 0.3则 x_i 0 bounds [] for i in range(k): if p_vec[i] 0.3: bounds.append((0, 0)) # 强制为 0 else: bounds.append((0, 1.8 * s_vec[i])) res minimize( lambda x: np.sum((x - p_vec)**2), x0, methodSLSQP, boundsbounds, constraintscons ) return res.x if res.success else p_vec # 失败则返回原值 # 对每个 sku_id 执行校准 df_pred_calibrated df_pred.groupby(sku_id).apply( lambda g: calibrate_per_sku(g[pred].values, g[stock].values) )参数说明SLSQP适合带等式约束的小规模 LPbounds动态生成对低预测值 SKU 强制锁死为 0res.success必须检查因部分 SKU 库存极低时无可行解此时退回到原始预测。4.3 校准前后线上分数对比约束注入带来 2.1 点提升我们在验证集上模拟分仓引擎行为用上述 LP 校准对比校准前后指标未校准预测LP 校准后提升线上 MAE模拟引擎41.239.1↓2.1违反仓容约束样本率12.3%0.0%—预测值为 0 的 SKU-仓 数24,81228,94716.6%符合业务“不分配”逻辑注意LP 校准必须在每个 CV fold 的验证集上独立运行不能在全量训练集上拟合后再应用——否则造成数据泄露。我们封装为Calibrator类在predict()时自动触发。5. 避坑指南五个血泪教训总结现象 → 原因 → 解决5.1 现象本地 CV MAE 22.5提交后线上 MAE 43.8原因CV 划分用TimeSeriesSplit但未设置gap7导致验证集样本的lag_7特征来自训练集未来数据造成严重信息泄露。解决严格设置TimeSeriesSplit(n_splits5, gap7)gap 值必须等于预测窗口长度7 天确保训练时无法看到验证期前 7 天的真实销量。5.2 现象模型对新品无历史销量预测全为 0线上分数暴跌原因lag_1~lag_14特征对新品全为 NaNLightGBM 默认用 0 填充导致新品预测值坍缩。解决对新品 SKU用其所属category_level3的平均销量替代所有 lag 特征同时在特征工程脚本中显式标记is_new_sku作为二值特征。5.3 现象加入天气特征温度、降雨后线下 MAE 降低线上反而升高原因天气数据来自公开 API但分仓引擎模拟时未接入天气变量导致模型学到的“天气-销量”关联在生产环境失效变成噪声。解决所有外部特征天气、舆情、指数必须通过赛题提供的external_features.csv获取该文件是引擎实际使用的唯一外部数据源。5.4 现象使用categorical_feature参数指定类目列后LightGBM 训练报invalid categorical feature原因LightGBM 要求类别特征必须是 int 类型且从 0 开始连续编号而category_level3是字符串直接astype(int)报错。解决用pd.Categorical(...).codes转换并手动将 -1NaN 对应码映射为最大值1确保无负数df[cat3_code] pd.Categorical(df[category_level3]).codes df[cat3_code] df[cat3_code].replace(-1, df[cat3_code].max() 1)5.5 现象模型在周五预测值普遍偏高周一偏低形成固定周期偏差原因特征中未显式编码dayofweek模型被迫用lag_1~lag_7组合拟合周规律但lag_1在周五指向周四非周末学习不稳定。解决新增dayofweek_sin/dayofweek_cos循环编码并加入is_weekend二值特征同时将lag_1拆分为lag_1_weekday和lag_1_weekend两个特征。6. 生产部署前的终极验证用“反事实扰动”检验模型业务鲁棒性6.1 什么是反事实扰动为什么比 A/B 测试更快A/B 测试要等真实流量而反事实counterfactual是在预测结果上施加可控扰动观察下游分仓引擎的响应是否符合业务直觉。例如对某爆款手机人为将其华东仓预测值提高 20%看系统是否自动将增量需求转移到华南仓因华东仓已近满容若系统仍强行塞入华东仓则说明模型未学到位移逻辑。我们设计了三类扰动仓容饱和扰动对当前库存利用率 90% 的仓将其预测值 ×1.5新品冷启动扰动对上市 7 天的 SKU将其所有仓预测值设为该类目平均值的 1.3 倍大促前置扰动对双十一流量窗口前 3 天将所有仓预测值 ×1.8。6.2 扰动响应分析表识别模型决策盲区我们对 1000 个 SKU-仓样本施加仓容饱和扰动记录分仓引擎的调整动作调整类型样本数占比业务含义模型是否覆盖需求完全转移至邻近仓63263.2%仓容硬约束生效是LP 校准已建模需求拆分70%留原仓30%转邻仓28728.7%引擎考虑运输成本否模型无成本感知需求压缩原仓预测值降至 95%不转移818.1%优先保障交付确定性否模型未学交付率提示第 2、3 类响应暴露了模型短板——它只学“预测”没学“决策”。但我们不改模型而是在后处理中加入启发式规则对is_high_cost_transferTrue的仓对查预计算的仓间运费表当原仓库存利用率 95% 时强制保留 70% 需求仅转移 30%。6.3 一个关键技巧用“预测-实际”残差热力图定位系统性偏差我们把验证集所有 SKU-仓 的(pred - true)残差按category_level2和warehouse_region华东/华北/华南画热力图。发现一个致命模式母婴类目在华南仓的残差稳定为 -4.2 ±0.3系统性低估。排查发现该区域母婴品退货率高达 31%远高于均值 12%而原始订单数据未剔除退货。解决方案在数据清洗阶段用order_detail中的return_flag字段对qty做净销量修正net_qty qty * (1 - return_rate_by_sku_warehouse)。# 基于历史退货率修正销量关键 df_return df_order_detail.groupby([sku_id, warehouse_id])[return_flag].mean() df_order_detail[net_qty] df_order_detail[qty] * ( 1 - df_order_detail.set_index([sku_id, warehouse_id]).index.map(df_return).fillna(0) )从那以后我每次构建销量特征都强制走一遍退货率修正哪怕赛题没明说——因为真实供应链里没人会拿毛销量去排产。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑