# GraphSAGE Recommender

> Inductive node embeddings via neighbor sampling + aggregation — for graph-based recommenders (PinSage style)

Pick when your recsys has a rich item-item or user-item graph and cold-start items must generalize via neighbors (PinSage-style production setup).

- Category: Recommendation
- Layers: 10
- Parameters: 263.0K
- Input shape (batchless): 128
- Output shape: 128 × 64
- Verifier verdict: warn
- Graph JSON: https://neurarch.com/templates/graph-sage-rec/model.json
- Open on the canvas: https://neurarch.com/?template=graph-sage-rec

## Structure

| # | Layer | Type | Parameters | Output shape |
| --- | --- | --- | --- | --- |
| 1 | Node Features | Input | shape=[128] | 128 |
| 2 | SAGE Layer 1 | GraphSAGE | outChannels=256, inChannels=128 | 128 × 64 |
| 3 | ReLU | ReLU |  | 128 × 64 |
| 4 | Dropout | Dropout | p=0.2 | 128 × 64 |
| 5 | SAGE Layer 2 | GraphSAGE | outChannels=256, inChannels=256 | 128 × 64 |
| 6 | ReLU | ReLU |  | 128 × 64 |
| 7 | Dropout | Dropout | p=0.2 | 128 × 64 |
| 8 | SAGE Layer 3 | GraphSAGE | outChannels=128, inChannels=256 | 128 × 64 |
| 9 | LayerNorm | LayerNorm | normalizedShape=[128] | 128 × 64 |
| 10 | Node Embedding | Output |  | 128 × 64 |

## Verifier findings

- **warn** `bn-at-output` at `LayerNorm`: "LayerNorm" (layerNorm) is the last layer before Output. Normalizing the raw logits constrains the output range and breaks standard loss functions. Fix: Move normalization before the final Linear/Conv layer.

## Exported PyTorch

```python
# Architecture designed with Neurarch: https://neurarch.com
# PyTorch: compatible with Python 3.8+ and torch>=1.12
# Colab: pip install torch torchvision  (usually pre-installed)

import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Tuple

class GraphSAGERecommender(nn.Module):
    def __init__(self):
        super().__init__()

        self.dropout_1 = nn.Dropout(p=0.2)
        self.dropout_2 = nn.Dropout(p=0.2)
        self.layerNorm_1 = nn.LayerNorm(64)

    def forward(self, x):
        # Node Features shape: [128]
        graph_s_a_g_e_sage1 = self.graphSAGE_1(x, edge_index)  # pass edge_index from graph data
        relu_relu1 = F.relu(graph_s_a_g_e_sage1)
        dropout_drop1 = self.dropout_1(relu_relu1)
        graph_s_a_g_e_sage2 = self.graphSAGE_2(dropout_drop1, edge_index)  # pass edge_index from graph data
        relu_relu2 = F.relu(graph_s_a_g_e_sage2)
        dropout_drop2 = self.dropout_2(relu_relu2)
        graph_s_a_g_e_sage3 = self.graphSAGE_3(dropout_drop2, edge_index)  # pass edge_index from graph data
        layer_norm_norm = self.layerNorm_1(graph_s_a_g_e_sage3)
        # Output
        return layer_norm_norm


if __name__ == '__main__':
    model = GraphSAGERecommender()
    model.eval()

    x = torch.randint(0, 50000, (1, 128))  # (batch, features)
    with torch.no_grad():
        output = model(x)

    print(f'Input  shape : {tuple(x.shape)}')
    print(f'Output shape : {tuple(output.shape)}')
    total = sum(p.numel() for p in model.parameters())
    trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print(f'Parameters   : {total:,} total, {trainable:,} trainable')
```

## Machine access

- Every architecture: https://neurarch.com/a/index.json
- Verify a graph of your own: `POST https://www.neurarch.com/api/v1/check` (see https://neurarch.com/developer.html)
- MCP server, so an agent edits the graph with the checks in the loop: https://neurarch.com/docs/mcp.md
