AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Graph Neural Networks: AI on Structured Data

📚 Deep Learning⏱️ 20 min read🎓 Grade 11
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 20 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

Every UPI payment app runs a fraud model in the background before your transfer clears. The naive version of that model looks at one account at a time: how many transactions this month, average amount, how old the account is. A convolutional network can't help here — there's no grid of pixels. A recurrent network can't help either — there's no single time-ordered sequence per account, only a tangle of who-paid-whom across millions of accounts. What actually catches a mule-account ring — three or four accounts that receive money and immediately forward it onward in a tight loop — is the shape of the connections between accounts, not any one account's own statistics. A ring where money enters at A, passes through B and C, and exits through D looks completely ordinary if you inspect each account in isolation. It only becomes visible when a model can see who is connected to whom, and can propagate information along those connections. That is exactly the problem a graph neural network (GNN) is built to solve: learning from data whose only fixed structure is a set of nodes and the edges between them.

Why grids and sequences run out

A convolutional layer works because every pixel has the same neighborhood shape — a 3×3 or 5×5 window of surrounding pixels, always in the same relative positions. A recurrent layer works because every token has exactly one predecessor and one successor. Both architectures bake in an assumption about structure: fixed-size, regularly-arranged neighborhoods. A graph refuses both assumptions. Formally, a graph is a pair G = (V, E): a set of nodes V (accounts, users, atoms, web pages, road junctions) and a set of edges E connecting pairs of them (transactions, follows, chemical bonds, hyperlinks, roads). Two things make this genuinely different from a grid or a sequence. First, nodes have no natural ordering — there's no "first" UPI account the way there's a first word in a sentence. Second, every node can have a different number of neighbors: one account might have two counterparties, another might have three hundred. Feed an adjacency matrix straight into a plain multilayer perceptron and you inherit a fatal flaw — permute the row/column order of the same graph (just relabel the accounts) and the MLP's output changes, even though nothing about the underlying network of transactions changed at all. A GNN's every operation has to be indifferent to how you happen to number the nodes. That single requirement — permutation invariance of the model with respect to node ordering — is the design constraint that produces the entire GNN architecture.

The standard bookkeeping for a graph's connectivity is the adjacency matrix A, an n×n matrix (for n nodes) where Aij = 1 if an edge connects node i and node j, else 0. The degree of a node is its number of neighbors — deg(v) = number of edges touching v. Every node also carries a feature vector: for a UPI account this might be transaction volume, account age, KYC tier; for a molecule's atom it might be atomic number and charge. A GNN's job is to combine each node's own features with its neighbors' features, and its neighbors' neighbors' features, layer by layer, until the resulting vector — the node's embedding — encodes not just what the node is, but what it's connected to.

The message-passing mechanism

Every mainstream GNN layer — Graph Convolutional Networks (Kipf & Welling, 2017), GraphSAGE (Hamilton et al., 2017), Graph Attention Networks — follows the same two-step recipe, called message passing. For a node v at layer k:

1. AGGREGATE. Collect the embeddings of v's immediate neighbors from the previous layer and combine them into a single vector using a function that doesn't care about neighbor order — sum, mean, or max are the usual choices, precisely because those functions give the same answer no matter what order you feed the inputs in.

2. UPDATE. Combine that aggregated neighbor-vector with the node's own previous-layer embedding, pass the result through a learned linear transformation and a non-linearity, and that becomes the node's new embedding.

Written as an equation for the mean-aggregation variant used below:

h_v^(k) = ReLU( W_self · h_v^(k-1)  +  W_neigh · MEAN({ h_u^(k-1) : u in N(v) }) )

where N(v) is the set of nodes directly connected to v, and W_self, W_neigh are weight matrices learned by gradient descent, shared across every node in the graph — the same two matrices are used whether you're updating account A or account Z, which is what lets the model generalize to graphs of any size, and even to nodes it never saw during training. Stack k of these layers and a node's final embedding is influenced by everything within k hops of it. That's worth sitting with: one layer only reaches direct neighbors. Reaching a neighbor's neighbor takes two layers. This becomes important below.

Worked example: one message-passing layer on a transaction graph

Take a toy version of the UPI fraud scenario: four accounts, A, B, C, D, with transactions forming the edges A–B, A–C, B–C, and C–D. Notice the triangle A–B–C — three accounts all paying each other — sitting next to D, which only ever transacts with C. A triangle among otherwise unremarkable accounts is exactly the shape a circular mule ring produces. Give each account a two-dimensional feature vector (say, a KYC-tier flag and a high-transaction-velocity flag, both scaled to [0,1]):

x_A = [1, 0]
x_B = [0, 1]
x_C = [1, 1]
x_D = [0, 0]

First, a sanity check on the graph itself before touching the model: degree(A)=2, degree(B)=2, degree(C)=3, degree(D)=1. Every edge contributes to two nodes' degree counts, so the degrees must sum to twice the edge count — 2+2+3+1 = 8, and the graph has 4 edges, 2×4 = 8. It checks out, which means the adjacency list below is internally consistent before a single number gets multiplied.

Now run one layer of message passing with W_self = W_neigh = 0.5·I (each weight matrix just halves its input — chosen here purely so the arithmetic is checkable by hand; a trained model would learn different values). For node C, whose neighbors are A, B, and D:

Step 1 — gather neighbor features:
  x_A = [1, 0]   x_B = [0, 1]   x_D = [0, 0]

Step 2 — aggregate (mean of the three):
  mean = ([1,0] + [0,1] + [0,0]) / 3 = [0.333, 0.333]

Step 3 — combine with C's own features and transform:
  h_C = ReLU( 0.5 · [1, 1]  +  0.5 · [0.333, 0.333] )
      = ReLU( [0.5, 0.5] + [0.1665, 0.1665] )
      = ReLU( [0.667, 0.667] )
      = [0.667, 0.667]

The same three steps for A (neighbors B, C), B (neighbors A, C), and D (neighbor C only) give:

h_A = ReLU(0.5·[1,0] + 0.5·mean([0,1],[1,1])) = ReLU([0.5,0]+[0.25,0.5]) = [0.75, 0.5]
h_B = ReLU(0.5·[0,1] + 0.5·mean([1,0],[1,1])) = ReLU([0,0.5]+[0.5,0.25]) = [0.5, 0.75]
h_D = ReLU(0.5·[0,0] + 0.5·mean([1,1]))       = ReLU([0,0]+[0.5,0.5])   = [0.5, 0.5]

Every ReLU here happened to leave its input untouched, because none of the pre-activations went negative in this particular example — but the clipping is still doing its job architecturally: if a feature encoded something like a z-scored transaction amount that could go negative, ReLU would zero it out exactly as it does in the feed-forward layers you've already seen in earlier deep-learning chapters. The same computation, run in code rather than by hand, should reproduce these four vectors exactly:

import numpy as np

neighbors = {'A': ['B', 'C'], 'B': ['A', 'C'], 'C': ['A', 'B', 'D'], 'D': ['C']}
X = {'A': np.array([1, 0]), 'B': np.array([0, 1]),
     'C': np.array([1, 1]), 'D': np.array([0, 0])}

W_self  = np.array([[0.5, 0], [0, 0.5]])
W_neigh = np.array([[0.5, 0], [0, 0.5]])

def relu(z):
    return np.maximum(0, z)

H1 = {}
for v in X:
    neigh_mean = np.mean([X[u] for u in neighbors[v]], axis=0)
    H1[v] = relu(W_self @ X[v] + W_neigh @ neigh_mean)

for v in ['A', 'B', 'C', 'D']:
    print(v, [round(float(val), 3) for val in H1[v]])
A [0.75, 0.5]
B [0.5, 0.75]
C [0.667, 0.667]
D [0.5, 0.5]

Look at what happened to C. It started with features identical in shape to A and B combined ([1,1] versus their [1,0] and [0,1]), but after one round of message passing its embedding sits at a distinctly different magnitude from every other node — the direct numerical footprint of being the only node with three neighbors instead of one or two. A downstream classifier reading these four embeddings has, after a single layer, already absorbed a piece of graph topology — degree — that no per-account feature table would have handed it directly unless someone manually engineered a "number of counterparties" column.

Message passing in a graph neural network A four-node transaction graph A, B, C, D with C as the hub. Arrows show A, B, and D sending messages into C during one aggregation step. A side panel shows the exact mean-aggregation and update arithmetic that produces C's new embedding. Transaction graph — one message-passing step D x=[0,0] A x=[1,0] B x=[0,1] C x=[1,1] deg=3 graph edge message this layer Computing h_C after one layer neighbors(C) = {A, B, D} 1. gather: x_A=[1,0] x_B=[0,1] x_D=[0,0] 2. aggregate (mean): mean = [0.333, 0.333] 3. combine with self x_C=[1,1]: 0.5·[1,1] + 0.5·[0.333,0.333] = [0.667, 0.667] 4. update (ReLU): h_C = [0.667, 0.667] C's higher magnitude vs. A, B, D encodes that it has 3 neighbors, not 1-2.

Receptive fields, and a misconception worth correcting now

A very natural — and wrong — assumption at this point is: "one layer of message passing lets a node see the whole graph, since the graph's edges are right there in the adjacency list." That isn't what happens. A single layer only pulls in direct neighbors. Node D's layer-1 embedding above depends only on C — it has no idea A and B even exist, because they're two hops away, not one. To let D's embedding be influenced by A, you need a second layer: at layer 2, D aggregates from C's layer-1 embedding, and C's layer-1 embedding already contains a trace of A (because C aggregated A at layer 1). Information from A reaches D only after it has been relayed through C — exactly like a rumor passed person to person, one hop per layer. The number of layers k in a GNN literally sets the radius of each node's receptive field: after k layers, a node's embedding can only depend on nodes within k hops.

The tempting fix — "so just stack twenty layers and every node sees the whole graph" — creates a real, well-documented failure mode called over-smoothing. Each aggregation step is an averaging operation, and averaging is a contraction: repeatedly averaging a value with its neighbors' values, over and over across many layers, drives all the node embeddings in a connected graph toward the same value, the way repeatedly averaging any set of numbers with their neighbors converges toward the global mean. Push a GNN to eight or ten layers on a small, densely connected graph and every node's embedding starts to look alike — the model loses the very thing that made a node distinguishable from its neighbors in the first place, which defeats the purpose of using a graph at all. This is why production GNNs are usually shallow — two to four layers is typical — and why "just add more layers," the reflex that works reasonably well for CNNs and Transformers, actively hurts a GNN past a fairly low ceiling.

Where this actually gets used

Message passing over graphs was popularized for molecule property prediction — an atom's chemical behavior depends on which atoms it's bonded to, so representing a molecule as a graph of atoms (nodes) and bonds (edges) and running message passing over it, rather than flattening it into a fixed-length feature vector, was the original use case that made the architecture worth inventing (Gilmer et al., 2017, on quantum chemistry prediction). The same mechanism generalizes to any relational data. Google's Maps team published a production system that models road segments and intersections as a graph and runs GNN layers over it to predict estimated arrival time, because travel time on one road segment genuinely depends on congestion propagating from connected segments, not just that segment's own historical speed. Recommendation systems built on co-purchase or co-view graphs — this product was bought alongside that one — use exactly the same neighborhood-aggregation idea to generate a product's embedding from what it's connected to, rather than from its own listing text alone. And the fraud scenario that opened this chapter is a real, standard use case at payment networks and banks: the transaction graph is the thing that gives a mule ring away, because the account-level features of each individual node in the ring can look completely unremarkable on their own.

Active recall

Attempt these before reading the answers.

  1. Why does feeding a graph's adjacency matrix directly into a plain multilayer perceptron generally fail to generalize, even if you get very good accuracy on the exact graph you trained on?
  2. Suppose the edge C–D in the worked example is replaced by an edge B–D instead (so D's only neighbor is now B, and C's neighbors become just A and B). Using the same weight matrices W_self = W_neigh = 0.5·I, compute D's new layer-1 embedding.
  3. Define "receptive field" for a node in a k-layer GNN, in one sentence.
  4. True or false, with a one-sentence justification: "Adding more GNN layers always improves accuracy, the same way adding more layers generally helps a CNN or Transformer."
  5. In the fraud-detection scenario, why might a GNN catch a mule ring that a decision tree trained on each account's own features (transaction count, average amount, account age) would miss — even if you also gave the tree each account's degree (number of counterparties) as an extra input column?
  6. Name the two functions every message-passing layer needs, and give one concrete choice for each.

Answers

1. An MLP applied to a flattened adjacency matrix has no built-in guarantee of permutation invariance: relabeling the same graph's nodes in a different order changes which row/column holds which node's information, and an MLP's weights are tied to fixed input positions, so it can produce a different output for what is structurally the identical graph. It also can't handle a different number of nodes than it was trained on, since its input layer has a fixed size. A GNN's aggregation step (sum/mean/max over neighbors) is invariant to neighbor order by construction and its weight matrices are shared across all nodes, so it naturally handles graphs of varying size and any node ordering.

2. D's only neighbor is now B, so mean({x_B}) = x_B = [0,1]. h_D = ReLU(0.5·[0,0] + 0.5·[0,1]) = ReLU([0,0] + [0,0.5]) = [0, 0.5]. (Verified numerically: matches exactly.)

3. A node's receptive field after k layers is the set of all nodes reachable from it within k hops along graph edges — that's the full set of nodes whose original features could possibly have influenced its final embedding.

4. False. Because each layer's aggregation step is an averaging operation, stacking many layers repeatedly averages neighboring embeddings together and drives all node embeddings in a connected region toward the same value — over-smoothing — which destroys the distinctions between nodes that made the graph structure useful in the first place; most GNNs top out at two to four layers for this reason.

5. Degree is a single first-order number — it tells you an account has, say, three counterparties, but nothing about who those counterparties are or how they're connected to each other. A GNN's embedding for an account is built recursively from its neighbors' embeddings, which were themselves built from their neighbors — so after two layers, an account's embedding encodes not just its own connection count but the shape of the local subgraph around it, including whether its neighbors also transact heavily with each other (the triangle pattern a ring produces). A decision tree treats each row of features independently and can only exploit relational structure that a human has already hand-engineered into a column; it has no mechanism to discover "my neighbors also pay each other" on its own.

6. AGGREGATE, a permutation-invariant function that combines a variable-size set of neighbor embeddings into one fixed-size vector — mean, sum, and max are the standard choices. UPDATE (sometimes called COMBINE), which merges the aggregated neighbor vector with the node's own previous embedding through a learned linear transform and non-linearity — in the worked example, ReLU(W_self · h_v + W_neigh · aggregated) is the UPDATE function.

Think About It

Think about this: How would you explain graph neural networks: ai on structured data to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

Practice Exercises

Now it is time to practice! Complete these challenges to solidify your understanding:

  • Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
  • Exercise 2: Find a real-world example where graph neural networks: ai on structured data is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
  • Exercise 3: Create a mind-map connecting graph neural networks: ai on structured data to at least 3 other topics you have studied.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind graph neural networks: ai on structured data, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← GANs: Generative Adversarial NetworksSequence-to-Sequence Models and Translation →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn