Graph Neural Networks: An Introduction
Imagine predicting whether a molecule is toxic. Or whether a user will click an ad in a social network. Or how proteins fold based on atomic bonds. In each case the data is not a grid of pixels or a sequence of tokens. Instead, it is a graph, a set of entities connected by relationships of arbitrary, irregular shape. Convolutional networks exploit the fixed grid structure of images. Recurrent networks exploit the linear order of sequences. Graphs have neither a fixed neighborhood size nor a canonical ordering of nodes. So neither architecture applies directly without redesign. Graph Neural Networks (GNNs) solve this by generalizing the convolution operator to arbitrary topologies. Instead of sliding a fixed-size filter over a regular grid, a GNN layer works differently. It aggregates information from a node's actual graph neighbors — however many there are, in whatever order they arrive. Then it combines that aggregate with the node's own features. This produces an updated representation. Stacking such layers lets information propagate across the graph. So after layers, every node's representation depends on its -hop neighborhood. This article is scoped to the core architecture question. How do we turn "convolution" into something well-defined on a graph? And how do the resulting layers get composed into models? These models handle node-level, edge-level, and whole-graph predictions. We build up from the Graph Convolutional Network (GCN), the cleanest and most influential instance of this idea. From there we move to the general message-passing template it exemplifies. Finally, we cover the pooling mechanisms that lift node representations up to a graph-level output. By the end, you will be able to do several things. You will derive the GCN propagation rule from first principles (spectral graph theory and a simpler neighborhood-averaging view). You will implement a multi-layer GCN in pseudocode. You will reason about its time/space complexity on sparse graphs. You will diagnose the classic failure mode of over-smoothing. And you will place GCNs in the historical arc from spectral graph convolutions to today's message-passing frameworks.
- GNNs generalize convolution to irregular graph structures by aggregating features from a node's neighbors instead of from a fixed spatial window.
- The Graph Convolutional Network (GCN) layer is a first-order approximation of spectral graph convolution, reducible to a simple symmetric-normalized neighborhood average.
- One GCN layer mixes 1-hop information; stacking layers mixes -hop information, at the cost of over-smoothing if is too large.
- Node-level tasks read off the final per-node embeddings directly; graph-level tasks require a readout/pooling step that must be permutation-invariant.
- Because real graphs are sparse, GCN forward/backward passes cost per layer, not .
- GCNs are a special case of the general message-passing neural network (MPNN) template, which also covers GraphSAGE, GAT, and GIN.
A GNN layer replaces the convolution filter's fixed spatial window with the graph's actual adjacency structure, learning to aggregate and transform neighbor features so that stacking layers propagates information exactly as far as the graph's hop distance allows.
The idea
A graph is a pair with node features , one feature row per node, and edges encoding pairwise relationships, often stored as an adjacency matrix . Unlike image pixels, graph nodes have no canonical ordering and no fixed number of neighbors — node 5 might have 2 neighbors, node 6 might have 200. Any layer we design must therefore be (a) applicable regardless of neighborhood size, and (b) invariant to the arbitrary order in which we list a node's neighbors — this is the permutation-invariance requirement, and it rules out simply concatenating neighbor features or treating the adjacency row like an image patch. The Graph Convolutional Network (GCN) layer, introduced by Kipf and Welling, satisfies both requirements with a strikingly simple update. Let (adding self-loops so a node includes itself in its own aggregation) and let be the diagonal degree matrix of , i.e. . The layer computes
with . Here is a learned weight matrix (shared across all nodes — this weight-sharing is the direct analogue of a convolution filter's shared weights across spatial positions), and is a nonlinearity such as ReLU. The matrix is the symmetric-normalized adjacency; row of is precisely a degree-normalized average of node 's own and neighbors' features. So the GCN layer can be read two equivalent ways: as a first-order Chebyshev approximation to spectral graph convolution (its original derivation), or — more usefully for intuition — as "average your neighbors' features, then apply a shared linear transform and a nonlinearity." This "aggregate, then transform" pattern is the instance, for GCNs, of the general message-passing template covered as a prerequisite concept: each node collects messages from neighbors, aggregates them with a permutation-invariant function (here, weighted sum/average), and updates its own state. GCN's specific contribution is the choice of aggregator — a fixed, symmetric, degree-normalized average, derivable from spectral theory — as opposed to, say, an attention-weighted sum (Graph Attention Networks) or a learned max-aggregator followed by a sampled-neighbor MLP (GraphSAGE). Understanding GCN as one point in this larger design space is essential: it is simple and effective, but its aggregator has no learnable per-edge weighting, which matters for later diagnosing when it underperforms.
How it works
Setup. We are given (node features) and adjacency . We choose a number of layers and per-layer output dimensions . For a node classification task, we typically want small (2–3) since the label of a node depends mostly on its local neighborhood; for whole-graph tasks we may go deeper and add a final pooling/readout layer.
Step 1 — Preprocessing. Compute , then , then the normalized propagation matrix . Because is sparse in essentially every real graph (a social network user does not know all other users), and are stored in sparse format (e.g. coordinate list / CSR), with nonzero entries — this sparsity is what makes the whole pipeline scale.
Step 2 — Layer-wise propagation. For layer : compute (neighborhood aggregation, a sparse matrix–dense matrix product), then (feature transform). Dropout and/or batch/layer normalization are commonly inserted between layers to control over-smoothing and overfitting.
Step 3 — Task head.
- Node classification/regression: apply a final linear layer to each row of , i.e. , and train with per-node cross-entropy (often only on a labeled subset of nodes — semi-supervised node classification, GCN's flagship setting).
- Link prediction: score an edge with a function of (e.g. dot product or an MLP on the concatenation).
- Graph classification/regression: pool all node embeddings into one graph-level vector, , where READOUT is permutation-invariant (mean, sum, max, or a learned attention pooling), then feed to a classifier.
```
# ---- GCN forward pass (single graph, node classification) ----
# Inputs:
# X : |V| x d0 node feature matrix
# A : |V| x |V| sparse adjacency matrix (0/1)
# W[0..L-1] : learned weight matrices, W[l] has shape d_l x d_{l+1}
# b[0..L-1] : learned bias vectors
# Output:
# Y_hat : |V| x num_classes predicted class distributions function PRECOMPUTE_PROPAGATION(A): A_hat = A + IDENTITY(|V|) # add self-loops D_hat = DIAGONAL(ROW_SUM(A_hat)) # degree matrix of A_hat D_inv_sqrt = DIAGONAL(1 / sqrt(diag(D_hat))) A_tilde = D_inv_sqrt @ A_hat @ D_inv_sqrt # symmetric normalization return A_tilde # sparse, O(|E|) nonzeros function GCN_FORWARD(X, A_tilde, W, b, L, activation=RELU): H = X for l in 0 .. L-1: Z = SPARSE_DENSE_MATMUL(A_tilde, H) # aggregate neighbor features H = Z @ W[l] + b[l] # linear transform if l < L - 1: H = activation(H) # nonlinearity on hidden layers H = DROPOUT(H, p) # regularization Y_hat = SOFTMAX(H, axis=1) # final layer: class distribution return Y_hat function TRAIN(X, A, labels, train_mask, epochs, lr): A_tilde = PRECOMPUTE_PROPAGATION(A) # done once, reused every epoch initialize W, b for epoch in 1 .. epochs: Y_hat = GCN_FORWARD(X, A_tilde, W, b, L) loss = CROSS_ENTROPY(Y_hat[train_mask], labels[train_mask]) grads = BACKPROP(loss, W, b) # standard backprop through
# sparse-dense matmuls W, b = SGD_UPDATE(W, b, grads, lr) return W, b
# ---- Graph-level task: add a readout after the last GCN layer ---- function GRAPH_READOUT(H_final, pool="mean"): if pool == "mean": return MEAN(H_final, axis=0) # |V| x d -> d if pool == "sum": return SUM(H_final, axis=0) if pool == "max": return MAX(H_final, axis=0) ``` Every step above is permutation-invariant in the nodes: relabeling node indices permutes rows of , , and consistently, and the matrix product commutes with that permutation. This is precisely why the architecture needs no canonical node ordering, unlike a CNN's implicit pixel grid ordering.
Complexity & correctness
Time complexity. Precomputing costs : we touch each edge (plus self-loops) a constant number of times to build and compute degrees. Each layer's forward pass does a sparse–dense matrix product , costing (one multiply–add per nonzero entry of , times the feature dimension), followed by a dense–dense product costing . Summed over layers, total forward cost is
taking as a representative width. Backpropagation has the same asymptotic cost as the forward pass (each matmul's gradient is another matmul of the same shape family). Because real graphs satisfy (social networks, citation graphs, molecules — all sparse, bounded average degree), this is linear in the number of nodes and edges, not quadratic. This is the essential efficiency argument for using the graph's sparsity structure directly, rather than materializing a dense matrix, which would force an unavoidable cost.
Space complexity. Storing sparsely costs . Storing all layers' activations for backprop costs . Weight matrices cost , independent of graph size — an important property, since it means the same trained GCN can be applied to graphs of different sizes at inference time (this is the graph analogue of a CNN filter's size being independent of image resolution).
Correctness / why the aggregator is principled. The spectral derivation justifies why specifically, rather than an arbitrary weighted average, is the right choice. Define the graph Laplacian , a symmetric positive semi-definite matrix whose eigendecomposition gives the graph Fourier basis . A spectral graph convolution filters a signal by for some learnable spectral filter . This is or worse (eigendecomposition is ) if computed directly — computationally infeasible for large graphs. Approximating by a degree- Chebyshev polynomial in turns into a sum of local sparse matrix–vector products (Chebyshev / ChebNet), avoiding the eigendecomposition entirely. GCN takes (a first-order truncation) and further ties parameters across the two resulting terms, collapsing the whole filter to a single application of — this is exactly the propagation rule above. So the GCN layer is not an ad hoc averaging trick; it is the cheapest nontrivial term of a principled spectral filter, made tractable by never actually diagonalizing .
Why self-loops and normalization matter. Without the self-loop term, a node's own previous-layer features would be discarded at each update, which empirically and theoretically degrades performance (a node's own information is usually the single most predictive signal). Without the symmetric normalization (versus, say, simple row-normalization ), high-degree "hub" nodes would dominate feature magnitudes; the symmetric normalization keeps symmetric, which preserves the connection to the (symmetric) graph Laplacian used in the spectral derivation, and empirically stabilizes training.
Tradeoff — over-smoothing. Repeated multiplication by is a form of Laplacian smoothing: as , converges (subject to connectivity conditions) toward a distribution proportional to node degree, regardless of the original node features — every node's representation collapses toward the same vector. This is why GCNs are typically shallow (2–4 layers): depth buys larger receptive field but linearly trades off against representational collapse, an intrinsic and well-documented limitation, not a training bug.
Worked example
Consider a tiny 4-node graph — a "triangle plus pendant" — with edges , i.e. nodes 1, 2, 3 form a triangle and node 4 hangs off node 3. Suppose 1-dimensional node features (say, a single scalar "activity score"):
Step 1: build .
Step 2: degrees of (row sums): .
Step 3: normalized propagation matrix . Using :
| entry | value |
|---|---|
Step 4: one layer of propagation, no transform yet (i.e. compute , taking , for clarity):
So . Notice the effect: node 3 (the bridge/hub, degree 4 including self-loop) absorbs a blended signal from the triangle and the pendant, becoming an intermediate value between them; nodes 1 and 2 converge toward the same value since they are symmetric in the graph (same neighbors); node 4's value shrinks from 4.0 toward 2.0 because it is pulled toward its only neighbor, node 3's original value of 0. This one-layer averaging is exactly the "smoothing along edges" behavior that, iterated too many times, causes over-smoothing — here after one layer it is a feature (local context injected), and this is the phenomenon to watch as grows.
Step 5: apply learned , bias, and ReLU to get , then repeat for a second layer to reach 2-hop context, then a final linear + softmax layer for node classification (e.g., "is this node an anomaly?").
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Forgetting self-loops in | Node loses access to its own features at each layer, hurting accuracy | Always use , not raw |
| Row-normalizing instead of symmetric-normalizing | Breaks the spectral-filter correspondence, destabilizes hub nodes | Use , not |
| Too many layers (e.g. 10+ for node classification) | Over-smoothing: node embeddings collapse toward a degree-weighted constant | Keep depth shallow (2–4 layers); use residual/skip connections or GNN variants designed for depth |
| Treating GCN as transductive-only when inductive generalization is needed | Original GCN formula bakes in a fixed for one graph; naive reuse on unseen nodes/graphs is ill-defined | Use inductive variants (e.g. GraphSAGE-style neighbor sampling) if new nodes/graphs appear at test time |
| Dense adjacency matrix in memory | storage explodes for large graphs even though | Always store (and ) in sparse format; use sparse matmul kernels |
| Ignoring class imbalance / label sparsity in semi-supervised node classification | Only a small train mask is labeled; loss dominated by frequent classes | Use masked loss correctly, consider weighted loss or graph-aware sampling |
Pioneers & history
The path to GCNs runs through spectral graph theory, then convolutional networks, then their fusion. Bruna, Zaremba, Szlam, and LeCun (2014) first proposed convolutions on graphs via the graph Laplacian's eigenbasis — theoretically elegant, but computationally expensive due to the required eigendecomposition. Defferrard, Bresson, and Vandergheynst (2016) introduced ChebNet, replacing the exact spectral filter with a truncated Chebyshev polynomial expansion, making the operation local and -scalable — this is the direct ancestor of the GCN layer. Thomas Kipf and Max Welling (2017) simplified ChebNet to first order, producing the now-canonical GCN propagation rule and demonstrating strong results on semi-supervised node classification with remarkably few labeled nodes, catalyzing the modern GNN research explosion. In parallel, the message-passing perspective was crystallized by Gilmer et al. (2017) with the Message Passing Neural Network (MPNN) framework for molecular property prediction, unifying GCN, GraphSAGE (Hamilton, Ying, Leskovec, 2017), and Graph Attention Networks (Veličković et al., 2018) as instances of a common aggregate-then-update template.
Origins · 1 minThomas N. Kipf and how this was discovered
Where this matters
GCN-style layers and their message-passing generalizations underpin production systems across many domains. Recommendation engines (e.g., large-scale systems inspired by PinSAGE, built on GraphSAGE) use GNNs over user–item interaction graphs to generate embeddings for candidate retrieval and ranking. Drug discovery and computational chemistry pipelines represent molecules as graphs of atoms and bonds, applying GNNs to predict properties like solubility, toxicity, and binding affinity — a use case central to Gilmer et al.'s original MPNN framing and to later systems like AlphaFold's use of graph-structured representations for protein modeling. Fraud and anomaly detection systems in finance model transactions as graphs (accounts as nodes, transfers as edges) and use GNNs to flag suspicious subgraphs via node/edge classification. Knowledge-graph completion and question-answering systems use GNN-style propagation to reason over entities and relations for link prediction. Traffic forecasting systems model road networks as graphs and use spatio-temporal GNNs (GCN layers combined with recurrent or temporal-convolution components) to predict congestion. In all these cases, the underlying justification is the same: whenever the natural relational structure of data is graph-shaped rather than grid- or sequence-shaped, message-passing/GCN-style layers provide the principled way to share learned representations across variably-connected entities, building directly on Message Passing on Graphs as the general computational template.
Ask the tutor: How would you modify the GCN propagation rule to make it inductive, so it generalizes to entirely new, unseen graphs at test time?
How is a GCN layer different from a standard fully connected (dense) layer?
A fully connected layer connects every input unit to every output unit with independently learned weights, ignoring any structure among inputs. A GCN layer instead restricts the "connections" used per node to its actual graph neighbors — encoded structurally via , not learned per-edge — and shares one weight matrix across all nodes, the same way a CNN filter is shared across spatial positions. So a GCN layer is closer to a highly structured, weight-tied linear layer preceded by a fixed (non-learned) neighbor-averaging step, rather than a fully general dense layer. This weight sharing plus structural sparsity is what lets GCNs generalize across graphs of different sizes and scale to large sparse graphs.
Can GCNs handle directed graphs or weighted edges?
Yes, with modification. The standard symmetric normalization assumes is symmetric (undirected graph); for directed graphs, variants use separate in-degree and out-degree normalizations, or replace with row-normalized , sacrificing the exact spectral-filter interpretation but retaining a valid, trainable propagation rule. Weighted edges are handled by simply letting contain real-valued weights instead of 0/1 entries before normalization; the degree matrix then sums those weights. Many practical GNN libraries support both extensions directly, since numerous real graphs (citation graphs with directed citations, transaction graphs with amounts) are naturally directed and/or weighted.
Is a GCN the same thing as a "graph neural network" in general?
No — GCN is one specific, historically important architecture within the broader family of GNNs. The general template is message passing: each node aggregates messages from neighbors using some permutation-invariant function and updates its state. GCN fixes that function to be a symmetric-normalized weighted sum derived from spectral graph theory. Other architectures choose different aggregators: GraphSAGE samples a fixed number of neighbors and supports mean/LSTM/pooling aggregators for inductive settings; Graph Attention Networks (GAT) learn per-edge attention weights instead of using fixed degree-based normalization; Graph Isomorphism Networks (GIN) use a sum-aggregator proven to be maximally expressive among message-passing schemes. GCN is best understood as the simplest, spectrally-motivated point in this design space.
Why does GCN work well with very few labeled nodes (semi-supervised learning)?
Because label information propagates through the graph structure itself, not just through features. Even a node with no informative raw features can receive a strong, useful representation if its neighbors (which may include labeled nodes) have informative features, since aggregation blends neighbor information at every layer. This effectively performs implicit label propagation and feature smoothing across the graph's edges, which is exactly the assumption underlying homophily: nodes connected by an edge tend to share similar labels. When homophily holds (as in citation networks, where papers cite similar-topic papers), a small number of labeled nodes can supervise the whole graph's representations through this propagation, which is why Kipf and Welling's original results needed strikingly few labels per class.
What happens if the input graph is disconnected, i.e. has multiple connected components?
Each connected component is processed independently by the propagation matrix , since has block-diagonal structure aligned with the components (no nonzero entry connects nodes in different components). Information never crosses between components regardless of depth , since the graph itself provides no path. This is expected and often desired behavior — a GCN cannot and should not invent relationships that don't exist in the input graph — but it does mean node classification accuracy for very small or isolated components depends entirely on their own (possibly sparse) local features and any labels available within that component.
Does adding more GCN layers always help, analogous to deeper CNNs improving image classification accuracy?
No, and this is one of the clearest divergences between GNNs and CNNs. Deeper CNNs generally help (up to diminishing returns and training difficulties addressed by residual connections) because deeper spatial hierarchies capture increasingly global image structure without any smoothing collapse. Deeper GCNs, by contrast, suffer from over-smoothing: repeated averaging over neighbors is mathematically a smoothing operator whose fixed point discards almost all node-specific information. Practical GCNs are usually shallow (2–4 layers); techniques like residual/skip connections, jumping-knowledge aggregation across layers, or normalization tricks are active mitigations, but the tension is architectural, not merely a matter of "needing more compute or data."
How do GCNs relate to embeddings learned by earlier methods like node2vec or DeepWalk?
Both produce vector embeddings per node, but the mechanisms and supervision differ fundamentally. node2vec/DeepWalk learn embeddings unsupervised, purely from graph structure, by treating random walks over the graph as "sentences" and applying a skip-gram-style objective — no node features are used at all. A GCN, by contrast, is typically trained end-to-end with a downstream task loss (e.g. node classification), directly incorporates node features into every layer, and produces embeddings that are simultaneously structure-aware and feature-aware. In practice, GCN embeddings tend to outperform random-walk embeddings when informative node features exist, while random-walk methods remain useful when only graph topology is available.