wikillm

Paper · 2021-05-10 · cs.LG, stat.ML

Z-GCNETs: Time Zigzags at Graph Convolutional Networks for Time Series Forecasting

Z-GCNETs turns a sliding window of time-varying graphs into a zigzag persistence image, passes it through a small CNN, and uses the result to scale graph-convolution features inside a GRU forecaster. Author-reported: lowest MAE, RMSE and MAPE against 13 baselines on PeMSD4 and PeMSD8 at horizon 12.

Time series forecastingGraph neural networkPersistent homologyRecurrent neural network (RNN)time-series-forecastingzigzag-persistencetopological-data-analysisgraph-convolutiontraffic-forecastingethereum

arXivPDFCodeMarkdown18 min read

AI-compiled by claude-haiku-5-5 on 2026-11-06, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.

In plain words

Traffic sensors on a road network and user addresses on a blockchain both form networks whose links change over time. A forecaster that only looks at the current links, or at one fixed map of links, misses how the structure has been changing. This paper looks at a short run of these network snapshots and asks which groups of connections keep appearing across the run, and for how long. It summarises that answer as a small fixed-size picture. A small convolutional network reads the picture, and its output decides how strongly each graph feature is passed on in a recurrent forecaster.

The authors test this on freeway traffic data from California and on two Ethereum token networks, built from user addresses and their transactions. Their reported numbers are better than those of 13 other forecasting methods on the traffic data. Those numbers come from the authors' own experiments, and the repository they released differs from the paper in several places, listed below.

TL;DR

Z-GCNETs adds a zigzag persistence image (ZPI) of a sliding window of graphs to a graph-convolutional GRU forecaster §4. A small CNN turns the ZPI into per-channel values that scale the spatial and temporal graph features §4 eq. 4. On PeMSD4 and PeMSD8, at horizon 12, the authors report the best MAE, RMSE and MAPE against 13 baselines §5.3 tab. 2. The released code implements the architecture, but several choices differ from the paper, and the token-network pipeline is absent from the repository.

Problem

Multi-step forecasting on spatio-temporal graphs, where the graph itself can change over time §1. The authors argue that most spatio-temporal graph convolution networks rely on feature-level correlation and a pre-defined graph structure §2. Persistent-homology features are known to add robust multi-scale information to deep networks §1, but earlier uses are static. Zigzag persistence, which tracks topological features across a sequence of graphs linked in both directions, is proposed as a time-aware alternative §1, §3. Inputs are a window of τ past graph signals and a horizon h §4. Traffic experiments use τ = h = 12; token experiments use τ = h = 7 §5.2.

Method

The model has two parts. First, each window of graphs is turned into a zigzag persistence image (ZPI) §3. Second, a graph-convolutional GRU consumes the window and uses the ZPI to scale its features §4. The paper's ZPI construction has three steps: map the zigzag persistence diagram to a surface made of weighted Gaussians, discretise that surface on a grid, and integrate over each grid box §3. The forecaster combines a learned adaptive adjacency with its matrix powers, a spatial graph convolution, a temporal graph convolution, and a ZPI gate, and then runs them through a GRU §4.

Graphs and features. A window of graph snapshots G_t = {V, E_t, W_t} shares a node set of size N. Each node has an F-dimensional feature vector per step §3.

Zigzag persistence. The snapshots are linked by inclusions that alternate direction: each snapshot is included in the union of itself and its neighbour in the sequence §3. A zigzag diagram of Vietoris–Rips complexes at a fixed scale ν* is built over these inclusions §3. Each feature gets a birth time and a death time. A union of two consecutive snapshots counts as a half-step, so times are k or k + 1/2 §3 [Definition 3.1].

Edge weights (traffic). For PeMS, edge weight is w = exp(−‖x_u − x_v‖² / γ) with γ = 1, and the edge is kept when its weight is at most ν* §5.1 eq. 6. The thresholds are ν* = 0.5 for PeMSD4 and 0.3 for PeMSD8 §5.2. For tokens, edge weight is the normalised number of transactions between two addresses, and the graph has N = 100 nodes after a maximum-weight subgraph reduction §5.1.

Adaptive adjacency and Laplacianlink. Node embeddings φ ∈ R^{N×c} define L = softmax(ReLU(φφᵀ)). The model stacks identity and powers of L §4 eq. 1:

\tilde{\boldsymbol{L}} = \left[\boldsymbol{I}, \boldsymbol{L}, \boldsymbol{L}^2, \dots, \boldsymbol{L}^{K}\right] eq. 1

Spatial graph convolution. Each layer multiplies the input by the stacked supports and applies a projection Θ = φV, where V is trainable and c is the embedding size §4 eq. 2:

\boldsymbol{H}^{(\ell)}_{i,\mathcal{S}} = \left(\tilde{\boldsymbol{L}}\,\boldsymbol{H}^{(\ell-1)}_{i,\mathcal{S}}\right)^{\top}\boldsymbol{\phi}\,\boldsymbol{V} eq. 2

Temporal graph convolution. The same supports are applied to the whole window, then a per-node projection U and a learned projection Q over the τ steps §4 eq. 3:

\boldsymbol{H}^{(\ell)}_{i,\mathcal{T}} = \left(\left(\tilde{\boldsymbol{L}}\,\boldsymbol{H}^{(\ell-1)}_{i,\mathcal{T}}\right)^{\top}\boldsymbol{\phi}\,\boldsymbol{U}^{(\ell-1)}\right)\boldsymbol{Q} eq. 3

Zigzag layer. A CNN reads the ZPI and produces a vector Z with C_out/2 entries, using global max-pooling §4 eq. 4. The paper writes the spatial and temporal outputs as products with Z and combines them §4 eq. 4:

\boldsymbol{Z}^{(\ell)} = \xi_{\max}\!\left(f^{(\ell)}_{\text{cnn}}(\text{ZPI}^{\tau})\right), \quad \boldsymbol{S}_i^{(\ell)} = \boldsymbol{H}^{(\ell)}_{i,\mathcal{S}}\boldsymbol{Z}^{(\ell)}, \quad \boldsymbol{T}_i^{(\ell)} = \boldsymbol{H}^{(\ell)}_{i,\mathcal{T}}\boldsymbol{Z}^{(\ell)} eq. 4

The output is H_out = COMBINE(S, T); the paper does not define COMBINE §4.

GRU. Gates use ReLU in the paper, the candidate uses tanh, and the state is interpolated §4 eq. 5. In the paper's notation, with O_{i−1} the previous state and H_out the layer output:

\boldsymbol{z}_i = \varphi\!\left(\boldsymbol{W}_z[\boldsymbol{O}_{i-1}, \boldsymbol{H}_{i,\text{out}}] + \boldsymbol{b}_z\right),\quad \boldsymbol{r}_i = \varphi\!\left(\boldsymbol{W}_r[\boldsymbol{O}_{i-1}, \boldsymbol{H}_{i,\text{out}}] + \boldsymbol{b}_r\right)

\tilde{\boldsymbol{O}}_i = \tanh\!\left(\boldsymbol{W}_o[\boldsymbol{r}_i \odot \boldsymbol{O}_{i-1}, \boldsymbol{H}_{i,\text{out}}] + \boldsymbol{b}_o\right),\quad \boldsymbol{O}_i = \boldsymbol{z}_i \odot \boldsymbol{O}_{i-1} + (1-\boldsymbol{z}_i) \odot \tilde{\boldsymbol{O}}_i eq. 5

The paper writes the update gate with a stray subscript in one place; the form above follows the rest of the equation block §4 eq. 5.

ZPI construction. The paper's three steps are summarised here §3. The surface is a sum of Gaussians centred on the transformed diagram points, weighted by g(μ) §3:

\mathrm{ZPI}_{\nu_*}(z) = \iint_{z} \sum_{\mu \in \mathrm{DgmZZ}'} g(\mu)\, \exp\!\left\{-\frac{\lVert z - \mu\rVert^2}{2\vartheta^2}\right\}\, dz_x\, dz_y §3

where the diagram is transformed to (x, y − x) coordinates and g(μ) depends on the distance from the diagonal. The paper gives the variance ϑ² as a symbol only §3. Proposition 3.1 states stability of the ZPI under the Wasserstein-1 distance for continuous, piecewise differentiable g §3. No proof appears in the compiled text §3.

Forward pass. The paper does not describe the output layer §4. The code has no decoder: the last encoder state feeds one convolution that emits all h steps at once ZGCNETs.py. The pseudocode below follows the code.

Pseudocode
# One zigzag-gated graph GRU layer over a window  [§4]
# [code: model/ZGCNCELL.py#L7-L25]
# Input:  layer_input (B, tau, N, C_in); zigzag_image (B, 100, 100)
# Output: step_states (B, tau, N, hidden)

def zigzag_gru_layer(layer_input, zigzag_image):
    # Node states start at zero  [code: model/ZGCNCELL.py#L27-L28]
    state = zeros(B, N, hidden)                       # hidden = 64 (PeMS)
    step_states = []
    for t in range(tau):
        x_t = layer_input[:, t]                       # (B, N, C_in)
        # Gate input: current step and previous state  [§4] [eq. 5]
        gate_in = concat(x_t, state, dim=-1)          # (B, N, C_in + hidden)
        gates = zpi_gated_conv(gate_in, layer_input, zigzag_image, gate_net)
        # Code uses sigmoid; the paper's gates use ReLU
        # [code: model/ZGCNCELL.py#L20]
        first, second = split(sigmoid(gates), hidden)
        # Candidate reads the first half on the state  [code: ZGCNCELL.py#L22]
        cand_in = concat(x_t, first * state, dim=-1)
        candidate = tanh(zpi_gated_conv(cand_in, layer_input,
                                        zigzag_image, update_net))
        # Interpolate with the second half  [code: ZGCNCELL.py#L24]
        state = second * state + (1 - second) * candidate
        step_states.append(state)
    return stack(step_states, dim=1)
Pseudocode
# Graph and window features, each scaled by the ZPI CNN  [§4] [eq. 2-4]
# [code: model/ZGCN.py#L60-L103]
# Input:  inp (B, N, C); window (B, tau, N, C_win); zigzag_image (B, 100, 100)
# Output: (B, N, 2 * half)

def zpi_gated_conv(inp, window, zigzag_image, branch):
    # Laplacianlink from node embeddings  [§4] [eq. 1] [code: ZGCN.py#L75-L81]
    supports = laplacianlink(node_embeddings, link_len)   # (K+1, N, N)
    # Spatial path: node-specific weights over the supports  [§4] [eq. 2]
    spatial = graph_mix(supports, inp, node_weights)      # (B, N, half)
    # Temporal path: per-node map of each window step, then a learned
    # projection over the window; the code applies no supports here
    # [code: model/ZGCN.py#L91-L94]
    temporal = window_mix(window, node_weights_window)    # (B, N, half)
    temporal = temporal @ time_projection                 # (B, N, half)
    # One value per channel from the ZPI CNN  [§4] [eq. 4] [code: ZGCN.py#L97]
    scale = branch.cnn(zigzag_image)                      # (B, half)
    # Scale each channel, concatenate, then add a node-specific bias
    # [code: ZGCN.py#L98-L102]
    return concat(spatial * scale, temporal * scale) + node_bias
Pseudocode
# Zigzag persistence image of one window  [§3]
# Code: ZIGZAG/PEMS_ZPD_ZPI_generation.py
# Input:  features (tau, N, F) per window; threshold alpha
# Output: image (100, 100); the grid is 100 x 100 per [app. A]

def zigzag_image(features, alpha):
    # Edge length: Euclidean distance between the two nodes' features,
    # divided by the largest distance in that step
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L64-L73]
    lengths = normalised_pair_distances(features)         # (tau, E)
    # Cut edges longer than alpha, so only close pairs are kept
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L75-L76]
    lengths[lengths > alpha] = 0                          # (tau, E)
    # Union of two consecutive steps: average of the two weight matrices;
    # absent edges get distance 1.1
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L111-L112]
    unions = build_unions(lengths)
    # Vietoris-Rips complexes at scale 1.0, zigzag persistence in dims 0, 1
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L135] [§3]
    diagrams = zigzag_persistence(unions, scale=1.0, dims=(0, 1))
    # Birth and death indices are halved
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L318]
    diagrams = [d / 2 for d in diagrams]
    # Sum of Gaussians weighted by persistence, evaluated on a grid
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L339-L348] [§3]
    surface = gaussian_surface(diagrams, bandwidth=1.0, power=1.0)
    # Min-max rescale to [0, 1]
    # [code: ZIGZAG/PEMS_ZPD_ZPI_generation.py#L352-L353]
    return rescale01(surface)

Hyperparameters

NameValueWhereNotes
Split, PeMS60 / 20 / 20 train / val / test, chronological§5.2Code: validation and test are the last 20% each dataloader.py
Split, tokens80 / 20 train / test§5.2No validation split stated; no training code in repo
Window τ, horizon h12 / 12 (PeMS); 7 / 7 (tokens)§5.2Code conf: lag 12, horizon 12 PEMSD4_ZGCNETs.conf
Layers2§5.2Code: num_layers = 2 PEMSD4_ZGCNETs.conf
Hidden units64 per layer (PeMS); 16 (tokens)§5.2Code: rnn_units = 64 PEMSD4_ZGCNETs.conf; token config not in repo
Embedding size cnot stated§4Code: embed_dim = 10 PEMSD4_ZGCNETs.conf
Laplacianlink length2 (PeMS); 3 (tokens)app. ACode: link_len = 2 for PeMS, read as the number of matrices including I; see Paper vs code
ZPI grid100 × 100app. AModel reshapes to 100 × 100 ZGCNETs.py; generator default is 50 × 50 PEMS_ZPD_ZPI_generation.py
ZPI CNN2 conv layers, 8 filters in the first, kernel 3, stride 2, 5 × 5 poolingapp. ACode adds 3 × 3 stride-2 max-pools between convs DNN.py; second-layer width not stated
Edge threshold ν*0.5 (PeMSD4); 0.3 (PeMSD8)§5.2Generator default alpha = 0.3 PEMS_ZPD_ZPI_generation.py
RBF γ1.0§5.1Not used by the code's edge weights PEMS_ZPD_ZPI_generation.py
Node features, PeMSflow, speed, occupancy (3)§5.1Model input_dim = 1 (flow only) PEMSD4_ZGCNETs.conf
Node features, tokensnode degree (1)§5.1Loader reads column 0 load_token_dataset.py
OptimiserAdam; lr 0.003 (PeMS); 0.001 (tokens)app. ACode: lr_init 0.003 PEMSD4_ZGCNETs.conf; Adam with eps 1e-8, no weight decay Run_PEMS_model.py
LR decayρ = 0.3 (PeMS); ρ = 0.1 (tokens)app. APeMSD4: no decay PEMSD4_ZGCNETs.conf; PeMSD8: decay at epochs 110–160 PEMSD8_ZGCNETs.conf
Batch size64 (PeMSD4); 8 (PeMSD8); 8 (tokens)app. ACode: 64 and 8 PEMSD4_ZGCNETs.conf PEMSD8_ZGCNETs.conf
Epochs300 (PeMS); 100 (tokens)app. ACode: 350 for both PeMS configs PEMSD4_ZGCNETs.conf
Lossnot stated—Code: MAE (L1) on real units Run_PEMS_model.py
Early stoppingnot stated—Code: patience 200 (PeMSD4), 100 (PeMSD8) PEMSD4_ZGCNETs.conf PEMSD8_ZGCNETs.conf; model kept at best validation loss Trainer.py
Random seednot stated—Code: 10 (PeMSD4); 1992 (PeMSD8) PEMSD4_ZGCNETs.conf
Normalisationmin–max§5.2Code: global z-score dataloader.py; see Paper vs code
ZPI Gaussian variance ϑ²not stated§3Code: bandwidth = 1 in exp(−d²/bandwidth) PEMS_ZPD_ZPI_generation.py PEMS_ZPD_ZPI_generation.py
HardwareNVIDIA GeForce RTX 3090, 24 GB§5.2—
Robustness noiseGaussian, 30% of training sets, σ ∈ {2, 4}§5.6No noise code in the repository

Results

Author-reported. Setup: PeMSD4 (307 nodes) and PeMSD8 (170 nodes), 5-minute aggregation, flow, speed and occupancy features; token networks Bytom and Decentraland, 100 nodes each §5.1. Dataset summary tab. 1:

Dataset# NodesAvg # edgesTime range
Bytom1009.9827/07/2017 – 07/05/2018
Decentraland10016.9414/10/2017 – 07/05/2018
PeMSD4307316.10†01/01/2018 – 28/02/2018
PeMSD8170193.53†01/07/2016 – 31/08/2016

† Average number of edges under the threshold ν* tab. 1.

Traffic forecasting, horizon 12, PeMSD4 and PeMSD8 tab. 2. Thirteen baselines: HA, VAR, FC-LSTM, GRU-ED, DSANet, DCRNN, STGCN, GraphWaveNet, ASTGCN, MSTGCN, STSGCN, AGCRN, LSGCN §5.2.

ModelPeMSD4 MAEPeMSD4 RMSEPeMSD4 MAPEPeMSD8 MAEPeMSD8 RMSEPeMSD8 MAPE
HA38.0359.2427.88%34.8652.0424.07%
VAR24.5438.6117.24%19.1929.8113.10%
FC-LSTM26.7740.6518.23%23.0935.1714.99%
GRU-ED23.6839.2716.44%22.0036.2313.33%
DSANet22.7935.7716.03%17.1426.9611.32%
DCRNN21.2237.2314.17%16.8226.3610.92%
STGCN21.1635.6913.83%17.5027.0911.29%
GraphWaveNet28.1539.8818.52%20.3030.8213.84%
ASTGCN22.9334.3316.56%18.2528.0611.64%
MSTGCN23.9637.2114.33%19.0029.1512.38%
STSGCN21.1933.6913.90%17.1326.8610.96%
AGCRN19.8332.3012.97%15.9525.2210.09%
LSGCN21.5333.8613.18%17.7326.7611.20%
Z-GCNETs (ours)19.5031.6112.78%15.7625.1110.01%

Text in the paper calls the gain over the next best method "0.44% to 2.06% in RMSE" §5.3; see Pitfalls for how this compares with the table.

Token networks, MAPE, horizon 7 tab. 4. Seven baselines: FC-LSTM, DCRNN, STGCN, GraphWaveNet, ASTGCN, AGCRN, LSGCN tab. 4.

ModelBytomDecentraland
FC-LSTM40.72%33.46%
DCRNN35.36%27.69%
STGCN37.33%28.22%
GraphWaveNet39.18%37.67%
ASTGCN34.49%27.43%
AGCRN34.46%26.75%
LSGCN34.91%28.37%
Z-GCNETs31.04%23.81%

Running time tab. 3: average ZPI computation and per-epoch training time, in seconds, as reported.

DatasetWindow sizeZPI (s)Z-GCNETs per epoch (s)
Decentraland70.032.09
Bytom70.032.05
PeMSD4120.8630.12
PeMSD8120.6536.76

Ablation, PeMS tab. 5. "W/o GCN_Spatial" applies the spatial graph convolution only at the most recent window step (marked * in the paper).

ArchitecturePeMSD4 MAEPeMSD4 RMSEPeMSD4 MAPEPeMSD8 MAEPeMSD8 RMSEPeMSD8 MAPE
Z-GCNETs19.5031.6112.78%15.7625.1110.01%
W/o zigzag learning19.6531.9413.01%17.1627.0610.77%
W/o GCN_Spatial*19.8631.9613.19%16.9226.8610.33%
W/o GCN_Temporal20.7633.1813.60%16.6626.4410.39%

Zigzag dimension and threshold tab. 6. The main result equals the 1-st ZPI row at ν* = 0.5 for PeMSD4 and at ν* = 0.3 for PeMSD8.

Zigzag modulePeMSD4 MAEPeMSD4 RMSEPeMSD4 MAPEPeMSD8 MAEPeMSD8 RMSEPeMSD8 MAPE
0-th ZPI, ν* = 0.319.7332.0412.93%17.1427.2410.66%
1-st ZPI, ν* = 0.319.4731.6612.75%15.7625.1110.01%
0-th ZPI, ν* = 0.519.7832.2012.98%17.2227.4110.77%
1-st ZPI, ν* = 0.519.5031.6112.78%16.7726.6210.39%

Robustness, RMSE under added Gaussian noise on 30% of training data tab. 7.

DatasetNoiseAGCRNLSGCNZ-GCNETs
DecentralandN(0, 2)27.6936.1024.12
DecentralandN(0, 4)28.1236.7925.03
PeMSD4N(0, 2)32.2434.1631.95
PeMSD4N(0, 4)32.6734.7532.18

Choice of filtration, MAPE, token networks tab. 8 (appendix B). The paper says the method works with any filtration and that the main results use the weight rank clique filtration §5.2.

DatasetWeighted-degree sublevel, TransactionWeighted-degree sublevel, VolumePower, Volume
Bytom30.5630.8030.79
Decentraland25.1824.9322.15

Ablation, token networks, MAPE tab. 9 (appendix C).

ArchitectureBytomDecentraland
Z-GCNETs31.04%23.81%
W/o zigzag learning33.19%24.24%
W/o GCN_Spatial34.32%25.22%
W/o GCN_Temporal31.25%24.62%

No standard deviations or repeated runs are reported [not stated].

Paper vs code

Code: official repository Z-GCNETs/Z-GCNETs at commit e55e12466eba3f98562b9992319172422c4d09e6 (MIT, copyright Yuzhou Chen, the first author). The paper links this repository in §5.2 §5.2.

Items below compare the paper with the code at that commit.

  1. Edge weights for traffic. The paper uses an RBF similarity exp(−d²/γ) and keeps weights at or below ν* §5.1 eq. 6. The code uses the Euclidean distance between node features, divided by the largest distance in that step, and cuts edges above alpha PEMS_ZPD_ZPI_generation.py. Under the paper's rule, dissimilar pairs survive; under the code's rule, similar pairs survive. The two rules do not select the same edges.
  2. Feature count. The paper uses flow, speed and occupancy §5.1. The ZPD script reads columns 0–3 PEMS_ZPD_ZPI_generation.py, while the model reads one channel (flow) PEMSD4_ZGCNETs.conf Trainer.py.
  3. Filtration. The paper states that the reported results use the weight rank clique filtration §5.2. The code uses Vietoris–Rips complexes built with dionysus fill_rips PEMS_ZPD_ZPI_generation.py Ethereum_Token_Networks_ZPD_ZPI_generation.py. The string "clique" does not appear in the repository's model or ZIGZAG code.
  4. Union weights. The paper defines the union's edge set but not its weights §3. The code takes the average of the two weight matrices PEMS_ZPD_ZPI_generation.py.
  5. ZPI formula. The paper transforms the diagram to (x, y − x) and integrates over each grid box §3. The code uses (birth, death) directly, evaluates the surface at grid points, and min-max normalises each image PEMS_ZPD_ZPI_generation.py. The variance ϑ² is not stated in the paper; the code uses bandwidth 1 PEMS_ZPD_ZPI_generation.py.
  6. ZPI grid. The paper and appendix give 100 × 100 app. A. The model reshapes to 100 × 100 ZGCNETs.py. The generator's default is 50 × 50 and no call in the repository passes another size PEMS_ZPD_ZPI_generation.py. The pre-computed ZPI are not in the repository: model/tda_data/ holds only .DS_Store files. The README points to an external download, which was not fetched.
  7. Laplacianlink length. The paper's "length 2 and 3" app. A is read as link_len, the number of matrices including I. Code: for link_len = 2 the set is [I, L] ZGCN.py PEMSD4_ZGCNETs.conf. The paper does not define "length" in the text.
  8. Temporal convolution. The paper's temporal path applies the Laplacianlink powers to the window §4 eq. 3. The code's model path uses a per-node projection of the window with no Laplacian ZGCN.py, via TFLSGCNCNN ZGCNCELL.py. The Laplacian-based TLSGCNCNN ZGCN.py is defined but never instantiated.
  9. Gate activation. The paper uses ReLU for the gates §4 eq. 5. The code uses a sigmoid ZGCNCELL.py.
  10. Gate naming. The code names the first half of the gate output z and uses it as the reset gate in the candidate; the second half is the update gate in the interpolation ZGCNCELL.py. The paper's names are reversed. The functional form matches eq. 5.
  11. Node bias. The code adds a node-specific bias E·B to each branch output ZGCN.py ZGCN.py. The paper's equations have no bias.
  12. Scaling by the ZPI. The paper writes S = H·Z as a product §4 eq. 4. The code multiplies each channel by the CNN output, a per-sample vector broadcast over nodes ZGCN.py.
  13. ZPI CNN. The paper says "global max-pooling" §4 and "5 × 5 pool" app. A. The code has two 3 × 3 stride-2 max-pools between convolutions, then a 5 × 5 pool and a flatten DNN.py. For a 100 × 100 input the map shrinks 100 → 49 → 24 → 11 → 5 → 1, so the last pool is global for that size.
  14. Number of ZPI CNNs. The code creates four CNNs, one for the gate and one for the update in each of the two layers. Each runs at every time step on the same image ZGCN.py ZGCNCELL.py. The gate CNN has 64 output channels and the update CNN has 32.
  15. Normalisation. The paper uses min–max normalisation §5.2. The config sets normalizer = std PEMSD4_ZGCNETs.conf, and the code computes a global mean and standard deviation over the full series before splitting dataloader.py dataloader.py.
  16. Epochs. The paper says 300 for PeMS app. A; the config says 350 PEMSD4_ZGCNETs.conf.
  17. Learning-rate decay. The paper uses ρ = 0.3 for PeMS app. A. PeMSD4's config disables decay PEMSD4_ZGCNETs.conf; PeMSD8's config decays at epochs 110–160 PEMSD8_ZGCNETs.conf.
  18. Output units. With real_value = True, labels are converted back to real units before the loss, while the model output is not Trainer.py Trainer.py. The network therefore learns real-unit flows from normalised inputs. Test predictions are also not converted Trainer.py.
  19. Unstated in the paper, set in the code. Loss: MAE Run_PEMS_model.py. Early stopping: patience 200 and 100 PEMSD4_ZGCNETs.conf PEMSD8_ZGCNETs.conf. Seeds: 10 and 1992 PEMSD4_ZGCNETs.conf PEMSD8_ZGCNETs.conf.
  20. Road graph. The training path never reads distance.csv. Only model/utils.py utils.py and the ZPD script PEMS_ZPD_ZPI_generation.py read it. The model's adjacency is learned from node embeddings alone ZGCN.py.
  21. Token pipeline. The repository has no training script or config for Bytom or Decentraland. The token ZPI files are H0 only load_token_dataset.py load_token_dataset.py, and the token ZPD script hard-codes the W_Bytom file prefix for both tokens and reads ReduceDataset100/, which is not in the repository Ethereum_Token_Networks_ZPD_ZPI_generation.py.
  22. Token sample counts. The loader comments give 259 (Bytom) and 193 (Decentraland) ZPI samples load_token_dataset.py. The paper gives 285 and 206 networks §5.1. The paper does not state how a window count maps to a network count.
  23. Token target. The paper says the Ethereum analysis uses historical daily closed prices §5.1 but does not state the forecast target. The loader has a close-price reader load_token_dataset.py, but it looks for Prices_Closed/, while the folder is prices_closed/ load_token_dataset.py .
  24. Unused config. gamma = 1 in the config is read into args Run_PEMS_model.py but not used by the model [grep of model/*.py].
  25. Window builder. add_window.py calls zigzag_persistence_images without importing it add_window.py. That path cannot run as written.
  26. Test logging. The trainer computes and logs test metrics every epoch Trainer.py. Selection uses validation loss only Trainer.py.

No discrepancy was found in the batch sizes for PeMS, the optimiser (Adam), the learning rate of 0.003, or the two layers of 64 units.

Pitfalls

  • Pre-computed ZPI are a hidden dependency. The model reads ZPI arrays from tda_data/ load_dataset.py. These are not in the repository. The generator functions have no entry point PEMS_ZPD_ZPI_generation.py, and their default grid is 50 × 50, not the 100 × 100 the model expects ZGCNETs.py. Reproducing the numbers needs the authors' ZPI files or a rewritten pipeline with the same edge rule, filtration, window alignment and grid.
  • Filtration not reproducible from the repository. The main results claim the weight rank clique filtration §5.2, but the code uses Vietoris–Rips. The token numbers can only be checked against a filtration the repository does not implement.
  • Edge rule. The paper's RBF weight and "keep at most ν*" rule and the code's distance and "keep at most alpha" rule select opposite edges (see Paper vs code, item 1). An implementer following the paper will not get the authors' graphs.
  • Gate activation. The activation sets the range of the interpolation weights. The code's sigmoid keeps them in (0, 1); the paper's ReLU does not bound them above ZGCNCELL.py.
  • Width-dependent weight shape. The temporal weight shape is chosen by testing (dim_in − 1) % 16 == 0, not by the actual window channel count ZGCN.py. The configuration in the repository gives the right shapes (65 and 128 inputs). Changing input_dim or rnn_units may select the wrong shape without a clear error. Not tested here.
  • Repeated CNN work. The ZPI CNN is recomputed at every step for every gate and update ZGCN.py ZGCNETs.py. The image is the same for all steps of a window, so one call per window gives the same result. This costs time and does not change the output.
  • Normalisation leakage. The code computes the z-score over the full series before splitting dataloader.py dataloader.py. Test statistics leak into training. The paper's min–max statement does not say which data it used §5.2.
  • Output scale. Predictions are in real flow units, not normalised ones. An implementer who inverts the normalisation at evaluation time will get wrong numbers Trainer.py.
  • Text-table mismatches in the paper.
    • §5.3: "0.44% to 2.06% in RMSE". Against AGCRN, the table gives 0.44% for PeMSD8 and 2.14% for PeMSD4 ((32.30 − 31.61) / 32.30).
    • §5.3 token paragraph: "RMSE" and gains of "3.42%" and "2.94%" over AGCRN. Table 4 is MAPE. The two figures equal the absolute MAPE gaps, 34.46 − 31.04 = 3.42 and 26.75 − 23.81 = 2.94 percentage points. The relative gains are about 9.9% and 11.0%.
    • §5.4: "relative gain 6.46% on RMSE for PeMSD4". The 6.46% matches the MAE column (20.76 vs 19.50). The RMSE relative gain is about 4.7–5.0% (33.18 vs 31.61).
  • Definition 3.1. The paper's set is written with "t_b < t_b" §3; the intended condition is between the birth and death times.
  • Single runs. The paper reports one number per cell without spread [not stated]. The PeMS improvements over the best baseline are small: 0.44–2.14% in RMSE tab. 2. Differences of this size may fall within seed noise.
  • Costs the abstract hides. ZPI computation takes 0.03–0.86 s per entry and an epoch takes 2–37 s on one RTX 3090 tab. 3 §5.2. The ZPD pipeline needs a dynamic-graph filtration for every window, and the code does this in Python with dionysus PEMS_ZPD_ZPI_generation.py.
  • Direct multi-step head. The output is one convolution over the last state that emits all h steps at once ZGCNETs.py ZGCNETs.py. The paper does not describe the head §4.

Lineage

  • Builds on: zigzag persistence, cited as carlsson2010zigzag §2 §3; persistence images, cited as adams2017persistence §3; the self-adaptive adjacency from Graph WaveNet, cited as graphwavenet §4; GRU §2.
  • Compared against: 13 traffic baselines tab. 2 and 7 token baselines tab. 4, listed in Results.
  • Superseded by: not stated.

Source and license

Adapted from “Z-GCNETs: Time Zigzags at Graph Convolutional Networks for Time Series Forecasting” by Yuzhou Chen, Ignacio Segovia-Dominguez, Yulia R. Gel (arXiv:2105.04100), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/Z-GCNETs/Z-GCNETs at commit e55e12466eba (MIT). Any quoted code is shown under that license.