MCM520 ← 资料站首页 供应链风险与预测鲁棒性评估:服务水平、误差结构与结构脆弱性(优秀范文二) 打开交互阅读器 →

供应链风险与预测鲁棒性评估:服务水平、误差结构与结构脆弱性(优秀范文二)

摘要

范文一已经回答了"订单怎么清洗、客户怎么分层、预测怎么建、补货怎么定"——通过 RFM 分群拿到轮廓系数 0.830、用 Holt-Winters 预测需求并以报童模型给出补货量。但库存与订货决策落地时,"给出一个点估计"只是故事的一半。本文换一个更贴近"这套供应决策靠不靠谱、风险在哪、失灵会怎样"的角度:把下 3 月补货建议放进服务水平、误差结构、结构与时间脆弱性的坐标系里重新检验。我们复用范文一的真源与模型,新增八组实验重新解剖同一批 4297 条有效订单,得到四个被单点预测掩盖的结论:第一,服务水平是很贵的,从 90% 提到 98%,家电补货从 82→92、食品从 374→403,高需求品类要花更多库存才能换一个百分点;第二,预测误差有显著长尾——均值 14.2% 但最大到 42.5%,超 10% 的误差占七成,而按 σ 构造的 95% 区间只在未来 3 个月测试里覆盖 6/9 个点(家电 3/3、服饰 1/3、食品 2/3),其中服饰明显失守;第三,结构存在脆弱点——前 10 家客户就拿走 42.8% 订单额,逐月 2 步前瞻误差普遍比 1 步翻倍以上(家电 18.8%→33.7%、服饰 3.1%→29.2%),长视界预测远不如短期可靠;第四,需求变异最剧烈的是食品(CV 0.28、峰值月 1.8×均值),恰好是促销拉动最强(x1.71)的品类,把"促销引发的高波动的品类的补货"当成平滑季节来处理是最危险的方法错配。这些发现把"算出一个补货数就完事"改成了"带服务水平预算、带误差边际、带结构红线的稳健决策脚本"。

一、问题重述与数据

赛题给出产品订单与需求预测数据,要求做订单分析与下月补货建议。范文一构造 4326 条原始订单(含退单 15、测试单 8、重复录入 6),清洗后得 4297 条有效订单、总销售额 255.06 万元;以 RFM+KMeans 把 60 家客户分为 10 高价值 / 14 流失风险 / 36 主力三群(轮廓系数 0.830);用验证型选参的 Holt-Winters 乘法季节模型预测三品类未来 3 月需求,接入报童模型给出下月补货(家电 87、服饰 136、食品 388 件)。

本文不改数据、不改建模流程,只换提问方式:

  • 服务水平值多少钱:报童补货对服务水平的敏感度;
  • 误差有多肥的尾巴:|相对误差|的分布形态是否适合用 σ 区间;
  • 促销效果归因:6/11 月大促对各品类分别拉动多少;
  • 需求变异结构:哪类需求波动最剧烈、最像"脉冲";
  • 客群营收贡献与流失:谁的订单额决定大局、谁已被边缘化;
  • 客户集中风险:能否被少数头部客户绑架;
  • 时间脆弱性:预测越往前推,误差放大多少;
  • 区间可信度:95% 区间是否真的兜住未来实际值。

二、符号与核心方法

沿用范文一的关键构成:订单元组含月份 midx、客户 cust、品类 prod、数量 qty、单价 price、金额 amount;Holt-Winters 乘法季节模型以季节长度 s=12s=12 拟合,预测 y^t+h=(lt+hbt)S(t+h) mod s\hat y_{t+h}=(l_t+h b_t)S_{(t+h) \bmod s};残差标准差 σ\sigma 由训练段拟合残差估计。

新增评测口径:

  • 报童服务水平:在需求近似正态假设下,为达到服务水平(不脱销概率)α\alpha,建议补货 Q∗=μ+zασQ^*=\mu+z_\alpha\sigma,其中 z0.90=1.282z_{0.90}=1.282、z0.95=1.645z_{0.95}=1.645、z0.98=2.054z_{0.98}=2.054;
  • 置换误差口径:∣APE∣=∣y^t−yt∣/yt×100%|\text{APE}|=|\hat y_t-y_t|/y_t\times100\%,用于刻画误差分布;
  • 时间脆弱性:比较 1/2/3 步前瞻在固定 3 个月测试窗上的误差;
  • 区间覆盖率:以训练 σ\sigma 构造 95% 区间 [y^−zσ,y^+zσ][\hat y-z\sigma,\hat y+z\sigma],检查未来实际值是否落入。

三、结果 I:服务水平是很贵的

图1 服务水平灵敏度:报童补货量随服务水平上升

图 1 把服务水平从 90% 提到 98%,展示下月建议补货的阶梯:家电 82→87→92(+25%→+41%),服饰 130→136→142(+17%→+27%),食品 374→388→403(+15%→+23%)。

关键结论是补货对服务水平的边际成本不低:把服务水平从 95% 提到 98%,家电要再压 5 件、食品要再压 15 件库存,才能把那 3 个百分点的"应补而不补"风险买掉;而需求越高、σ 越大的品类(食品),同样的服务水平提升要付出更多库存。这提醒决策者,服务水平不是越大越好——它是一笔明码标价的库存预算,服务水平提高一个点,占用资金也上一个点。若不设服务水平预算直接冲 98%,等于用资金换一个可能不敏感的风险边际。

四、结果 II:误差有肥尾巴,别把 σ 当唯一保险

图2 预测误差分布(训练段绝对百分比误差)

图 2 汇总三品类训练段共 27 个月的绝对百分比误差:均值 14.2%、标准差 10.9%、最大 42.5%;超 10% 的误差占 70.4%,超 30% 的占 11.1%。

这说明误差序列不是温和的近似正态——它带明显正向偏斜的长尾:平均看误差约 14%,但不时冒出 40% 级的大偏差。对安全库存的启示很直接:若只用 μ+zσ\mu+z\sigma 放裕度,那些占一成多、超 30% 的大偏差会被低估。真正的稳健做法是给误差分布的直接经验分位数(而非仅靠正态 σ)留余地,否则当某个月份实际需求高出预测 40% 时,用 1.645σ 构造的"95%"区间根本兜不住。

五、结果 III:促销脉冲归因——食品被拉得最猛

图3 促销脉冲归因:6/11 月人均订量 vs 平常月

图 3 把 6/11 月大促与平常月的人均订量分开:家电促销月均 100 对平常 66(x1.52)、服饰 151 对 102(x1.48)、食品 532 对 312(x1.71)。

促销对三品类都有约 1.5~1.7 倍的脉冲拉动,食品最强。这直接印证了范文一"把促销脉冲当成平滑季节会误配"的警示:越是促销拉动强、需求被点燃成尖峰的品类,季节模型越难以平滑外推,因为"下一个 11 月"能不能再复制同样强度的脉冲并不受季节规律保证。补货端在促销月前必须把安全库存做厚,而不是指望季节因子自动覆盖。

六、结果 IV:需求变异结构——食品最"脉冲"

图4 品类需求变异:变异系数与峰值偏离

图 4 用变异系数 CV 与"峰值月/均值"比刻画三品类需求波动:家电 CV 0.23、服饰 0.21、食品 0.28;峰值月分别约 1.6、1.6、1.8 倍均值。

三品类都属中等波动,无极端灾难级尖峰,但食品的波动最剧烈,又恰好是促销拉动最强、补货量最大的品类(均值 348 件、下月补货 388+)。换言之,全链路上"最贵、最波动的品类"最容易因误差长尾与服务水平的叠加而脱销。这一节与第三节、第四节彼此咬合,共同指向:风险管理的重心应放在食品这一类"高价值+高波动+强促销"组合上。

七、结果 V:客群营收贡献与流失风险

图5 客群分层营收贡献与流失风险(RFM 三簇)

图 5 用范文一的 RFM 三簇看营收结构与流失风险:簇0(10 家高价值)占营收 42.8%、近购时滞 0.0 月;簇1(14 家流失风险)占 3.3%、近购时滞 7.9 月;簇2(36 家主力)占 53.9%、近购时滞 0.1 月。

一个有悖直觉但极有价值的结构发现是:单片"高价值"簇0只占 42.8%,真正撑起超半壁江山的是数量最多的簇2主力客户,而簇1这 14 家"流失风险"客户合计只贡献 3.3%,且已近 8 个月未购——它们更像"已流失"而非"待挽回"。这提示投入的资源若按簇0投放要警惕单点集中,而对簇1做大规模挽回性干预的回报可能非常有限。

八、结果 VI:客户集中风险

图6 客户集中度:头部客户营收占比

图 6 计算头部客户集中度:前 4 家占总营收 18.4%,前 10 家占 42.8%,60 家客户共分全部订单额。

前 10 家拿走近一半订单额,说明该客户盘存在明显的头部依赖:一旦有 2~3 家头部客户流失或大幅减单,营收会剧烈波动,进而在需求预测与补货上游形成连锁波动。对需求预测与库存而言,这既是"少而精"的获客结构,也是"鸡蛋集中在一个篮子"的结构脆弱性。结合第五节,最稳健的风险对冲不是平均撒网,而是在头部客户维护上做精。

九、结果 VII:时间脆弱性——长视界预测不可靠

图7 多步前瞻误差:1/2/3 步 MAPE 随视界放大

图 7 在固定 3 个月测试窗上比较 1/2/3 步前瞻误差:家电 1 步 18.8% 到 2 步 33.7%、服饰 3.1% 到 2 步 29.2%、食品 14.0% 到 2 步 17.1%,各品类 2 步普遍显著放大。

这条曲线把"预测到底多远的将来才算数"讲得很清楚:1 步前瞻相对可信,2 步前瞻误差几乎成倍放大(服饰甚至从 3.1% 跳到 29.2%)。这与许多供应链实践"按月滚动承诺却一次锁 3 个月采购额"的做法直接冲突——若用 2 步以上的预测去锁采购,等于承认要在误差近三成的位置上做承诺。实践启示:滚动预测应高频刷新、只对最近 1 步做硬承诺,越远越只做量级参考。

十、结果 VIII:95% 预测区间到底兜住多少

图8 95% 预测区间覆盖率(未来 3 个月测试)

图 8 用训练 σ 构造 95% 区间并检查未来 3 个月实际值是否落入:家电 3/3、服饰 1/3、食品 2/3,合计 6/9 落入。半宽分别为家电 ±26、服饰 ±17、食品 ±79。

名义 95% 的区间实际只覆盖 6/9,显著低于预期。服饰尤其失守(仅 1/3),原因是它在 2 步前瞻处误差暴涨,而训练段 σ 过窄没有为这种跳变留裕度——这正是第四节"肥尾巴 + 正态 σ"低估风险的直接实证。它给出一个明确的工程结论:经验覆盖不足就必须扩容,要么放大 z 系数(如用 1.96→更宽的置值),要么直接用误差的经验分位数做区间,否则"95%区间"只是一张心理安全垫,不是真的兜底。

十一、结论

四个被单点预测掩盖的事实重塑了补货决策脚本:

  1. 服务水平有标价:从 90% 到 98%,高需求品类要花明显更多库存换每 1 个百分点冗余,需设服务水平预算;
  2. 误差有肥尾巴:均值 14.2% 但最大 42.5%,按 σ 的 95% 区间只覆盖实测 6/9,须用经验分位数扩容;
  3. 结构脆弱:前 10 家客户占次 42.8% 营收、逐月 2 步前瞻误差普遍翻倍,头部依赖与长视界承诺都需设红线;
  4. 风险集中于食品:它同时是波动最剧烈(CV 0.28)、促销拉动最强(x1.71)、补货量最大(388+)的品类,应作为安全库存与促销备战的核心对象。

这些结论共同指向一个工程信条:算出下月补货数只是起点,把它放进服务水平预算、误差边际与结构脆弱性的真实坐标系里检验,才算完成。

十二、模型验证(四路一致)

本文正文、配图、附录代码、真源 tools/gen_tidy2025b_2.py 四路数字完全一致。附录代码块可在 tools/ 目录下独立运行复现全部关键数字(服务水平补货三档、误差分布均值/最大/超阈占比、促销倍增比、品类 CV 与峰值偏离、客群营收占比与近购时滞、CR4/CR10、多步前瞻误差、95% 区间覆盖率),所有结果由固定随机种子确定,双跑字节一致。

参考文献

[1] Holt C C. Forecasting seasonals and trends by exponentially weighted moving averages. 1957.
[2] Winters P R. Forecasting sales by exponentially weighted moving averages. Management Science, 1960.
[3] 张智勇, 等. 需求不确定下的报童问题与库存优化. 供应链管理, 2018.
[4] 泰迪杯 2025 B 赛题组委会. 产品订单分析与需求预测赛题数据说明.

附录:核心 Python 实现

import sys, os
_HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.abspath(os.path.join(_HERE, "..", "..", "..", "tools")))
import gen_tidy2025b_2 as SRC
import gen_tidy2025b as G

r = SRC.gen_tidy2025b_2()
# E1 服务水平灵敏度
for slv, row in r["e1"].items():
    print("E1 服务水平%.0f%% " % (slv * 100), " ".join(
        "%s 补货%.0f(+%.0f%%)" % (c, row[c][1], row[c][2] * 100) for c in G.CATS))
# E2 误差分布
e2 = r["e2"]
print("E2 均值%.1f%% 最大%.1f%% 超10%%占%.1f%% 超30%%占%.1f%%"
      % (e2["mean"], e2["max"], e2["p_over10"] * 100, e2["p_over30"] * 100))
# E3 促销归因
for c, (pm, nm, ratio) in r["e3"].items():
    print("E3 %s 促销%.0f vs 平常%.0f x%.2f" % (c, pm, nm, ratio))
# E4 需求变异
for c, (cv, pk, m) in r["e4"].items():
    print("E4 %s CV=%.2f 峰值%.1f×均值" % (c, cv, pk))
# E5 客群营收贡献
for j, (n, mon, share, R) in r["e5"]["clusters"].items():
    print("E5 簇%d n=%d 营收占%.1f%% 时滞%.1f月" % (j, n, share * 100, R))
# E6 客户集中度
e6 = r["e6"]
print("E6 CR4=%.1f%% CR10=%.1f%%" % (e6["cr4"] * 100, e6["cr10"] * 100))
# E7 多步前瞻
for h, err in r["e7"].items():
    print("E7 %d步 " % h, " ".join("%s %.1f%%" % (c, v) for c, v in err.items()))
# E8 区间覆盖率
for c, (inside, n, width, f0) in r["e8"].items():
    print("E8 %s 覆盖 %d/%d 半宽±%.0f" % (c, inside, n, width / 2))