# Torch Geometric

> Graph Neural Networks (PyG). Classificação de nós/grafos, predição de links, GCN, GAT, GraphSAGE, grafos heterogêneos, predição de propriedades moleculares, para deep learning geométrico.

- Skill: `artubss/torch-geometric` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add artubss/torch-geometric`
- Raw SKILL.md: https://api.skillmd.com/api/skills/artubss/torch-geometric/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: artubss (https://skillmd.com/u/artubss)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/artubss/torch-geometric

---


# PyTorch Geometric (PyG)

## Visão Geral

PyTorch Geometric é uma biblioteca construída sobre PyTorch para desenvolvimento e treinamento de Graph Neural Networks (GNNs). Use esta skill para deep learning em grafos e estruturas irregulares, incluindo processamento em mini-batch, treinamento multi-GPU e aplicações de deep learning geométrico.

## Quando Usar Esta Skill

Esta skill deve ser usada ao trabalhar com:
- **Machine learning baseado em grafos**: Classificação de nós, classificação de grafos, predição de links
- **Predição de propriedades moleculares**: Descoberta de fármacos, predição de propriedades químicas
- **Análise de redes sociais**: Detecção de comunidades, predição de influência
- **Redes de citação**: Classificação de papers, sistemas de recomendação
- **Dados geométricos 3D**: Point clouds, malhas, estruturas moleculares
- **Grafos heterogêneos**: Nós e arestas de múltiplos tipos (ex: knowledge graphs)
- **Aprendizado em grafos em larga escala**: Amostragem de vizinhos, treinamento distribuído

## Início Rápido

### Instalação

```bash
uv pip install torch_geometric
```

Para dependências adicionais (operações esparsas, clustering):
```bash
uv pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-${TORCH}+${CUDA}.html
```

### Criação Básica de Grafos

```python
import torch
from torch_geometric.data import Data

# Create a simple graph with 3 nodes
edge_index = torch.tensor([[0, 1, 1, 2],  # source nodes
                           [1, 0, 2, 1]], dtype=torch.long)  # target nodes
x = torch.tensor([[-1], [0], [1]], dtype=torch.float)  # node features

data = Data(x=x, edge_index=edge_index)
print(f"Nodes: {data.num_nodes}, Edges: {data.num_edges}")
```

### Carregamento de Dataset de Benchmark

```python
from torch_geometric.datasets import Planetoid

# Load Cora citation network
dataset = Planetoid(root='/tmp/Cora', name='Cora')
data = dataset[0]  # Get the first (and only) graph

print(f"Dataset: {dataset}")
print(f"Nodes: {data.num_nodes}, Edges: {data.num_edges}")
print(f"Features: {data.num_node_features}, Classes: {dataset.num_classes}")
```

## Conceitos Principais

### Estrutura de Dados

PyG representa grafos usando a classe `torch_geometric.data.Data` com estes atributos principais:

- **`data.x`**: Matriz de features de nós `[num_nodes, num_node_features]`
- **`data.edge_index`**: Conectividade do grafo em formato COO `[2, num_edges]`
- **`data.edge_attr`**: Matriz de features de arestas `[num_edges, num_edge_features]` (opcional)
- **`data.y`**: Labels alvo para nós ou grafos
- **`data.pos`**: Posições espaciais dos nós `[num_nodes, num_dimensions]` (opcional)
- **Atributos customizados**: Você pode adicionar qualquer atributo (ex: `data.train_mask`, `data.batch`)

**Importante**: Esses atributos não são obrigatórios—estenda objetos Data com atributos customizados conforme necessário.

### Formato de Edge Index

Arestas são armazenadas em formato COO (coordinate) como um tensor `[2, num_edges]`:
- Primeira linha: índices dos nós de origem
- Segunda linha: índices dos nós de destino

```python
# Edge list: (0→1), (1→0), (1→2), (2→1)
edge_index = torch.tensor([[0, 1, 1, 2],
                           [1, 0, 2, 1]], dtype=torch.long)
