4. Fundamentals of graph neural networks

In industrial fault localization, the data rarely arrives as a flat table. Sensors sit on specific machines, machines belong to production cells, cells feed downstream processes, and alarms propagate along physical or logical connections. A graph neural network (GNN) is the natural model class for this structure because it learns by exchanging information along edges. This chapter builds the three operations that define nearly every GNN: message passing, aggregation, and readout. Understanding them at the tensor level is what lets you later debug why a fault score is high on the wrong asset.

The graph as a computational object

A graph is written as G = (V, E), where V is the node set and E the edge set. Each node v carries a feature vector xv ∈ ℝd, and each edge (u, v) may carry an edge feature euv. In a plant, a node could be a pump, a motor, a PLC tag, or a vibration sensor; an edge could be a piping connection, a shared power bus, or a control dependency. The adjacency matrix A ∈ ℝN×N encodes connectivity, and the degree matrix D counts neighbors. These two matrices are the backbone of every operation below.

A single GNN layer updates node representations by combining a node's own state with information from its neighbors. Stacking L layers gives each node a receptive field of L hops, which is exactly what you want when a fault on a compressor should influence the representation of a downstream separator two connections away.

Message passing: computing what a neighbor says

Message passing is the local computation step. For each edge (u, v), the model computes a message from the source node u to the target node v:

m_uv = φ( h_u, h_v, e_uv )

Here hu and hv are current node embeddings, euv is the optional edge feature, and φ is a learnable function — typically a small MLP or a linear transform. In the simplest formulation, φ is just a linear projection of the source embedding:

m_uv = W · h_u

The choice of φ matters in industrial settings. If edges carry physical meaning such as pipe diameter or signal type, include euv so the model can weight a high-bandwidth connection differently from a weak one. A common practical form is a concatenation followed by an MLP:

m_uv = MLP( [ h_u || h_v || e_uv ] )

where || denotes concatenation. This is the message-passing stage of the GraphSAGE and GAT families, and it is where edge directionality is enforced: messages flow from u to v, not the reverse, unless you explicitly add the transposed edges.

Aggregation: combining neighbor messages

After messages are computed, each node collects all incoming messages and reduces them to a single vector. This reduction must be permutation-invariant — the result cannot depend on the order in which neighbors are listed. The standard choices are:

  • Sum: a_v = Σ_{u∈N(v)} m_uv — preserves magnitude, sensitive to node degree.
  • Mean: a_v = (1/|N(v)|) Σ m_uv — normalizes for degree, stable across varying fan-in.
  • Max: a_v = max_{u∈N(v)} m_uv — captures the strongest signal, useful for alarm propagation.

For fault localization, mean aggregation is a safe default because equipment nodes have very different neighbor counts. Max aggregation is attractive when a single upstream anomaly should dominate, for example a sudden pressure spike. The aggregated vector is then combined with the node's own state through an update function:

h_v' = σ( W_self · h_v + W_neigh · a_v + b )

This is the update step of a GraphSAGE-style layer. The self-loop term W_self · h_v ensures a node does not forget its own sensor reading while absorbing neighbor context. Without it, deep stacks tend to over-smooth and all nodes converge to similar embeddings — a failure mode that destroys localization resolution.

Over-smoothing is the most common GNN failure in fault diagnosis. If your model assigns nearly identical scores to all assets, reduce depth to 2–3 layers or add residual connections between layers.

Readout: from node embeddings to a graph-level decision

Fault localization often needs a per-node score, but many tasks require a graph-level output: is this batch faulty, is this subsystem degraded? Readout (also called pooling) maps the set of node embeddings to a single graph embedding:

h_G = READOUT( { h_v : v ∈ V } )

Common readout functions mirror aggregation: sum, mean, or max over all nodes. A more expressive option is attention-based readout, where each node receives a learned weight:

α_v = softmax( MLP( h_v ) )
h_G = Σ_v α_v · h_v

Attention readout is valuable when only a few nodes carry diagnostic evidence — for instance, one overheating bearing among fifty healthy components. The learned weights tell you which nodes the model considered important, giving a built-in explanation channel.

How the three operations compose

The figure below shows the full data flow for a two-layer GNN applied to a small industrial graph. Node features enter, messages are computed per edge, aggregated per node, and updated; after the final layer, readout produces a graph-level embedding that feeds a classifier or a per-node fault head.

GNN Layer Data Flow: Message Passing → Aggregation → Update → Readout Input Graph P1 P2 P3 Message Passing m_uv = φ(h_u, h_v, e_uv) per-edge MLP direction: u → v Aggregation a_v = Σ / mean / max over N(v) permutation-invariant Update h_v' = σ(W_s·h_v + W_n·a_v + b) self-loop retained stack L layers (receptive field = L hops) Readout / Pooling h_G = READOUT({h_v}) sum · mean · max · attention Fault Head per-node score / graph label node feature aggregation update

A worked example with concrete tensors

Consider a three-node subgraph: a pump P1, a motor P2, and a valve P3. Suppose each node has a 2-dimensional feature vector representing normalized vibration and temperature:

h_P1 = [0.8, 0.3]
h_P2 = [0.2, 0.9]
h_P3 = [0.5, 0.5]

Edges exist P1→P2 and P1→P3, meaning the pump feeds both the motor and the valve. Using a linear message function m_uv = W · h_u with W = [[1, 0], [0, 1]] (identity, for clarity), the messages are simply the source embeddings. For node P2, the only incoming message is from P1, so mean aggregation gives a_P2 = [0.8, 0.3]. With W_self = W_neigh = I and a ReLU activation, the updated embedding is:

h_P2' = ReLU( [0.2, 0.9] + [0.8, 0.3] ) = [1.0, 1.2]

Node P2 now carries information about the pump's vibration, which is exactly the propagation behavior that lets a downstream fault be detected before the motor's own sensors saturate. After two layers, P2 would also receive information from P3 if a reverse path existed, extending the receptive field.

Practical considerations for fault localization

Three design decisions dominate real deployments. First, edge direction: physical flow is often directed, but fault influence can propagate both ways. Adding reverse edges doubles the message volume but captures bidirectional coupling. Second, normalization: dividing aggregated messages by sqrt(deg(u) · deg(v)), as in GCN, prevents high-degree hub nodes from dominating. Third, readout granularity: if you need to localize a fault to a specific asset, attach a per-node classification head after the last layer rather than relying on graph-level readout alone.

Do not confuse aggregation with readout. Aggregation operates within a layer across neighbors of a single node; readout operates after the final layer across all nodes of the graph. Mixing them up leads to shape errors that are easy to miss in code but fatal to localization accuracy.

Verification checklist

CheckExpected observationFailure symptom
Message tensor shape[E, d] where E = edge countShape mismatch on concatenation
Aggregation invarianceShuffling neighbor order leaves output unchangedOutput changes with edge ordering
Self-loop presenceNode embedding retains own featuresAll nodes converge to same vector
Receptive fieldL layers reach L hopsDistant fault not detected
Readout outputSingle vector per graphPer-node tensor passed to graph classifier

With these three operations understood at the tensor level, you can read any GNN architecture — GCN, GraphSAGE, GAT, GIN — as a variation on the same message-passing skeleton. The next step is to connect this skeleton to the fault-localization objective, where node labels, class imbalance, and graph construction from sensor topology become the dominant engineering concerns.