Applied & Industry · 2025

Optimizing Supply Chain Networks with the Power of Graph Neural Networks

Applied & Industry Optimizing Supply Chain Networks with the Power of Graph Neural Networks 2025 · arXiv 2501.06221
Topic
Applied & Industry
Venue
arXiv preprint 2025
Read
14 min
Source
arXiv:2501.06221

In one line

This paper takes a real-world supply-chain dataset (41 FMCG products, their dependencies, and 221 days of demand), defines "predict next demand for one product" as a benchmark task, and shows that graph-aware neural networks can forecast it far more accurately than a plain neural net — establishing baselines others can build on.

The breakdown

TL;DR

Supply chains are naturally graphs: products, plants, and warehouses are nodes, and “shares a factory / shares a raw material / shares storage” are edges. Demand forecasting on these networks usually ignores that structure and treats each product as an isolated time series. This paper uses the SupplyGraph dataset to define a clean single-node demand forecasting benchmark and compares three models — a plain MLP, a self-loop-only “GNN,” and a real Graph Convolutional Network (GCN). The headline result: the GCN crushes the others, driving Mean Squared Error to near-zero (often 0.00–0.10) on most products while the MLP and GNN sit in the tens-to-hundreds. The contribution is less a new algorithm and more a usable task definition + open-source baselines for a domain (supply-chain ML) that has lacked public benchmarks. It is an honest, modest, foundational paper — useful as a starting template, not a breakthrough method.

Problem & Motivation

The concrete pain: demand forecasting drives every expensive decision in a supply chain — how much to produce, how much inventory to hold, how to route logistics. Get it wrong and you either stock out (lost sales) or overstock (tied-up cash and spoilage). For an FMCG company (think shampoo, snacks, detergent — high volume, thin margins), even a few percent of forecast error compounds across hundreds of SKUs into real money.

The standard tools — MLPs, LSTMs, classic statistical models — treat each product’s demand as a standalone time series. But products are not independent. Two products made on the same line compete for capacity; products sharing a raw material rise and fall together when that material is constrained; substitute products cannibalize each other. All of that relational signal is thrown away when you forecast each SKU in isolation.

Graph Neural Networks are the obvious fix — they’re built to exploit exactly this kind of “who connects to whom” structure, and they’ve worked in social networks, traffic, and molecules. So why hadn’t supply chain caught up? No public dataset. Real supply-chain data is proprietary and messy, so there was nothing to benchmark on, and therefore no agreed-upon tasks. The 2024 release of the SupplyGraph dataset (real data from a Bangladeshi FMCG firm) removed that blocker. This paper’s job is to turn that raw dataset into actual, evaluable ML tasks and report the first baseline numbers.

What’s New (Core Contribution)

Be precise: this is a benchmarking / task-definition paper, not a new-architecture paper. The genuine contributions:

  • A concrete downstream task definition. Before: SupplyGraph was a raw dataset with no standardized task — you couldn’t compare methods. Now: a clearly specified single-node demand forecasting task (given a product’s recent history via a sliding window, predict its future demand), with a fixed train/val/test protocol (7:2:1), normalization recipe, and metrics (MAE, MSE).
  • Open-source baseline implementations. Before: no reference code. Now: a public repo with MLP, GNN, and GCN baselines anyone can fork to beat.
  • An empirical finding worth knowing: when you actually let the model use the graph structure (the GCN), forecasting error collapses versus a structure-blind MLP — strong evidence that the relational signal is real and exploitable. The intermediate “GNN” (which uses an identity adjacency matrix, i.e. no real edges) performs no better than — sometimes worse than — the MLP, which is itself an instructive negative result.

What is not new: the models (MLP, GCN, GAT, GIN are all off-the-shelf), the dataset (released by others), and the math. The value is packaging and baselining.

How It Works (Technically)

The pipeline is: take the supply-chain graph → attach each product’s demand time-series as node features → slide a window over time → feed to a model → predict the next demand value → score with MAE/MSE. The interesting part is how much of the graph each model is allowed to use. Let’s demystify the three models.

The data. 41 products are nodes. 684 edges encode relationships (shared production plant, shared storage, shared raw material). Each node carries temporal features over 221 time points: production volume, sales orders, deliveries, factory issues. Preprocessing: drop duplicates and sparse rows, z-score normalize each feature (subtract mean, divide by std — so all features live on a comparable scale and gradients behave), and discard “low-quality” nodes that are mostly zeros. A sliding window of size window_size turns the series into supervised pairs: the last k days predict day k+1.

Model 1 — MLP (the structure-blind baseline). A plain feedforward net. Each layer does:

h⁽ˡ⁾ = σ(W⁽ˡ⁾ h⁽ˡ⁻¹⁾ + b⁽ˡ⁾)

In plain English: multiply the previous layer’s vector by a learned weight matrix W, add a bias b, then squash through a nonlinearity σ (ReLU here — “keep positives, zero out negatives”). Stacking these lets the net learn arbitrary nonlinear functions of the input. The MLP sees each product’s windowed history but knows nothing about which products are connected. It’s the “every SKU is an island” baseline.

Model 3 — GCN (the structure-aware model). A Graph Convolutional Network. One layer:

H⁽ˡ⁾ = σ(Â H⁽ˡ⁻¹⁾ W⁽ˡ⁾)

This single equation is the whole idea, so unpack it:

  • H⁽ˡ⁻¹⁾ is a matrix — one row per node, holding that node’s current feature vector.
  • W⁽ˡ⁾ is a learned weight matrix (same role as in the MLP: transform features).
  •  is the normalized adjacency matrix — it encodes the edges.  = D̃^(−½) à D̃^(−½) where à = A + I (the original edges plus self-loops so a node keeps its own info) and is the degree matrix used to normalize. The D̃^(−½) ... D̃^(−½) sandwich just prevents high-degree nodes from dominating — without it, a product connected to 40 others would have a hugely inflated signal.

Operationally, Â H means: each node’s new feature = a normalized average of its neighbors’ features (including itself). Multiplying by W then transforms that mixed signal, and σ adds nonlinearity. So one GCN layer = “look one hop out, blend in your neighbors, transform.” Stack L layers and information flows L hops across the graph. This is message passing, and it’s why the GCN can exploit “products that share a plant move together” — the MLP literally cannot represent that.

The general GNN message-passing recipe the paper states is just the abstract version of the above:

  1. Message: mᵥ = AGGREGATE({hᵤ : u ∈ N(v)}) — gather neighbors’ embeddings.
  2. Update: hᵥ = UPDATE(hᵥ, mᵥ) — fold the aggregated message into the node’s own state.
  3. Readout (optional): pool all node embeddings into one vector for whole-graph tasks (not needed here, since we predict per-node).

Model 2 — the “GNN” (the accidental control group). Here’s the subtle, important detail buried in the experiments: the paper’s middle model is described as a GNN but uses the identity matrix as its adjacency matrix — i.e.  = I, only self-loops, no edges at all. Plug  = I into the GCN equation and you get H = σ(H W) — which is… just an MLP applied per node. So Model 2 isn’t really graph-aware; it’s an MLP with extra reshaping. This is why its scores look like the MLP’s (and sometimes worse, due to the extra batched matmul overhead and no benefit). The real lesson of the three-way comparison: the gains come from the edges, not from calling something a “GNN.”

Architecture & data flow

flowchart LR
  RAW[SupplyGraph: 41 products, 684 edges, 221 days] --> PRE[Preprocess: dedup, z-score, drop sparse nodes]
  PRE --> WIN[Sliding window over time series]
  WIN --> NF[Node features H: one row per product]
  ADJ[Adjacency A: shared plant / material / storage] --> NORM[Normalize: A-hat = D^-1/2 (A+I) D^-1/2]
  NF --> MODEL{Which model?}
  NORM --> MODEL
  MODEL -->|MLP: ignores A| OUT[Predicted demand]
  MODEL -->|GNN: A = Identity, no edges| OUT
  MODEL -->|GCN: uses real A-hat| OUT
  OUT --> EVAL[Score: MAE and MSE vs actual]

Schematic of one GCN layer's message passing on a small product graph. Click a node to inject a "demand spike"; watch it diffuse to neighbors over successive hops. This illustrates *why* the structure-aware GCN can forecast a product using signals from products it shares a plant or raw material with — something the MLP (and the identity-matrix "GNN") cannot do.

The algorithm, simplified

# Single-node demand forecasting, GCN baseline (the core idea, stubs for I/O)
import torch, torch.nn.functional as F

def normalize_adj(A):                      # A: [N, N] binary adjacency
    A_hat = A + torch.eye(A.size(0))       # add self-loops so a node keeps its own signal
    deg = A_hat.sum(1)                      # degree of each node
    d_inv_sqrt = deg.pow(-0.5)             # D^(-1/2)
    return d_inv_sqrt[:, None] * A_hat * d_inv_sqrt[None, :]   # symmetric normalization

class GCN(torch.nn.Module):
    def __init__(self, in_dim, hidden, out_dim):
        super().__init__()
        self.W1 = torch.nn.Linear(in_dim, hidden)
        self.W2 = torch.nn.Linear(hidden, out_dim)
    def forward(self, H, A_hat):           # H: [N, in_dim] windowed features per product
        H = F.relu(A_hat @ self.W1(H))     # hop 1: blend neighbors, transform, nonlinearity
        return A_hat @ self.W2(H)          # hop 2: predict next-step demand per node

