用图神经网络做零售需求预测:数学原理、受控实验与生产落地边界

目录
周五上午十点,某连锁商超的一家社区门店突然迎来了客流高峰。店长看着在冷柜前排起长队的顾客一头雾水:今天既不是法定假日,店里也没有安排任何打折促销,后台的单店预测系统昨天给出的备货建议明明只有平时的正常水平——这波毫无征兆的抢购潮究竟是从哪儿冒出来的?
答案并不在这家店自己的历史账本里,而在两公里外的同一个商圈:街对面的兄弟门店今天原本有一场重磅大促,不料负责给那家店配货的东区仓库前一晚发生分拣故障,导致它一早开门就陷入断货;被促销吸引出门、却扑了个空的顾客,顺着街区涌向了这家由西区仓库正常供货的门店。
在之前的博客《用 Prophet 做零售需求预测:从模型原理到工程实践》和《从一棵树到一片森林:树模型的数学原理、算法演进与工程实践》中,我们无论是分解趋势与季节性,还是构造滞后与日历特征,都隐含着同一个前提——一家门店的未来,主要由它自己的过去决定。然而,在真实的连锁零售网络中,相邻门店之间随时都在发生两类反向的客流拉扯:
- 促销虹吸(Promotional Cannibalization,零售业也常称“客流分流”或“内部蚕食”):当隔壁门店大搞打折促销且货源充足时,会把原本打算来本店消费的比价顾客吸走;
- 断货溢出(Stockout Spillover,也称“缺货替代”或“需求外溢”):当隔壁门店因为上游仓库断供而把货架卖空时,买不到商品的顾客又会转头涌向本店。
一旦进入这样的零售网络,只盯住单店自身历史的预测方法就会出现天然的信息盲区。预测任务不再是孤立地外推一条时间序列,而是要同时回答两个交织在一起的问题:仓库与门店的状态怎样随时间一步步演变?供货与销售信号(零售与物流业统称“履约”,Fulfillment)又怎样顺着“仓库—门店”的关系网络向外传导?
这正是随机过程(Stochastic Processes) 与 图神经网络(Graph Neural Networks, GNN) 交汇的地方:前者用概率语言描述状态在时间轴上的演化,后者则学习信号如何顺着供应链和街区邻里关系接力传导。但把数学视角从“单条序列”升级到“关系网络”,真的能直接解决生产环境里的库存预测难题吗?本文不想写一篇“图模型全面碾压传统模型”的爽文,而是带你走完一段完整的“机制拆解 \(\to\) 受控验证 \(\to\) 现实泼冷水”之旅:
- 看清机制(第一至三章):从状态随时间的随机转移出发,一步步引入实体关系图,拆解单店时间序列、空间图传播与时空图模型各自的适用边界;
- 受控验证(第四章):在专门设计的「2 座仓库 + 10 家门店 + 异步促销」合成网络中,剥离模型复杂度干扰,检验图结构究竟在什么条件下能找回单店模型丢失的信号;
- 直面鸿沟(第五章):跳出纸面实验,剖析为什么一个在理论上跑赢所有传统对照模型的图算法,一旦撞上真实零售系统中“账面有货、货架无货”的库存失真、数据延迟、单店故障波及全网以及严苛的业务执行红线,在生产中可能根本无法直接执行。
配套数据生成器、多工具包对比与绘图代码已开源至 GitHub:retail-gnn-demand-forecast。
一、随机过程如何刻画状态随时间的演化? #
零售需求首先随日历时间波动,上游仓库的供货能力也会在不同运行状态之间切换。在引入图结构之前,第一章先用随机过程建立描述“状态怎样随时间演化”的数学语言;第二章再将观察范围从单个实体扩展到实体之间的关系网络。
1.1 随机过程、马尔可夫性质与状态转移矩阵 #
随机过程(Stochastic Process)是一族由时间或其他索引参数标记的随机变量:
\[ \{X_t:t\in T\} \]其中,\(T\) 是索引集合,\(X_t\) 是索引 \(t\) 对应的随机变量。索引通常表示时间,也可以表示空间位置或其他有序维度;\(X_t\) 则可以是离散状态、连续数值或向量。这个定义本身不要求过程满足马尔可夫性质,也不要求分布保持平稳。
如果进一步引入离散时间马尔可夫链,核心假设只有一条:只要“当前状态”定义得当,它就浓缩了预测下一步所需的全部历史信息:
\[ \Pr(X_{t+1}\mid X_t,X_{t-1},\ldots,X_0)=\Pr(X_{t+1}\mid X_t) \]这被称为马尔可夫性质(Markov Property)。它并不是说更早的历史没有价值,而是要求模型定义的“当前状态”已经概括相关历史。如果下一步还取决于持续时间或近期轨迹,就应把这些信息纳入状态,或者改用更高阶模型。
对于有限状态空间 \(\mathcal S=\{s_1,s_2,\ldots,s_m\}\),可以用行随机矩阵 \(\mathbf P\)(Row-Stochastic Matrix,也常直接称为状态转移矩阵 Transition Matrix 或马尔可夫矩阵)表示一步状态转移。所谓“行随机”,就是矩阵里的每个元素都是非负概率,且每一行的概率加起来恰好等于 1(代表从当前状态出发,下一步必定落入某一个状态):
\[ P_{ab}=\Pr(X_{t+1}=s_b\mid X_t=s_a),\qquad \boldsymbol\pi_{t+1}=\boldsymbol\pi_t\mathbf P,\qquad \boldsymbol\pi_{t+k}=\boldsymbol\pi_t\mathbf P^k \]这里,\(\boldsymbol\pi_t\) 是时刻 \(t\) 的状态概率分布。如果转移矩阵在整个研究期内保持不变,这条马尔可夫链就具有时间齐次性(Time Homogeneity,简称时齐性)——通俗地说,就是“转移规则不随日历改变”,今天从正常变拥堵的概率和下个月完全一样。此时只需计算矩阵幂 \(\mathbf P^k\),就能把一步转移直接推演到 \(k\) 步之后的状态分布。真实应用需要从历史数据估计转移概率,并检验时间齐次假设是否成立。
1.2 两种“平稳性”:从单店销量、仓库状态到商圈客流 #
在时间序列与随机过程中,我们会遇到两个名称相近、却从不同角度刻画零售系统稳定性的概念:
1. 时间序列的弱平稳性(Weak Stationarity)——以“单店日常销量”为例
弱平稳性要求序列的均值和方差不随日历时间变化,且序列在前后不同日期之间的关联程度(即自协方差,Autocovariance)只取决于相隔几天,而与具体是哪一天无关。在真实的零售门店里,原始日销量序列几乎从不平稳:周末客流高于工作日、冬夏换季带来周期性起伏、大促和节假日又会制造剧烈的销量尖峰。因此,单店预测模型(如前文介绍的 Prophet)才需要显式引入趋势项、季节项与促销等协变量(Covariates,在机器学习特征工程中常直接称为外部特征,在计量经济学与 Prophet 等时序工具中又叫外生变量 Exogenous Variables 或额外回归项 Extra Regressors)——其本质就是先剥离这些随日历变化的非平稳成分,让剩余的随机扰动回到近似平稳、可预测的轨道上。
2. 马尔可夫链的平稳分布(Stationary Distribution)——从“仓库运行状态”到“商圈门店客流”
与讨论“序列均值随日历变不变”不同,马尔可夫链的平稳分布讨论的是:当同一个状态转移算子 \(\mathbf P\) 反复作用时,系统最终会停留在怎样的比例结构上? 它定义为满足下式的概率向量 \(\boldsymbol\pi^*\):
\[ \boldsymbol\pi^*=\boldsymbol\pi^*\mathbf P \]对于有限、不可约且非周期(Irreducible and Aperiodic——简单理解,就是任意两个状态之间最终都能互通、不会卡死在某个局部小圈子里,也不会像单双号限行那样陷入机械的固定周期循环)的马尔可夫链,无论初始状态如何,重复应用 \(\mathbf P\) 都会收敛到 \(\boldsymbol\pi^*\)。在本文的零售网络中,这一个方程可以同时解释时间和空间两个维度上的稳态现象:
- 时间维度的稳态(仓库运行状态演化):假定一座区域仓库每天处于“正常供货”、“分拣拥堵”或“断货受限”三种状态之一,并按固定的状态转移矩阵 \(\mathbf P\) 随日期切换。哪怕仓库今天断货、明天恢复,单日状态不断波动,只要底层的转移机制保持不变,长期运行下来就会收敛到平稳分布 \(\boldsymbol\pi^*\)——即从长期看,该仓库有多大比例的交易日正常供货、多大比例拥堵或断货。这组长期稳态概率,正是供应链规划常规安全库存的理论基准。
- 空间维度的稳态与打破(门店间的客流转移):如果把状态空间从“一座仓库的不同状态”换成“同一商圈内的多家门店”,转移矩阵 \(\mathbf P\) 描述的就是顾客在相邻门店之间的日常流动与替代比例。在没有外部冲击的常规期,各店之间的客流交换保持平衡,商圈内各店的市场份额稳定在 \(\boldsymbol\pi^*=\boldsymbol\pi^*\mathbf P\) 附近;但一旦某家门店突然发起深度大促,或者因为上游仓库突发断货而空架,原有的商圈稳态就会在当天被瞬间打破,迫使客流顺着门店间的关系网络发生剧烈的虹吸或溢出。反过来看,如果把同一套转移矩阵不受限制地反复相乘很多次(\(\mathbf P^k,\,k\to\infty\)),无论最初客流集中在哪家门店,最终都会被平均摊平到同一个稳态比例上——先记住这个“反复相乘会抹平个体差异”的数学性质,第三章讨论图模型为什么不能无限加深层数时,我们还会与它重逢。
可以看到,单靠第一章的马尔可夫链,我们只能追踪“单个仓库”或“单家门店”在时间轴上的状态演化;要想刻画“仓库断货如何打破相邻门店的客流稳态”,就必须把视角从单个实体扩展到实体之间的关系网络——这正是第二章引入图结构的起点。
二、图神经网络如何从实体之间的关系中学习? #
第一章描述了单个实体的状态怎样随时间变化。现在把观察范围扩展到由仓库和门店组成的关系网络。同一种商品可能同时存在于数十家乃至数百家门店:一个仓库向多个区域的门店配送,某家门店的促销或断货还可能直接改变相邻门店的客流。
假设我们需要预测某家门店明天的商品需求。传统单店时间序列模型可以利用该门店的历史销量、价格、节假日和促销活动进行预测;但如果附近门店正在大促,或者因上游仓库延误而断货,顾客就可能跨店流动。这种影响无法仅靠单个门店自身的历史数据充分反映。问题因此发生了变化:系统的状态不仅与自身历史有关,还可能受到其他相连实体的影响。
2.1 用一个最小零售网络理解图结构 #
为了避免在不同公式之间反复更换场景,本章只观察一个局部网络:仓库 \(W_1\) 向门店 \(S_1\) 供货,仓库 \(W_2\) 向门店 \(S_2\) 供货,两家门店之间还可能相互影响客流。我们的任务是同时预测 \(S_1\) 和 \(S_2\) 的次日需求。
图(Graph)把业务实体表示为节点(Node),把实体之间的关系表示为边(Edge)。一个图可以写成 \(G=(\mathcal V,\mathcal E)\),其中 \(\mathcal V\) 是节点集合,\(\mathcal E\) 是边集合。这个四节点教学子图可写为:
\[ \begin{aligned} \mathcal V&=\{W_1,W_2,S_1,S_2\},\\ \mathcal E&=\{(W_1,S_1),(W_2,S_2),(S_2,S_1),(S_1,S_2)\}. \end{aligned} \]这里,\(W_1,W_2\) 是仓库节点,\(S_1,S_2\) 是门店节点。边 \((W_1,S_1)\) 和 \((W_2,S_2)\) 表示供应关系;两家门店之间的空间关系则展开为 \((S_1,S_2)\) 与 \((S_2,S_1)\) 两条边。用箭头压缩表示就是:
\[ W_1\overset{\mathrm{sup}}{\longrightarrow}S_1 \overset{\mathrm{spa}}{\longleftrightarrow}S_2 \overset{\mathrm{sup}}{\longleftarrow}W_2 \]箭头上方的 \(\mathrm{sup}\) 是供应关系(supply),\(\mathrm{spa}\) 是空间关系(spatial)。箭头方向表示信息可以从来源节点传向目标节点:供应信息由仓库传向门店;空间关系使用两个方向,因此任一家门店的信息都可以进入另一家门店的预测。
这里的边区分来源和目标,因此这是有向图(Directed Graph)。例如,\(W_1\to S_1\) 只允许供应信息从仓库 \(W_1\) 传向门店 \(S_1\);\(S_2\to S_1\) 则允许 \(S_2\) 的状态影响 \(S_1\) 的预测。方向描述“哪个实体的信息可以用于更新哪个实体”,不等同于顾客实际移动的固定方向,也没有预先规定影响是增加还是减少需求。
这个例子同时包含仓库、门店两类节点,以及供应、空间两类边,因此属于异构图(Heterogeneous Graph),也称异构信息网络(Heterogeneous Information Network)。“异构”强调节点或关系存在不同类型;如果所有节点和边都只有一种类型,则称为同构图。
图中的距离和运输时间属于边特征(Edge Features),用来描述关系本身。若再为每条边指定一个表示连接强度的标量 \(w_e\),便得到加权图(Weighted Graph)。二者需要区分:一条边可以同时具有距离、时间等多个特征,但“权重”通常专指参与聚合或计算的标量强度。
最后,图中的边只表示模型允许使用的关系,并不自动证明因果关系。\(S_2\) 促销且供货充足时可能从 \(S_1\) 吸走客流;\(S_2\) 在促销期间断货时,同一空间关系又可能把需求溢出到 \(S_1\)。图结构回答“谁与谁有关联”,当天影响的方向和大小还要结合节点与边的当前状态判断。
2.2 怎样把图结构和业务状态写成数据? #
第 2.1 节中的 \(G\) 只回答“哪些实体通过什么关系相连”,还没有记录某一天的需求、促销和供货情况。要把这个图交给模型,还需要为节点和边补上日期 \(t\) 的业务状态。因为这是异构图,仓库和门店可以使用不同的节点特征,供应边和空间边也可以使用不同的边特征,不必为了得到一张大矩阵而人为补零。
下面沿用四节点网络构造一个日期 \(t\) 的快照。每个矩阵的一行对应一个节点或一条边,每一列对应一个特征;行的排列顺序必须固定,才能知道数值属于哪个业务实体。所有数值均为作者设定的教学数据,不代表真实零售企业的平均水平。
先把节点集合分成仓库和门店两部分:
\[ \mathcal V^{\mathrm W}=\{W_1,W_2\},\qquad \mathcal V^{\mathrm S}=\{S_1,S_2\}. \]仓库只保留供货可用度 \(a_{W_i,t}^{\mathrm W}\in[0,1]\):1 表示可以正常向下游供货,0 表示无法供货,中间值表示部分供货能力。门店使用需求信号 \(b_{S_i,t}\)(预测 \(t+1\) 日时已能获得的最近一期需求,以件数表示,不能使用 \(t+1\) 日真实结果)和当日促销标记 \(p_{S_i,t}\in\{0,1\}\)。固定行序后,两类节点特征矩阵可紧凑写为:
\[ \begin{aligned} \mathbf X_t^{\mathrm W} &= \begin{bmatrix} a_{W_1,t}^{\mathrm W}\\ a_{W_2,t}^{\mathrm W} \end{bmatrix} = \begin{bmatrix} 1\\ 0 \end{bmatrix} \quad \begin{array}{l} \leftarrow W_1\text{(供货可用度 }a^{\mathrm W}\text{)}\\ \leftarrow W_2 \end{array} \\[6pt] \mathbf X_t^{\mathrm S} &= \begin{bmatrix} b_{S_1,t}&p_{S_1,t}\\ b_{S_2,t}&p_{S_2,t} \end{bmatrix} = \begin{bmatrix} 100&0\\ 80&1 \end{bmatrix} \quad \begin{array}{l} \leftarrow S_1\text{(列:需求 }b\text{,促销 }p\text{)}\\ \leftarrow S_2 \end{array} \end{aligned} \]因此,\(\mathbf X_t=\{\mathbf X_t^{\mathrm W},\mathbf X_t^{\mathrm S}\}\) 表示日期 \(t\) 的节点特征集合。它不必强行补零拼成一张大表,而是按节点类型各司其职——仓库有仓库的特征矩阵,门店有门店的特征矩阵。
节点状态之外,还要表示关系本身的属性。先把边按供应关系和空间关系拆分,并固定各自的行顺序:
\[ \mathcal E^{\mathrm{sup}}=\{W_1\to S_1,\,W_2\to S_2\},\qquad \mathcal E^{\mathrm{spa}}=\{S_1\to S_2,\,S_2\to S_1\}. \]一条边属于哪个集合,已经说明它是供应关系还是空间关系,无需再添加独热编码(One-Hot Encoding,即用一串只有一位为 1、其余全为 0 的二进制列来表示类别)类型列。用 \(d\) 表示距离(km),用 \(\tau\) 表示运输时间(min),两类边特征矩阵分别为:
\[ \begin{aligned} \mathbf R_t^{\mathrm{sup}} &= \begin{bmatrix} d_{W_1\to S_1}&\tau_{W_1\to S_1,t}\\ d_{W_2\to S_2}&\tau_{W_2\to S_2,t} \end{bmatrix} = \begin{bmatrix} 10&20\\ 12&25 \end{bmatrix} \quad \begin{array}{l} \leftarrow W_1\to S_1\text{(列:距离 km,时间 min)}\\ \leftarrow W_2\to S_2 \end{array} \\[6pt] \mathbf R_t^{\mathrm{spa}} &= \begin{bmatrix} d_{S_1\to S_2}&\tau_{S_1\to S_2,t}\\ d_{S_2\to S_1}&\tau_{S_2\to S_1,t} \end{bmatrix} = \begin{bmatrix} 2&6\\ 2&6 \end{bmatrix} \quad \begin{array}{l} \leftarrow S_1\to S_2\text{(列:距离 km,时间 min)}\\ \leftarrow S_2\to S_1 \end{array} \end{aligned} \]距离通常保持不变,运输时间可能随日期变化,因此边特征矩阵仍保留下标 \(t\),记为 \(\mathbf R_t=\{\mathbf R_t^{\mathrm{sup}},\mathbf R_t^{\mathrm{spa}}\}\)。第 2.4 节的手算推演为了简化纸笔计算,暂不使用距离和运输时间;真实模型则可以把这些边特征一并纳入计算。
至此,一个日期 \(t\) 的图快照可以写成 \((G,\mathbf X_t,\mathbf R_t)\):\(G\) 记录连接关系,\(\mathbf X_t\) 记录仓库和门店的状态,\(\mathbf R_t\) 记录供应边和空间边的属性。下一节说明模型怎样读取这组输入并预测两家门店的次日需求。
2.3 怎样定义并训练图神经网络的预测任务? #
如果普通神经网络只读取单店特征,它就看不到仓库状态和邻店变化;除非人工把这些关系信息预先加工成输入,否则模型也不会按照 \(G\) 中的连接交换信息。图神经网络(Graph Neural Network, GNN)的区别正在这里:它把图结构纳入计算,沿边传递信息,并用共享的可学习规则更新相连节点的表示。
先把预测任务定义清楚。将整个 GNN 记为 \(f_\Theta\),其中 \(\Theta\) 表示需要从训练数据中学习的参数。模型输入日期 \(t\) 的网络快照 \((G,\mathbf X_t,\mathbf R_t)\),同时输出两家门店在 \(t+1\) 日的需求预测:
\[ \widehat{\mathbf y}_{t+1}= \begin{bmatrix} \hat y_{S_1,t+1}\\ \hat y_{S_2,t+1} \end{bmatrix}=f_\Theta\!\left(G,\{\mathbf X_t^{\mathrm W},\mathbf X_t^{\mathrm S}\},\{\mathbf R_t^{\mathrm{sup}},\mathbf R_t^{\mathrm{spa}}\}\right). \]训练时,每个历史日期都要形成一组“当日输入—次日标签”。为简化写法,把日期 \(t\) 的完整输入记为 \(\mathbf z_t\),把根据 \(t+1\) 日真实观测构造的两店标签记为 \(\mathbf y_{t+1}^{\mathrm{target}}\):
\[ \mathbf z_t=(G,\mathbf X_t,\mathbf R_t),\qquad \mathbf y_{t+1}^{\mathrm{target}}= \begin{bmatrix} y_{S_1,t+1}^{\mathrm{target}}\\ y_{S_2,t+1}^{\mathrm{target}} \end{bmatrix}. \]设 \(\mathcal T_{\mathrm{train}}\) 为所有训练输入日期的集合,训练集就是这些输入—标签对的集合:
\[ \mathcal D_{\mathrm{train}}=\left\{\left(\mathbf z_t,\mathbf y_{t+1}^{\mathrm{target}}\right):t\in\mathcal T_{\mathrm{train}}\right\}. \]如果把四个矩阵中的每个元素都展开成独立字段,一个日期的训练样本就可以保存成传统关系数据库中的一行 宽表(Wide Table,即把同一天所有仓库和门店的十几个属性全部横向铺开成一行、列数很多的表格) 数据。为适应网页宽度,下面把这张宽表转置展示:数据库中的字段变成行,三个日期的记录变成列。表头日期表示输入日期 \(t\),最后两行则来自下一天 \(t+1\) 的真实需求观测。
| 字段 | 业务含义 | 2025-01-01 | 2025-01-02 | 2025-01-03 |
|---|---|---|---|---|
| \(a_{W_1}^{\mathrm W}\) | \(W_1\) 供货可用度 | 1 | 1 | 0 |
| \(a_{W_2}^{\mathrm W}\) | \(W_2\) 供货可用度 | 0 | 1 | 1 |
| \(p_{S_1}\) | \(S_1\) 当日促销标记 | 0 | 0 | 1 |
| \(p_{S_2}\) | \(S_2\) 当日促销标记 | 1 | 0 | 0 |
| \(d_{W_1\to S_1}\) | \(W_1\to S_1\) 距离(km) | 10 | 10 | 10 |
| \(\tau_{W_1\to S_1}\) | \(W_1\to S_1\) 运输时间(min) | 20 | 20 | 30 |
| \(d_{W_2\to S_2}\) | \(W_2\to S_2\) 距离(km) | 12 | 12 | 12 |
| \(\tau_{W_2\to S_2}\) | \(W_2\to S_2\) 运输时间(min) | 25 | 20 | 22 |
| \(d_{S_1\to S_2}\) | \(S_1\to S_2\) 距离(km) | 2 | 2 | 2 |
| \(\tau_{S_1\to S_2}\) | \(S_1\to S_2\) 运输时间(min) | 6 | 6 | 8 |
| \(d_{S_2\to S_1}\) | \(S_2\to S_1\) 距离(km) | 2 | 2 | 2 |
| \(\tau_{S_2\to S_1}\) | \(S_2\to S_1\) 运输时间(min) | 6 | 6 | 8 |
| \(b_{S_1}\) | \(S_1\) 最近需求信号(件) | 100 | 115 | 104 |
| \(b_{S_2}\) | \(S_2\) 最近需求信号(件) | 80 | 52 | 83 |
| \(y^{\mathrm{target}}_{S_1,t+1}\) | \(S_1\) 次日需求标签(件) | 115 | 104 | 72 |
| \(y^{\mathrm{target}}_{S_2,t+1}\) | \(S_2\) 次日需求标签(件) | 52 | 83 | 86 |
除最后两行外,符号都省略了共同的日期下标 \(t\);例如,表中的 \(a_{W_1}^{\mathrm W}\) 就是前文的 \(a_{W_1,t}^{\mathrm W}\),\(\tau_{W_1\to S_1}\) 就是 \(\tau_{W_1\to S_1,t}\)。每一列的前十四个数值共同来自当日的 \(\mathbf X_t^{\mathrm W}\)、\(\mathbf X_t^{\mathrm S}\)、\(\mathbf R_t^{\mathrm{sup}}\) 和 \(\mathbf R_t^{\mathrm{spa}}\);最后两行是日期 \(t+1\) 的监督目标。这里特意把两个需求信号移到目标标签上方,方便纵向比较输入与次日结果。实际数据库仍按“一个日期一行”保存;这里只为阅读方便交换了行列。
标签不是根据 2025-01-01 当天的输入随意设定的,而是由下一天的真实数据构造。具体来说,2025-01-01 这一列以当天的图数据作为输入;其中 \((y^{\mathrm{target}}_{S_1,t+1},y^{\mathrm{target}}_{S_2,t+1})=(115,52)\) 来自 2025-01-02 两家门店的真实需求观测。到了 2025-01-02 这一列,同一组观测又成为当天的最近需求信号 \((b_{S_1},b_{S_2})=(115,52)\),而该列的标签 \((104,83)\) 则来自 2025-01-03。因此,向右移动一列,可以看到上一列的 \(t+1\) 标签怎样与下一列日期 \(t\) 的需求信号对齐。
这组数值只用于展示训练数据的时间对齐和存储方式,不表示目标值由表中某个单一字段直接决定。训练集会为 \(\mathcal T_{\mathrm{train}}=\{t_1,t_2,\ldots,t_T\}\) 中的每个输入日期保存一行,因此 \(|\mathcal D_{\mathrm{train}}|=T\)。
固定图 \(G\) 不需要在宽表中重复保存。它可以单独存为节点表和边表,并通过稳定的节点、边编号说明每个字段属于哪个节点或哪条边。这个宽表适合解释当前固定的四节点小图;真实网络如果包含大量门店、仓库或动态边,通常会把节点特征、边特征和标签拆成长表(Long Table,也称窄表,即按“日期—实体编号—特征值”纵向堆叠、列数固定而行数很多的表格),再在训练时组装为 \(\mathbf X_t\)、\(\mathbf R_t\) 和 \(\mathbf y_{t+1}^{\mathrm{target}}\)。
有了这些监督样本,GNN 就可以通过最小化所有训练日期、所有目标门店上的预测误差来学习 \(\Theta\)。以均方误差为例:
\[ \Theta^*=\underset{\Theta}{\operatorname{argmin}}\; \frac{1}{|\mathcal D_{\mathrm{train}}|\,|\mathcal V^{\mathrm S}|} \sum_{t\in\mathcal T_{\mathrm{train}}} \sum_{S_i\in\mathcal V^{\mathrm S}} \left(y_{S_i,t+1}^{\mathrm{target}}-\hat y_{S_i,t+1}\right)^2 \]其中,\(|\mathcal V^{\mathrm S}|=2\) 是目标门店数,因此分母就是参与损失计算的“日期—门店”样本总数。训练过程不断调整 \(\Theta\),让预测值 \(\hat y_{S_i,t+1}\) 接近标签 \(y_{S_i,t+1}^{\mathrm{target}}\)。这里必须区分“账面记录的标签”和“真正想预测的业务量”:在零售业务中,经常发生销量截断(Sales Censoring,统计学中称为“删失数据” Censored Data,零售业常称“缺货封顶”或“受抑需求”)——比如某门店当天只剩 50 件库存,哪怕实际上有 80 位顾客想买,收银台最多也只能记下 50 件销量,多出来的 30 件真实需求就像被一刀截断了一样,在销售报表上彻底隐形了。如果直接拿受缺货影响的实际销量当标签,模型学到的也只是被封顶后的观测销量;若目标是未被库存限制的潜在真实需求(Latent Demand),就需要先剔除或还原被截断的缺货样本(称为去截断,Uncensoring),或者在模型中显式建模库存上限。GNN 本身不会自动消除标签偏差。
2.4 消息传递怎样把图数据变成预测? #
明确输入、输出和训练目标以后,再看 \(f_\Theta\) 怎样完成一次预测。最常见的一类 GNN 架构被称为消息传递神经网络(Message Passing Neural Network, 简称 MPNN):它先根据边的类型和两端节点的状态生成消息,再汇总流向同一节点的消息,随后更新节点表示,最后由预测头把更新后的表示转换成需求预测。
完整过程:从图数据到需求预测 #
第 2.3 节把整个模型写成 \(f_\Theta\),但没有展开它的内部计算。现在先看模型最终要得到什么,再从预测结果向前追溯:预测头读取了什么,门店的最终表示又是怎样形成的。
GNN 为每个节点维护一个节点表示(Node Representation,在深度学习中也常称为“节点嵌入” Node Embedding、“隐藏状态” Hidden State 或“节点特征向量”),记为粗体 \(\mathbf h\)。它是模型当前掌握的节点信息摘要,通常是一个向量。日期 \(t\) 的节点特征 \(\mathbf X_t\) 先被转换成初始表示 \(\mathbf h^{(0)}\);经过 \(K\) 轮消息传递后,目标门店 \(S_i\) 得到最终表示 \(\mathbf h_{S_i,t}^{(K)}\)。两家门店的次日预测可以先写成[1]:
\[ \begin{aligned} \widehat{\mathbf y}_{t+1} &= \begin{bmatrix} \hat y_{S_1,t+1}\\ \hat y_{S_2,t+1} \end{bmatrix} = \begin{bmatrix} g_\omega\!\left(\mathbf h_{S_1,t}^{(K)}\right)\\ g_\omega\!\left(\mathbf h_{S_2,t}^{(K)}\right) \end{bmatrix}. \end{aligned} \]这里的 \(g_\omega\) 是预测头(Prediction Head,也称输出层 Output Layer 或读出层 Readout Layer,通常是一个简单的线性回归或全连接层)。它不再沿图传递消息,而是把一家门店的最终表示 \(\mathbf h_{S_i,t}^{(K)}\) 转换成一个具体的次日需求预测 \(\hat y_{S_i,t+1}\)。其中,\(g\) 表示“从最终表示到预测值”的映射,\(\omega\) 表示映射中通过训练学习的参数。两家门店共享同一个 \(g_\omega\),但因为各自的最终表示不同,输出也可以不同。
接下来再解释 \(\mathbf h_{S_i,t}^{(K)}\) 从哪里来。公式用 \(S_i\) 表示接收信息的目标门店,用 \(W_j\) 和 \(S_j\) 分别表示发送信息的仓库和邻店。小写粗体 \(\mathbf r\) 表示一条边的特征向量;例如,\(\mathbf r_{W_j\to S_i,t}^{\mathrm{sup}}\) 是 \(\mathbf R_t^{\mathrm{sup}}\) 中对应供应边的一行。
每一轮传播都包含三个连续动作:
生成消息:消息函数 \(\phi\) 可以读取来源节点、目标节点和边特征,为一条边生成消息。供应边与空间边含义不同,因此分别使用 \(\phi^{\mathrm{sup}}\) 和 \(\phi^{\mathrm{spa}}\)。具体函数不一定使用所有可用输入。
聚合消息:\(\operatorname{AGG}\) 把指向同一目标门店、属于同一关系类型的消息合并。小写 \(m\) 表示一条边的消息,粗体 \(\mathbf m\) 表示聚合后的结果。
更新表示:更新函数 \(\psi\) 把门店当前的表示与两类聚合消息结合,得到下一轮表示。
用公式表示这一轮计算,就是:
\[ \begin{aligned} \mathbf m_{S_i,t}^{(k),\mathrm{sup}} &= \operatorname{AGG}\left\{ \phi^{\mathrm{sup}}\!\left(\mathbf h_{W_j,t}^{(k)}, \mathbf r_{W_j\to S_i,t}^{\mathrm{sup}}\right): W_j\to S_i\in\mathcal E^{\mathrm{sup}}\right\},\\ \mathbf m_{S_i,t}^{(k),\mathrm{spa}} &= \operatorname{AGG}\left\{ \phi^{\mathrm{spa}}\!\left(\mathbf h_{S_j,t}^{(k)}, \mathbf h_{S_i,t}^{(k)}, \mathbf r_{S_j\to S_i,t}^{\mathrm{spa}}\right): S_j\to S_i\in\mathcal E^{\mathrm{spa}}\right\},\\ \mathbf h_{S_i,t}^{(k+1)} &= \psi\!\left(\mathbf h_{S_i,t}^{(k)}, \mathbf m_{S_i,t}^{(k),\mathrm{sup}}, \mathbf m_{S_i,t}^{(k),\mathrm{spa}}\right), \qquad k=0,1,\ldots,K-1. \end{aligned} \]模型令 \(k=0,1,\ldots,K-1\),把上述计算重复 \(K\) 次:
\[ \mathbf h_{S_i,t}^{(0)} \longrightarrow \mathbf h_{S_i,t}^{(1)} \longrightarrow \cdots \longrightarrow \mathbf h_{S_i,t}^{(K)}. \]上标 \((0)\) 表示尚未传播消息的初始状态;当 \(k=K-1\) 时,更新式得到的 \(\mathbf h_{S_i,t}^{(k+1)}\) 就是 \(\mathbf h_{S_i,t}^{(K)}\),随后由预测头生成结果。这里的 \(K\) 只计算同一日期快照内的传播轮数,不表示业务时间向前推进了 \(K\) 天。
真实模型需要从训练数据中学习消息函数、更新函数和预测头的参数。把两类消息函数的参数统记为 \(\theta_\phi\),把更新函数的参数记为 \(\theta_\psi\),完整参数集合便可写成 \(\Theta=\{\theta_\phi,\theta_\psi,\omega\}\)。这也把本节的内部计算与第 2.3 节中整个 GNN 的 \(f_\Theta\) 对应起来。
数值例子:四节点网络怎样完成预测? #
下面回到四节点零售网络,完整走一遍训练完成后的推理过程。第 2.3 节中的训练会利用多个日期的输入—标签配对学习 \(\Theta\);这里假定参数已经固定,只把日期 \(t\) 的新快照转换成日期 \(t+1\) 的预测。为了方便纸笔手算,这里直接给定 \(-30\)、\(2/3\)、\(0.9\) 和 \(5\) 四个教学参数——它们并非从前文三天样本中拟合而来,也不代表真实零售业务的经验系数。
通用模型中的节点表示 \(\mathbf h_{S_i,t}^{(k)}\) 通常是向量。本例把它简化为标量 \(h_{S_i}^{(k)}\),并省略表示中共同的日期下标 \(t\)。两家门店用最近需求信号初始化:\(h_{S_1}^{(0)}=100\)、\(h_{S_2}^{(0)}=80\)。同一快照还给出 \(a_{W_1,t}^{\mathrm W}=1\)、\(a_{W_2,t}^{\mathrm W}=0\),以及 \(p_{S_1,t}=0\)、\(p_{S_2,t}=1\)。这些数值都直接来自第 2.2 节的 \(\mathbf X_t^{\mathrm W}\) 和 \(\mathbf X_t^{\mathrm S}\)。日期 \(t\) 的 \(G\)、\(\mathbf X_t\) 和 \(\mathbf R_t\) 在整个计算中保持不变;上标 \((0)\)、\((1)\)、\((2)\) 只表示这张快照内的初始状态和两轮传播,不表示日期推进到了 \(t+2\)。
在图论与图神经网络中,沿着一条边走一步称为 “一跳”(1-hop):直接相连的节点称为 一跳邻居(或一阶邻域),中间隔了一个节点、需要连走两步才能到达的关系则称为 两跳路径(2-hop path)。本例的两轮传播恰好对应一条两跳路径:第一轮让门店接收相连仓库的信息,第二轮再让两家门店交换更新后的信息。因此,\(W_2\) 沿 \(W_2\to S_2\) 一跳到达 \(S_2\),再沿 \(S_2\to S_1\) 经过第二跳影响 \(S_1\)。常见 GNN 中一轮传播通常对应一层网络,但 自连接(Self-loop,也称自环,即节点给自己连一条边以保留自身当前状态)、残差连接(Residual / Skip Connection,即把上一层的自身特征直接跨层加到下一层输出上) 等设计会改变实际保留的信息,不能把网络层数机械地等同于有效传播距离。
下面把通用函数简化成四条可以手算的规则。通用模型通常先把仓库特征编码为仓库表示 \(\mathbf h_W\);本例省略这一步,让供应消息直接读取仓库可用度 \(a^{\mathrm W}\)。空间消息读取来源门店需求 \(b_{S_j,t}\)、促销标记 \(p_{S_j,t}\) 与第一轮表示 \(h_{S_j}^{(1)}\);距离和运输时间仍保存在 \(\mathbf R_t\) 中,但暂不参与这次计算。
| 组件 | 输入变量 | 本例简化规则 |
|---|---|---|
| 供应消息 \(\phi^{\mathrm{sup}}\) | \(a_{W_j,t}^{\mathrm W}\) | \(m_{W_j\to S_i,t}^{(0),\mathrm{sup}}=-30(1-a_{W_j,t}^{\mathrm W})\) |
| 空间消息 \(\phi^{\mathrm{spa}}\) | \(b_{S_j,t},\,p_{S_j,t},\,h_{S_j}^{(1)}\) | \(m_{S_j\to S_i,t}^{(1),\mathrm{spa}}=p_{S_j,t}\frac{2}{3}\max(0,b_{S_j,t}-h_{S_j}^{(1)})\) |
| 更新函数 \(\psi\) | \(h_{\mathrm{old}},\,m\) | \(h_{\mathrm{new}}=h_{\mathrm{old}}+m\) |
| 预测头 \(g_\omega\) | \(h_{S_i}^{(2)}\) | \(\hat y=g_\omega(h)=0.9h+5\) |
消息下标说明消息沿哪条有向边传递;上标 \((0)\) 和 \((1)\) 分别表示它由初始表示和第一轮表示生成;\(\mathrm{sup}\) 与 \(\mathrm{spa}\) 区分供应消息和空间消息。在本例的预测头中,\(\omega\) 对应系数 \(0.9\) 和截距 \(5\)。真实模型中的函数通常更复杂;这四条规则只用于展示一次前向计算。
把完整推理先写成两家门店组成的预测向量:
\[ \begin{aligned} \widehat{\mathbf y}_{t+1} &= \begin{bmatrix} \hat y_{S_1,t+1}\\ \hat y_{S_2,t+1} \end{bmatrix} = \begin{bmatrix} 0.9h_{S_1}^{(2)}+5\\ 0.9h_{S_2}^{(2)}+5 \end{bmatrix} = \begin{bmatrix} 0.9\times120+5\\ 0.9\times50+5 \end{bmatrix} = \begin{bmatrix} 113\\ 50 \end{bmatrix}. \end{aligned} \]式中的 \(120\) 和 \(50\) 来自下面两轮传播。
第一轮:供应消息更新门店表示。 当 \(a^{\mathrm W}=1\) 时,仓库正常供货,消息为 0;当 \(a^{\mathrm W}=0\) 时,消息为 \(-30\)。因此,\(W_1\) 不改变 \(S_1\) 的表示,而供货不可用的 \(W_2\) 把 \(S_2\) 从 80 调整为 50:
\[ \begin{aligned} m_{W_1\to S_1,t}^{(0),\mathrm{sup}} &= -30(1-1)=0,\\ m_{W_2\to S_2,t}^{(0),\mathrm{sup}} &= -30(1-0)=-30,\\[4pt] h_{S_1}^{(1)} &= 100+m_{W_1\to S_1,t}^{(0),\mathrm{sup}}=100,\\ h_{S_2}^{(1)} &= 80+m_{W_2\to S_2,t}^{(0),\mathrm{sup}}=50. \end{aligned} \]这里的 \(-30\) 只是表示供货受限会降低门店内部状态的教学值,不表示真实需求必然减少 30 件。
第二轮:空间消息把供货影响传给邻店。 空间函数先用 \(b_{S_j,t}-h_{S_j}^{(1)}\) 计算来源门店的需求缺口,再用 \(p_{S_j,t}\) 决定是否传递:\(p=0\) 时整条消息为 0;\(p=1\) 时,本例把缺口的三分之二传给邻店。于是,正在促销的 \(S_2\) 有 \(80-50=30\) 件缺口,向 \(S_1\) 发送 20;未促销的 \(S_1\) 向 \(S_2\) 发送 0:
\[ \begin{aligned} m_{S_2\to S_1,t}^{(1),\mathrm{spa}} &= 1\times\frac{2}{3}(80-50)=20,\\ m_{S_1\to S_2,t}^{(1),\mathrm{spa}} &= 0\times\frac{2}{3}\max(0,100-100)=0,\\[4pt] h_{S_1}^{(2)} &= 100+m_{S_2\to S_1,t}^{(1),\mathrm{spa}}=120,\\ h_{S_2}^{(2)} &= 50+m_{S_1\to S_2,t}^{(1),\mathrm{spa}}=50. \end{aligned} \]最终,预测头分别读取 \(h_{S_1}^{(2)}=120\) 和 \(h_{S_2}^{(2)}=50\),得到预测值 113 和 50(与第 2.3 节宽表中 2025-01-01 对应的次日真实观测标签 115 和 52 相比,只相差 2 件,属于正常的模型预测误差)。注意,空间消息 20 不是 \(S_1\) 的最终预测,而是先把它的内部表示从 100 更新为 120,再通过 \(g_\omega\) 间接改变预测。这个数值只表示消息函数生成的内部需求转移信号,不表示现实中一定有 20 位顾客或 20 件订单发生转移。
这个例子体现了 GNN 的什么特点? #
这个例子的重点不只是“模型看到了更多字段”,而是这些字段怎样按照图结构参与计算。它体现了 GNN 的三个核心特点:
图决定信息可以沿哪里传播:\(W_2\) 的供货状态先沿供应边进入 \(S_2\),再沿空间边影响 \(S_1\)。如果缺少 \(W_2\to S_2\) 或 \(S_2\to S_1\) 中任何一条有向边,这条两跳信息路径就不存在。图在这里提供的是计算约束,而不只是数据标签。
多轮传播可以组合多种关系:第一轮把“仓库是否可供货”写入门店表示,第二轮再根据门店需求和促销状态计算跨店消息。因此,\(S_1\) 的预测不只使用自身的 100,还包含由“上游供货受限 + 邻店促销”共同形成的两跳信号。这种组合不是简单地把两个字段并排放进输入,而是让后一轮计算读取前一轮更新后的结果。
同类关系共享计算规则:\(W_1\to S_1\) 与 \(W_2\to S_2\) 共用供应消息函数,\(S_1\to S_2\) 与 \(S_2\to S_1\) 共用空间消息函数。网络扩展到更多仓库和门店时,模型可以复用按关系类型学习的规则,而不必为每个节点单独设计一套参数。
这些特点不意味着普通神经网络在数学上无法利用仓库或邻店信息。工程师也可以把邻域字段预先展开成宽表,再交给 多层感知机(Multilayer Perceptron, 简称 MLP,即最基础的多层全连接神经网络)、线性模型或树模型。GNN 的区别在于把“谁能向谁传递哪一类信息”直接写进模型结构,并在不同节点和边上复用同一套关系计算。它是否比经过良好特征工程的 传统表格模型(机器学习中常称“基准模型”或“参照模型”,Baseline Model,即用来检验复杂新模型到底有没有带来真实增量的基础对照标准) 更准确,仍需要通过 跨期样本外验证(Out-of-Time Validation,时序工程中常称时间切片回测 Backtesting,即严格用过去的时间段训练、在未来的时间段测试,绝不随机打乱时间顺序) 来回答。
这次手算推演为了突出 \(W_2\to S_2\to S_1\) 的两跳路径,第一轮只处理供应边,第二轮只处理空间边;通用模型也可以在每一轮同时生成两类消息并分别聚合。下图把节点特征、两轮传播和预测头放在同一条计算链上。
2.5 GCN、GraphSAGE 和 GAT 如何实现不同的邻居聚合? #
第 2.4 节的 MPNN 只规定了“生成消息—聚合消息—更新表示”的通用流程,没有规定每轮必须读取哪些邻居,也没有规定不同邻居应占多大权重。图卷积网络(Graph Convolutional Network, GCN)、基于采样与聚合的图网络(Graph Sample and Aggregate, GraphSAGE) 和 图注意力网络(Graph Attention Network, GAT) 本质上都是这一框架的经典变体,区别只在于 “挑哪些邻居、按什么比重汇总信息”。
不过在对比三者之前,需要先跨过一道门槛:节点异构性。经典 GCN、GraphSAGE 和 GAT 公式默认参与同一层计算的所有节点特征维度相同、含义一致,可以直接做矩阵运算。本文的零售网络并不直接满足这个前提:仓库节点只有供货可用度,门店节点则包含需求和促销,两类原始特征的维度与含义都不同。因此,实际模型通常先按节点类型分别编码,再把结果投影到相同的隐藏维度:
\[ \begin{aligned} \mathbf H_{\mathrm W}^{(0)}=f_{\mathrm W}(\mathbf X_t^{\mathrm W}), \qquad \mathbf H_{\mathrm S}^{(0)}=f_{\mathrm S}(\mathbf X_t^{\mathrm S}), \\[4pt] \mathbf H_{\mathrm W}^{(0)}\in \mathbb R^{|\mathcal V^{\mathrm W}|\times d_h}, \qquad \mathbf H_{\mathrm S}^{(0)}\in \mathbb R^{|\mathcal V^{\mathrm S}|\times d_h}. \end{aligned} \]这里,\(f_{\mathrm W}\) 和 \(f_{\mathrm S}\) 是仓库与门店各自的输入编码器,\(d_h\) 是统一的隐藏维度。完成这一步以后,仓库表示与门店表示才能在后续消息函数中进行组合。边的异构性是第二层问题:供应边和空间边即使连接了相同隐藏维度的表示,也不一定应该共享同一套消息参数。下面的经典公式用于解释聚合思想,不表示它们会自动处理本文的两类节点和两类边。
GCN(图卷积网络):按图结构和节点度数归一化。 Kipf 和 Welling 提出的经典 GCN通常读取完整的一阶邻域,并用归一化邻接矩阵混合自身与邻居的表示[2]。下面的形式假定所有行已经具有相同隐藏维度,并把连接暂时视为一种关系:
\[ \mathbf H^{(k+1)}= \sigma\!\left( \widehat{\mathbf D}^{-1/2} \widehat{\mathbf A} \widehat{\mathbf D}^{-1/2} \mathbf H^{(k)} \mathbf W^{(k)} \right), \qquad \widehat{\mathbf A}=\mathbf A+\mathbf I. \]这里,\(\mathbf H^{(k)}\) 的每一行是一家门店或仓库在第 \(k\) 层的表示;\(\mathbf A\) 记录连接,\(\mathbf I\) 加入自连接,\(\widehat{\mathbf D}\) 按节点度数(即每个节点连接的边数)完成归一化;\(\mathbf W^{(k)}\) 和非线性激活函数 \(\sigma\) 则由模型学习特征变换。以两个相连、标量特征分别为 100 和 60 的门店为例,若暂时令 \(W^{(k)}=1\)、\(\sigma\) 为恒等映射,那么加入自连接后的简化聚合为:
\[ \frac{1}{2}\times100+\frac{1}{2}\times60=80 \]GCN 的权重主要由连接结构和节点度数决定,同一层前向计算时不会因为当天某家邻店正在促销,就自动提高该邻店的结构权重。它结构简单,适合作为图模型的入门基准(基础对照模型);但在邻居作用随业务状态变化时,固定的结构归一化可能不够灵活。
GraphSAGE(采样与聚合图网络):先采样邻居,再用共享函数聚合。 Hamilton 等人提出的 GraphSAGE 可以从每个节点的邻域中随机抽取固定数量的邻居(即邻居采样),再用均值、池化(Pooling,如取最大值)或其他可学习函数形成邻域摘要[3]。下面的经典形式假定采到的邻居表示彼此可比较,并使用同一个聚合器:
\[ \begin{aligned} \widetilde{\mathbf h}_{S_i}^{(k)} &= \operatorname{AGG}\!\left( \{\mathbf h_{S_j}^{(k)}:S_j\in\mathcal N(S_i)\} \right),\\ \mathbf h_{S_i}^{(k+1)} &= \sigma\!\left( \mathbf W^{(k)} [\mathbf h_{S_i}^{(k)}\,\|\,\widetilde{\mathbf h}_{S_i}^{(k)}] \right). \end{aligned} \]这里的 \(\mathcal N(S_i)\) 可以表示本轮实际采到的邻居集合,符号 \(\|\) 表示向量拼接(Concatenation,即把目标门店自身表示与邻域摘要首尾相连拼成更长的向量)。采样使每个节点每轮处理的邻居数量可以受控,适合大图;共享的聚合与变换函数还可以直接为训练时从未见过的新开门店生成表示,这种能力在机器学习中称为归纳式学习(Inductive Learning,即学到通用的“汇总邻居规则”泛化到新节点;与之相对的是直推式学习 Transductive Learning,要求所有测试节点在训练时就已固定在图中)。当然,这里的“可处理未见节点”仍有条件:新门店必须具备模型需要的特征和可用邻域,训练分布与新区域差异过大时也未必能够可靠迁移。在异构图中,还需要决定按节点或关系类型分别采样,还是让不同类型共享采样预算与聚合器。
GAT(图注意力网络):根据当前特征学习邻居权重。 Veličković 等人提出的 GAT 将自然语言处理中常见的注意力机制(Attention Mechanism)引入图结构,为目标节点的不同邻居动态计算注意力分数,再在该邻域内做归一化(Softmax)[4]。下面的经典形式假定这些邻居已经进入可比较的表示空间,并在一种关系内计算注意力:
\[ \alpha_{S_iS_j}= \frac{\exp(e_{S_iS_j})} {\sum_{S_\ell\in\mathcal N(S_i)}\exp(e_{S_iS_\ell})} \]其中,\(e_{S_iS_j}\) 是模型根据目标门店 \(S_i\) 与邻店 \(S_j\) 的当前表示计算的相关性打分。假设 \(S_1\) 的两个邻店得到 \(e_{S_1S_2}=\ln 3\) 和 \(e_{S_1S_3}=0\),那么:
\[ \alpha_{S_1S_2}=\frac{3}{3+1}=0.75, \qquad \alpha_{S_1S_3}=\frac{1}{3+1}=0.25 \]若两个邻店传来的标量表示分别为 80 和 120,聚合结果就是 \(0.75\times80+0.25\times120=90\)。与固定的度数归一化相比,GAT 可以让邻居权重随当天门店特征(如是否促销)动态变化。不过,注意力权重只说明模型在这次聚合中怎样分配比重,不足以证明某家邻店在因果上导致了预测变化。异构图若让供应边与空间边共同参与注意力,还需要关系专属的投影或打分函数,否则不同关系的分数未必具有可比意义。
把三者放到相同维度比较,可以得到:
| 模型 | 每轮使用哪些邻居 | 怎样形成邻域摘要 | 主要适用考虑 |
|---|---|---|---|
| GCN | 通常使用完整的一阶邻域 | 按图结构与节点度数归一化 | 结构简单,适合作为图模型的入门基准 |
| GraphSAGE | 可以采样固定数量的邻居 | 共享的均值、池化或可学习聚合器 | 控制大图计算量,并支持对新节点的归纳式泛化 |
| GAT | 通常使用目标节点的当前邻域 | 根据当前节点表示动态学习注意力权重 | 邻居作用需要随输入状态变化时更灵活 |
三者都遵循“邻居发送信息—目标节点聚合—更新节点表示”,不是从低级到高级的性能排序。选择取决于图规模、是否需要处理新节点、邻居作用是否随状态变化,以及跨期样本外验证的结果。还要注意,上述经典公式主要展示单一关系上的聚合,经典 GCN 还常在无向或经过对称化的图上使用;对于本文的异构有向零售图,实际实现仍需像第 2.4 节那样保留边方向、区分供应边与空间边,并为不同关系设置相应的消息或变换函数。
2.6 图神经网络并不是所有关系预测问题的最佳选择 #
理解了图神经网络怎样利用关系,并不意味着遇到门店网络就应该使用 GNN。AI 工程师的目标是以可接受的成本持续产生可靠预测,而不是选择看起来最复杂的模型。多数零售需求预测项目更合理的起点,是先把业务关系转化为可验证的特征,再建立扎实、成本可控的基础需求预测模型(基准对照模型)。
例如,可以为每个“门店—日期”样本加入:
- 附近门店过去七天的销量;
- 邻近门店的加权促销强度;
- 本店上游仓库的库存状态;
- 门店与仓库之间的配送距离。
这些网络特征可以作为线性回归模型的输入,也可以作为外生变量交给 Prophet;如果关系明显非线性,还可以使用梯度提升决策树(Gradient Boosted Decision Trees, 简称 GBDT,工程上最常用的是 XGBoost 与 LightGBM)。经过精细特征工程的传统模型往往性价比极高:训练和推理快、数据门槛低、报错好排查,也更容易向业务方讲清楚逻辑。扎实的基础对照模型(基准模型)绝不是上马 GNN 之前的“走过场”,而是衡量图结构究竟能带来多少增量价值的标尺。
模型选择还不能只比较一次离线实验中的误差。生产系统需要考虑鲁棒性(Robustness,也称稳健性,指系统在异常输入或环境变化下的抗干扰与稳定运行能力)与机器学习运维(MLOps)投资回报率(ROI):图中的边缺失、仓库或门店上线下线、关系权重漂移时,模型是否仍然稳定?团队能否监控节点特征、边特征和图结构的变化?出现异常预测时,能否判断问题来自原始数据、图构建、消息传递还是预测头?如果 GNN 只带来很小或不稳定的精度提升,却显著增加数据管道、训练、部署、监控和维护成本,那么它的总体 ROI 可能低于结构更简单的模型。
但这并不否定 GNN 的价值。当预测目标确实受到多跳关系、异构实体和随状态变化的邻居作用影响,而且这些信息难以稳定地展开成固定宽表时,GNN 可以直接复用图上的局部计算规则,让信息沿供应边和空间边传播。它还能在不同门店之间共享关系参数,并随着网络规模扩大继续处理可变数量的邻居。这种直接写进网络结构里的先验假设(机器学习中称为“结构归纳偏置”,Structural Inductive Bias,即模型天生默认相连实体按固定规则互相影响),是线性回归、Prophet 或梯度提升树仅靠固定字段不容易自然表达的。
因此,更稳妥的工程落地路径是:先搭好可解释的单店模型与传统特征工程参照模型,再通过严格的跨期样本外回测检验 GNN 的增量收益,同时记录延迟、资源消耗、故障率和维护工作量。只有当关系结构带来的收益稳定覆盖额外复杂度时,才值得把 GNN 推向生产。下一章将在时间变化的零售网络中,把节点状态、关系传播和随机扰动放进同一个框架,进一步说明这种优势可能从哪里产生。
三、从随机过程到时空图神经网络:状态与关系如何共同演化? #
第一章介绍了单个实体的状态怎样随时间演化,第二章回答了在当天网络快照上信息怎样沿图结构传递。真实零售需求同时包含这两个维度:要预测明天,不仅要知道今天哪些仓库与门店相连,还要理解仓库状态、促销和门店需求怎样从过去演化到现在。因此,时空预测需要同时处理两个方向:沿时间轴读取连续快照,沿图结构交换同一快照内的信息。
3.1 从单变量时间序列到动态网络 #
先考虑一家门店。它在日期 \(t+1\) 的状态可能由当前状态、已知外部条件和无法解释的扰动共同决定:
\[ x_{t+1}=f(x_t,u_t)+\epsilon_t \]这里,\(x_t\) 是日期 \(t\) 的需求状态或相关特征,\(u_t\) 是促销、价格、节假日等外部输入,\(\epsilon_t\) 则表示模型没有解释的随机扰动。这是对业务过程怎样演化的抽象,不是某个预测模型的训练公式。
当预测对象扩展到多个门店和仓库以后,每个日期不再只有一个 \(x_t\),而是第二章定义的一张图快照:
\[ \mathcal Z_t=(G_t,\mathbf X_t,\mathbf R_t) \]其中,\(G_t\) 记录日期 \(t\) 的节点与边,\(\mathbf X_t\) 保存各节点当日的特征,\(\mathbf R_t\) 保存各类边当日的特征。若仓库供货关系和门店位置短期不变,\(G_t\) 可以保持固定;需求、促销、供货可用度和运输时间仍会使 \(\mathbf X_t\) 与 \(\mathbf R_t\) 随日期变化。
预测模型真正读取的是一段历史快照,并输出下一日各门店的需求:
\[ \widehat{\mathbf y}_{t+1} = F_\theta\!\left( \mathcal Z_{t-L+1},\ldots,\mathcal Z_t \right) \]\(L\) 是回看窗口长度,\(F_\theta\) 是需要从训练数据学习的预测函数,\(\widehat{\mathbf y}_{t+1}\) 则同时包含各门店的次日预测。这个表达式把“系统怎样演化”和“模型怎样利用历史预测”区分开来,也不限定 \(F_\theta\) 必须采用哪一种 GNN 或时间序列结构。
3.2 为什么图结构可以帮助时间序列预测? #
继续观察第二章的四节点网络(\(W_1\to S_1 \leftrightarrow S_2 \leftarrow W_2\))。即使连接结构连续多日保持不变,边所承载的业务信号也会因为上游状态的切换而发生 「符号翻转」(Sign-flipping):
日期 \(t\)(负向虹吸):上游仓 \(W_2\) 供货正常(\(a_{W_2,t}^{\mathrm W}=1\))且邻店 \(S_2\) 正在促销(\(p_{S_2,t}=1\)),货源充足的 \(S_2\) 会从 \(S_1\) 吸走部分价格敏感客流,使 \(S_1\) 需求下降;
日期 \(t+1\)(正向溢出):\(W_2\) 突发断货(\(a_{W_2,t+1}^{\mathrm W}=0\))而 \(S_2\) 仍在促销(\(p_{S_2,t+1}=1\)),\(S_2\) 货架迅速售罄,跑空趟的顾客顺着同一条空间边涌向货源正常的 \(S_1\),反而使 \(S_1\) 需求上升。
注意这里的致命陷阱:站在门店 \(S_1\) 的视角,单看一跳邻居 \(S_2\) 的促销标记 \(p_{S_2}=1\),根本无法判断它对本店是利空还是利好——它的正负号完全取决于两跳之外的邻店上游仓库 \(W_2\) 是否正常供货。只看 \(S_1\) 自身历史的单店模型对此一无所知;即便工程师把一跳邻店的促销强行拼进宽表特征,如果不结合两跳外的仓库状态,一正一负的相反效应也会在训练样本中互相抵消。而两层 GNN 可以沿着 \(W_2 \to S_2 \to S_1\) 的有向异构路径,先在 \(S_2\) 处组合“仓库状态 \(\times\) 门店促销”,再把有效信号传给 \(S_1\)。
这里必须区分两个下标。\(t,t+1\) 表示业务日期向前推进;\(k,k+1\) 表示模型在同一日期快照内又完成一轮消息传递。换句话说,多跑两轮图传播并不会让日历翻到下一天,只是帮门店在当天把视线从“一跳之内的仓库与邻店”延伸到“两跳之外的邻店上游仓”。时空图模型需要分别处理这两个维度,再把结果用于下一日预测。
3.3 图上的随机游走、特征扩散与时空预测 #
第一章用行随机矩阵 \(\mathbf P\) 描述了单个仓库状态随时间的转移;同样的矩阵形式也可以刻画同一快照内特征怎样沿门店空间图传播。要把空间连接变成可计算的传播算子,需要依次构造三个矩阵:
- 空间邻接矩阵 \(\mathbf A\)(谁和谁挨着、有多近):非负元素 \(A_{ij}\ge 0\) 表示门店 \(i\) 与门店 \(j\) 的原始空间亲和度(距离越近数值越大;超出同一生活商圈则记为 \(0\))。此时的 \(A_{ij}\) 只是原始亲疏打分,同一行的总和未必等于 \(1\)。
- 行度矩阵 \(\mathbf D=\operatorname{diag}(\mathbf A\mathbf 1)\)(每家店周边的总连接强度):对角线元素 \(D_{ii}=\sum_j A_{ij}\) 汇总了门店 \(i\) 与周边所有邻店亲和度的总和。处于核心密集商圈的门店 \(D_{ii}\) 较大,外围门店则较小。
- 随机游走转移矩阵 \(\mathbf P=\mathbf D^{-1}\mathbf A\)(商圈内的分流与聚合权重):当每个节点至少有一条出边时,左乘 \(\mathbf D^{-1}\) 相当于把 \(\mathbf A\) 的每一行除以该行总和 \(D_{ii}\),使每行权重加总严格等于 \(1\):
我们可以直接在第二章 \(S_1, S_2\) 的小例子旁再添一家同商圈门店 \(S_3\),看清这三个矩阵如何配合。假设以 \(S_1\) 为中心:它离紧邻门店 \(S_2\) 很近(原始亲和度 \(A_{12}=0.6\)),离稍远的 \(S_3\) 亲和度为 \(A_{13}=0.2\),且不计自环(\(A_{11}=0\))。那么 \(S_1\) 这一行的原始邻接向量为 \(\begin{bmatrix}0 & 0.6 & 0.2\end{bmatrix}\),对应的行度(周边连接总强度)为 \(D_{11}=0.6+0.2=0.8\)。经 \(D_{11}^{-1}\) 行归一化后,转移矩阵 \(\mathbf P\) 的第一行就变成:
\[ P_{1\cdot} = \frac{1}{0.8}\begin{bmatrix}0 & 0.6 & 0.2\end{bmatrix} = \begin{bmatrix}0 & 0.75 & 0.25\end{bmatrix}. \]这个结果在零售业务上非常直观:在 \(S_1\) 周边的所有邻店中,无论模型是向内汇总邻店的促销竞争信号,还是在 \(S_1\) 缺货时向外估算未满足客流往哪家邻店分流,紧邻的 \(S_2\) 都占 75% 的权重,稍远的 \(S_3\) 占 25% 的权重。
若 \(\mathbf H^{(0)}\) 的每一行保存一家门店的初始特征,最简单的线性图传播就是用 \(\mathbf P\) 反复左乘特征矩阵:
\[ \mathbf H^{(k+1)}=\mathbf P\mathbf H^{(k)},\qquad \mathbf H^{(K)}=\mathbf P^K\mathbf H^{(0)} \]按行归一化约定,\(P_{ij}\) 是从门店 \(i\) 指向邻店 \(j\) 的一步权重;每次矩阵乘法都会把邻居特征按转移比例混合进当前节点,\(\mathbf P^2\) 组合两步路径,\(\mathbf P^K\) 则使信息最多到达 \(K\) 跳之外。比如在上面的三店例子中,若 \(S_2\) 与 \(S_3\) 当日的促销强度分别为 \(0.20\) 和 \(0.00\),那么一步传播 \(\mathbf P\mathbf H^{(0)}\) 为 \(S_1\) 聚合到的邻域有效促销强度就是 \(0.75\times 0.20 + 0.25\times 0.00 = 0.15\)。这个“步数”与 GNN 的传播轮数直接对应:一轮聚合读取一跳邻居,多轮聚合逐渐扩大 感受野(Receptive Field,借用自计算机视觉的术语,指当前门店在预测时能够“看”到多远范围内的邻居信息);区别在于 GNN 还会加入可学习变换、非线性激活与残差连接,并区分不同边类型。
这里也能看到反复传播为什么会削弱节点差异:在线性、固定传播算子的简化情形中,随着 \(K\) 增大,矩阵幂 \(\mathbf P^K\) 可能趋向第 1.2 节讨论的平稳极限,使各节点的特征越来越相似。这就解释了为什么深层图模型容易出现 过平滑(Over-smoothing)——当邻域平均次数过多时,局部门店特征反而被抹平。
把第 3.2 节的四节点小图与本节的矩阵算子放在一起,就能看清异构零售网络中两类边的分工:仓库\(\to\)门店供应边 \(\mathcal E^{\mathrm{sup}}\) 负责跨层传导上游货源状态(决定门店次日是否面临补货受限),而门店\(\leftrightarrow\)门店空间边 \(\mathcal E^{\mathrm{spa}}\) 则通过行归一化矩阵 \(\mathbf P^{\mathrm{spa}}\) 负责同层邻店之间的客流分流与信号聚合。同时要注意,相同的行随机矩阵形式在本文中承担了两种不同业务含义:在仓库马尔可夫链 \(\mathbf P^{\mathrm{wh}}\) 中,索引是同一座仓库的运行状态,元素是“正常、拥堵、断货”随时间转换的概率;在门店空间图 \(\mathbf P^{\mathrm{spa}}\) 中,索引是不同门店,\(P_{ij}\) 是同一快照内的商圈分流权重。第四章的合成实验正是从正反两个方向使用同一个 \(\mathbf P^{\mathrm{spa}}\):计算邻店大促对本店 \(i\) 的客流虹吸时,以本店为中心按行读取 \(P_{ij}\);计算缺货邻店 \(j\) 向本店 \(i\) 的需求溢出时,则以邻店为中心读取 \(P_{ji}\)。
在更一般的时空图建模中,若把离散传播步数 \(K\) 推广为连续扩散时间 \(\tau\),并用 图拉普拉斯矩阵 \(\mathbf L = \mathbf D - \mathbf A\)(衡量每个节点与周围邻居特征差异程度的算子)替代转移矩阵,特征扩散还可写为 图热方程 \(\frac{\mathrm d\mathbf H(\tau)}{\mathrm d\tau}=-\mathbf L\mathbf H(\tau)\)(类比物理学中热量顺着管道从高温节点向周围低温节点自然扩散的过程)。经典时空图神经网络(Spatio-Temporal GNN, 简称 ST-GNN)文献正是沿着“图扩散 + 时序演化”的思路展开:Li 等人提出的 DCRNN(扩散卷积循环神经网络,Diffusion Convolutional Recurrent Neural Network) 利用有向图上的双向扩散捕捉空间依赖,并配合循环神经网络(RNN)处理时间动态[5];Yu 等人提出的 STGCN(时空图卷积网络,Spatio-Temporal Graph Convolutional Network) 则通过交替堆叠图卷积与时间卷积模块联合建模时空依赖[6]。
3.4 为什么从交通预测迁移到零售预测并不简单? #
交通网络中的道路连接具有相对明确的物理含义,车辆也确实沿道路移动。零售网络中的边更像建模假设:地理相邻不一定代表顾客会跨店购买,销量相关也不一定来自门店之间的直接影响,仓库供货关系更不能与门店竞争关系使用同一种传播规则。把交通模型迁移到零售问题以前,至少要回答四个问题:
关系是否可信:边来自明确的供货记录和距离规则,还是只来自销量相关性?
构图是否造成未来信息泄漏(Data Leakage,工程上俗称“数据穿越”,即在训练或预测今天时不小心偷看了明天之后才会发生的真实数据):如果根据完整时间段的销量相关性连边,测试期信息是否提前进入训练图?
图是否随时间变化:门店开关、仓库改配和运输时效变化以后,训练时的关系还能否用于推理?
复杂度是否产生增量价值:加入邻店销量、促销和仓库状态的传统模型,能否达到与 GNN 相当的效果?
这些问题无法仅靠介绍模型结构回答。下一章将构建一个完全可控、可复现的合成零售网络:我们明确规定仓库状态怎样变化、需求怎样沿关系传导,再通过预留未来时段的样本外测试,观察单店模型、特征工程模型与图模型在何种业务机制下拉开差距。
四、合成实验:图结构能否提供传统参照模型之外的预测信息? #
在前文《用 Prophet 做零售需求预测:从模型原理到工程实践》中,我们曾构造过一个单门店合成数据集(2023-01-01 至 2025-12-31 共 1,096 天,前 1,068 天训练,最后 28 天测试)。在单店满足“局部自足性”的假设下,Prophet 对剔除随机噪声后的营业日真实需求均值(记为 \(\mu_t^{\mathrm{open}}\)),加权绝对百分比误差(Weighted Absolute Percentage Error, 简称 WAPE,即总绝对误差除以总真实需求,数值越小越准) 仅为 0.46%。第 4.5 节将给出本文使用的完整指标公式。
现在,我们将这一单店基准扩展为由 2 座区域仓库 + 10 家零售门店(分布在 3 个商圈)+ 异步交错促销计划 构成的复杂零售网络。数据生成过程(Data Generating Process, 简称 DGP,即人为设定好底层的真实数学规则来生成仿真数据) 组合了 营业与库存硬约束、仓库马尔可夫链 以及 基于空间图的客流分配机制;预测模型再尝试从可观测特征中恢复这些信号。合成数据生成器、对比计算、绘图代码及后续更新见 GitHub:retail-gnn-demand-forecast。
实验设计说明:为“GNN 需求预测”这碟醋,包一顿合成数据的饺子。 需要先摊牌的是,本文的 2 仓 10 店数据并非对真实零售系统的随机抽样,而是专门为了验证和展示 GNN 的建模能力而设计的“主场实验”——借用一句俗话,就是为了“用 GNN 做需求预测”这一碟醋,特意包了“跨仓冲击与邻店客流转移”这顿饺子。既然数据生成机制里预先埋好了仓库断货、1 天物流延迟和商圈客流溢出等两跳耦合关系,后文的模型比拼中 GNN 自然会胜出。设计这组受控实验的真正目的,不是宣布“GNN 在所有零售场景都通杀”,而是打开黑盒,直观展示当业务中确实存在跨实体传导时,单店模型与人工特征工程会在哪里触及信息天花板,而图结构又是如何把这些信号捡回来的。至于真实零售系统是否具备这些条件、何时不该上 GNN,第五章将专门拆解。
4.1 异构零售网络拓扑 #
沿用第 2.2 节与第 3.3 节的符号,我们将零售网络定义为异构有向加权图 \(G = (\mathcal V^{\mathrm W} \cup \mathcal V^{\mathrm S},\; \mathcal E^{\mathrm{sup}} \cup \mathcal E^{\mathrm{spa}})\),其中仓库节点 \(\mathcal V^{\mathrm W} = \{W_1, W_2\}\),门店节点 \(\mathcal V^{\mathrm S} = \{S_1, \dots, S_{10}\}\)。图 1 展示了整个网络的平面布局与空间转移矩阵:

如图 1(a) 所示,10 家门店分布在三个商圈中。大多数门店由本区域的仓库就近供货,但在东区高密商圈(Cluster A)和中轴走廊商圈(Cluster B)内,我们特意设置了两家跨仓枢纽门店(\(S_4\) 与 \(S_6\)):它们在地理位置上紧贴由东区仓 \(W_1\) 供货的门店,货源却来自西区仓 \(W_2\)。
为什么这样设计?在真实的连锁商超、便利店或 生鲜前置仓(Front Warehouse / Dark Store,指开在社区内、不对顾客开放逛街、专供线上即时配送的小型仓库) 网络中,消费者的自然生活商圈与后台物流的分仓边界往往并不重合。隔街相望的两家门店,常常因为分属不同行政片区、开业或并购批次不同、干线配送路线切分,或是单一 区域配送中心(Distribution Center, 简称 DC,即负责向大片区域门店集中分拣配货的大型中心仓) 库容饱和,而被划归给两座不同的区域仓供货;不少零售企业也会在双仓交界地带主动配置由另一座仓库供货的“缓冲店”以分散断供风险。这就形成了“前台处于同一消费者步行圈、后台挂在不同仓库上”的真实业务结构。各商圈门店的坐标、主供仓库 \(w(i)\) 与空间邻域(\(d_{ij} \le 3.5\text{ km}\))汇总如下:
| 商圈与门店 | 主供仓 | 平面坐标 (km) | 空间邻域与拓扑角色 |
|---|---|---|---|
| A 东区高密:\(S_1, S_2, S_3\) | \(W_1\)(东区仓) | \((2.0, 8.0),\,(3.0, 8.4),\,(2.4, 7.0)\) | 商圈内两两相邻(含 \(S_4\));东区主力店,受 \(W_1\) 补货约束 |
| A 东区高密:\(S_4\) | \(W_2\)(西区仓) | \((3.5, 7.3)\) | 紧邻 \(S_1, S_2, S_3\);东区跨仓交界店,承接 \(W_1\) 断货时的溢出客流 |
| B 中轴走廊:\(S_5, S_7\) | \(W_1\)(东区仓) | \((7.5, 5.0),\,(8.8, 4.6)\) | 分居 \(S_6\) 两侧且紧邻 \(S_6\);受 \(W_1\) 补货约束 |
| B 中轴走廊:\(S_6\) | \(W_2\)(西区仓) | \((8.3, 5.5)\) | 连接 \(S_5, S_7\);中轴跨仓枢纽店,与两侧邻店双向虹吸与溢出 |
| C 西区中心:\(S_8, S_9, S_{10}\) | \(W_2\)(西区仓) | \((14.0, 2.5),\,(15.0, 2.9),\,(14.4, 1.7)\) | 商圈内两两相邻;西区区域门店,均由 \(W_2\) 独立供货 |
呼应第 3.3 节对两类边的分工,连接这些节点的异构边直接对应零售运营中的两条物理链条:
仓库—门店供应链边 \(\mathcal E^{\mathrm{sup}}\)(图 1a 虚线,对应 日配(Daily Replenishment,即零售业“每日订货、夜间拣配、次晨上架”的高频补货模式) 链条):将主供仓库 \(w(i)\) 在第 \(t-1\) 日的运行状态传导至门店 \(i\),决定该店在第 \(t\) 日的库存上限 \(V_{i,t}\)。这对应连锁零售“夜间拣配、次晨上架”的日配作业节奏——门店今日(\(t\) 日)货架上能卖多少货,取决于昨晚(\(t-1\) 日)上游仓库是否发生爆仓拥堵或断货停发。
门店—门店空间转移边 \(\mathcal E^{\mathrm{spa}}\)(图 1a 实线,对应商圈客流替代链条):高频日用品的消费者通常只会在生活半径(此处设为 \(d_{ij} \le 3.5\text{ km}\),约 10 分钟骑行或车程圈)内就近替代购买,不会为了买日常快消品跨城区跑去另一个商圈;而在同一商圈内,两家店相隔越近,促销吸客与断货替代就越强。因此,我们先用带 \(3.5\text{ km}\) 截断的 高斯核(Gaussian Kernel,一种平滑的钟形距离衰减函数,距离越近权重越接近 1,距离越远衰减越快,此处衰减尺度设为 \(\sigma_d = 2.0\text{ km}\)) 计算原始空间亲和度矩阵 \(\mathbf A^{\mathrm{spa}}\),再按第 3.3 节的公式左乘行度矩阵之逆 \((\mathbf D^{\mathrm{spa}})^{-1}\) 做行归一化,将距离远近转化为每家店周边加总为 \(100\%\) 的空间随机游走转移矩阵 \(\mathbf P^{\mathrm{spa}}\):
对照图 1(b) 的热力图可以看到,\(\mathbf P^{\mathrm{spa}}\) 呈现出清晰的 块对角结构(Block-Diagonal Structure,即非零权重全部集中在沿着主对角线排列的三个商圈方框内,方框之外的跨商圈区域全为 \(0\))——超过 \(3.5\text{ km}\) 的不同商圈之间互不串流,而在 Cluster A(左上蓝框)与 Cluster B(中部橙框)内部,分属东、西两座不同仓库的门店被高权重空间边紧密捆绑在一起。一旦东区仓 \(W_1\) 发生断货,受困门店的未满足客流就会顺着 \(\mathbf P^{\mathrm{spa}}\) 自然溢出到同商圈但由西区仓 \(W_2\) 供货的 \(S_4\) 与 \(S_6\)。
4.2 四层耦合数据生成过程(DGP) #
为了保证与单店文章严密对齐,时间跨度同样设为 2023-01-01 至 2025-12-31(共 \(T=1,096\) 天),前 1,068 天为训练集,最后 28 天(2025-12-04 至 2025-12-31)为测试集。每日数据按以下四层机制依次生成:
第一层:仓库状态的离散时间马尔可夫链(对应第一章)
每座仓库 \(w \in \{W_1, W_2\}\) 的日运行状态记为 \(Z_{w,t}^{\mathrm{wh}} \in \{0\text{ (Normal 正常)}, 1\text{ (Congested 拥堵)}, 2\text{ (Disrupted 断货)}\}\)。上标 \(\mathrm{wh}\) 表示仓库状态,避免与门店节点 \(S_i\) 混淆。它服从转移矩阵:
\[ \mathbf P^{\mathrm{wh}} = \begin{bmatrix} 0.78 & 0.15 & 0.07 \\ 0.42 & 0.40 & 0.18 \\ 0.38 & 0.32 & 0.30 \end{bmatrix} \]由于门店夜间补货存在 1 天物流提前期,门店 \(i\) 在第 \(t\) 天的有效库存供给上限 \(V_{i,t}\) 由其主供仓库在 \(t-1\) 日的滞后状态 \(Z_{w(i),t-1}^{\mathrm{wh}}\) 决定(因此在预测第 \(t\) 天需求时,该状态属于完全已知的历史观测,不存在任何未来信息泄漏):
\[ V_{i,t} = \begin{cases} 999\text{(供货充裕,无截断)}, & \text{若 } Z_{w(i),t-1}^{\mathrm{wh}} = 0 \\ 95\text{(轻度补货受限)}, & \text{若 } Z_{w(i),t-1}^{\mathrm{wh}} = 1 \\ 50\text{(严重断货受限)}, & \text{若 } Z_{w(i),t-1}^{\mathrm{wh}} = 2 \end{cases} \]第二层:异步交错促销计划与单店自身基准需求(对齐 Prophet 博客公式)
在真实连锁零售中,不同门店常因档期轮动、店型差异或区域闪促而采用错峰促销排期;正是这种排期错位,在同商圈相邻门店之间制造了日常价格差。因此,我们为各门店设置带有相位偏移 \(\phi_i \in \{0, 1, \dots, 12\}\) 的异步促销日历,包含 14 天周期的常规促销(八折,\(q_{i,t}=0.20\))与 21 天周期的深度闪促(六五折,\(q_{i,t}=0.35\))。在无跨店干扰时,门店 \(i\) 的自身基准需求 \(\mu_{i,t}^{\mathrm{self}}\) 完全沿用单店博客的加性模型结构:
\[ \mu_{i,t}^{\mathrm{self}} = \beta_{0,i} + \beta_{1,i} \cdot t + \beta_{W,i} \cdot W_t + A_i \cdot \sin\left(\frac{2\pi(t-15)}{365.25}\right) + \beta_{H,i} \cdot H_t + \gamma_i \cdot q_{i,t} \]式中六项依次对应零售门店的日常基础客流 \(\beta_{0,i}\)、长期增长趋势 \(\beta_{1,i}t\)、周末采购增量 \(\beta_{W,i}W_t\)、年度冬夏淡旺季波动 \(A_i\sin(\cdot)\)、法定节假日脉冲 \(\beta_{H,i}H_t\),以及本店折扣带来的直接销量拉升 \(\gamma_i q_{i,t}\)。
第三层:图上的「符号翻转」双向非线性耦合(对应第 3.2 与 3.3 节)
有了单店自身需求后,同一商圈内的门店开始通过第 4.1 节构造的空间转移矩阵 \(\mathbf P^{\mathrm{spa}}\) 发生正反双向客流交互——注意下方两个公式中矩阵下标 \(P_{ij}^{\mathrm{spa}}\) 与 \(P_{ji}^{\mathrm{spa}}\) 的方向差异:
- 一阶邻店促销虹吸效应 \(C_{i,t}\)(按行读取 \(P_{ij}^{\mathrm{spa}}\),本店顾客向外流失):当邻居门店 \(j \in \mathcal{N}(i)\) 的折扣比本店更狠(\(q_{j,t} > q_{i,t}\))且邻店当日货源充足(\(V_{j,t} > 120\),即其上游仓 \(Z_{w(j),t-1}^{\mathrm{wh}} = 0\))时,原本打算光顾本店 \(i\) 的比价顾客,会按本店指向邻店的空间权重 \(P_{ij}^{\mathrm{spa}}\) 被吸走:
- 两跳仓库状态传导与邻店溢出效应 \(L_{i,t}\)(按列读取 \(P_{ji}^{\mathrm{spa}}\),缺货邻店顾客向内涌入):反过来,当邻居门店 \(j\) 因两跳外的上游仓库拥堵或断货,导致扣除虹吸后的净需求超过库存上限 \(V_{j,t}\) 时,邻店货架被买空,产生未满足需求 \(U_{j,t}\)。假设跑空趟的顾客中有 \(15\%\) 直接放弃购买,其余 \(\eta = 85\%\) 的顾客选择在同商圈就近换一家店买,这部分需求就会从缺货店 \(j\) 出发,按 \(P_{ji}^{\mathrm{spa}}\) 的分流比例涌入本店 \(i\)(用 \(L\) 表示流入本店的溢出量,避免与门店编号 \(S_i\) 混淆):
第四层:营业与库存观测规则造成的销量截断
叠加上述三项得到营业条件均值 \(\mu_{i,t}^{\mathrm{open}}\) 与含噪潜在真实需求 \(D_{i,t}^{\mathrm{sim}}\)(其中 \(\epsilon_{i,t} \sim \mathcal{N}(0, 4.5^2)\))。最终,业务系统在门店营业状态开关变量 \(O_{i,t} \in \{0, 1\}\)(耶稣受难日与圣诞节全店闭店时 \(O_{i,t}=0\))和库存上限 \(V_{i,t}\) 的约束下,落库记录实际观测销量 \(y_{i,t}^{\mathrm{sales}}\) 与缺货标记 \(\operatorname{stockout}_{i,t}\):
\[ \begin{aligned} \mu_{i,t}^{\mathrm{open}} &= \max\!\big(10,\; \mu_{i,t}^{\mathrm{self}} - C_{i,t} + L_{i,t}\big), \\ D_{i,t}^{\mathrm{sim}} &= \max\!\big(0,\; \mu_{i,t}^{\mathrm{open}} + \epsilon_{i,t}\big), \\ y_{i,t}^{\mathrm{sales}} &= O_{i,t} \cdot \operatorname{round}\!\big(\min(D_{i,t}^{\mathrm{sim}}, V_{i,t})\big), \\ \operatorname{stockout}_{i,t} &= \mathbb{I}\!\big(D_{i,t}^{\mathrm{sim}} > V_{i,t} \land O_{i,t} = 1\big). \end{aligned} \]4.3 单店销售数据长什么样?全网趋势与单店结构 #
定义数据生成机制以后,我们再观察各家门店的销售数据形态。
与前作《用 Prophet 做零售需求预测:从模型原理到工程实践》中孤立平稳的单店时序不同,在一个包含多座仓库与相邻门店的网络中,单店日度销量同时呈现出规律性的自身周期与来自外部网络的结构性扰动。
图 2 展示了 2025 年下半年(7 月至 12 月,共 184 天)10 家门店的日度销量与潜在需求:

观察图 2 可以发现几个关键事实:
梯次规模与异步大促节奏:10 家门店日均销量在 85 到 103 件之间分布。各店通过浅黄色底色标注的 14 天/21 天促销窗口交错起伏,避免了全网在同一天集中大促的同质化。
缺货截断的广泛分布(红色三角):当上游仓库进入拥堵(补货限额 95 件)或断货(补货限额 50 件)状态时,受该仓库供货的门店即使处于促销高峰,其实际观测销量 \(y_{i,t}^{\mathrm{sales}}\) 也会被截断在水平线上,形成灰色虚线(潜在需求 \(D_{i,t}^{\mathrm{sim}}\))与实际销量之间的明显差距。
跨仓枢纽门店(S4、S6,橙色线)的差异:S4 和 S6 靠近由 \(W_1\) 供货的门店,但自身由西区枢纽仓 \(W_2\) 供货。当 \(W_1\) 发生断货时,它们不受同一库存上限约束,并在合成数据中承接邻店未满足需求,形成明显峰值。
为了进一步拆解单店数据的生成机制,图 3 对比了东区主力店 S1 与跨仓枢纽店 S6 的组成结构:

在图 3 中,可以分离单家门店面临的三类影响:
邻店大促虹吸凹陷(蓝色/黄色阴影):当自身未做促销,而相邻门店发起深度折扣且供货充足时,单店销量低于自身基准需求 \(\mu^{\mathrm{self}}\);
跨店断货溢出峰值(绿色阴影):当邻店大促遭遇上游仓库断货时,未满足需求沿空间转移边分配到库存充足的本店,推动本店条件均值 \(\mu^{\mathrm{open}}\) 高于自身基准需求;
库存上限截断损失(粉色阴影):当本店自身遭遇仓库断货时,潜在需求无法全部转化为实际销量,观测结果受库存上限截断。
单店序列同时包含自身周期、邻店促销造成的低谷和邻店断货造成的峰值。只观察本店历史时,模型很难区分这些来源;这正是后文比较邻域特征与图模型的原因。
4.4 无未来信息泄漏的对比实验:单店、邻域特征与两层解析图模型 #
遵循第 2.6 节与第 3.4 节的建模准则,所有参评模型在训练和推理时仅允许读取实际观测销量 \(y_{i,t}^{\mathrm{sales}}\)、缺货标记 \(\operatorname{stockout}_{i,t}\)、已知促销计划 \(q_{i,t}\) 与滞后一期的仓库状态 \(Z_{w,t-1}^{\mathrm{wh}}\),绝不接触未观测到的潜在需求真值 \(D_{i,t}^{\mathrm{sim}}\)。
为什么本节先用 NumPy 闭式最小二乘(Ordinary Least Squares, 简称 OLS,即普通最小二乘法,直接用线性代数公式一步算出使误差平方和最小的精确解析解,无需多轮迭代) 来对比四类模型,到第 4.6 节再引入 PyG 和 LibCity 等深度学习框架?因为在机器学习实验中,如果直接拿端到端训练的深度 GNN 与传统统计模型对比,读者很容易产生疑问:“误差下降究竟是因为图结构提供了额外信息,还是因为神经网络调参、非线性层或随机种子占了便宜?”为了做一场纯粹的 “信息边界消融实验”(Ablation Study,即像控制变量实验一样,固定底层求解器不变,每次只放开一层信息视野,从而精确检验每一层关系信息的真实贡献),我们在这里故意剥离所有梯度下降与超参数干扰,让四类模型全部采用 完全相同的最小二乘闭式求解器,唯一改变的只有模型的 信息视野(从单店自身 \(\to\) 一跳邻域宽表 \(\to\) 两跳异构图传播)。
其中,模型 1A、1B 与模型 2 按单家门店独立拟合参数:
\[ \hat y_{i,t}^{(m)}=\mathbf{x}_{i,t}^{(m)\mathsf T}\hat{\boldsymbol\beta}_{i}^{(m)},\qquad \hat{\boldsymbol\beta}_{i}^{(m)}=\arg\min_{\boldsymbol\beta} \sum_{t\in\mathcal T_i^{(m)}} \left(y_{i,t}^{\mathrm{sales}}-\mathbf{x}_{i,t}^{(m)\mathsf T}\boldsymbol\beta\right)^2, \]这里 \(m\in\{1A,1B,2\}\),\(\mathcal T_i^{(m)}\) 是门店 \(i\) 在模型 \(m\) 下使用的训练日期集合。模型 3 则像第 2.3 节的 GNN 一样,合并全网所有门店的未截断样本,跨门店共享同一套关系参数:
\[ \hat y_{i,t}^{(3)}=\mathbf{x}_{i,t}^{(3)\mathsf T}\hat{\boldsymbol\beta}^{(3)},\qquad \hat{\boldsymbol\beta}^{(3)}=\arg\min_{\boldsymbol\beta} \sum_{(i,t)\in\mathcal T^{(3)}} \left(y_{i,t}^{\mathrm{sales}}-\mathbf{x}_{i,t}^{(3)\mathsf T}\boldsymbol\beta\right)^2. \]四个模型依序放开信息视野,恰好构成检验图结构价值的四个台阶:
模型 1A:单店全量基础模型(Naive Local OLS,零跳 + 含截断脏数据):只看门店 \(i\) 自身的日历与促销特征,且不剔除历史缺货日,直接在全部营业日观测销量 \(y_{i,t}^{\mathrm{sales}}\) 上拟合。
模型 1B:单店去截断基础模型(Uncensored Local OLS,零跳 + 去截断):输入特征与 1A 完全相同,但利用历史缺货标记 \(\operatorname{stockout}_{i,t} = 0\) 过滤掉被库存上限截断的样本,代表“把单店自身历史信息用到极致”的干净参照基准。
模型 2:传统一阶邻域特征工程(1-Hop Flat Features,对应第 2.6 节宽表特征):在模型 1B 基础上,把一跳之内的邻居信息拼成宽表特征——包括一阶空间加权邻店促销 \(\sum_j P_{ij}^{\mathrm{spa}} q_{j,t}\)、邻店滞后销量 \(\sum_j P_{ij}^{\mathrm{spa}} y_{j,t-1}^{\mathrm{sales}}\) 以及本店上游仓库的滞后状态 \(Z_{w(i),t-1}^{\mathrm{wh}}\)。但它看不到两跳之外“邻店的上游仓库状态” \(Z_{w(j),t-1}^{\mathrm{wh}}\),也未对邻店供货状态与邻店促销做条件交互。
模型 3:两层解析图模型(Two-Layer Analytical Graph Model,对应第 2.4 与 3.2 节两跳路径):严格复现第 3.2 节阐述的“邻店上游仓库 \(\to\) 邻店 \(\to\) 本店”两跳消息传递路径(全程仅使用可观测特征,绝不触碰 \(D_{i,t}^{\mathrm{sim}}\) 或 \(\mu_{i,t}^{\mathrm{open}}\)):
第一跳(仓库 \(\to\) 门店,沿供应边 \(\mathcal E^{\mathrm{sup}}\)):先在每家邻居门店 \(j\) 内部,将其上游仓库滞后状态 \(Z_{w(j),t-1}^{\mathrm{wh}}\) 与当日促销 \(q_{j,t}\) 及日历特征做状态条件组合,生成区分“有货促销”与“断货受限”的一阶节点表示 \(\mathbf{h}_{j,t}^{(1)}\);
第二跳(门店 \(\to\) 门店,沿空间边 \(\mathcal E^{\mathrm{spa}}\)):再用空间转移矩阵 \(\mathbf P^{\mathrm{spa}}\) 将邻店的一阶表示 \(\mathbf{h}_{j,t}^{(1)}\) 聚合到目标门店 \(i\),使本店得以同时分辨一跳虹吸与两跳跨仓溢出。
图 4 展示了测试集 28 天内中轴走廊商圈两家相邻门店的动态耦合过程:

在图 4(a) 中,受东区中心仓 \(W_1\) 供货的门店 \(S_5\) 在
12-08至12-12遭遇上游断货传导,尽管其潜在需求高达 170–180 件,实际观测销量 \(y_{5,t}^{\mathrm{sales}}\)(灰色点线)却被截断在 \(V_{5,t}=50\) 件,产生粉色阴影所示的未满足需求 \(U_{5,t}\)。在图 4(b) 中,紧邻 \(S_5, S_7\) 但由健康仓库 \(W_2\) 供货的跨仓枢纽门店 \(S_6\) 承接了这股溢出客流,其潜在需求在
12-08升至 201 件。单店模型(橙色虚线)和传统一阶邻域特征模型(紫色点划线)漏报超过 70 件;解析图模型(绿色实线)则恢复了大部分需求峰值与后续低谷。这里的差异来自合成数据中明确设定的两跳跨仓传导机制。
4.5 定量评估与三大业务场景(Regimes)消融分析 #
在预留的最后 28 天测试集(270 个有效营业门店日)上,本文使用三个指标评价预测值 \(\hat y_n\) 与目标值 \(y_n\) 的差异:
\[ \operatorname{WAPE}=\frac{\sum_n|\hat y_n-y_n|}{\sum_n|y_n|},\qquad \operatorname{MAE}=\frac{1}{N}\sum_n|\hat y_n-y_n|,\qquad \operatorname{Bias}=\frac{\sum_n(\hat y_n-y_n)}{\sum_n y_n}. \]其中,\(N\) 是当前评估集合中的样本数;本文的需求与销量目标均非负,且各评估集合的目标总量大于零,因此 WAPE 与 Bias 的分母均有定义。WAPE(加权绝对百分比误差) 衡量相对于目标总量的绝对误差比例,MAE(Mean Absolute Error,平均绝对误差) 保留“平均每店每日差多少件”的直观单位;Bias(系统性相对偏差) 保留正负号,为正表示整体高估,为负表示整体低估。同一组预测分别与无噪声条件均值、含噪潜在需求和实际观测销量比较,得到下表(单元格格式为 WAPE / MAE / Bias):
| 参评模型 | 无噪声条件均值 \(\mu_{i,t}^{\mathrm{open}}\) | 潜在真实需求 \(D_{i,t}^{\mathrm{sim}}\) | 实际观测销量 \(y_{i,t}^{\mathrm{sales}}\) |
|---|---|---|---|
| 1A. 单店全量 (Naive OLS) | 17.62% / 19.97 / -13.97% | 18.14% / 20.54 / -13.87% | 21.14% / 20.45 / +0.78% |
| 1B. 单店去截断 (Uncensored OLS) | 12.99% / 14.72 / -5.74% | 13.78% / 15.59 / -5.63% | 20.02% / 19.37 / +10.43% |
| 2. 一阶邻域特征 (1-Hop Flat) | 11.79% / 13.36 / -5.17% | 12.75% / 14.43 / -5.06% | 14.40% / 13.93 / +0.17% |
| 3. 两层解析图模型 (Analytical Graph) | 1.92% / 2.17 / -1.01% | 3.79% / 4.29 / -0.90% | 3.18% / 3.08 / -0.21% |
从上表可以看到四类模型的机制差异:模型 1A 受历史断货截断影响,对潜在需求产生整体低估(Bias -13.87%);模型 1B 通过过滤缺货样本减少了自身截断偏误,但仍无法感知邻店虹吸与跨店溢出;模型 2 加入一阶线性邻域特征后,潜在需求 WAPE 仅比 1B 下降约 1 个百分点;而与当前两跳交互生成机制匹配的模型 3 将潜在需求 WAPE 降至 3.79%。
为了进一步回答第 2.6 节提出的问题——“什么时候单店模型已经足够,什么时候才值得评估图结构模型?”——我们按条件从测试集筛选出三个业务状态子场景进行对比(三个条件可能重叠,因此各行样本量之和不等于测试集总量,指标针对潜在真实需求 \(D_{i,t}^{\mathrm{sim}}\) 计算):
| 业务状态子场景 (Test Regimes) | 样本量 | 1A. 单店全量 WAPE (Bias) | 1B. 单店去截断 WAPE (Bias) | 2. 一阶邻域特征 WAPE (Bias) | 3. 两层解析图 WAPE (Bias) |
|---|---|---|---|---|---|
| 常规平稳期 (Normal) | 57 门店日 | 13.50% (-13.50%) | 3.73% (+0.05%) | 4.06% (+1.72%) | 2.74% (+0.11%) |
| 邻店促销虹吸期 (Cannibalization) | 142 门店日 | 10.13% (+0.35%) | 11.40% (+5.72%) | 10.15% (+4.79%) | 4.24% (+0.62%) |
| 跨店断货溢出期 (Spillover) | 88 门店日 | 29.23% (-29.23%) | 22.35% (-22.24%) | 21.11% (-20.65%) | 3.95% (-3.17%) |

把分场景表格与图 5(b) 的系统性偏差(Bias)结合起来,可以得到清晰的工程启示:在无跨店冲击的常规平稳期,去截断单店模型(1B)已经达到 3.73% WAPE,引入图模型的增量收益有限。但在冲击期内,邻店促销 \(q_{j,t}\) 的作用方向随上游供货状态翻转——仓库正常期对应负向虹吸,不建模时产生 +5.72% 的高估偏差;邻店仓库断货期又对应正向溢出,不建模时产生 -22.24% 的低估偏差。如果只把邻店促销作为一阶线性特征(模型 2),方向相反的效应会在训练样本中部分抵消。本文的两层构造先组合「仓库状态 \(\times\) 邻店促销」,再沿空间图聚合,因此能够区分这两种条件不同、方向相反的机制。当然,这一优势源于实验预设的两跳生成机制,并不意味着只有图模型才能捕捉此类交互;在实际项目中,显式构造交叉特征的梯度提升树模型(如 LightGBM、XGBoost)同样应当纳入基准对照比较。为保持实验聚焦于“从时序到图传播”的主线,本文不再重复展开树模型与单店统计时序的细节,感兴趣的读者可参阅前作《从一棵树到一片森林:树模型的数学原理、算法演进与工程实践》与《用 Prophet 做零售需求预测:从模型原理到工程实践》。
不过,至此的四组对比全部基于手写的 NumPy 闭式最小二乘(OLS)。在实际工程落地中,开发者很少直接手写解析解,而是调用成熟的开源或商用级工具包。这就引出了本实验的第二阶段追问:如果固定上述三个去截断信息层级(模型 1B 单店、模型 2 一阶邻域、模型 3 两跳时空图)不变,把底层求解器替换成工业界主流的时序与图学习工具包,单店和一阶模型能否靠工具包自身的拟合能力追平差距?而真正的图神经网络框架又能达到什么精度、需要付出多少算力耗时?
4.6 主流时序与图学习工具包全景横向评测 #
如果说第 4.4—4.5 节是 “固定求解器(闭式 OLS),改变信息视野”,那么本节就是 “固定信息视野,替换底层工具包与求解机制”。我们在配套仓库同一套无未来泄漏的数据切分下,把模型 1B、模型 2 和模型 3 对应的三类信息输入,分别交给四款主流开源工具包与闭式解析解进行横向评测。
在查看评分卡之前,先简要介绍本次参评的四款代表性工具包及其核心机制:
- Meta Prophet:Meta 开源的可分解统计时间序列框架,将序列拆解为趋势、多重傅里叶季节性(用正弦与余弦波叠加拟合周、年周期)、节假日与外生回归项(
add_regressor),底层通过概率建模语言 Stan 的拟牛顿优化算法(L-BFGS)快速求解最大后验概率(Maximum A Posteriori, MAP)参数估计,适合周期规律清晰、需要高度可解释性的业务序列。 - Darts:瑞士 Unit8 团队开源的统一时间序列预测库,用标准化的
TimeSeries对象封装了从经典统计模型、线性回归到深度时序网络的训练、回测与多序列管理流水线。 - PyTorch Geometric (PyG) / PyG Temporal:基于 PyTorch 的主流图神经网络框架,原生支持异构图结构(
HeteroData)与“生成消息—聚合—更新”算子(MessagePassing),便于按“仓库\(\to\)门店供应边 + 门店\(\leftrightarrow\)门店空间边”定制两跳时空图消息传递网络。 - LibCity:面向城市计算与时空数据挖掘(如交通流、网格客流与多门店零售需求预测)的开源统一基准库,将时空图数据切片、主流 ST-GNN 模型训练与标准化评估封装成端到端流水线。
全景工具评测评分卡 #
测试集同样为 2025-12-04 至 2025-12-31(共 270 个有效营业门店日)。上文已介绍各工具包的底层求解机制,下表按第 4.4 节的三个去截断信息层级(1B 单店、2 一阶、3 两跳图)汇总相同硬件下的单次耗时与各场景指标(针对潜在真实需求 \(D_{i,t}^{\mathrm{sim}}\) 计算):
| 层级与参评工具 | 耗时 | 全量 / 平稳 WAPE | 虹吸 WAPE (Bias) | 溢出 WAPE (Bias) |
|---|---|---|---|---|
| 1B 单店 · Formula OLS | 0.005s | 13.78% / 3.73% | 11.40% (+5.7%) | 22.35% (-22.2%) |
| 1B 单店 · Meta Prophet | 25.4s | 13.98% / 3.84% | 11.73% (+5.6%) | 22.43% (-22.4%) |
| 1B 单店 · Darts | 12.1s | 13.78% / 3.73% | 11.40% (+5.7%) | 22.35% (-22.2%) |
| 2 一阶 · 1-Hop Flat OLS | 0.010s | 12.75% / 4.06% | 10.15% (+4.8%) | 21.11% (-20.7%) |
2 一阶 · Prophet add_regressor | 38.6s | 12.89% / 4.09% | 10.48% (+4.9%) | 21.08% (-20.7%) |
| 3 两跳图 · ST-GNN 解析解 | 0.015s | 3.79% / 2.74% | 4.24% (+0.6%) | 3.95% (-3.2%) |
| 3 两跳图 · PyG Temporal | 4.8s | 3.82% / 2.74% | 4.24% (+0.6%) | 3.98% (-3.2%) |
| 3 两跳图 · LibCity | 18.5s | 3.80% / 2.74% | 4.24% (+0.6%) | 3.96% (-3.2%) |
精度台阶与算力耗时:为什么“调包救不了信息盲区”? #

对照评分卡与图 6,我们可以从“纵向精度台阶”和“横向算力开销”两个维度读出清晰的工程结论:
纵向看精度:决定上限的是信息边界,调包救不了信息盲区。 在同一个信息层级内部,无论使用
0.005s的 NumPy 闭式 OLS,还是换用25.4s的 Meta Prophet 或12.1s的 Darts,单店模型的全量 WAPE 统统卡在 13.78%—13.98%,在断货溢出期则一致停留在 22.35%—22.43%;一阶邻域层级下的 OLS 与 Prophetadd_regressor也几乎完全重合(全量约 12.8%,溢出期约 21.1%)。唯有进入打通“仓库\(\to\)邻店\(\to\)本店”的两跳异构图层级后,ST-GNN 解析解、PyG 消息传递与 LibCity 才同步将全量 WAPE 压低至 3.79%—3.82%(溢出期降至约 4.0%)。这有力印证了前文的核心论点:如果模型压根看不到两跳外的仓库状态与邻店促销交互,换再复杂的软件框架也无法凭空补回缺失的信号。横向看耗时与生态分层:算力开销必须与工程抽象需求匹配。 图 6(a) 显示,单次运行耗时从 Formula OLS 的 0.005 秒 到 Prophet 外生回归的 38.6 秒,跨度接近 7,000 倍;图 6(b) 则将工具生态划分为四层——NumPy / SciPy 解析解提供零开销的机制核对基准;Prophet / Darts 提供开箱即用的时序工程接口;PyG / DGL 提供可灵活定制的异构图消息传递算子;LibCity 提供标准化的时空模型评测流水线。它们解决的工程抽象层次不同,不能仅凭单次耗时或单一误差指标简单排序。而在真实生产环境中,引入图框架除了带来数秒至数十秒的训练开销外,还会涉及特征时效、故障传导与运维复杂度——这正是下一章要讨论的工程决策边界。
五、理论与实践的鸿沟:为什么纸面胜出的图算法,在生产中可能无法执行? #
看到第四章的实验结果——两跳异构图模型把断货溢出期的预测误差从 22.35% 一举压低到 3.95%——一个最自然的冲动就是立刻在生产系统里立项上马 GNN。然而,在工业界做算法落地,最危险的错觉莫过于把“受控实验里的纸面胜出”直接等同于“生产环境里的可执行方案”。
第四章之所以能够干净利落地展示图结构的威力,是因为我们站在“上帝视角”下,默认了四个在实验室里天经地义、但在真实零售系统中几乎从不成立的前提:数据完美准时、拓扑关系纯净、节点故障彼此独立、预测结果天然可用。一旦撤掉这四块实验室挡板,一个在数学推导和离线回测中无懈可击的图神经网络,在真实的生产流水线里甚至可能无法安全执行。要跨越从理论到实践的鸿沟,工程团队必须依次迈过下面三道现实关卡。
5.1 第一道坎:当理论上的“完美状态”撞上现实中的“数据失真与时效倒挂” #
在随机过程与图神经网络的公式里,无论是观测历史销量 \(y_{i,t}^{\mathrm{sales}}\)、上游仓库状态 \(Z_{w,t-1}^{\mathrm{wh}}\),还是空间转移矩阵 \(\mathbf P^{\mathrm{spa}}\),都是含义明确、准时到达的确定性输入。但在真实零售现场,算法工程师首先面对的却是错漏百出的底层数据:
整体指标的幻觉与缺货自锁(Censoring Feedback Loop) 在理论建模中,我们习惯用全量测试集上的平均 MAE 或 WAPE 给模型排座次;而在实践中,平均值恰恰是最容易掩盖重大生产事故的遮羞布。回顾第 4.5 节的模型 1A(直接在全量观测销量上拟合的单店基础模型):它在整个测试集上的销量 Bias 仅为 +0.78%,看起来几乎不偏不倚;但在最关键的跨店断货溢出期,它对潜在需求的真实 Bias 高达 -29.23%。更致命的是,真实系统里往往没有完美记录“哪一刻货架真正卖空”的传感器。如果模型直接拿被缺货截断的销量去训练,再把低估了近三成的预测值直接喂给补货系统,就会触发“断货 \(\to\) 销量被截断 \(\to\) 预测被压低 \(\to\) 下次少补货 \(\to\) 更快断货”的死锁循环。算法在报表上的平均误差越来越好看,门店现场的缺货却越来越严重。
幽灵库存与时效倒挂(Phantom Inventory & Data Latency) 在第四章的合成实验里,上游仓库的运行状态 \(Z_{w,t-1}^{\mathrm{wh}}\) 没有任何测量误差,并且假定在每天凌晨预测任务启动前准时就位。现实零售系统中的账面库存与货架可售库存却常年存在偏差:生鲜损耗、货损(Shrinkage)、盗损、收银漏扫、顾客拿走未结账以及店员理货错放,都会制造出账面上有货、货架上空空如也的幽灵库存(Phantom / Ghost Inventory)。与此同时,上游仓库的分拣拥堵、干线物流的车辆延误,往往要等到门店第二天收货清点完、甚至财务月底对账时才会迟迟录入系统。当模型依赖的关键跨节点信号在推理时刻根本还没到达(时效倒挂)时,纸面上再精妙的两跳图算子,在生产推理那一刻也只能面对一片空白或过期特征。
未观测混杂因素对空间边的稀释(Unobserved Confounders) 合成实验假定顾客在各店之间的流动严格遵循随地理距离衰减的高斯核。然而现实中的客流转移同时受到大量企业数据库根本没有记录的混杂因素支配:门店门口突然围挡修路、午后局部雷暴、街对面竞品便利店开业买一送一、周边写字楼退租或学校放假。这些未观测混杂因素会严重冲淡跨店信号的信噪比——训练式 GNN 既可能学到真正可复用的跨店替代规律,也完全可能把某两周修路造成的偶然销量同涨同跌,死记硬背成一条错误的图边权重。一旦施工结束、外部环境切换,这条学出来的“伪边”就会在生产中持续输出错误预测。
5.2 第二道坎:当图上的“消息传递”变成生产故障的“污染扩音器” #
在教科书和论文里,消息传递(Message Passing)被描绘成一种优雅的协同机制:每个节点从邻居那里汲取补充信息,共同提升表征能力。但在负责线上稳定性的工程团队眼中,每一次跨节点的信息聚合,本质上都是在打破系统的故障隔离边界(Fault Isolation)。
从“单点故障”到“全图污染”的级联放大 在传统的单店独立架构(例如按门店各自运行的 Prophet 或 LightGBM)中,每家门店的预测管道是一座天然的防火墙:即便 \(S_5\) 门店因为 收银机(Point of Sale, 简称 POS 机,即门店结账收银终端) 断网、盘点枪(店员手持的条码扫描盘点终端) 故障或 数据抽取—清洗—入库流水线(Extract-Transform-Load, 简称 ETL 管道) 卡死而上报了极其离谱的脏数据(比如库存瞬间变成负数或销量暴涨一百倍),这起事故的 爆炸半径(Blast Radius,借用自安全工程的术语,指单个故障最多能波及的系统范围) 也严格锁死在 \(S_5\) 一家店内部,其余 9 家门店的补货完全不受牵连。 一旦换用两跳图模型,情况就会发生质变:邻居的脏数据不再是它自己的家务事,而会顺着 \(\mathbf P^{\mathrm{spa}}\) 和 \(\mathbf P^{\mathrm{sup}}\) 的矩阵乘法,在几毫秒内传导并污染整个相连子图。如果那家上报错误数据的节点恰好是像 \(S_4\) 或 \(S_6\) 这样的跨仓枢纽节点,一处局部的传感器故障就会被图传播算子放大成整片商圈的预测雪崩。没有节点级 异常熔断(Circuit Breaker,像家庭电路里的保险丝一样,一旦检测到某节点输入异常就自动切断它向外传播的通道) 与 局部回退(Fallback,即当图信号不可靠时,自动降级切回安全的单店预测模型) 机制的图模型,在生产环境中就是一个高危的“故障扩音器”。
门店异质性、SKU 组合爆炸与过平滑(Over-smoothing) 合成实验里只有 10 家同质门店和 1 种抽象商品;而一家大型连锁商超往往同时管理数千家门店(从 50 平米的街角便利店、500 平米的社区生鲜店到 10,000 平米的仓储大卖场)和数万个活跃 最小存货单位(Stock Keeping Unit, 简称 SKU,俗称单品,即按规格、口味、包装区分到最细颗粒度的具体商品)。不同门店的客群画像、货架排面(Shelf Facing,指同一种商品在货架最外侧朝向顾客并排陈列的面数,排面越多曝光率越高) 和价格弹性天差地别。如果试图把“门店空间邻近 + 仓库供货 + 数万个 SKU 的品类替代”全部塞进同一张巨型异构图:
- 一方面,边数会呈组合级爆炸,图采样、显存占用与特征对齐流水线的运维成本会瞬间击穿工程团队的算力预算;
- 另一方面,盲目堆叠图卷积层数还会引发过平滑(Over-smoothing),把各具特色的社区小店与旗舰大卖场“平均化”成高度趋同的节点向量,反而抹杀了最宝贵的本地门店特征。
动态拓扑与冷启动的运维泥潭 零售网络从来不是静止的:新店开业、老店闭店装修、大促期间临时启用前置仓、干线物流路线按季调整,都会不断改写图的节点集合 \(\mathcal V_t\) 与边集合 \(\mathcal E_t\)。单店模型遇到新开店,只需走一套标准的单店 冷启动(Cold Start,即新门店或新商品刚上线、还没有积累足够历史数据时的初始预测) 模板;而图模型每遇到一次拓扑调整,不仅要重新校准邻域归一化权重(否则邻居数量变化会直接引起消息尺度漂移),还要验证训练期学到的聚合参数在新拓扑上是否依然平滑有效。
5.3 第三道坎:为什么“预测准确率高”依然不等于“算法指令可以直接执行”? #
退一步讲,假设我们的工程团队极其出色,通过去截断修正、实时数据校验和异常熔断,终于让图模型在生产环境中稳定跑出了比传统参照模型更低的预测误差——这时候,模型给出的预测或调拨建议,就可以直接下发给业务系统自动执行了吗?
依然不能。因为概率预测与确定性业务执行,根本处于两个不同的数学世界。
统计与机器学习模型遵循的是“软约束与期望最优”:哪怕一个图模型的 WAPE 低至 3.8%,它本质上仍然是在回答“在历史分布下,明天 \(S_6\) 门店的需求期望值大概是多少”,它允许在个别样本上犯错。而企业的核心交易与履约系统——包括 企业资源计划系统(ERP,负责采购与财务台账)、仓库管理系统(WMS,负责库内货位与拣货发运) 和 订单管理系统(OMS,负责订单路由与履约状态)——遵循的则是“硬约束与状态机不变量(Invariants,指系统在任何时刻、任何操作前后都绝不能违反的业务铁律,例如实物可售库存绝不能小于 0)”:哪怕在 99.9% 的情况下算法都算对了,只要有一次指令突破了物理或财务红线,就会引发严重的生产事故。
设想这样一个场景:图预测模块敏锐地捕捉到东区仓 \(W_1\) 出现拥堵、且明天 \(S_5\) 有大促,精准预测出跨仓邻店 \(S_6\) 明天将迎来 +45% 的溢出客流;下游的智能决策 Agent 随即生成了一条看似完美的指令——“今晚立即从同属西区仓供货的 \(S_4\) 门店抽调 80 箱商品,紧急调拨支援 \(S_6\)”。如果这套系统没有严格的执行边界,直接让算法调用接口改库,明天一早就会撞上一连串现实铁壁:
- 物理与履约硬约束:\(S_4\) 门店账面上的 100 箱库存里,其实有 35 箱已经被线上 O2O 订单(Online-to-Offline,即顾客在手机 App 下单、由附近门店直接拣货并即时配送到家的订单) 锁定预留(Reserved),另有 10 箱在冷库最里层尚未拆托盘,真正可调拨的现货根本不足 80 箱;强行扣减只会直接导致 \(S_4\) 门店自己的线上订单出现 库存打穿(仓储行话常称“穿仓”,即系统账面接了单、仓库实物却不够发,导致订单违约与客诉赔付)。
- 物流与作业边界:夜间跨店调拨的冷链货车最小起运量是 100 箱,或者 \(S_6\) 门店今晚的夜间收货窗口已经排满,调拨单下发下去根本无人接货。
- 系统安全与幂等性(Idempotency,分布式系统核心概念,指同一个操作指令因网络卡顿被重复发送多次时,系统能保证只执行一次真实的库存扣减和发车,绝不重复执行):如果仓库管理系统(WMS)因为网络抖动在 3 秒内没有返回确认,AI Agent 自动发起的第二次重试请求,是会被识别为同一次操作,还是会重复生成两张调拨单、把 160 箱货发上路?
这也是为什么在今天大语言模型(LLM)和 AI Agent 加速进入企业供应链工作流的背景下,我们比以往任何时候都更需要强调“预测归预测,执行归执行”。模型算得准,绝不能成为绕过库存锁定规则去超卖现货的理由;Agent 推理得再合乎逻辑,也不能跨过权限审批与幂等校验直接修改库存台账。一个真正能在生产环境存活的智能补货系统,必须把不确定的算法与确定的业务规则分层隔离:
| 系统层级 | 核心职责 | 遵循的数学/工程原则 | 典型技术与防护机制 |
|---|---|---|---|
| 1. 观测与清洗层 | 识别闭店、缺货截断与异常脏数据 | 还原真实观测边界,阻断脏数据入图 | 缺货标记(Censoring Flag)、盘点校准、节点异常熔断 |
| 2. 预测层(本文焦点) | 估计未来状态或未被截断的潜在需求分布 | 概率统计与条件期望(允许受控误差) | Prophet、树模型、异构 ST-GNN、局部回退(Fallback) |
| 3. 决策与规划层 | 在收益、成本与运力约束下求解补货/调拨方案 | 运筹优化与约束规划(平衡风险与收益) | 混合整数规划(MIP)、安全库存策略、跨店调拨启发式规则 |
| 4. 业务执行层 | 可靠完成跨服务状态变更,守住业务红线 | 确定性有限状态机(FSM)与硬不变量 | 现货预留校验、权限守卫(Guard)、事务与幂等键控制 |
| 5. 监控与审计层 | 追踪线上分场景误差与系统健康度 | 闭环反馈与分布漂移检测 | 分场景指标(Regime WAPE/Bias)、拓扑漂移告警、操作审计 |
只有当第 2 层的图模型被稳稳地安放在第 1 层的数据熔断保护之上、并严格受制于第 4 层的状态机硬约束之下时,我们在第四章看到的算法精度优势,才能真正安全地转化为真金白银的业务价值。
结语:从看清关系,到敬畏边界 #
回顾全文,我们从单店时序出发,一路走过了随机过程、图传播算子、异构时空图网络,最终回到了真实的零售工程现场。贯穿这趟旅程的,其实始终是同一个最本质的系统问题:为了面对充满不确定性的明天,一个系统究竟需要以什么形式,保留哪些关于“过去”和“周围环境”的信息?
- 当一家门店足以靠自身历史自洽演进时,单店时间序列就是最经济、最稳健的状态记录;
- 当上游履约状态随时间随机切换时,马尔可夫链为我们提供了刻画状态演化的概率语言;
- 当单个节点自身的历史已经不足以确定未来的条件分布——即邻店的促销与跨仓的断供正在通过网络重塑本店需求时,图神经网络通过消息传递,把相连仓库与邻店的状态编码进了当前节点的隐藏表示 \(\mathbf h_{i,t}^{(k)}\) 之中;
- 而当算法最终要走向物理世界下达指令时,确定性状态机与业务硬约束则为概率模型划定了不可逾越的安全护栏。
站在今天展望图网络模型在工业界与零售供应链中的未来,单纯靠“在固定静态图上堆叠更复杂的图卷积层”来刷榜的时代正在过去,三个更具生命力的演进方向正在浮现:
从“相关性图”走向“因果与动态稀疏图”(Causal & Dynamic Graph Learning) 未来的工业级图模型不再满足于在静态距离图上拟合表面相关性,而是越来越多地与 因果推断(Causal Inference) 和稀疏结构学习结合——在包含大量天气、施工、竞品等未观测混杂因素的现实环境中,自动甄别并剪枝掉短期巧合的“伪边”,只保留真正具备因果传导机制的跨实体连接,并随门店开关与供应链重组动态演进。
从“从头训练专用大图”走向“时序基础模型 + 轻量级图适配器” 随着时间序列基础模型(Time-Series Foundation Models)与通用表征模型的成熟,未来的需求预测架构未必需要为每个商品、每家门店从零训练庞大的端到端 GNN。更务实的范式是:用经过海量序列预训练的基础模型或强力树模型托底单店趋势与季节性,再在顶层挂载极轻量、低跳数、可解释的图传播适配器(Graph Adapter),专门负责修正促销虹吸、断供溢出与跨仓调拨带来的结构性残差。
从“孤立追求预测精度”走向“预测—优化—执行一体化”(Predict-Then-Optimize & Agentic Workflows) 图模型的价值将不再仅仅体现为一个更低的离线 WAPE 数字,而是作为整个供应链 数字孪生(Digital Twin,即在计算机中构建的、与物理世界真实仓库—门店网络同步演化的虚拟镜像系统) 的感知层:它输出的图结构化不确定性估计,将直接喂给下游的运筹优化求解器(End-to-End Predict-Then-Optimize),并在确定性状态机与业务守卫规则(Guardrails)的监管下,驱动智能体(AI Agents)安全、幂等地完成从感知异常到跨店协同履约的闭环。
看清实体之间的真实关系,让我们知道何时该引入图模型去突破单店的信息天花板;而敬畏现实世界的数据噪声与执行边界,则让我们知道何时该停下堆砌复杂度的脚步,用最朴素的工程架构守住系统的底线。 唯有同时握住这两把标尺,算法工程师才能既不被传统单店视角困住想象力,也不在追逐前沿模型的浪潮中迷失工程判断。
复现说明:配套 GitHub 仓库包含第四章的合成数据生成器、多工具包对比、运行耗时记录和图表绘制代码,后续实验与复核结果也将在该仓库持续更新。
参考文献 #
[1] Gilmer, J., et al. Neural Message Passing for Quantum Chemistry. Proceedings of the 34th International Conference on Machine Learning, 2017. https://proceedings.mlr.press/v70/gilmer17a.html
[2] Kipf, T. N., & Welling, M. Semi-Supervised Classification with Graph Convolutional Networks. International Conference on Learning Representations, 2017. https://arxiv.org/abs/1609.02907
[3] Hamilton, W. L., Ying, R., & Leskovec, J. Inductive Representation Learning on Large Graphs. Advances in Neural Information Processing Systems, 2017. https://proceedings.neurips.cc/paper/2017/hash/5dd9db5e033da9c6fb5ba83c7a7ebea9-Abstract.html
[4] Veličković, P., et al. Graph Attention Networks. International Conference on Learning Representations, 2018. https://arxiv.org/abs/1710.10903
[5] Li, Y., Yu, R., Shahabi, C., & Liu, Y. Diffusion Convolutional Recurrent Neural Network: Data-Driven Traffic Forecasting. International Conference on Learning Representations, 2018. https://arxiv.org/abs/1707.01926
[6] Yu, B., Yin, H., & Zhu, Z. Spatio-Temporal Graph Convolutional Networks: A Deep Learning Framework for Traffic Forecasting. Proceedings of the Twenty-Seventh International Joint Conference on Artificial Intelligence, 2018. https://doi.org/10.24963/ijcai.2018/505