```

### Processamento em Mini-Batch

PyG lida com batching criando matrizes de adjacência block-diagonais, concatenando múltiplos grafos em um grande grafo desconectado:

- Matrizes de adjacência são empilhadas diagonalmente
- Features de nós são concatenadas ao longo da dimensão do nó
- Um vetor `batch` mapeia cada nó ao seu grafo de origem
- Sem necessidade de padding—computacionalmente eficiente

```python
from torch_geometric.loader import DataLoader

loader = DataLoader(dataset, batch_size=32, shuffle=True)
for batch in loader:
    print(f"Batch size: {batch.num_graphs}")
    print(f"Total nodes: {batch.num_nodes}")
    # batch.batch maps nodes to graphs
```

## Construindo Graph Neural Networks

### Paradigma de Message Passing

GNNs em PyG seguem um esquema de agregação de vizinhança:
1. Transformar features de nós
2. Propagar mensagens ao longo das arestas
3. Agregar mensagens dos vizinhos
4. Atualizar representações de nós

### Usando Camadas Pré-Construídas

PyG fornece 40+ camadas convolucionais. As mais comuns incluem:

**GCNConv** (Graph Convolutional Network):
```python
from torch_geometric.nn import GCNConv
import torch.nn.functional as F

class GCN(torch.nn.Module):
    def __init__(self, num_features, num_classes):
        super().__init__()
        self.conv1 = GCNConv(num_features, 16)
        self.conv2 = GCNConv(16, num_classes)

    def forward(self, data):
        x, edge_index = data.x, data.edge_index
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, training=self.training)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)
```

**GATConv** (Graph Attention Network):
```python
from torch_geometric.nn import GATConv

class GAT(torch.nn.Module):
    def __init__(self, num_features, num_classes):
        super().__init__()
        self.conv1 = GATConv(num_features, 8, heads=8, dropout=0.6)
        self.conv2 = GATConv(8 * 8, num_classes, heads=1, concat=False, dropout=0.6)

    def forward(self, data):
        x, edge_index = data.x, data.edge_index
        x = F.dropout(x, p=0.6, training=self.training)
        x = F.elu(self.conv1(x, edge_index))
        x = F.dropout(x, p=0.6, training=self.training)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)
```

**GraphSAGE**:
```python
from torch_geometric.nn import SAGEConv

class GraphSAGE(torch.nn.Module):
    def __init__(self, num_features, num_classes):
        super().__init__()
        self.conv1 = SAGEConv(num_features, 64)
        self.conv2 = SAGEConv(64, num_classes)

    def forward(self, data):
        x, edge_index = data.x, data.edge_index
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, training=self.training)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)
```

### Camadas de Message Passing Customizadas

Para camadas customizadas, herde de `MessagePassing`:

```python
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import add_self_loops, degree

class CustomConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super().__init__(aggr='add')  # "add", "mean", or "max"
        self.lin = torch.nn.Linear(in_channels, out_channels)

    def forward(self, x, edge_index):
        # Add self-loops to adjacency matrix
        edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))

        # Transform node features
        x = self.lin(x)

        # Compute normalization
        row, col = edge_index
        deg = degree(col, x.size(0), dtype=x.dtype)
        deg_inv_sqrt = deg.pow(-0.5)
        norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]

        # Propagate messages
        return self.propagate(edge_index, x=x, norm=norm)

    def message(self, x_j, norm):
        # x_j: features of source nodes
        return norm.view(-1, 1) * x_j
```

Métodos principais:
- **`forward()`**: Ponto de entrada principal
- **`message()`**: Constrói mensagens dos nós de origem para os de destino
- **`aggregate()`**: Agrega mensagens (geralmente não sobrescreva—defina o parâmetro `aggr`)
- **`update()`**: Atualiza embeddings de nós após agregação

**Convenção de nomenclatura**: Adicionar `_i` ou `_j` aos nomes de tensores mapeia automaticamente para nós de destino ou de origem.

## Trabalhando com Datasets

### Carregando Datasets Incorporados

PyG fornece datasets de benchmark extensivos:

```python
# Citation networks (node classification)
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root='/tmp/Cora', name='Cora')  # or 'CiteSeer', 'PubMed'

# Graph classification
from torch_geometric.datasets import TUDataset
dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES')

# Molecular datasets
from torch_geometric.datasets import QM9
dataset = QM9(root='/tmp/QM9')