A_hat = normalize_adj(load_adjacency())    # real edges -> GCN wins; torch.eye(N) -> degrades to MLP
H, y  = load_windowed_features()           # sliding window over 221 days, 7:2:1 split
model = GCN(in_dim=H.size(1), hidden=64, out_dim=1)
opt   = torch.optim.Adam(model.parameters(), lr=1e-3)   # Adam, lr 0.001, 50 epochs (paper's setup)

for epoch in range(50):
    pred = model(H, A_hat)
    loss = F.mse_loss(pred, y)             # MSE; MAE also reported for interpretability
    opt.zero_grad(); loss.backward(); opt.step()

The single load-bearing line is A_hat @ self.W1(H): swap A_hat for the identity and you have the paper’s “GNN” / MLP; keep the real edges and you have the GCN that wins.

Built on Prior Work

Prior ideaWhat it gaveWhat this paper changes
GCN — Kipf & Welling 2017 [2]The H = σ(ÂHW) message-passing layer used as the winning modelApplies it, unchanged, to supply-chain demand forecasting
MLP / backprop — Rumelhart [24], universal approximation [25]The structure-blind baselineUses it as the “ignore the graph” control
GAT [22], GIN [23], GraphSAGE [29], FastGCN [28]More expressive / scalable GNN variantsSurveyed as future options; not yet benchmarked here
SupplyGraph dataset — Wasi et al. 2024 [11]Raw real-world FMCG supply graph, no tasksDefines evaluable tasks + protocol + open-source baselines on top of it
Hidden-link prediction in supply chains [12, 21]Evidence GNNs find latent supply dependenciesExtends the GNN-for-supply-chain case to forecasting

Results & Evidence

What was tested. Single-node demand forecasting across ~36 products, three models each (MLP, GNN, GCN), 50 epochs, Adam (lr 0.001), 7:2:1 split, reported as Test MSE and MAE.

Headline numbers. The GCN dominates by a wide margin. Representative rows from the paper’s tables:

ProductMLP MSE“GNN” MSEGCN MSE
POP015K60.3472.270.0000
MAR02K12P59.98208.200.0286
SOS002L09P98.82339.870.0192
MAP1K25P111.09276.811.0749
EEA200G24P66.71181.480.0000

Across essentially every product, GCN MSE is in the 0.00–1.10 range while MLP and GNN are in the tens-to-hundreds. The “GNN” (identity adjacency) is frequently the worst model — concrete evidence that the win comes from edges, not branding.

What the evidence establishes: relational structure carries real, exploitable forecasting signal for FMCG demand, and a vanilla GCN captures it.

Caveats — read these before believing the hype:

  • GCN MSE of exactly 0.0000 is a red flag, not a triumph. Several products report perfect-zero error. That strongly suggests leakage or a trivialized target — e.g. the GCN’s input window includes the target value, or the normalization let the model copy an input straight to the output. A genuine forecaster on noisy demand does not hit machine-precision zero. The paper does not investigate this, which is the single biggest weakness.
  • The “GNN” baseline is mislabeled. Calling an identity-adjacency MLP a “GNN” muddies the comparison; the real contrast is MLP-with-edges vs MLP-without.
  • No strong baselines. No LSTM, no ARIMA, no GAT/GIN/GraphSAGE, no naive “predict last value” baseline — which on smooth demand series is often very hard to beat and would contextualize the GCN’s numbers.
  • Single dataset, single company, single task. Generalization is unproven.
  • No variance / seeds. Single runs; no error bars.

Net read: the direction (structure helps) is credible and matches theory; the magnitude (near-zero error) is almost certainly too good to be true and needs a leakage audit before anyone trusts it in production.

How You’d Use It

For an AI services company, the value here is a template and a sales narrative, not a model to drop into a client.

  • Client-facing capability: “demand forecasting that uses your relationships, not just your history.” Most clients’ forecasting (if they have any) is per-SKU. The pitch is concrete and demoable: “Your products share plants and raw materials; we model those links so a constraint on one product informs the forecast for the others.” This paper is your proof-of-concept reference.
  • Diagnostic / discovery offering. Even before forecasting, building the supply graph (who shares what) surfaces hidden dependencies and single points of failure — a deliverable in itself for risk/resilience reviews. The dependency-link-prediction lineage [12, 21] supports this.
  • Where it slots in an agentic system. A GCN forecaster becomes a tool an inventory/planning agent calls: forecast_demand(product_id, horizon) -> distribution. The agent then reasons over forecasts to propose POs, reorder points, or production schedules — and a multi-agent setup (a forecasting agent, a procurement agent, a logistics agent) maps cleanly onto the node/edge structure you already built.
  • Effort/payoff read: the modeling is a few days for someone who knows PyTorch Geometric. The 80% of the work — and the 80% of the client value — is building a clean, correct graph from messy ERP data (which products share which plants/materials, kept current). That data-engineering moat is the real offering.

Build Your Own (Minimal Recipe)

Smallest version that captures most of the value:

  1. Build the graph. Nodes = SKUs. Edges = “shares production line / raw material / storage.” Pull from the client’s ERP/BOM data. This is the hard, high-value part.
  2. Assemble node features. For each SKU, a time series (daily/weekly demand, plus production, deliveries). Z-score normalize per feature.
  3. Window it. Sliding window of k periods → predict period k+1. Crucially, exclude the target from the input window (this is where the paper likely slipped — guard against leakage explicitly).
  4. Model. Use PyTorch Geometric; a 2-layer GCNConv net is plenty to start. Adam, lr 1e-3, MSE loss. (torch_geometric.nn.GCNConv does the  H W for you.)
  5. Baseline honestly. Always include (a) “predict last value,” (b) a per-SKU MLP, and (c) a classic like ARIMA/Prophet. If the GCN can’t beat naive-last-value, the graph isn’t earning its keep.
  6. Validate for leakage. If you see near-zero error, assume a bug until proven otherwise.

The two genuinely hard parts: (1) constructing and maintaining a correct graph from operational data, and (2) honest evaluation (leakage guards, real baselines, temporal splits that respect time ordering). The neural net itself is the easy 20%.

How to Improve It

Limitations as leverage — concrete, testable next steps:

  1. Audit and fix the zero-error leakage. Re-run with a strict temporal split where inputs strictly precede the target window. If error stays near-zero with the leak closed, that’s a genuine (and publishable) finding; if it jumps, the original numbers are artifacts. This is the first thing to do.
  2. Use real graph variants and learned adjacency. The paper’s GNN used identity (no edges). Benchmark GAT (attention weights which neighbors matter — e.g. a shared-raw-material link may matter more than shared storage) and GIN. Better: learn the adjacency rather than hand-specifying it, since the true demand-coupling graph may differ from the physical one.
  3. Add the temporal model GNNs lack. Stack a sequence model on top — a Temporal GNN (e.g. T-GCN, or GCN features → LSTM/Transformer) — so the model captures seasonality and trend, not just one window. This is the obvious 2x.
  4. Add real baselines and uncertainty. ARIMA/Prophet/LSTM baselines, multiple seeds with error bars, and probabilistic forecasts (predict a distribution, not a point) — inventory decisions need the uncertainty, not just the mean.
  5. Multi-horizon and multi-task. Forecast the next week and quarter jointly, and share the graph backbone across related tasks (demand + factory-issue/anomaly detection) for transfer — the dataset already has the features for it.

Glossary

  • GNN (Graph Neural Network) — a neural net that updates each node’s representation by mixing in its neighbors’ representations along the graph’s edges.
  • Message passing — the core GNN step: gather neighbor features (message), fold them into the node’s state (update); repeat per layer to reach farther neighbors.
  • GCN (Graph Convolutional Network) — a GNN whose layer is H = σ(ÂHW): normalized-neighbor-average, then a learned linear transform, then a nonlinearity.
  • Adjacency matrix (A) — an N×N matrix where entry (i,j)=1 if nodes i and j are connected; encodes the graph’s edges.
  • Normalized adjacency (Â)D̃^(−½)(A+I)D̃^(−½); adds self-loops and scales by node degree so high-degree nodes don’t dominate.
  • Self-loop — an edge from a node to itself (the +I), so a node retains its own features during aggregation.
  • Identity matrix (I) — 1s on the diagonal, 0s elsewhere; used as “adjacency,” it means no edges — every node sees only itself.
  • MLP (Multilayer Perceptron) — a plain fully-connected feedforward neural net; the structure-blind baseline here.
  • ReLU / σ (activation) — a nonlinearity (ReLU = max(0, x)) that lets stacked layers represent nonlinear functions.
  • Sliding window — turning a time series into supervised examples: the last k observations predict the next one.
  • Z-score normalization — rescale a feature to mean 0, std 1 so features are comparable and training is stable.
  • MAE / MSE — Mean Absolute Error (average |error|, robust, same units as target) / Mean Squared Error (average error², punishes big misses, outlier-sensitive).
  • Over-smoothing — a deep-GNN failure where, after too many layers, all nodes’ representations blur together and become indistinguishable.
  • FMCG — Fast-Moving Consumer Goods (high-volume, low-margin everyday products); the dataset’s domain.
  • SupplyGraph — the 2024 benchmark dataset (real Bangladeshi FMCG firm) this paper builds tasks on.
  • Data leakage — when information from the prediction target sneaks into the inputs, producing unrealistically perfect scores.