Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
Message
passing.
Images are grids. Sentences are sequences. But a molecule, a social network, a citation web — those are graphs, with no fixed order and every node wired to a different number of others. A convolution has nothing to slide over. Graph neural networks answer with a beautifully simple idea: let every node whisper its features to its neighbors, and update itself by listening. Do it once and a node knows its neighbors; do it twice and it knows their neighbors too. The Graph Convolutional Network boiled this down to a single matrix multiply — a degree-normalized adjacency times the features. Here's that layer, self-loops and all, worked out and runnable.
What changes here: the data has no canonical order
Rung 1, so the comparison is against the two architectures that dominate the rest of this site. A convolution assumes a grid: every pixel has the same number of neighbours, in fixed relative positions. A transformer assumes a sequence, or at least a set whose elements can be given positions. A graph offers neither — a node may have two neighbours or two thousand, there is no "left" neighbour, and relabelling the nodes must not change the answer.
The change is to define the layer in terms of the only structure that survives those constraints: each node updates itself from an order-independent aggregate of its neighbours. One layer mixes a node's own features with a pooled summary of the features one hop away and applies a learned transform. Stack k layers and each node's representation depends on everything within k hops, which is the graph analogue of a receptive field.
The specific choices in this rung's layer are where the content is, and both are corrections to the naive version. Aggregating over neighbours alone loses the node itself, so self-loops are added to the adjacency before aggregating. And summing over neighbours means a node with a thousand edges produces activations orders of magnitude larger than a node with two, so the adjacency is normalised by degree symmetrically — each edge's contribution is scaled down by the degrees at both of its ends, which keeps the scale of the representation comparable across a graph with wildly uneven connectivity.
Learning on a graph
Convolutional networks exploit the grid of an image; recurrent nets and Transformers exploit the order of a sequence. But an enormous amount of real data has neither. A molecule is atoms joined by bonds; a citation network is papers linked by references; a social graph is people connected by follows. These are graphs — sets of nodes joined by edges — and they have no canonical ordering and no fixed neighborhood size. There's nothing for a convolution to slide over.
A graph neural network learns on this structure directly. The Graph Convolutional Network (Kipf & Welling, 2017) is the canonical, minimal form. Its promise: give each node a feature vector and a list of neighbors, and it produces a new feature vector per node that blends in information from the neighborhood — useful for classifying nodes, predicting missing edges, or scoring an entire graph. The whole thing reduces to one operation applied over and over: a node updates itself from its neighbors.
A GCN layer updates every node by taking a degree-normalized weighted average of its neighbors' features (plus its own, via self-loops), then applying a learned linear map and nonlinearity: H' = σ( H W) with  = D̃^(-1/2)(A+I)D̃^(-1/2).
Message passing
The engine of every GNN is message passing. In one layer, each node gathers messages from its neighbors, aggregates them into a single summary, and combines that with its own current feature to produce a new one. For a GCN the aggregation is a normalized weighted sum; other GNNs use a max, a mean, or an attention-weighted sum, but the shape is always the same: gather, aggregate, update.
Because every node runs the same shared function, the network handles graphs of any size and any connectivity — a property images and sequences don't demand. And depth buys reach:
Stacking layers grows the receptive field one hop at a time. But there's a tension: go too deep and repeated averaging causes over-smoothing, where every node's feature converges to nearly the same value and the network loses its ability to tell nodes apart. Most GCNs are shallow — two or three layers — for exactly this reason.
Self-loops and normalization
Two details separate a naive "sum my neighbors" from a GCN layer that actually trains, and both live inside the normalized adjacency Â:
The self-loops matter because without A + I a node's new feature would be built only from its neighbors, discarding what it already knew. The symmetric normalization matters because raw neighbor-sums are dominated by high-degree hubs — a node with 1,000 neighbors would accumulate a huge vector and drown out low-degree nodes, and repeated layers would make magnitudes explode or vanish. Dividing each edge weight by √(dᵢ·dⱼ) turns every aggregation into a stable, degree-balanced average that behaves well no matter the graph's structure. That single normalized matrix is the whole trick.
The GCN layer
One layer of a Graph Convolutional Network is a single line:
H⁽ˡ⁾ is the node-feature matrix at layer l;  mixes each node with its neighbors (normalized); W⁽ˡ⁾ is the learned weight; σ is a nonlinearity (ReLU). ·H is the message passing; ·W and σ are an ordinary neural layer on top.
Read right to left, it does exactly three things:  H replaces every node's feature with the degree-normalized average of its neighborhood (the message passing); · W applies a learned linear transformation shared across all nodes; and σ adds a nonlinearity. Stack two of these with a softmax on top and you have a node classifier that, on citation benchmarks, beat far more complicated methods. The runnable version below builds  from a raw adjacency and propagates features one hop — the entire heart of the model in a few lines of plain Python.
How Kipf and Welling derived and trained the GCN, step by step
Kipf and Welling's paper does two things: it derives a cheap graph convolution from spectral graph theory, and it uses that layer for semi-supervised node classification, where only a few nodes carry labels. The derivation is short enough to follow step by step.
- Start from a spectral filter. On a graph with adjacency A and degree matrix D, the normalized Laplacian L = I − D^−1/2AD^−1/2 has eigendecomposition UΛU^T. A spectral convolution filters a signal x as U gθ(Λ) U^Tx, which needs the eigenvectors: O(N³) to compute and O(N²) per multiplication.
- Approximate with Chebyshev polynomials. Following Hammond and colleagues and the ChebNet model of Defferrard and colleagues, the filter is truncated to a K-th order polynomial in a rescaled Laplacian. No eigenvectors are needed, the cost is linear in the number of edges, and each filter reaches at most K hops.
- Keep only first order. Set K = 1 and approximate the largest eigenvalue by 2, trusting the network's parameters to adapt to the scale. The filter becomes θ′0x − θ′1D^−1/2AD^−1/2x, and depth, not polynomial order, now controls how far information travels.
- Share one parameter. Setting θ = θ′0 = −θ′1 curbs overfitting and gives θ(I + D^−1/2AD^−1/2)x.
- Renormalize. The operator I + D^−1/2AD^−1/2 has eigenvalues between 0 and 2, so stacking it can make activations and gradients explode or vanish. The renormalization trick replaces it with D̃^−1/2ÃD̃^−1/2, where à = A + I adds self-loops and D̃ is Ã's degree matrix; its eigenvalues are at most 1.
- Generalize to feature matrices. With C input channels and F filters, a layer computes ÂXΘ with  = D̃^−1/2ÃD̃^−1/2. As a sparse-dense product it costs O(|E|FC).
- Build the classifier. Compute  once, in preprocessing. The model is Z = softmax( ReLU(ÂXW^(0)) W^(1)): two layers, so each prediction depends on a node's two-hop neighborhood.
- Train on the labeled nodes only. The loss is cross-entropy over the labeled nodes, but every node's features flow through Â, which is what makes the method semi-supervised. Training is full-batch gradient descent with Adam: learning rate 0.01, up to 200 epochs, early stopping after 10 epochs without improvement in validation loss, dropout 0.5, L2 regularization of 5 × 10^−4 on the first layer, 16 hidden units and row-normalized input features.
The evaluation protocol
The citation datasets (Citeseer, Cora, Pubmed) use the split from Yang and colleagues' Planetoid paper: 20 labeled nodes per class for training, 500 nodes for validation and hyperparameter tuning, and 1,000 for testing. Documents are nodes, citations are treated as undirected edges and bag-of-words vectors are the features. NELL, a knowledge-graph dataset, used larger hidden layers and lighter regularization.
Where this sits in message passing
Seen from outside spectral theory, the layer is one round of message passing: each node takes a fixed, degree-normalized weighted sum of its own and its neighbors' transformed features. Later models change the aggregation, with learned attention weights in GAT or a sum followed by an MLP in GIN, and add a pooling readout over nodes when the task is to classify a whole graph.
From spectral filter to the renormalized GCN layer
Tk— Chebyshev polynomials (T0 = 1, T1(x) = x, Tk = 2xTk−1 − Tk−2);L̃— the Laplacian rescaled so its eigenvalues lie in [−1, 1].θ′k— the filter's coefficients: one per hop in ChebNet, a single shared θ after the simplification.Ã, D̃— adjacency with self-loops and its degree matrix; the result has 1/√(d̃id̃j) for each edge and 1/d̃i on the diagonal.Y, Z— one-hot labels and the softmax output; only labeled rows enter the loss.
At the extremes: a large K lets one layer see K hops at the price of more parameters per filter, while K = 1 relies on stacking layers. Without renormalization, ten stacked layers could amplify one component of the signal by up to 2^10, before weights and nonlinearities; with it, no component grows, since the largest eigenvalue is 1.
What the GCN paper measured on Cora, Citeseer, Pubmed and NELL
The paper compared against label propagation, manifold regularization, semi-supervised embedding, iterative classification, DeepWalk and Planetoid on the three citation graphs and on NELL, using the Planetoid splits described above. Accuracy is on 1,000 test nodes, averaged over 100 runs with random weight initializations.
| Method | Citeseer | Cora | Pubmed | NELL |
|---|---|---|---|---|
| Planetoid (best variant per dataset) | 64.7 | 75.7 | 77.2 | 61.9 |
| GCN | 70.3 | 81.5 | 79.0 | 66.0 |
| GCN, random splits | 67.9 | 80.1 | 78.9 | 58.4 |
The GCN also trained quickly, in seconds on the citation graphs and under a minute on NELL, with the whole graph in memory. The more informative experiment compared propagation rules with everything else held fixed:
| Propagation rule | Citeseer | Cora | Pubmed |
|---|---|---|---|
| Chebyshev filter, K = 3 | 69.8 | 79.5 | 74.4 |
| Renormalized first order (the GCN) | 70.3 | 81.5 | 79.0 |
| MLP, no graph | 46.5 | 55.1 | 71.4 |
The renormalized first-order rule beat both the higher-order Chebyshev filters and the unrenormalized first-order variants, so the simplification, once renormalized, cost no accuracy. The MLP row measures what the graph itself contributes: 26 points on Cora. On the negative side, a depth experiment with 1 to 10 layers found the best results at two or three, and without residual connections models deeper than about seven layers became hard to train. Full-batch training also needs the whole graph in memory, which the authors flag as a limit for large graphs.
What the authors admit, and what happened next
Two limits are visible in the paper's own setup. It is transductive: the normalised adjacency of the whole graph is part of the computation, so a node that did not exist at training time cannot simply be handed to the model — which is a poor fit for the social and commerce graphs the method is most wanted for. And the networks are shallow, typically two layers, which is the opposite of the pattern everywhere else in deep learning.
The reason for the shallowness turned out to be a real phenomenon rather than a tuning accident. Repeatedly averaging each node with its neighbours is, viewed from far enough away, a diffusion process, and it converges: stack enough layers and every node's representation approaches the same value. Over-smoothing means depth on a graph has a cost that depth on an image does not, and the fixes — residual connections, explicitly keeping the initial features, normalising per node — are patches on it rather than a solution.
How it held up: the frame held and the specific layer did not. GraphSAGE made the method inductive by sampling a fixed-size neighbourhood, so unseen nodes work; attention-based variants replaced the fixed degree normalisation with learned per-edge weights, which is worth noting for a reason beyond graphs — a transformer is exactly that layer on a complete graph, so the attention paper and this one are two points in one design space rather than two fields. Later work also bounded what any message-passing network can distinguish, showing the standard layer cannot tell apart certain non-isomorphic graphs however wide or deep it is, which is an unusually crisp expressiveness limit.
A GCN layer, from scratch
The GCN layer is  H W. Here's the  H part — the actual message passing — with no framework: add self-loops (A + I), compute degrees, build the symmetric-normalized adjacency (divide each edge by √(dᵢ·dⱼ)), and propagate features one hop. On a two-node graph, node 0's feature becomes the average of the two node features: message passing smooths features across edges. Change the adjacency or the features and watch the mixing.
Write the body of normalize. Everything else in the file is
already written; this is the function the paper is about. Press ▶ Run to check it against
the 5 assertions below — they start red.
- Adding self-loops is A + I -- a node keeps its own feature
- The degree (with self-loops) counts neighbors plus itself
- Symmetric normalization divides each edge by sqrt(d_i*d_j) = 1/sqrt(2*2) = 0.5
- For a regular graph the normalized row sums to 1 (a stable weighted average)
- Propagation MIXES neighbor features -- node 0 becomes the average of 1 and 3 = 2
Graphs are everywhere
Because so much important data is a graph, GNNs became a standard tool across science and industry. They predict molecular properties for drug discovery, estimate travel times in mapping apps by treating road networks as graphs, catch fraud in transaction networks, power recommendation systems over user–item graphs, and simulate physical systems of interacting particles. Anywhere relationships matter as much as the entities, message passing is the natural model.
There's also a deep connection to the rest of modern AI. Self-attention — the core of the Transformer — is essentially message passing on a fully connected graph, where the attention weights play the role of learned edge strengths. Graph attention networks made that link explicit, and the two families remain mathematically close cousins. Meanwhile the "learn node representations" goal connects back to embedding methods like word2vec (node2vec and DeepWalk borrow its skip-gram idea for graphs). The GCN's clean, degree-normalized layer is still the clearest doorway into all of it — one matrix multiply that taught neural networks to reason over structure.
The symmetric normalization D̃^(-1/2)(A+I)D̃^(-1/2) isn't the only option. A simpler "random-walk" normalization D̃^(-1)(A+I) — divide each row by that node's own degree — gives a plain average of neighbors and is sometimes used instead. The symmetric form the GCN chose has a nice spectral interpretation (it approximates a first-order graph-Laplacian filter) and tends to train a little more stably, which is why it became the default.
Questions people ask about Graph Neural Networks
What is the GCN renormalization trick?
It is the last step of Kipf and Welling's derivation. Simplifying a spectral graph filter to first order with one shared parameter gives the operator I + D^(-1/2) A D^(-1/2), whose eigenvalues lie between 0 and 2, so applying it layer after layer can blow up or shrink activations and gradients. The trick adds self-loops to the adjacency first, Ã = A + I, recomputes degrees from Ã, and uses D̃^(-1/2) Ã D̃^(-1/2) instead. Its largest eigenvalue is 1, so stacked layers stay numerically stable. In the paper's comparison of propagation rules, the renormalized version was the most accurate on all three citation datasets.
What is the message passing neural network formula?
In the general framework described by Gilmer and colleagues in 2017, each node v first forms a message by aggregating over its neighbors u, m_v = Σ M(h_v, h_u, e_uv), where M is a learned message function and e_uv are edge features, then updates its state with h_v' = U(h_v, m_v). After several rounds, a readout function pools the node states into a graph-level vector when the task concerns the whole graph. The GCN layer is a special case: the message is the neighbor's feature vector times a shared weight matrix, scaled by 1/√(d_u d_v), the node's own vector enters through a self-loop, and the update is a nonlinearity.
Why do graph convolutional networks use only two layers?
Two layers give every node a two-hop receptive field, which was enough for the citation benchmarks, and the paper's depth experiments found little to gain beyond that. Testing models from 1 to 10 layers in a cross-validation run that used all labels, Kipf and Welling found the best accuracy at two or three; beyond about seven layers, training without residual connections became difficult, and overfitting became more of a risk as depth added parameters. Later analysis tied part of the problem to over-smoothing: repeated neighborhood averaging pushes node representations toward one another, so very deep GCNs lose the differences between the nodes they were meant to classify.
How is a GCN trained for semi-supervised node classification?
All node features and the normalized adjacency go into the model at once, but the cross-entropy loss is computed only on the labeled nodes. Because each layer mixes a node with its neighbors, gradients from those few labels shape the representations of unlabeled nodes too. In the paper's setup on Cora, Citeseer and Pubmed there are 20 labels per class, 500 validation nodes and 1,000 test nodes. Training is full-batch: Adam with a learning rate of 0.01, up to 200 epochs with early stopping after 10 epochs without improvement in validation loss, dropout of 0.5, L2 weight decay of 5 × 10^-4 on the first layer, and 16 hidden units.
How is GCN different from GAT and GIN?
All three are message-passing networks; they differ in how neighbors are weighted and combined. A GCN uses fixed weights set by node degrees, 1/√(d_i d_j) for each edge. Graph attention networks, from Veličković and colleagues in 2018, learn a weight for each edge from the two nodes' features with an attention mechanism, so important neighbors can count more. Graph isomorphism networks, from Xu and colleagues in 2019, sum neighbor features without normalization and pass the result through an MLP; they showed this makes the layer as discriminative as the Weisfeiler-Lehman graph isomorphism test, which degree-normalized aggregation like the GCN's is not.
What are the Cora, Citeseer and Pubmed splits used by the GCN paper?
They are the splits from Yang, Cohen and Salakhutdinov's Planetoid paper, which the GCN paper adopted so results could be compared directly. Each citation graph treats documents as nodes, citations as undirected edges and bag-of-words vectors as features. Cora has 2,708 nodes, 5,429 edges and 7 classes; Citeseer 3,327 nodes and 6 classes; Pubmed 19,717 nodes and 3 classes. Training uses only 20 labeled nodes per class, a label rate of 5.2 percent on Cora and 0.3 percent on Pubmed, with 500 nodes for validation and 1,000 for testing. The paper also reports results on random splits, which come out somewhat lower.
4 questions to test what you just read
Work each one out before you open it — a number, a prediction or a reason, not a definition. Producing the answer is what makes it stick. Your progress is kept on this device.
Node v has neighbors a and b. With self-loops, v's degree is 3, a's is 4 and b's is 3. What weights does the renormalized adjacency give v, a and b when updating v, and do they sum to 1?
Show answer
Each entry is 1/√(d̃vd̃u): v itself gets 1/√(3·3) = 1/3, a gets 1/√(3·4) = 1/√12 ≈ 0.289, and b gets 1/3. They sum to about 0.955, not 1: symmetric normalization is not an average, which is the price of keeping the matrix symmetric.
Why can't you simply stack ten layers of θ(I + D^-1/2 A D^-1/2) without the renormalization trick?
Show answer
That operator's eigenvalues run from 0 to 2, so ten applications can scale one component of the signal by up to 2^10 = 1,024 (before weights and nonlinearities), making activations and gradients unstable. After renormalization the largest eigenvalue is 1, so repeated application cannot amplify any component.
With 20 labeled nodes per class, how many labeled nodes does Cora's training set have, and what share of its 2,708 nodes is that?
Show answer
Cora has 7 classes, so 7 × 20 = 140 labeled nodes, or 140 / 2,708 ≈ 5.2%. The other 94.8% of nodes still feed their features through the normalized adjacency, which is how the few labels reach them.
In the figure's graph, which nodes can influence v's output in a two-layer GCN, and what would change if a node f hung off c?
Show answer
Two layers reach two hops: v's own features, its neighbors a and b, and their neighbors c, d and e, so all six nodes count. A node f attached to c would be three hops away and could not affect v's prediction at all unless a third layer were added.
Structure, not sequence
What to do when the data is a graph rather than a line of tokens — message passing, and the protein-folding system built on it.
- 1Graph Neural Networks2017
- 2AlphaFold 22021
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Paper Breakdowns
- Attention Is All You NeedThe paper that introduced the Transformer — the architecture underneath GPT, BERT, Claude, and Gemini. Self-attention, Query/Key/Value, multi-head attention, positional encoding, and the real BLEU results, explained in depth with analogies and animated visuals, not just equations.Read →
- Word2VecHow words became math. Dense word vectors learned from the company a word keeps, so king − man + woman lands near queen. Skip-gram, CBOW, the distributional hypothesis, and the origin of every embedding model powering search and RAG.Read →
- ResNetThe skip connection that made deep learning deep. Why stacking layers made networks worse, and how residual learning let them reach 150+ layers and win ImageNet — the identity shortcut every Transformer still uses inside every block.Read →
- Vision Transformer (ViT)When an image became 16×16 words. Cut a picture into patches, treat each patch as a token, and feed them to a plain Transformer — no convolutions. Patch embeddings, the class token, and why data scale lets attention beat the CNNs that ruled computer vision for a decade.Read →
- Latent DiffusionThe paper behind Stable Diffusion. Compress an image into a small latent space with a VAE, run the diffusion denoising there instead of on pixels for a tens-of-times efficiency win, and steer it with text via cross-attention. The architecture that put text-to-image generation on consumer GPUs.Read →
- DDPMThe original denoising diffusion math — the paper that made diffusion work. A fixed forward process adds Gaussian noise over many steps; a U-Net learns the reverse. The masterstroke: train the network to simply predict the added noise with a plain MSE loss. The foundation under every modern image generator.Read →