# Large-scale datasets
from torch_geometric.datasets import Reddit
dataset = Reddit(root='/tmp/Reddit')
```

Verifique `references/datasets_reference.md` para uma lista abrangente.

### Criando Datasets Customizados

Para datasets que cabem em memória, herde de `InMemoryDataset`:

```python
from torch_geometric.data import InMemoryDataset, Data
import torch

class MyOwnDataset(InMemoryDataset):
    def __init__(self, root, transform=None, pre_transform=None):
        super().__init__(root, transform, pre_transform)
        self.load(self.processed_paths[0])

    @property
    def raw_file_names(self):
        return ['my_data.csv']  # Files needed in raw_dir

    @property
    def processed_file_names(self):
        return ['data.pt']  # Files in processed_dir

    def download(self):
        # Download raw data to self.raw_dir
        pass

    def process(self):
        # Read data, create Data objects
        data_list = []

        # Example: Create a simple graph
        edge_index = torch.tensor([[0, 1], [1, 0]], dtype=torch.long)
        x = torch.randn(2, 16)
        y = torch.tensor([0], dtype=torch.long)

        data = Data(x=x, edge_index=edge_index, y=y)
        data_list.append(data)

        # Apply pre_filter and pre_transform
        if self.pre_filter is not None:
            data_list = [d for d in data_list if self.pre_filter(d)]

        if self.pre_transform is not None:
            data_list = [self.pre_transform(d) for d in data_list]

        # Save processed data
        self.save(data_list, self.processed_paths[0])
```

Para datasets grandes que não cabem em memória, herde de `Dataset` e implemente `len()` e `get(idx)`.

### Carregando Grafos de CSV

```python
import pandas as pd
import torch
from torch_geometric.data import HeteroData

# Load nodes
nodes_df = pd.read_csv('nodes.csv')
x = torch.tensor(nodes_df[['feat1', 'feat2']].values, dtype=torch.float)

# Load edges
edges_df = pd.read_csv('edges.csv')
edge_index = torch.tensor([edges_df['source'].values,
                           edges_df['target'].values], dtype=torch.long)

data = Data(x=x, edge_index=edge_index)
```

## Workflows de Treinamento

### Classificação de Nós (Grafo Único)

```python
import torch
import torch.nn.functional as F
from torch_geometric.datasets import Planetoid

# Load dataset
dataset = Planetoid(root='/tmp/Cora', name='Cora')
data = dataset[0]

# Create model
model = GCN(dataset.num_features, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

# Training
model.train()
for epoch in range(200):
    optimizer.zero_grad()
    out = model(data)
    loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
    loss.backward()
    optimizer.step()

    if epoch % 10 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item():.4f}')

# Evaluation
model.eval()
pred = model(data).argmax(dim=1)
correct = (pred[data.test_mask] == data.y[data.test_mask]).sum()
acc = int(correct) / int(data.test_mask.sum())
print(f'Test Accuracy: {acc:.4f}')
```

### Classificação de Grafos (Múltiplos Grafos)

```python
from torch_geometric.datasets import TUDataset
from torch_geometric.loader import DataLoader
from torch_geometric.nn import global_mean_pool

class GraphClassifier(torch.nn.Module):
    def __init__(self, num_features, num_classes):
        super().__init__()
        self.conv1 = GCNConv(num_features, 64)
        self.conv2 = GCNConv(64, 64)
        self.lin = torch.nn.Linear(64, num_classes)

    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch

        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = self.conv2(x, edge_index)
        x = F.relu(x)

        # Global pooling (aggregate node features to graph-level)
        x = global_mean_pool(x, batch)

        x = self.lin(x)
        return F.log_softmax(x, dim=1)

# Load dataset
dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES')
loader = DataLoader(dataset, batch_size=32, shuffle=True)

