gnn-augment
Boost a graph neural network for semi-supervised node classification by augmenting the graph with node-similarity representatives.
A graph neural network (GNN) propagates node features through the graph's
adjacency matrix, so at layer l only l-hop neighbours influence a
node. gnn-augment replaces the adjacency with a node-similarity
representative — Katz, Rooted PageRank, or Graph Gravity (a
gravitational link-prediction score: degree as mass, shortest path as
distance) — letting distant-but-similar nodes influence each other even
in a shallow network, which improves node-classification accuracy.
The trick is architecture-agnostic — it changes which graph the network sees — so it works across four GNN architectures: the Kipf–Welling GCN, GraphSAGE, GAT (graph attention), and GIN.
This is a packaged, reusable implementation of the augmentation method from:
A. Wahid-Ul-Ashraf, M. Budka, K. Musial. Simulation and Augmentation of Social Networks for Building Deep Learning Models. arXiv:1905.09087
The Graph Gravity representative is the same gravitational method whose link-prediction reference implementation is akanda-method.
Installation
pip install gnn-augment # release
pip install "gnn-augment[semantic]" # + sentence-transformers, for the semantic (LLM-embedding) view
pip install git+https://github.com/AkandaAshraf/gnn-augment # latest main
Requires Python ≥ 3.10; depends on torch, networkx, numpy, scipy
and scikit-learn. A CUDA build of PyTorch is used automatically when a
GPU is available (device="auto") — install the torch wheel you want (CPU
or a specific CUDA) first if pip's default is not it. From a clone:
pip install -e ".[dev,semantic]" (see Development and tests below).
Quick start
from gnn_augment import GNNClassifier, load_cora, make_semi_supervised_split
G, X, y = load_cora() # networkx graph, features, labels
y_semi, train, val, test = make_semi_supervised_split(y, seed=0) # -1 = unlabeled
clf = GNNClassifier(model="gcn", representative="rpr", threshold="auto")
clf.fit(G, X, y_semi, train_mask=train, val_mask=val)
print("test accuracy:", clf.score(G, X, y, mask=test))
Pick any architecture with model= — "gcn", "sage" (GraphSAGE),
"gat" (graph attention), or "gin". The augmentation (representative,
threshold) is independent of the architecture, so any combination works.
The estimator follows the scikit-learn fit / predict / predict_proba
/ score convention. Semi-supervised learning is expressed the
scikit-learn way — set -1 for unlabeled nodes in y — or pass explicit
train_mask / val_mask arrays. The graph may be a networkx graph, a
dense adjacency matrix, or a SciPy sparse matrix.
The augmented representatives
representative |
what replaces the adjacency | paper |
|---|---|---|
"adjacency" |
none — the original Kipf–Welling GCN | Eq. 10–12 |
"katz" |
Katz index Σ βˡ Aˡ (truncated at A⁵) | Eq. 17 |
"rpr" |
Rooted PageRank | Eq. 20 |
"gravity" |
Graph Gravity: DC(i)·DC(j) / SP(i,j)² | Eq. 22 |
Similarity matrices are L2 row-normalised and thresholded (paper
§9.1): pass threshold=(t1, t2) (entries ≤ t1 → 0, > t2 → 1), "auto"
(binarise at the mean), None (keep weights), or "select" to pick the
best threshold on the validation set automatically. weighted_features=True
adds the learnable per-feature weight vector (Eq. 25).
Build a representative on its own for use in another model:
from gnn_augment import build_representative
G_matrix = build_representative(A, kind="gravity", threshold="auto") # normalised Laplacian
Multi-view training: a different augmented graph every epoch
The paper's conclusions suggest using several augmented matrices of the
same graph as a regulariser. views= turns that on: each epoch the
network trains on a different view of the graph (graph data augmentation),
and at inference the class probabilities can be averaged over the views
(test-time augmentation):
clf = GNNClassifier(
model="gin",
views=["adjacency", ("rpr", (0.0, 0.5)), ("gravity", (0.0, 1.0))],
view_schedule="random", # "random" | "cycle" | "mix" (Dirichlet mixture)
predict_views="mean", # "mean" (TTA) | "first"
)
On Cora (3 splits; benchmarks/multiview-cora.md),
multi-view training with TTA beats the raw adjacency on every architecture
without any view selection, and on the GCN it is the best configuration
of all (0.819 vs 0.816 for the best single view). When one view clearly
dominates (RPR on GraphSAGE/GIN), committing to it still wins — multi-view
is the robust "don't know the best view" strategy. Most of the gain comes
from averaging over views at inference, so predict_views="mean" is the
recommended mode.
Is it a real regulariser? Tested properly — high-capacity models (up to
256 hidden × 4 layers), no dropout, no weight decay, no early stopping,
so they memorise the training set (benchmarks/overfitting-cora.md):
multi-view training beats the raw adjacency at 3 of 4 capacities on the GCN
(up to +2.3 points at 256 × 4) and the single RPR view at all 4 — a genuine
but modest effect that complements early stopping (the dominant
regulariser) rather than replacing it. A fixed augmented view, by contrast,
does not regularise: its gains depend on early stopping. One limitation:
deep unregularised GIN fails to train under multi-view (its
un-normalised sum aggregation cannot handle the message-scale jump between
views) — use a single view, "cycle", or scaled views for deep GIN.
The semantic view: LLM embeddings of node text as a graph (Graph + LLM)
Nodes of citation graphs are papers with real text. "semantic" builds a
graph representative from text embeddings: cosine-similarity kNN over
sentence-transformer (or any LLM) embeddings of each node's text, with the
original edges kept — a text-similarity view next to the topological ones.
Embeddings are computed locally (MiniLM, 22M parameters; no API):
from gnn_augment import GNNClassifier, embed_texts, load_cora, load_cora_text
G, X, y = load_cora()
E = embed_texts(load_cora_text()) # n x 384, local sentence-transformer
clf = GNNClassifier(representative="semantic", threshold=None, semantic_k=10)
clf.fit(G, X, y_semi, train_mask=train, val_mask=val, node_embeddings=E)
# or as one view among several in multi-view training:
clf = GNNClassifier(views=["adjacency", ("rpr", (0.0, 0.5)), ("semantic", None)])
Raw texts: load_cora_text() (aligned with load_cora) and
load_pubmed_raw() (graph + features + labels + texts), both reading the
TAPE raw-text releases (data/raw_text/{cora_orig,PubMed_orig}.zip, from
the Google-Drive links in the TAPE
README). embed_texts(model_name=...) also accepts a local model
directory — handy offline or behind TLS-intercepting proxies: download the
sentence-transformers/all-MiniLM-L6-v2 files once (e.g. with curl) into
data/models/all-MiniLM-L6-v2 and the example uses it automatically.
cache_representatives=True caches augmented adjacencies in-process so
repeated fits on the same graph (seeds, architectures, views) reuse the
expensive Katz/RPR/gravity builds.
On Cora the semantic view, added to multi-view training, gives the best accuracy yet on every architecture (3 splits; benchmarks/semantic.md):
| architecture | adjacency | best topological | multi adj+RPR+gravity+semantic |
|---|---|---|---|
| GCN | 0.797 | 0.819 | 0.829 |
| GraphSAGE | 0.750 | 0.794 | 0.805 |
| GIN | 0.729 | 0.784 | 0.792 |
| GAT | 0.814 | 0.818 | 0.828 |
Text complements topology rather than replacing it: the semantic graph alone is weaker than the citation graph, but as an extra view it adds what the topological views lack — and it is the first augmentation that moves GAT.
At 7× the scale (PubMed, 19,717 nodes, run in sparse mode, 2 splits) the semantic view transfers: semantic kNN + adjacency is the most consistent single view (+2.4 / +3.3 / +3.4 points on GCN / GraphSAGE / GIN; the best single view on GCN and GraphSAGE), and multi-view training that includes it is best or tied-best on GraphSAGE (+3.4) and GIN (+4.0, with gravity as a fourth view) — every architecture gains at least 2.4 points where the topological views alone gave at most +2.3 at this scale.
The augmentation helps every architecture
The paper applied augmentation only to the GCN, but the trick generalises. On Cora, the RPR representative improves all four architectures (mean test accuracy over 3 splits; see benchmarks/architectures-cora.md):
| architecture | adjacency | + RPR |
|---|---|---|
| GCN | 0.797 | 0.816 (+0.018) |
| GraphSAGE | 0.750 | 0.794 (+0.044) |
| GAT | 0.814 | 0.818 (+0.003) |
| GIN | 0.729 | 0.784 (+0.054) |
python examples/cora_demo.py # vanilla vs augmented GCN
python examples/architectures_demo.py # augmentation across GCN/SAGE/GAT/GIN
python examples/pubmed_demo.py # scale up to ~20k nodes (GCN/SAGE/GIN)
Datasets: load_cora(), load_planetoid("cora" | "citeseer" | "pubmed"),
plus load_cora_text() / load_pubmed_raw() for node texts. Dense views
fit to roughly 20k nodes on an 8 GB GPU (see
benchmarks/pubmed.md); GAT's O(N²·heads) attention
caps it at ~8–10k nodes; beyond that, sparse mode (next paragraph).
Large graphs — sparse mode. GNNClassifier(sparse="auto") (the
default) switches above 8k nodes to chunked, sparse views: every
representative is built in row chunks keeping only its view_top_k
strongest entries per row (RPR via a sparse LU solve, gravity via chunked
BFS, Katz via chunked powers, semantic via chunked kNN) and stored as a
SciPy CSR matrix — O(n·k) memory instead of O(n²) — with sparse propagation
on the GPU for GCN / GraphSAGE / GIN. A 20k-node view drops from ~1.5 GB to
~10 MB, and much larger graphs fit (the PubMed table in
benchmarks/semantic.md ran this way in a few GB
of host RAM). Force it with sparse=True; GAT needs the dense path.
The best representative is dataset-dependent (as the paper notes): RPR is the most robust across architectures on Cora, while Graph Gravity suits friendship/social networks rather than citation graphs. At PubMed scale the purely topological gains shrink and become architecture-specific (GIN benefits most; benchmarks/pubmed.md), whereas the semantic view keeps paying off there (+2.4 to +4.0 points; benchmarks/semantic.md).
Development and tests
git clone https://github.com/AkandaAshraf/gnn-augment && cd gnn-augment
pip install -e ".[dev,semantic]"
pytest # unit + integration (synthetic graphs, offline, ~1 min on CPU)
pytest -m unit # fast, isolated tests of each function / class
pytest -m integration # end-to-end through the public API, dense and sparse paths
pytest -m "not slow" # skip the real-data tests (Cora / local MiniLM; auto-skipped when absent)
python -m build && twine check dist/*
Layout: src/gnn_augment/ (classifier, representatives, sparse,
models, data), tests/unit/, tests/integration/, examples/, and
benchmarks/ (every result table in this README, with the script that
produced it). CI (ci.yml) runs the unit and
integration suites on Python 3.10 and 3.12 with the CPU build of torch,
then builds the sdist/wheel, installs the wheel into a clean venv and runs
the integration tests against it; tagged releases are published to PyPI by
release.yml (see RELEASING.md).
Relation to other work
The "gravity" representative is the node-topological gravitational
score (centrality as mass, path length as distance). It is unrelated to
gravity-inspired graph autoencoders, which apply a gravity term to learned
embeddings — see the akanda-method README for that distinction.
Citing
Please cite the arXiv paper above. See CITATION.cff.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gnn_augment-0.4.0.tar.gz.
File metadata
- Download URL: gnn_augment-0.4.0.tar.gz
- Upload date:
- Size: 58.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bd8bd3e6514db78e5883ccb4fef74d1d2e5765cae99b0cbc7ba42635a74e814
|
|
| MD5 |
258fefa2fcd5955764cec5e118b768d7
|
|
| BLAKE2b-256 |
6596c1bad1e828a98ce100395c29985233ece913dec073463c01f887f150a400
|
Provenance
The following attestation bundles were made for gnn_augment-0.4.0.tar.gz:
Publisher:
release.yml on AkandaAshraf/gnn-augment
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gnn_augment-0.4.0.tar.gz -
Subject digest:
4bd8bd3e6514db78e5883ccb4fef74d1d2e5765cae99b0cbc7ba42635a74e814 - Sigstore transparency entry: 2547374643
- Sigstore integration time:
-
Permalink:
AkandaAshraf/gnn-augment@5ed9a6a66e21059b84612e6be44b16d2a29029dc -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/AkandaAshraf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5ed9a6a66e21059b84612e6be44b16d2a29029dc -
Trigger Event:
push
-
Statement type:
File details
Details for the file gnn_augment-0.4.0-py3-none-any.whl.
File metadata
- Download URL: gnn_augment-0.4.0-py3-none-any.whl
- Upload date:
- Size: 30.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
133861a2dcc134ba06f8dc80c4379f172ca16c589c64976558809768a340e7e9
|
|
| MD5 |
28c4ea7d73e7eb9edef28e406901c817
|
|
| BLAKE2b-256 |
b45e196b0f8953df3beae1e5158abf92b87dec96bc1332de756946ba05f5a283
|
Provenance
The following attestation bundles were made for gnn_augment-0.4.0-py3-none-any.whl:
Publisher:
release.yml on AkandaAshraf/gnn-augment
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gnn_augment-0.4.0-py3-none-any.whl -
Subject digest:
133861a2dcc134ba06f8dc80c4379f172ca16c589c64976558809768a340e7e9 - Sigstore transparency entry: 2547374719
- Sigstore integration time:
-
Permalink:
AkandaAshraf/gnn-augment@5ed9a6a66e21059b84612e6be44b16d2a29029dc -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/AkandaAshraf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5ed9a6a66e21059b84612e6be44b16d2a29029dc -
Trigger Event:
push
-
Statement type: