Graph Neural Networks

Learn how GNNs read graph-structured data — nodes, edges, and features — by passing messages between neighbours, and build one message-passing step by hand in plain Python before meeting GCN, GraphSAGE, and GAT in PyTorch Geometric.

Learn Graph Neural Networks in our free AI & Machine Learning course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…

Part of the free AI & Machine Learning course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

🌍 Real-World Analogy: Learning From Your Friends

Want to guess someone's politics, music taste, or whether they will repay a loan? You learn a surprising amount just from who they spend time with . "You are the average of the five people you hang out with" is folk wisdom — and it is almost exactly how a GNN thinks.

Picture a friendship network. Each person holds a few facts about themselves (their features ). In one "round of gossip", everyone updates their own view by averaging in what their direct friends think . Do it again and your friends-of-friends start to influence you too. After a few rounds, your updated profile quietly encodes your whole local neighbourhood — not just you. That round of gossip is message passing , and stacking rounds is exactly what stacking GNN layers does.

1 Graph Data — Nodes, Edges, Features, Adjacency

A graph is data made of nodes (the things — people, atoms, products) joined by edges (the relationships — friendships, bonds, "bought together"). Unlike a table, where every row is independent, a graph's whole point is that the connections carry meaning.

🗂️ The simplest way to store a graph in code

An adjacency dict — every example below uses this:

2 Message Passing — The One Idea Behind Every GNN

Almost every GNN is a stack of message-passing layers, and each layer does the same three steps for every node:

After one layer, a node's new embedding reflects its 1-hop neighbourhood. Stack a second layer and it reaches 2 hops (friends of friends); k layers reach k hops. That is the entire trick — aggregate, then transform, then repeat.

The worked example below does step 1 and step 2 in plain Python: each node's new feature is simply the average of its neighbours' features . No NumPy, no PyTorch — just a dictionary and a loop. Read every comment, then run it.

Run the same averaging rule several times and watch a single signal spread across the graph — and start to flatten out. That flattening is a clue to a real GNN pitfall you will meet in Common Errors.

3 GCN, GraphSAGE, and GAT — The Three You Must Know

All three are message-passing layers. They differ in how they aggregate neighbours :

Takes a degree-normalised average of all neighbours. The simplest layer and a strong baseline — it is exactly the averaging you coded above, with a normalisation that stops high-degree nodes from dominating.

Samples a fixed number of neighbours instead of using all of them. That makes it scale to graphs with billions of edges and lets it handle nodes it never saw in training ( inductive learning).

Learns an attention weight for each neighbour, so important neighbours count more. Not every friend should count equally — GAT figures out who matters, the same idea that powers Transformers.

In practice you rarely hand-code these. PyTorch Geometric (PyG) ships GCNConv , SAGEConv , and GATConv as drop-in layers. The next example is read-only PyG showing a single GCNConv doing aggregate-then-transform for you.

4 Three Task Levels — Node, Edge, and Graph

Once message passing has given every node a rich embedding, you can predict at three different levels:

Level

Question

Example

How

Node

What is this node?

Flag a fraudulent account

Classify each node embedding

Edge

Is there a link?

Recommend a friend; predict a bond

Score pairs of node embeddings

Graph

What is the whole graph?

Is this molecule toxic?

Pool all nodes into one vector (a "readout")

The read-only example below uses a GATConv (attention) layer and then produces all three: per-node logits, per-edge scores, and one graph-level vector from mean pooling .

🚀 Where GNNs Win in the Real World

GNNs shine wherever relationships matter as much as the items themselves :

Now you try. Fill in the blanks marked ___ . Gathering a node's neighbours and averaging them is message passing — get this and you have the heart of every GNN.

Your turn again. Real GNN layers include each node in its own neighbourhood — a self-loop — so a node never forgets its own features. Add it here.

5 Common Errors (And How to Fix Them)

These four mistakes trip up almost everyone building their first GNN:

Stack too many message-passing layers and every node keeps averaging in more of the graph until all embeddings collapse to nearly the same vector — the model can no longer tell nodes apart and accuracy crashes.

✅ Fix: keep it shallow (2-4 layers); add residual / jumping-knowledge connections only if you truly need depth.

If you aggregate only over neighbours, a node throws away its own features in every layer and loses its identity.

✅ Fix: add a self-loop — include the node in its own neighbourhood. PyG's GCNConv does this automatically.

Full-batch GCN on a graph with millions of nodes builds an enormous adjacency operation and runs out of memory.

✅ Fix: sample neighbourhoods in mini-batches (GraphSAGE-style) with a neighbour loader.

Mean aggregation throws away how many neighbours a node has — yet for tasks like counting substructures in molecules, degree is the signal. Mean can't distinguish "1 neighbour" from "100 identical neighbours".

✅ Fix: match the aggregator to the task — sum (GIN) preserves count and is most expressive; mean and max suit others.

📋 Quick Reference

Concept

What it is

Use when

Adjacency

Who connects to whom (dict or matrix)

Describing any graph's structure

Message passing

Gather, aggregate, transform neighbours

The core of every GNN layer

GCN

Degree-normalised mean of neighbours

Strong, simple baseline

GraphSAGE

Sample neighbours; inductive

Huge / changing graphs

GAT

Attention-weighted neighbours

Some neighbours matter more

Self-loop

Node aggregates itself too

Always — keep a node's own features

Readout / pooling

Pool nodes into one graph vector

Graph-level tasks (e.g. molecules)

❓ Frequently Asked Questions

🎯 Mini-Challenge: Two Rounds of Message Passing

Time to fly with less scaffolding. The starter gives you a line graph and a comment outline — write the step() function yourself and apply it twice. Remember the self-loop: average each node together with its neighbours.

Lesson 46 complete — you can now reason about graphs the way a GNN does!

You can describe a graph with nodes, edges, features, and an adjacency dict; you have hand-coded a message-passing step (average of neighbours) and watched information spread; you can explain how GCN, GraphSAGE, and GAT aggregate differently; you know the node-, edge-, and graph-level tasks; and you can spot the classic traps — over-smoothing, missing self-loops, scalability, and the wrong aggregator.

🚀 Up next: AutoML & NAS — let algorithms search for the best model and architecture for you.

Practice quiz

What kind of data does a graph neural network operate on?

  • Fixed grids like images only
  • Sequences of text only
  • Nodes joined by edges, where nodes and edges can carry features
  • Single independent rows in a table

Answer: Nodes joined by edges, where nodes and edges can carry features. A GNN works on graph-structured data: nodes (entities) joined by edges (relationships), each carrying features.

What are the three steps of message passing in a GNN layer?

  • Gather neighbours' features, aggregate them, then transform
  • Encode, attend, decode
  • Split, shuffle, merge
  • Normalise, dropout, activate

Answer: Gather neighbours' features, aggregate them, then transform. Each layer gathers neighbour messages, aggregates them (mean/sum/max), then transforms with a learnable weight and non-linearity.

After one message-passing layer, a node's embedding reflects information from how far away?

  • The whole graph
  • Only itself
  • Exactly 5 hops
  • Its 1-hop neighbours

Answer: Its 1-hop neighbours. After one layer a node knows its 1-hop neighbourhood; stacking k layers reaches k hops.

How does a GCN aggregate neighbours?

  • By sampling a fixed number of neighbours
  • A degree-normalised average of all neighbours
  • With a learned attention weight per neighbour
  • By taking the maximum only

Answer: A degree-normalised average of all neighbours. GCN takes a degree-normalised average of all neighbours — a simple, strong baseline.

What makes GraphSAGE different from GCN?

  • It samples a fixed number of neighbours, so it scales and is inductive
  • It uses attention weights
  • It ignores node features
  • It only works on small graphs

Answer: It samples a fixed number of neighbours, so it scales and is inductive. GraphSAGE samples a fixed number of neighbours, letting it scale to huge graphs and generalise to unseen nodes (inductive).

What does a GAT layer add over a GCN?

  • Self-loops only
  • A larger learning rate
  • A learned attention weight for each neighbour
  • Random neighbour dropout

Answer: A learned attention weight for each neighbour. GAT learns an attention weight per neighbour, so important neighbours count more than unimportant ones.

Which is an example of a node-level task?

  • Deciding whether a whole molecule is toxic
  • Flagging a fraudulent account
  • Predicting whether two users will connect
  • Pooling all nodes into one vector

Answer: Flagging a fraudulent account. Node-level tasks label individual nodes — for example flagging a fraudulent account in a transaction graph.

How do you get a single prediction for a whole graph (graph-level task)?

  • Take the first node's embedding
  • Use only the edges
  • Ignore the node features
  • Pool (read out) all node embeddings into one vector

Answer: Pool (read out) all node embeddings into one vector. Graph-level tasks pool all node embeddings into one vector (a readout), e.g. via global mean pooling.

What is over-smoothing in a GNN?

  • Using too few features
  • Stacking too many layers until all node embeddings collapse toward the same value
  • Adding too many self-loops
  • Sampling too few neighbours

Answer: Stacking too many layers until all node embeddings collapse toward the same value. Over-smoothing happens when too many layers make every node embedding converge, so nodes become indistinguishable.

Why add a self-loop in message passing?

  • To remove the node's own features
  • To make the graph directed
  • So a node keeps its own features instead of losing its identity each layer
  • To speed up training

Answer: So a node keeps its own features instead of losing its identity each layer. A self-loop includes the node in its own neighbourhood so its original features are preserved in the aggregate.

Continue this course

Related lessons