model = GraphClassifier(dataset.num_features, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

# Training
model.train()
for epoch in range(100):
    total_loss = 0
    for batch in loader:
        optimizer.zero_grad()
        out = model(batch)
        loss = F.nll_loss(out, batch.y)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    if epoch % 10 == 0:
        print(f'Epoch {epoch}, Loss: {total_loss / len(loader):.4f}')
```

### Grafos em Larga Escala com Amostragem de Vizinhos

Para grafos grandes, use `NeighborLoader` para amostrar subgrafos:

```python
from torch_geometric.loader import NeighborLoader

# Create a neighbor sampler
train_loader = NeighborLoader(
    data,
    num_neighbors=[25, 10],  # Sample 25 neighbors for 1st hop, 10 for 2nd hop
    batch_size=128,
    input_nodes=data.train_mask,
)

# Training
model.train()
for batch in train_loader:
    optimizer.zero_grad()
    out = model(batch)
    # Only compute loss on seed nodes (first batch_size nodes)
    loss = F.nll_loss(out[:batch.batch_size], batch.y[:batch.batch_size])
    loss.backward()
    optimizer.step()
```

**Importante**:
- Subgrafos de saída são direcionados
- Índices de nós são renomeados (0 a batch.num_nodes - 1)
- Use apenas predições de nós seed para computar a perda
- Amostragem além de 2-3 saltos geralmente não é viável

## Funcionalidades Avançadas

### Grafos Heterogêneos

Para grafos com múltiplos tipos de nós e arestas, use `HeteroData`:

```python
from torch_geometric.data import HeteroData

data = HeteroData()

# Add node features for different types
data['paper'].x = torch.randn(100, 128)  # 100 papers with 128 features
data['author'].x = torch.randn(200, 64)  # 200 authors with 64 features

# Add edges for different types (source_type, edge_type, target_type)
data['author', 'writes', 'paper'].edge_index = torch.randint(0, 200, (2, 500))
data['paper', 'cites', 'paper'].edge_index = torch.randint(0, 100, (2, 300))

print(data)
```

Converta modelos homogêneos para heterogêneos:

```python
from torch_geometric.nn import to_hetero

# Define homogeneous model
model = GNN(...)

# Convert to heterogeneous
model = to_hetero(model, data.metadata(), aggr='sum')

# Use as normal
out = model(data.x_dict, data.edge_index_dict)
```

Ou use `HeteroConv` para operações customizadas específicas do tipo de aresta:

```python
from torch_geometric.nn import HeteroConv, GCNConv, SAGEConv

class HeteroGNN(torch.nn.Module):
    def __init__(self, metadata):
        super().__init__()
        self.conv1 = HeteroConv({
            ('paper', 'cites', 'paper'): GCNConv(-1, 64),
            ('author', 'writes', 'paper'): SAGEConv((-1, -1), 64),
        }, aggr='sum')

        self.conv2 = HeteroConv({
            ('paper', 'cites', 'paper'): GCNConv(64, 32),
            ('author', 'writes', 'paper'): SAGEConv((64, 64), 32),
        }, aggr='sum')

    def forward(self, x_dict, edge_index_dict):
        x_dict = self.conv1(x_dict, edge_index_dict)
        x_dict = {key: F.relu(x) for key, x in x_dict.items()}
        x_dict = self.conv2(x_dict, edge_index_dict)
        return x_dict
```

### Transforms

Aplique transforms para modificar estrutura ou features do grafo:

```python
from torch_geometric.transforms import NormalizeFeatures, AddSelfLoops, Compose

# Single transform
transform = NormalizeFeatures()
dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=transform)

# Compose multiple transforms
transform = Compose([
    AddSelfLoops(),
    NormalizeFeatures(),
])
dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=transform)
```

Transforms comuns:
- **Estrutura**: `ToUndirected`, `AddSelfLoops`, `RemoveSelfLoops`, `KNNGraph`, `RadiusGraph`
- **Features**: `NormalizeFeatures`, `NormalizeScale`, `Center`
- **Amostragem**: `RandomNodeSplit`, `RandomLinkSplit`
- **Positional Encoding**: `AddLaplacianEigenvectorPE`, `AddRandomWalkPE`

Veja `references/transforms_reference.md` para a lista completa.

### Explicabilidade de Modelo

PyG fornece ferramentas de explicabilidade para entender predições do modelo:

```python
from torch_geometric.explain import Explainer, GNNExplainer

# Create explainer
explainer = Explainer(
    model=model,
    algorithm=GNNExplainer(epochs=200),
    explanation_type='model',  # or 'phenomenon'
    node_mask_type='attributes',
    edge_mask_type='object',
    model_config=dict(
        mode='multiclass_classification',
        task_level='node',
        return_type='log_probs',
    ),
)

