Technical GuideProduction AI

Graph Neural Networks for Recommendation Systems

Collaborative filtering flattens relationships. Graph models keep them. A practical guide to building and serving GNN recommenders.

Syed Sartaj

Founder & AI Engineer

·15 min read
About the author

Your catalogue has millions of items. Most users have interacted with a handful. Matrix factorisation gives you something to ship, then cold-start and long-tail items quietly degrade the experience. Product asks why recommendations feel generic. Eng points at sparsity.

Recommendation problems are relational. Users touch items. Items sit in categories. Users resemble other users. Interactions arrive in time and context. Graph Neural Networks (GNNs) keep that structure instead of flattening everything into a matrix and hoping the signal survives.

Why graphs for recommendations?

Real systems are graphs whether you model them that way or not:

  • Users interact with items
  • Items belong to categories and carry attributes
  • Users are similar to other users
  • Items are related to other items
  • Interactions happen in sequences and contexts

Collaborative filtering compresses those relationships. Graph approaches preserve them and let message passing move signal across neighbourhoods.

Graph construction

Entities as nodes

User nodes: Behavioural patterns, preference embeddings, sparse profile features you are allowed to use.

Item nodes: Category, brand, price band, content features, temporal properties.

Context nodes (optional): Tags, seasons, events, locations when they materially change relevance.

Relationships as edges

Explicit: Purchases, ratings, follows.

Implicit: Views, clicks, dwell time.

Derived: User-user or item-item similarity edges when they help, used carefully so you do not bake popularity bias deeper into the graph.

Edge attributes

Edges can carry weight, time, and context:

edge = {
    "source": user_id,
    "target": item_id,
    "weight": interaction_strength,
    "timestamp": interaction_time,
    "context": {
        "device": "mobile",
        "session_length": 15.2,
        "position": 3
    }
}

Timestamps matter. A click from last year should not shout as loudly as one from this morning unless your product says otherwise.

Graph neural network architectures

Message passing

GNNs iteratively pass information between connected nodes:

  1. Message creation: Neighbours prepare messages
  2. Aggregation: A node collects messages (sum, mean, attention)
  3. Update: The node embedding updates from the aggregate
for node in graph.nodes:
    messages = []
    for neighbor in node.neighbors:
        message = create_message(neighbor.embedding, edge_features)
        messages.append(message)

    aggregated = aggregate(messages)
    node.embedding = update(node.embedding, aggregated)

Architectures teams actually use

1. Graph Convolutional Networks (GCN)

# Layer k embedding (schematic)
H^(k+1) = σ(D^(-1/2) A D^(-1/2) H^(k) W^(k))

Captures multi-hop neighbourhoods. Computationally approachable. Can over-smooth if you stack too many layers.

2. Graph Attention Networks (GAT)

Learn which neighbours matter. Helpful when degrees vary wildly and you want interpretable attention weights. Costs more than plain GCN.

3. LightGCN

Built for collaborative filtering. Drops some nonlinear transformations. Often trains faster and behaves well on sparse interaction graphs. A strong default starting point for many product recommenders.

4. Sampling-based models (PinSage-style)

For very large graphs, you cannot materialise full neighbourhoods. Random-walk sampling, pooling aggregation, and careful negative mining are how production systems stay within latency budgets. The lesson is less “copy Pinterest” and more “sample deliberately when the graph outgrows full-neighbour training.”

Training graph recommendation models

Loss functions

BPR optimises pairwise ranking of positive vs negative items.

Multi-task mixes click, purchase, and rating objectives when your funnel needs it:

L = α * L_click + β * L_purchase + γ * L_rating

Contrastive losses pull related nodes together and push unrelated ones apart. Useful when labels are noisy and structure is rich.

Negative sampling

Negatives make or break training:

  • Random: Fast, often too easy
  • Hard: Informative, expensive to mine
  • In-batch: Convenient at scale
  • Mixed: Usually what you end up with

Handling scale

Neighbour sampling for mini-batches. Graph partitioning across workers. Precomputed embeddings for parts of the graph that change slowly. Serving rarely runs full GNN inference on every request at large scale. You train, materialise embeddings, and retrieve with a vector index.

Advanced techniques worth the complexity

Temporal dynamics

Weight recent edges higher. Model sessions as short paths. If your product is time-sensitive, a static graph will quietly lie.

Multi-modal features

Fuse GNN embeddings with text and image embeddings when catalogue content carries signal collaborative history lacks. Cold items especially benefit.

Context-aware scoring

score = MLP([
    user_embedding,
    item_embedding,
    context_embedding(time_of_day, device, location)
])

Explainability

Path-based and attention-based explanations help support and trust. “Users like you who bought X also bought Y” is not marketing fluff when the path is real.

Cold start

GNNs help when you can connect new users or items through attributes and content edges:

  • New users: Attribute similarity and early interactions propagate signal
  • New items: Content features and category links bootstrap the node before collaborative data arrives

Still measure cold-start cohorts separately. A global NDCG number will hide the pain.

Production shape

A practical serving path:

Data pipeline
    ↓
Graph construction and storage
    ↓
Feature engineering
    ↓
GNN training (e.g. PyTorch Geometric)
    ↓
Embedding materialisation (batch)
    ↓
Vector index
    ↓
Real-time ranking / re-ranking API

Evaluation

Ranking: NDCG@K, MRR, MAP.

Classification-style: Precision@K, Recall@K, Hit Rate@K.

Product: CTR, conversion, retention, diversity. Pick the ones your business actually cares about and hold them steady across experiments.

A/B testing notes

Recommendations change behaviour, which changes the graph, which changes future recommendations. Stratify carefully. Watch long-term metrics, not only next-day clicks. Popularity bias and feedback loops deserve explicit mitigations.

Common pitfalls

Over-smoothing: Too many layers, embeddings collapse. Prefer shallow stacks or residual connections.

Popularity bias: Message passing can amplify what is already popular. Debias in training or post-processing.

Feedback loops: Explore/exploit and randomisation keep the system from eating its own history.

Compute cost: Full-graph training is expensive. Sample, cache, update incrementally.

Getting started

  1. Convert your interaction matrix into a bipartite user-item graph
  2. Train a simple LightGCN or GCN baseline with 2–3 layers
  3. Compare against your current collaborative filtering baseline on the same splits
  4. Add features, temporal weighting, and better negatives only when the metrics move
  5. Serve via materialised embeddings and a vector index

Tools: PyTorch Geometric, DGL, Spektral if you are in TensorFlow-land, plus a graph store if your graph ops need it.

Closing

GNNs are not magic. They are a way to respect structure that matrix methods discard. The complexity pays off when recommendation quality is a product lever and you are willing to invest in training, evaluation, and serving discipline.

If recommendation quality is blocking growth in your product, discuss your use case. For adjacent production AI work on features and evaluation, see AI Product Engineering and evaluation suites.

Written by Syed Sartaj

Founder of Neurocell. Builds production AI for growth-stage and mid-market teams: agents, knowledge systems, and product features that ship and stay reliable.

Book a 2-week architecture sprint

Keep going

Continue reading

All notes