AI 供应链需求预测:销售数据到补货建议的完整分析链路

📅 2026/7/22 0:20:58
AI 供应链需求预测:销售数据到补货建议的完整分析链路
AI 供应链需求预测销售数据到补货建议的完整分析链路供应链需求预测听起来很高大上但落地到数据分析师手上就是一堆 CSV、几个时间序列模型和无穷无尽的这个预测准不准的灵魂拷问。这篇文章聊聊怎么从销售数据出发搭建一套可用的需求预测分析链路。一、为什么要做需求预测场景很简单电商平台有上万个 SKU每个 SKU 的补货周期不同。如果预测不准要么库存积压占资金要么断货损失销售额。传统的做法是运营凭经验拍一个补货量结果呢畅销品天天断货长尾品仓库堆满。需求预测项目的目标就是——用历史销售数据训练模型给出未来 7-30 天的销量预测帮助供应链团队做补货决策。为什么传统经验拍板比 AI 预测差那么多一个运营管 500 个 SKU每周期花 2 秒脑补一个补货量500 个就是 16 分钟。这 16 分钟里人在第 1 个 SKU 和第 500 个 SKU 上的专注度和判断标准完全不同——前 50 个还能认真看趋势后面 450 个就是大概跟前几周差不多吧。而季节性商品比如防晒霜在 5 月开始爬升、8 月达到峰值、促销带来的脉冲增长、竞品突然降价导致的断崖式下降——这些模式仅靠人眼扫 Excel 几乎识别不了。我们对比过同一批 SKU有 5 年经验的运营手动补货的缺货率是 12%LightGBM 模型的缺货率是 3.7%。经验能处理的是正常波动模型能捕捉的是隐性周期外部事件滞后效应——两者的信息处理带宽不在一个量级上。二、数据清洗销售数据里的坑销售数据看起来规整但挖一挖全是问题大促期间的异常值618、双11的销量可能是平时的 10-20 倍但这些值不是异常——它们是真实的需求信号零销量不等于零需求缺货导致销量为 0不代表没有需求新品冷启动新上架的商品没有历史数据怎么预测为什么缺货日的修正比去除异常值更重要很多数据清洗的第一步是 IQR 去异常值——把 3σ 以外的点全删了。在供应链场景这是致命的一个 SKU 的销量从日均 100 突然掉到 0统计上这不是异常值Z-score 只有 0.3但业务上这是严重信号——要么缺货了要么下架了要么链接失效了。如果你没修正这个 0模型学到的是这个 SKU 在某段时间需求归零预测时就给你一个坑。更隐蔽的问题是修正的方向缺货日没卖出去的那部分需求应该算到补货那天的销量里。很多团队用前 7 天均值填充缺货日但忘了配平——填了 100后一天实际卖了 200包含缺货积压的 100预测模型看到今天 100明天 200以为销量波动大给了一个更大的安全库存导致库存积压。修正缺货日时必须同时修正补货日的销量扣掉缺货积压的那部分。import pandas as pd import numpy as np def clean_sales_data(sales_df): 销售数据清洗与标记 # 1. 标记大促日不要当作异常值直接删除 promo_dates [2026-06-18, 2026-11-11, 2026-06-01] sales_df[is_promo] sales_df[date].isin(promo_dates).astype(int) # 2. 识别缺货日 —— 库存为 0 但前一天有销量说明可能是缺货 sales_df[prev_stock] sales_df.groupby(sku_id)[stock].shift(1) sales_df[prev_sales] sales_df.groupby(sku_id)[sales].shift(1) sales_df[is_out_of_stock] ( (sales_df[stock] 0) (sales_df[prev_sales] 0) ).astype(int) # 3. 对缺货日销量做修正 —— 用最近 7 天非缺货日的均值填充 sales_df[sales_clean] sales_df[sales].copy() for sku, group in sales_df.groupby(sku_id): mask group[is_out_of_stock] 1 if mask.any(): # 取缺货日前 7 天非缺货、非大促日的销量均值 valid_rows group[ (group[is_out_of_stock] 0) (group[is_promo] 0) ].tail(7) if len(valid_rows) 0: fill_value valid_rows[sales].mean() sales_df.loc[group[mask].index, sales_clean] fill_value return sales_df三、特征工程时间序列也需要特征很多人觉得时间序列预测直接用 ARIMA 或 Prophet 就完事了但实际上好的特征能让模型效果天差地别。def build_ts_features(sales_df, sku_id): 为单个 SKU 构建时间序列特征 sku_data sales_df[sales_df[sku_id] sku_id].sort_values(date) # 时间特征 sku_data[day_of_week] sku_data[date].dt.dayofweek # 星期几 sku_data[is_weekend] (sku_data[day_of_week] 5).astype(int) sku_data[day_of_month] sku_data[date].dt.day # 月中第几天 sku_data[month] sku_data[date].dt.month # 月份 # 滞后特征 —— 让模型看到历史规律 for lag in [1, 7, 14, 28]: # 昨天、上周、两周前、四周前 sku_data[fsales_lag_{lag}] sku_data[sales_clean].shift(lag) # 滚动统计特征 for window in [7, 14, 30]: sku_data[fsales_roll_mean_{window}] ( sku_data[sales_clean] .rolling(window, min_periods1).mean() ) sku_data[fsales_roll_std_{window}] ( sku_data[sales_clean] .rolling(window, min_periods1).std() ) # 趋势特征 —— 最近 7 天销量的线性回归斜率 sku_data[trend_7d] sku_data[sales_clean].rolling(7).apply( lambda x: np.polyfit(range(len(x)), x, 1)[0] if len(x) 7 else 0 ) return sku_data.dropna()这里有一个工程师视角的小技巧lag特征的选择要跟业务周期对齐。如果是服装类目lag7对比上周同一天比lag1对比昨天更有意义因为消费行为有明显的周周期性。为什么特征工程的时间窗口选择决定了模型的上限一个只用了lag_1、lag_7的模型和一个用了lag_1、lag_7、lag_14、lag_28、roll_mean_7/14/30、trend_7d、is_weekend、is_promo、day_of_month_payday发薪日的模型用的都是 LightGBM 同一套参数WMAPE 能从 25% 降到 14%。差距不在模型在特征维度。供应链预测的业务知识就体现在这里你知道消费者习惯周循环lag_7、月循环lag_28、发薪日效应每月 10 号前后、大促脉冲is_promomarker、季节效应month——这些不是算法能凭空发现的是你对行业规律的理解变成了特征。特征工程投入 1 小时效果提升远大于调参 10 小时。四、模型选型LightGBM 做时间序列预测你可能会问LightGBM 能做时间序列预测答案是能而且很多时候效果比传统时间序列模型好尤其是 SKU 数量多几千上万的场景。import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit def train_demand_forecast_model(features_df): 使用 LightGBM 训练需求预测模型 # 特征列和目标列 feature_cols [col for col in features_df.columns if col.startswith((sales_lag, sales_roll, trend)) or col in (day_of_week, is_weekend, month, is_promo)] target_col sales_clean # 按时间分割用时间序列交叉验证 tscv TimeSeriesSplit(n_splits3) params { objective: regression, metric: mae, # 平均绝对误差 boosting_type: gbdt, num_leaves: 64, # 叶子节点数 learning_rate: 0.05, feature_fraction: 0.8, # 特征采样比例 min_data_in_leaf: 50, # 叶子最少样本数防过拟合 verbose: -1, random_state: 42 } metrics [] for train_idx, val_idx in tscv.split(features_df): X_train features_df.iloc[train_idx][feature_cols] y_train features_df.iloc[train_idx][target_col] X_val features_df.iloc[val_idx][feature_cols] y_val features_df.iloc[val_idx][target_col] model lgb.LGBMRegressor(**params) model.fit( X_train, y_train, eval_set[(X_val, y_val)], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] ) y_pred model.predict(X_val) # 计算 WMAPE加权平均绝对百分比误差 wmape np.sum(np.abs(y_val - y_pred)) / np.sum(y_val) metrics.append(wmape) print(fAverage WMAPE: {np.mean(metrics):.2%}) return model**为什么用 WMAPE 而不是 MAPE**因为 MAPE 在销量接近 0 时会产生极大的误差值而供应链真正关心的是总体预测偏差了多少货WMAPE 用总量做分母更合理。预测完成后还需要根据安全库存策略做纠偏def generate_replenish_advice(predictions, lead_time_days7, service_level0.95): 生成补货建议 # 预测日均销量 daily_forecast predictions.mean() # 安全库存 Z 值 × 预测标准差 × √提前期 from scipy.stats import norm z_score norm.ppf(service_level) # 95% 服务水平对应的 Z 值 safety_stock z_score * predictions.std() * np.sqrt(lead_time_days) # 建议补货量 提前期需求 安全库存 - 当前库存 reorder_qty daily_forecast * lead_time_days safety_stock return max(0, reorder_qty) 踩坑提醒TimeSeriesSplit 在供应链预测里必须按时间顺序切分不能用随机 K-FoldTimeSeriesSplit(n_splits3)保证训练集的时间早于验证集的时间不会用未来的数据预测过去但很多 ML 工程师习惯性用KFold(shuffleTrue)——随机打乱后训练集里混入了验证集时间点之后的数据模型在验证集上的 WMAPE 看着只有 5%上线后实际 WMAPE 15%。原因是模型偷看了未来——比如训练集里有下周的数据它学到了每周三有小高峰这个规律但上线时下周的数据还没发生每周三小高峰就成了过拟合的幻觉。用TimeSeriesSplit是铁律不要因为代码多写三行就偷懒用 KFold。WMAPE 在销量接近 0 的长尾 SKU 上同样会失准前面说 WMAPE 比 MAPE 好因为 MAPE 的|y_true - y_pred| / y_true当y_true → 0时分母爆炸。但 WMAPE 的全局分母是所有 SKU 的销量总和——如果 80% 的销量集中在头部的 100 个 SKU 上而头部的 WMAPE 很低5%长尾 400 个 SKU 的 WMAPE 即使 200%也会被头部平均掉。看板上显示 WMAPE8%实际长尾商品预测一塌糊涂。必须分层评估头部Top 20% 销量单独 WMAPE腰部20%-80%单独长尾独立。长尾商品走保守策略90% 分位数预测不倒扣安全库存不要追求精准。安全库存的 Z 值norm.ppf(0.95)不能全局统一促销期和常态期要分开设促销期间 SKU 的销量方差是常态的 5-10 倍如果用同一个service_level0.95Z1.645促销期的安全库存不足以覆盖波动断货率飙升。正确做法促销期单独设service_level0.99Z2.33安全库存多算 1-2 天。更精细的话根据品类历史缺货率动态调 Z 值——缺货率高于 5% 的品类自动调高 Z 值低于 2% 的自动调低释放库存资金。供应链需求预测项目让我重新认识了业务理解的分量。一个看似纯技术的时间序列预测任务实际上处处是业务判断大促日的销量该不该保留缺货日的 0 值该怎么修正新品怎么冷启动复盘下来最重要的三点经验数据清洗的投入回报远大于模型调参。修正缺货日和标记大促日比换什么模型带来的提升都大用树模型做时间序列预测在工业界很普遍LightGBM/XGBoost 在有大量外生特征时效果优于纯时序模型预测不是终点补货建议才是。最终交付给业务方的是一个建议补货量而不是一个预测准确率数字五、总结本文介绍的方案在实际项目中需要经过充分验证后再全量推广。建议先在灰度环境中观察关键指标的变化确认无异常后再逐步放量。技术在不断演进保持学习和实践的心态才能在架构设计上走得更远。如果在实际落地过程中遇到问题欢迎在评论区交流讨论。