liveBook · Manning

Graph neural network

As explained in Financial AI in Practice

A neural network that learns from graph-structured data by aggregating information across connected nodes.

Fraud can hide in connections that look harmless one transaction at a time. A graph neural network (GNN) scores activity using both its attributes and its relationships—for example, transactions linked through a shared user or device. The goal is to surface suspicious patterns that an isolated-transaction model may miss. section 9.0 section 9.4.2

A GNN learns representations by passing information between connected nodes. In a simple two-layer model, each layer transforms a transaction’s features, aggregates transformed features from its neighbors, and combines the two. The resulting representation carries local context into a Normal/Fraud classifier. Unlike community detection, which finds densely connected groups, this approach learns features for a prediction task. section 9.4 section 9.4.2

A simplified transaction-fraud workflow is:

  • Create one node per transaction, using amount and transaction-time features; connect transactions that share a user ID or device ID. section 9.4.2
  • Pass the feature matrix and adjacency matrix through two neighborhood-aggregation layers and a two-class classifier. section 9.4.2
  • If fraud labels exist, train with cross-entropy loss; otherwise, run inference. Convert class scores to fraud probabilities and inspect the five highest-ranked transactions. section 9.4.2
Listing 9.7 Defining the GNN model
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from scipy.sparse import csr_matrix

# Simple GNN Model
class FraudGNN(nn.Module):
    def __init__(self, input_dim, hidden_dim=32):
        super(FraudGNN, self).__init__()
        self.conv1 = nn.Linear(input_dim, hidden_dim)
        self.conv2 = nn.Linear(hidden_dim, hidden_dim)
        self.classifier = nn.Linear(hidden_dim, 2)  # Normal/Fraud

    def forward(self, x, adj):
        # First GNN layer: Aggregate neighbor information
        h1 = torch.relu(self.conv1(x))
        h1_agg = torch.mm(adj, h1)  # Average neighbors' information
        h1 = h1 + h1_agg

        # Second GNN layer
        h2 = torch.relu(self.conv2(h1))
        h2_agg = torch.mm(adj, h2)
        h2 = h2 + h2_agg

        # Classification
        out = self.classifier(h2)
        return out

Inspect the PyTorch model to see the feature transformation, adjacency-matrix aggregation, and combination of a node’s own and neighboring information. section 9.4.2

Listing 9.8 Function to create graph data (create_fast_graph)
def create_fast_graph(df): 
    """Fast graph generation using vectorized operations"""
    print("   Creating node features...")
    n = len(df)

    times = pd.to_datetime(df['transaction_time'])
    features = np.column_stack([
        df['amount'].values / 1000,
        times.dt.hour.values / 24.0,
        times.dt.weekday.values / 6.0
    ])

    print("   Computing connections...")
    row_indices = []
    col_indices = []

    for user_id in df['user_id'].unique(): #A
        user_mask = df['user_id'] == user_id
        user_indices = np.where(user_mask)[0]
        if len(user_indices) > 1:
            for i in user_indices:
                for j in user_indices:
                    if i != j:
                        row_indices.append(i)
                        col_indices.append(j)

    for device_id in df['device_id'].unique():    #1
        device_mask = df['device_id'] == device_id
        device_indices = np.where(device_mask)[0]
        if len(device_indices) > 1:
            for i in device_indices:
                for j in device_indices:
                    if i != j:
                        row_indices.append(i)
                        col_indices.append(j)

    print("   Creating sparse matrix...")
    if row_indices:
        data = np.ones(len(row_indices))
        adj_sparse = csr_matrix((data, (row_indices, col_indices)), shape=(n, n))

        row_sum = np.array(adj_sparse.sum(axis=1)).flatten()
        row_sum[row_sum == 0] = 1
        adj_sparse = adj_sparse.multiply(1.0 / row_sum.reshape(-1, 1)) # Normalized sparse matrix

        if n < 1000: # Original logic to convert to dense for small graphs
            adj_matrix = adj_sparse.toarray()
        else:
            adj_matrix = adj_sparse # Keep sparse for larger graphs
    else:
        adj_matrix = np.eye(n) 

    return features, adj_matrix

Inspect how tabular transactions become node features and connections based on shared identifiers. section 9.4.2

Listing 9.10 Defining the orchestration function (2/3): Model training logic
    # --- Continuation of run_fast_gnn_demo function ---

    # 4. Train if labels exist, otherwise inference only
    if 'is_fraud' in df.columns:
        print("\n Training GNN...")
        y = torch.LongTensor(df['is_fraud'].values) # Using original df['is_fraud'].values

        optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=1e-4)
        criterion = nn.CrossEntropyLoss()

        best_loss = float('inf')
        patience = 0

        for epoch in range(100):
            model.train() # Set model to training mode
            optimizer.zero_grad()
            outputs = model(X, A)
            loss = criterion(outputs, y)
            loss.backward()
            optimizer.step()

            if epoch % 20 == 0: # Original condition for printing
                acc = (outputs.argmax(1) == y).float().mean()
                print(f"   Epoch {epoch}: Loss={loss.item():.3f}, Acc={acc.item():.3f}") # Using .item() for scalar loss

                if loss.item() < best_loss: # Early stopping based on training loss
                    best_loss = loss.item()
                    patience = 0
                else:
                    patience += 1
                    if patience > 3 and epoch > 40: 
                        print(f"   Early stopping at epoch {epoch}")
                        break 
        # Removed "GNN training finished." from here to match original single print at the end of function

    else: # This else block was part of the original code for the 'is_fraud' in df.columns check
        print("\n GNN inference mode...")

    # The function continues in Listing 9.11 with results analysis and return...

Inspect the labeled-data training path, including the optimizer, loss function, and epoch loop. section 9.4.2

The PyTorch workflow is an educational bridge from graph construction to graph learning, not a production fraud system. Large, changing financial graphs call for scalable training approaches such as neighbor sampling, attention to class imbalance, and explainability. GNN signals belong in a multilayered fraud defense rather than replacing other detection methods or investigator judgment. section 9.4.2 section 9.6

Beyond fraud: investment discovery

Two investors hold the same asset; one reads a niche report that the other has not searched for. Their shared holding supplies a graph-based signal for recommending the report, extending discovery beyond matching users and content as independent vectors. section 13.3.3

How Financial AI in Practice covers it

How deep it goes
Works through graph preparation, a two-layer PyTorch model, training, and ranked output, while explicitly stopping short of production-grade design.
Examples
Uses transactions linked by shared users or devices, a Normal/Fraud classifier, and a shared-holding recommendation scenario.
What it assumes
Builds on tabular transaction data and graph thinking, while introducing neighborhood aggregation through a simplified neural-network implementation.
What you can do after
Build it from scratch as a simplified PyTorch transaction GNN and inspect its highest-ranked fraud probabilities.
How it connects
Connects message passing and learned embeddings to transaction graphs, community detection, fraud classification, and vector-based recommendation.

Also explained in

Compare how 5 books explain graph neural network →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Financial AI in Practice. The section links point to the parts of the book it draws on.