# Generate explanation for a specific node
node_idx = 10
explanation = explainer(data.x, data.edge_index, index=node_idx)

# Visualize
print(f'Node {node_idx} explanation:')
print(f'Important edges: {explanation.edge_mask.topk(5).indices}')
print(f'Important features: {explanation.node_mask[node_idx].topk(5).indices}')
```

### Operações de Pooling

Para representações hierárquicas de grafos:

```python
from torch_geometric.nn import TopKPooling, global_mean_pool

class HierarchicalGNN(torch.nn.Module):
    def __init__(self, num_features, num_classes):
        super().__init__()
        self.conv1 = GCNConv(num_features, 64)
        self.pool1 = TopKPooling(64, ratio=0.8)
        self.conv2 = GCNConv(64, 64)
        self.pool2 = TopKPooling(64, ratio=0.8)
        self.lin = torch.nn.Linear(64, num_classes)

    def forward(self, data):
        x, edge_index, batch = data.x, data.edge_index, data.batch

        x = F.relu(self.conv1(x, edge_index))
        x, edge_index, _, batch, _, _ = self.pool1(x, edge_index, None, batch)

        x = F.relu(self.conv2(x, edge_index))
        x, edge_index, _, batch, _, _ = self.pool2(x, edge_index, None, batch)

        x = global_mean_pool(x, batch)
        x = self.lin(x)
        return F.log_softmax(x, dim=1)
```

## Padrões Comuns e Melhores Práticas

### Verificar Propriedades do Grafo

```python
# Undirected check
from torch_geometric.utils import is_undirected
print(f"Is undirected: {is_undirected(data.edge_index)}")

# Connected components
from torch_geometric.utils import connected_components
print(f"Connected components: {connected_components(data.edge_index)}")

# Contains self-loops
from torch_geometric.utils import contains_self_loops
print(f"Has self-loops: {contains_self_loops(data.edge_index)}")
```

### Treinamento em GPU

```python
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
data = data.to(device)

# For DataLoader
for batch in loader:
    batch = batch.to(device)
    # Train...
```

### Salvar e Carregar Modelos

```python
# Save
torch.save(model.state_dict(), 'model.pth')

# Load
model = GCN(num_features, num_classes)
model.load_state_dict(torch.load('model.pth'))
model.eval()
```

### Capacidades de Camadas

Ao escolher camadas, considere essas capacidades:
- **SparseTensor**: Suporta operações eficientes com matrizes esparsas
- **edge_weight**: Lida com pesos de arestas unidimensionais
- **edge_attr**: Processa features de arestas multidimensionais
- **Bipartite**: Funciona com grafos bipartidos (dimensões diferentes de origem/destino)
- **Lazy**: Habilita inicialização sem especificar dimensões de entrada

Veja o cheatsheet de GNN em `references/layer_capabilities.md`.

## Recursos

### Referências Incluídas

Esta skill inclui documentação de referência detalhada:

- **`references/layers_reference.md`**: Listagem completa de todos os 40+ layers de GNN com descrições e capacidades
- **`references/datasets_reference.md`**: Catálogo abrangente de datasets organizado por categoria
- **`references/transforms_reference.md`**: Todos os transforms disponíveis e seus casos de uso
- **`references/api_patterns.md`**: Padrões comuns de API e exemplos de código

### Scripts

Scripts de utilidade são fornecidos em `scripts/`:

- **`scripts/visualize_graph.py`**: Visualiza estrutura do grafo usando networkx e matplotlib
- **`scripts/create_gnn_template.py`**: Gera código boilerplate para arquiteturas GNN comuns
- **`scripts/benchmark_model.py`**: Benchmarks de desempenho do modelo em datasets padrão

Execute scripts diretamente ou leia-os para padrões de implementação.

### Recursos Oficiais

- **Documentação**: https://pytorch-geometric.readthedocs.io/
- **GitHub**: https://github.com/pyg-team/pytorch_geometric
- **Tutorials**: https://pytorch-geometric.readthedocs.io/en/latest/get_started/introduction.html
- **Examples**: https://github.com/pyg-team/pytorch_geometric/tree/master/examples
