
1. 项目背景与核心痛点1.1 库存路径优化到底在解决什么问题做供应链优化的人对IRPInventory Routing Problem库存路径问题一定不陌生。它把两个原本分开管的决策捏在了一起给谁补货、补多少以及用什么顺序送。传统做法是销售预测团队先给一版需求量计划团队照着预测做配送计划仓储团队按计划备货三个环节各管一段链条一长就容易失真。我这次要分享的项目核心是给某零售企业的区域配送网络做一套在线库存路径优化方案发表在Transportation Science这个方向上的一次完整落地实践。场景本身不复杂一个配送中心服务几十家门店每家门店有独立的库存容量和消耗速率配送中心有车辆资源限制每天要在销量预测的基础上决定给各家门店送多少货、走什么路线。难点在于门店的需求是随机的节假日、天气、促销活动都会造成需求波动。如果你用固定的预测值去排补货计划预测偏差大的门店要么库存积压要么直接断货。断货是零售的大忌损失的不只是当天销量还有顾客信任。积压也不好看占用资金还增加仓储成本。所以在真实场景里大家真正想要的是一套能“看得到不确定性”的决策系统而不是只输出一个平均数的预测模型。1.2 传统两段式方案的三个硬伤先说传统做法先预测后优化Predict-then-Optimize这也是大多数企业已经在用的框架。第一步用时间序列模型或机器学习模型预测每个门店未来几天的需求第二步把这些预测值当成真实需求输入到车辆路径和补货量的优化模型里求解。听起来很顺但实际跑起来有三个硬伤。第一个硬伤是预测误差被静默放大。优化模型是确定性的它默认预测值就是真值于是会把最敏感的约束条件卡在预测值的边界上。比如预测某门店明天卖100件实际可能卖120件车辆调度的时候刚好少装了几件结果就是断货。因为优化器不会主动给你留安全余量除非你在模型里显式地加。第二个硬伤是预测模型和优化目标错位。预测模型训练时用的是均方误差或平均绝对误差衡量的是“预测值离真实值有多近”。但下游决策真正关心的不是预测准不准而是“决策损失”大不大。有时候预测偏了10%因为碰巧不触发补货阈值决策损失是零有时候预测只偏了2%正好卡在阈值附近就会导致漏补一批货损失很大。预测误差和决策损失之间不是线性关系这导致预测阶段的优化目标跟决策目标根本不在一个频道上。第三个硬伤是缺少场景概念。传统预测输出的是一个点估计最多再加个置信区间但优化模型需要的是一组“可能会发生的需求样本”而不是一个均值。均值只能代表最可能的情况恰恰忽略了尾部的风险场景。这个项目的核心思路就是把这三点一起解决在预测阶段就面向下游决策去建模用多个场景表达不确定性再把场景递交给优化器做鲁棒决策。下面我会把这套架构的设计逻辑和实操细节完整拆开来讲。2. 预测后优化的整体设计思路2.1 为什么还要用“预测后优化”而不是端到端联合训练先解释一个很多人会问的问题现在深度学习这么流行为什么不直接做端到端的联合优化把预测模型和优化模型糅在一起训练联合优化Decision-focused Learning确实是学术热点思路也很诱人将决策损失直接作为预测模型的训练损失让预测模型知道下游要什么从而反向修正自己的输出。但落地时有个现实障碍——最优化求解器是不可导的。库存路径问题在数学上是个混合整数规划MIP内部还有车辆路径选择解空间非常大。你要把不可导的求解器嵌入神经网络反向传播需要引入凸近似、解析梯度或者黑盒梯度估计每种方法都伴随额外的计算开销和实现复杂度。在工业项目里我更看重可解释性和可迭代性。预测后优化是一种模块化设计预测模块坏了可以单独换优化模块升级了不影响预测。业务团队也能理解这套逻辑——先预测需求再算补货方案链条透明。联合训练的黑盒感太强出了问题很难排查是哪一侧的责任。不过这也意味着我们必须主动去弥合“预测”和“优化”之间的缝隙。前面的三个硬伤不解决模块化反而是放任问题存在。所以我在这套方案里做了一个折中保持模块化的预测后优化框架但在预测端引入决策感知的损失修正在优化端引入多场景随机规划。这样既兼顾了模块之间的清晰边界又让两个模块在“信息”层面实现了协同而不是在“梯度”层面硬耦合。2.2 整体架构从数据流看系统的四层结构整个系统我把它分成四层数据层、预测层、场景层、决策层。数据层负责汇总门店的历史销售流水、促销日历、天气数据、库存水位和配送履历。这块的基建很重要因为后面所有模型的效果上限都取决于数据的质量和粒度。我们做的时候发现很多门店的销售数据存在漏传、重复记账的问题清洗口径不统一这件事花了整整两周才理顺。预测层是核心模块之一用了多个模型组成的预测引擎以梯度提升树模型为主力辅以季节性时间序列模型做交叉验证。预测目标是每个门店未来7天逐日的需求分布不只是均值还要给出分位数。这样后面生成场景时才有概率信息可用。场景层负责把预测分布转成优化器能吃的场景集合。这里用到了分位数采样的思想把每天的预测分成多个分位数段每段代表一种可能的行情组合起来生成数百个需求场景。场景之间的一致性约束很重要——同一个门店前后两天的需求量不能出现荒谬的跳跃否则优化器会钻空子。决策层是一个两阶段随机规划模型。第一阶段决定车辆的出车路线和整体装载计划第二阶段根据场景实现情况进行补货量的调整。求解用的是商业求解器加启发式算法混合的框架在可接受的几分钟内给出可用方案。这四层结构看着并不花哨难点全部藏在细节里。下面我逐个模块说明重点讲清楚每一步的选型理由和踩坑经过。3. 核心模块拆解与关键实现3.1 需求预测引擎分位数预测比点预测重要得多我在这类项目里反复验证过一件事对下游优化而言预测分布比分均值重要得多。如果你只给优化器一个点预测值它必然会输出一个紧贴这个值的补货量和路线方案完全没有缓冲空间。而如果你给的是一个分位数区间优化器在做约束规划时就能自动权衡“少送导致缺货”和“多送导致积压”两种风险的代价。具体实现上我用LightGBM做了分位数回归设置三个目标分位10%、50%、90%。特征工程是重头戏包括门店维度历史平均销量、销量方差、门店类型、仓储容量时间维度星期几、是否节假日、月份外部变量天气温度、降雨概率、促销事件标记。这里有个容易踩的坑促销事件的标记一定要做成“促销前/中/后”三段而不是只打一个促销日标记。因为零售促销有很强的透支效应和回补效应促销当天销量暴涨促销后一两天销量往往低于基线。如果不处理这个效应预测模型会把促销前的低值学成常态导致促销前备货不足促销后库存积压。时序交叉验证是另一个必须做对的事。常规的K折随机交叉验证在时序数据上会泄露未来信息必须用滚动窗口的方式训练集是过去12个月验证集是紧接着的4周然后窗口逐步向前滚动。我习惯的做法是至少验证4个滚动窗口覆盖不同的季节段避免模型只在某个月份表现好。最后输出的不只是分位数还要做一次后校准。用预测区间覆盖率的指标来检查比如10%分位对应的实际覆盖率应该接近10%如果覆盖率偏差太大说明模型有系统性偏差需要调整。这个校准步骤经常被忽略但对下游场景生成的影响非常大。3.2 场景生成从分位数到多阶段需求路径的桥场景层是整个方案里最考验工程判断的部分。预测模型输出每天每个门店的三个分位数但优化器不想要“分位数”它想要的是“一组完整的需求场景”。我的做法是采用分位数分段抽样。具体来说把0到1的累计概率区间切成L层每一层是一个等概率的分段。比如L10每层覆盖10%的概率质量。然后对每一层取该分段的条件中位数作为这一层的代表需求量。这样每个门店每天能抽出10个代表值每个代表值对应一种“行情强度”。接下来要做跨时间的场景拼接。门店第1天的行情弱、第2天的行情强这种组合必须控制在一个合理的联合概率范围内不能随机乱拼。解决方案是为每个门店构造一个马尔可夫链行情状态之间的转移概率从历史数据里估计。比如“昨天弱、今天强”的概率是多少直接统计历史天数就能得到。场景生成时先按初始状态分布采样第1天的行情档位然后根据转移概率逐渐生成后面每一天的档位。这样生成的场景序列带有时间自相关性更贴近真实需求波动。场景数量怎么定我测试过50、100、200、500四档。场景太少优化结果方差大这次跑出来是这个方案下次同样的输入跑出另一个方案业务团队没法接受。场景太多求解时间直线上升。最后我选了200个场景兼顾稳定性和求解效率。这中间有个细节值得说场景生成之后一定要做一次可视化检查。把历史真实的需求路径和生成的需求路径叠在一张图上肉眼观察两者是否形态相似。我遇到过这样的情况——模型在数值指标上表现很好但生成的场景在高峰期过于平滑、把尖峰全磨平了导致优化器严重低估峰值需求。这种问题不看图是发现不了的。3.3 优化模型两阶段随机规划的数学表达与求解决策层的核心模型我采用了两阶段随机整数规划的框架。先看一个简化的数学表达第一阶段变量是车辆的路径决策。设车辆集合为V门店集合为C配送中心为0定义变量 x_{ij}^v 表示车辆v是否从点i行驶到点j。目标函数包含固定出车成本、行驶距离成本。第二阶段变量是每个场景下的补货量和对库存的惩罚。设场景s下门店i在t天的需求为 d_{i,t}^s补货后库存为 I_{i,t}^s那么第二阶段会对缺货和超容计算惩罚成本。完整的模型表达式实际上是[ \min \sum_{v \in V} \sum_{(i,j) \in A} c_{ij} x_{ij}^v \frac{1}{S} \sum_{s1}^{S} \sum_{i,t} \big( p_{\text{stockout}} \cdot \max(0, d_{i,t}^s - I_{i,t}^s) p_{\text{overflow}} \cdot \max(0, I_{i,t}^s - C_i) \big) ]这里有几个业务上需要仔细斟酌的参数缺货惩罚 p_stockout应该等于单位商品的毛利损失加上品牌损失折算。我们按商品平均毛利做了估算同时给了一个上调系数因为断货还影响消费者忠诚度超容惩罚 p_overflow应该等于临时调拨成本或者仓库二次处理成本。这个值如果设得太低优化器会让门店库存顶到天花板现场根本放不下设得太高又会导致车辆少装货、频次变高运输成本失控车辆数上限设置时参考了现有车队规模和高峰期运力冗余不是拍脑袋定的而是统计了过去三个月的出车记录取90分位数。路径约束部分用的是标准的车辆路径问题约束加上了每个车辆的最大行驶时长限制。这里要特别提一个工程问题门店数量六七十家时精确求解已经非常吃力。我尝试过直接调用求解器跑完整MIP两小时内给不出稳定解。后来换成了“列生成局部搜索”的混合策略先构造初始可行解再用邻域搜索做改进最后用求解器对剩余的小规模子问题做精确优化。效果是在5到8分钟内能给出一版稳定方案满足调度时效。3.4 决策感知修正在预测阶段注入优化目标的信息这一节是方案里最有意思的部分也是我认为整个系统真正值钱的地方。既然联合训练不划算那有没有办法在保持模块化设计的前提下让预测模型“知道”下游优化器关心什么我的做法是构造一个决策损失代理函数。库存路径问题里真正让优化器敏感的不是预测均值偏差而是补货阈值附近的需求跨越。如果一个门店当前库存只够卖两天而预测明天需求刚好超过某个临界值那么优化器就会决定派车。预测值在这个临界值上下微小的移动会完全改变决策结果。所以预测模型应当在临界值附近的区段付出更大的拟合代价。具体实现是在原有回归损失的基础上加了一个权重函数[ L \frac{1}{N} \sum_{n1}^{N} w(y_n) \cdot (q_{\theta}(x_n) - y_n)^2 ]权重 w(y_n) 由库存状态决定当预测值接近补货触发阈值时权重提高远离阈值时权重保持基准值。这个权重的形状经过了好几轮迭代。第一版用的是阶跃函数效果不好因为阈值附近的梯度突变会让训练不稳定。后来改成了以阈值为中心的高斯凸起宽度取历史需求标准差的一半训练稳定性明显改善。这个方法说起来简单但它的效果立竿见影。对比同一个小区的模型加了决策感知修正之后优化层的平均决策损失下降了大约6%而预测的均方误差几乎没变。这说明什么说明我们在不牺牲预测精度的前提下用很少的额外计算量让预测分布的位置更贴合决策需求。坦白讲这个6%在学术实验里不算大数字但在真实配送网络中它意味着每个月能少几十次缺货事件收益非常可观。4. 实操过程与效果复盘4.1 数据工程清洗口径是绕不过去的硬骨头进入训练和调优之前我先说数据清洗。这个环节不性感但在这类项目中它决定成败。我们处理的是某区域连锁零售企业过去18个月的配送数据涉及几十家门店、上千个SKU原始数据分散在三个系统里POS销售系统、仓库管理系统、运输管理系统。第一道坎是销售数据的口径问题。POS系统里记录的是“销售时间”仓库配送记录的是“到达时间”两个时间口径相差一两天在做库存推演时需要对齐。我们选择以“门店营业日”作为统一的时间粒度把跨日销售按照营业时段进行划分。这块没有标准答案但一定要在项目初期定出口径文档否则后续所有分析都是空中楼阁。第二道坎是SKU聚合。每家门店上千个SKU不可能逐个建模。我按商品的补货周期、体积、冷链要求、销量波动特征把SKU聚合成几个大类。大类内部的需求相关性较高用加权的总销量作为模型输入。这个聚合粒度直接影响优化器输出的颗粒度——太粗方案没法落地太细优化规模爆炸。最后选了中等粒度常温标品、低温乳品、生鲜果蔬、日配面包四大类。第三道坎是异常值处理。门店盘点错误、临时促销、系统故障都会产生异常销售记录。我的处理方式是做两轮过滤第一轮用统计阈值剔除超出历史均值5倍标准差的极端值第二轮靠人工抽检重点检查节假日前后的大额订单记录确认是真实需求还是重复记账。这两轮过滤下来数据质量才勉强够资格进模型。4.2 实验设计与消融对比整套系统做完之后我做了一组严格的消融实验目的是回答三个问题多场景优化到底比点预测优化强多少决策感知修正的价值有多大整套方案相比企业原来的手工计划提高了什么实验设计如下方案编号预测端优化端说明A点预测确定优化模拟传统方案B分位数预测确定优化只用预测分布均值做输入C分位数预测多场景随机规划不加决策感知修正D分位数预测多场景随机规划 决策感知修正完整方案评价指标用三个月度缺货次数、库存周转天数、每单配送成本。结果比预想的更有说服力。方案A的月度缺货次数是完整方案D的2.3倍库存周转天数多出1.4天。方案B和方案A的差异不大说明只改进预测端的输出形式而不改优化端收益有限。方案C到方案D的提升主要体现在缺货次数上减少了约15%而库存和运输成本几乎不变——决策感知修正带来的是一种零成本的“白捡”收益。这个对比说明一个很重要的道理系统的性能瓶颈往往不在某一个单独模块而在模块之间的信息传递方式。预测端输出更丰富的不确定性信息、优化端利用这些信息做权衡决策两端一起改效果才会指数级放大。4.3 上线验证从离线回测到小范围试运行离线实验做完紧接着是回测验证。我用的是历史回放的方式拿到3个月的原始数据每天按照系统的输入输出做模拟把系统推荐的补货和配送方案应用到当天的库存状态上逐日推演最后统计缺货、库存、运力使用的累计指标。回测阶段最大的意义是能在不惊动业务的前提下暴露问题比如某些门店的容量约束被频繁打破或者某些路线的配送时长超过司机工作时长。回测跑通后我建议运营团队先选一个中等规模的门店簇做两周的试运行。试运行期间系统输出只作为建议方案每天由调度员进行人工确认。这里有一个经验一定要记录人工修改了哪些地方以及修改的原因。第一周人工修改率高达30%主要集中在对促销临时加量的调整上第二周随着调度员对系统输出风格的熟悉修改率降到10%以下。这个“人机磨合”的过程必不可少直接跳过的话业务团队很难建立起对系统的信任。5. 常见问题与排查实录5.1 预测分布过窄导致场景多样性不足第一个遇到的问题是场景生成的需求路径多样性不足。症状很典型系统跑出来的方案总是倾向于少出车、小装载量对峰值需求的准备不足。排查后发现根因在预测模型输出的分位数间距太小——模型对需求波动估计不足把分布收窄了。这经常发生在训练数据里方差被低估的情况下特别是我们用了梯度提升树做分位数回归如果叶节点最小样本数设置得过大模型就会倾向于输出比较保守、平滑的分位数尾部信息被抹掉。我的解决办法是调低叶子节点最小样本数提高分位数的灵敏度同时在损失函数里对尾部预测误差加了额外权重让模型更关注10%和90%分位数的拟合。5.2 优化求解时间不稳定多场景模型加上全量门店最怕的是求解时间爆炸。我遇到过几次平时5分钟出解某天突然跑了40分钟还不停直接导致调度延误。定位后发现问题出在“坏场景”上——某些生成场景中需求极端偏高使得对应的约束特别紧分支定界树膨胀得厉害。解决思路有两个层面。第一是在场景生成时做截尾把需求超过历史最大值1.5倍的场景样本直接裁剪到上限避免极端值拖慢求解。第二是在求解器参数上做调节设置MIP的节点数上限和求解时间上限到时间就返回当前最优可行解。实践中第二个方案更可靠因为它保证求解时间的上界。5.3 业务团队对“概率方案”的不信任技术问题都好解决人心的问题最难。业务调度员习惯了“今天送50件”这种明确的指令拿到“我应该送40到70件取决于后续天气和销售走势”这种带区间的建议第一反应往往是不信任。我的处理办法很笨但有效做一次详细的Case复盘。找两个真实门店把系统输出的场景集合和决策依据摊开在桌面上讲解释为什么系统建议给A店多留库存而给B店少送一些。当业务人员看到系统在面对不确定需求时做的权衡逻辑跟老练调度员的思路一致时信任感就建立起来了。技术方案再先进最终还是要靠人来用。交付一个“好用的系统”不只是交付模型和代码还包括教会业务团队用概率思维做决策。5.4 模型再训练频率与概念漂移零售需求的特征会随时间缓慢变化季节性、新品引入、周边商圈变迁都会导致需求分布漂移。如果模型长期不更新预测分位数会越来越失真。我们设定的是每周增量训练一次每月做一次全量重训。增量训练用的是最近4周的新数据配合旧数据加权衰减让模型既能捕捉近期趋势又不会忘记历史模式。这个频率是我在几次测试后确定的。两周更新一次新品上市时的需求变化跟不上每天都更新训练资源和模型稳定性又扛不住。以周为周期是比较折中的选择也方便跟业务周会节奏对齐。6. 最后分享一点个人体会这套方案解决了一个我入行以来一直很纠结的问题预测模型和优化模型到底应该怎么配合。过去做纯预测项目模型再准落到业务上总觉得使不上劲做纯优化项目模型再精巧输入的需求数据一塌糊涂解出来的方案也只是纸上谈兵。预测后优化这类系统真正把两个领域串在了一起。如果你也想在自己的供应链场景里做类似的尝试我的建议是先别追求复杂的联合训练把基础的分位数预测和多场景优化做好就已经能超过多数传统方案。等到你对场景生成、求解稳定性都有了手感再去探索更进阶的方向也不迟。我们目前还在做两个后续扩展一个是把门店的实时库存数据接入场景生成让优化决策能响应当天的突发情况另一个是在场景层引入季节性外部变量比如节假日零售指数让预测分布随宏观节奏动态调整。这个方向可以延伸的东西还很多后面有新进展再继续更新。