alterlab-torch-geometric
Graph Neural Networks with PyTorch Geometric (PyG) — node and graph classification, link prediction, GCN, GAT, and GraphSAGE layers, heterogeneous graphs, and molecular property prediction. Use when building or training GNNs for geometric deep learning on graph-structured data. P
Install
npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/data-science/alterlab-torch-geometric
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
PyTorch Geometric (PyG)
Overview
PyTorch Geometric is a library built on PyTorch for developing and training Graph Neural Networks (GNNs). Apply this skill for deep learning on graphs and irregular structures, including mini-batch processing, multi-GPU training, and geometric deep learning applications.
When to Use This Skill
This skill should be used when working with:
- Graph-based machine learning: Node classification, graph classification, link prediction
- Molecular property prediction: Drug discovery, chemical property prediction
- Social network analysis: Community detection, influence prediction
- Citation networks: Paper classification, recommendation systems
- 3D geometric data: Point clouds, meshes, molecular structures
- Heterogeneous graphs: Multi-type nodes and edges (e.g., knowledge graphs)
- Large-scale graph learning: Neighbor sampling, distributed training
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| Classical graph algorithms, centrality, shortest paths, or network visualization without learning | alterlab-networkx |
| Social-network-analysis designs (centrality, community detection, ERGMs) for substantive social-science claims | alterlab-sna |
| End-to-end molecular property prediction with MoleculeNet featurizers and pretrained chemistry models | alterlab-deepchem |
| Projects built on TorchDrug's datasets and tasks (GearNet, retrosynthesis, molecule generation) | alterlab-torchdrug |
Quick Start
uv pip install torch_geometric
# Optional compiled ops (neighbor sampling, knn/radius graphs, fps): pick TORCH/CUDA to match your install
uv pip install pyg-lib -f https://data.pyg.org/whl/torch-${TORCH}+${CUDA}.html
PyG 2.8 folded torch-cluster and torch-spline-conv into pyg-lib (>= 0.7), so those two
packages are no longer needed. NeighborLoader raises ImportError unless pyg-lib (or
torch-sparse) is installed; for current PyTorch (2.13/2.14) data.pyg.org publishes only
pyg-lib wheels, not torch-scatter/torch-sparse.
Graphs are torch_geometric.data.Data objects: x (node features [N, F]), edge_index
(connectivity in COO [2, E]), optional edge_attr, y, pos, and any custom attribute
(train_mask, etc.). DataLoader batches multiple graphs into one block-diagonal graph
(no padding); a batch vector maps nodes back to their source graph.
Full install/sparse-deps, basic graph creation, benchmark loading, edge-index format, and
mini-batching details: references/getting_started.md.
Core Workflow
- Load or build data — benchmark datasets, custom
InMemoryDataset, or from CSV (references/datasets_and_loading.md; full catalog inreferences/datasets_reference.md). - Define a GNN — stack pre-built conv layers (GCNConv, GATConv, SAGEConv) or subclass
MessagePassingfor custom layers (references/building_gnns.md; full layer list inreferences/layers_reference.md). - Train — node classification (single graph, train/test masks), graph classification
(
DataLoader+ global pooling), or large-scale viaNeighborLoaderneighbor sampling (references/training_workflows.md). - Go advanced if needed —
HeteroData/to_heterofor heterogeneous graphs, transforms,GNNExplainerexplainability, hierarchical pooling, GPU, save/load (references/advanced_features.md; transforms catalog inreferences/transforms_reference.md).
Building GNNs (at a glance)
GNNs follow neighborhood aggregation: transform node features → propagate messages along edges →
aggregate from neighbors → update representations. PyG ships 40+ conv layers. When choosing one,
check its capabilities: SparseTensor support, edge_weight, edge_attr, bipartite, and lazy
(-1 channel) initialization. Code for GCN/GAT/GraphSAGE and custom MessagePassing layers
(including the _i/_j target/source naming convention) is in references/building_gnns.md.
Resources
Bundled References
This skill includes detailed reference documentation:
references/getting_started.md: Install, basic graph creation,Datastructure, edge-index format, mini-batchingreferences/building_gnns.md: Message passing, GCN/GAT/GraphSAGE code, customMessagePassinglayers, layer capabilitiesreferences/datasets_and_loading.md: Built-in datasets, customInMemoryDataset, loading graphs from CSVreferences/training_workflows.md: Node classification, graph classification, large-scale neighbor samplingreferences/advanced_features.md: Heterogeneous graphs, transforms, explainability, pooling, GPU, save/loadreferences/layers_reference.md: Complete listing of all 40+ GNN layers with descriptions and capabilitiesreferences/datasets_reference.md: Comprehensive dataset catalog organized by categoryreferences/transforms_reference.md: All available transforms and their use cases
Scripts
Utility scripts are provided in scripts/:
scripts/visualize_graph.py: Visualize graph structure using networkx and matplotlibscripts/create_gnn_template.py: Generate boilerplate code for common GNN architecturesscripts/benchmark_model.py: Benchmark model performance on standard datasets
Execute scripts directly or read them for implementation patterns.
Official Resources
- Documentation: 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
Part of the AlterLab Academic Skills suite.
Files (alterlab-academic-skills)
-
evals
-
evals.json 5.2 KB
{ "skill": "alterlab-torch-geometric", "evals": [ { "id": "gcn-node-classification-cora", "prompt": "Load the Cora citation dataset and train a two-layer GCN for node classification, using the train mask for the loss and reporting test accuracy.", "expected_output": "Invokes alterlab-torch-geometric: loads Planetoid(root=..., name='Cora'), defines a GCN with two GCNConv layers and log_softmax, trains with F.nll_loss on out[data.train_mask], and reports accuracy on data.test_mask. Uses PyG's Data object (data.x, data.edge_index).", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "gcnconv" }, { "type": "behavior", "value": "Uses torch_geometric Planetoid/Cora and GCNConv layers with train_mask/test_mask masking rather than a generic dense network." } ] }, { "id": "graph-classification-tudataset", "prompt": "I want to classify whole molecules from the ENZYMES dataset. Build a GNN that pools node features to a graph-level embedding and train it with mini-batching.", "expected_output": "Invokes alterlab-torch-geometric: loads TUDataset(name='ENZYMES'), batches with torch_geometric.loader.DataLoader, builds a model with GCNConv layers and global_mean_pool over data.batch to get graph-level embeddings, and trains for graph classification with nll_loss on batch.y.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "global_mean_pool" }, { "type": "behavior", "value": "Uses a graph-level readout (global_mean_pool over data.batch) and the PyG DataLoader for mini-batching multiple graphs, loading TUDataset/ENZYMES." } ] }, { "id": "gat-attention-citeseer", "prompt": "Set up a Graph Attention Network with multi-head attention for node classification on CiteSeer, with dropout on the attention.", "expected_output": "Invokes alterlab-torch-geometric: loads Planetoid CiteSeer, defines a model with GATConv layers (multiple heads, dropout), applying elu activations and log_softmax, and trains node classification. Uses attention-based PyG convolution.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "gatconv" }, { "type": "behavior", "value": "Uses GATConv with multiple attention heads and dropout (PyG attention layer) rather than a plain GCN or non-PyG attention implementation." } ] }, { "id": "heterogeneous-knowledge-graph", "prompt": "I have a knowledge graph with paper and author nodes and 'writes' and 'cites' edges. Set up a heterogeneous GNN over these multiple node and edge types.", "expected_output": "Invokes alterlab-torch-geometric: represents the graph with HeteroData (data['paper'].x, data['author'].x, typed edge_index like ('author','writes','paper')), and builds a heterogeneous GNN via to_hetero on a homogeneous model or HeteroConv with per-edge-type convolutions.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "heterodata" }, { "type": "behavior", "value": "Uses HeteroData with typed nodes/edges and a heterogeneous model (to_hetero or HeteroConv) rather than collapsing the graph to a single homogeneous type." } ] }, { "id": "near-miss-networkx", "prompt": "I just need to compute betweenness centrality and detect communities on my collaboration graph and visualize it - no machine learning, just classical graph metrics.", "expected_output": "Does NOT invoke alterlab-torch-geometric. Computing classical centrality and community-detection metrics and drawing the graph is plain graph analytics with no learned model, which is alterlab-networkx territory rather than GNN/geometric deep learning. Should defer to alterlab-networkx.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "networkx" } ] }, { "id": "near-miss-alterlab-deepchem", "prompt": "I want to benchmark solubility and toxicity prediction on the MoleculeNet datasets, comparing ECFP fingerprints with a pretrained ChemBERTa model and using scaffold splits. What's the quickest end-to-end setup?", "expected_output": "Does NOT invoke this skill; defers to alterlab-deepchem. The request is an end-to-end molecular ML benchmark (MoleculeNet loaders, featurizers such as ECFP, pretrained ChemBERTa, scaffold splitting), which DeepChem packages directly, rather than hand-building GNN layers and training loops in PyTorch Geometric.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "alterlab-deepchem" } ] } ] }
-
-
references
-
advanced_features.md 5.5 KB
# Advanced PyG Features Heterogeneous graphs, transforms, explainability, and hierarchical pooling. For the full transforms catalog, see `references/transforms_reference.md`. ## Heterogeneous Graphs For graphs with multiple node and edge types, 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) ``` Convert homogeneous models to heterogeneous: ```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) ``` Or use `HeteroConv` for custom edge-type-specific operations: ```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 Apply transforms to modify graph structure or features: ```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) ``` Common transforms: - **Structure**: `ToUndirected`, `AddSelfLoops`, `RemoveSelfLoops`, `KNNGraph`, `RadiusGraph` - **Features**: `NormalizeFeatures`, `NormalizeScale`, `Center` - **Sampling**: `RandomNodeSplit`, `RandomLinkSplit` - **Positional Encoding**: `AddLaplacianEigenvectorPE`, `AddRandomWalkPE` See `references/transforms_reference.md` for the full list. ## Model Explainability PyG provides explainability tools to understand model predictions: ```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}') ``` ## Pooling Operations For hierarchical graph representations: ```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) ``` ## Common Patterns and Best Practices ### Check Graph Properties ```python # Undirected check from torch_geometric.utils import is_undirected print(f"Is undirected: {is_undirected(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)}") # Connected components — PyG has no utils.connected_components; convert to # networkx, or use the LargestConnectedComponents transform (see transforms ref). import networkx as nx from torch_geometric.utils import to_networkx G = to_networkx(data, to_undirected=True) print(f"Connected components: {nx.number_connected_components(G)}") ``` ### GPU Training ```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... ``` ### Save and Load Models ```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() ``` -
building_gnns.md 4.1 KB
# Building Graph Neural Networks Message-passing paradigm, pre-built layers (GCN, GAT, GraphSAGE), and custom `MessagePassing` layers. For the full layer catalog, see `references/layers_reference.md`. ## Message Passing Paradigm GNNs in PyG follow a neighborhood aggregation scheme: 1. Transform node features 2. Propagate messages along edges 3. Aggregate messages from neighbors 4. Update node representations ## Using Pre-Built Layers PyG provides 40+ convolutional layers. Common ones: **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) ``` ## Custom Message Passing Layers For custom layers, inherit from `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 ``` Key methods: - **`forward()`**: Main entry point - **`message()`**: Constructs messages from source to target nodes - **`aggregate()`**: Aggregates messages (usually don't override—set `aggr` parameter) - **`update()`**: Updates node embeddings after aggregation **Variable naming convention**: Appending `_i` or `_j` to tensor names automatically maps them to target or source nodes. ## Layer Capabilities When choosing layers, consider these capabilities: - **SparseTensor**: Supports efficient sparse matrix operations - **edge_weight**: Handles one-dimensional edge weights - **edge_attr**: Processes multi-dimensional edge features - **Bipartite**: Works with bipartite graphs (different source/target dimensions) - **Lazy**: Enables initialization without specifying input dimensions See the GNN cheatsheet at `references/layers_reference.md`. -
datasets_and_loading.md 2.7 KB
# Working with Datasets Loading built-in datasets, creating custom in-memory datasets, and loading graphs from CSV. For the full dataset catalog, see `references/datasets_reference.md`. ## Loading Built-in Datasets PyG provides extensive benchmark datasets: ```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') ``` Check `references/datasets_reference.md` for a comprehensive list. ## Creating Custom Datasets For datasets that fit in memory, inherit from `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]) ``` For large datasets that don't fit in memory, inherit from `Dataset` and implement `len()` and `get(idx)`. ## Loading Graphs from 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) ``` -
datasets_reference.md 16.4 KB
# PyTorch Geometric Datasets Reference This document provides a comprehensive catalog of all datasets available in `torch_geometric.datasets`. ## Citation Networks ### Planetoid **Usage**: Node classification, semi-supervised learning **Networks**: Cora, CiteSeer, PubMed **Description**: Citation networks where nodes are papers and edges are citations - **Cora**: 2,708 nodes, 5,429 edges, 7 classes, 1,433 features - **CiteSeer**: 3,327 nodes, 4,732 edges, 6 classes, 3,703 features - **PubMed**: 19,717 nodes, 44,338 edges, 3 classes, 500 features ```python from torch_geometric.datasets import Planetoid dataset = Planetoid(root='/tmp/Cora', name='Cora') ``` ### Coauthor **Usage**: Node classification on collaboration networks **Networks**: CS, Physics **Description**: Co-authorship networks from Microsoft Academic Graph - **CS**: 18,333 nodes, 81,894 edges, 15 classes (computer science) - **Physics**: 34,493 nodes, 247,962 edges, 5 classes (physics) ```python from torch_geometric.datasets import Coauthor dataset = Coauthor(root='/tmp/CS', name='CS') ``` ### Amazon **Usage**: Node classification on product networks **Networks**: Computers, Photo **Description**: Amazon co-purchase networks where nodes are products - **Computers**: 13,752 nodes, 245,861 edges, 10 classes - **Photo**: 7,650 nodes, 119,081 edges, 8 classes ```python from torch_geometric.datasets import Amazon dataset = Amazon(root='/tmp/Computers', name='Computers') ``` ### CitationFull **Usage**: Citation network analysis **Networks**: Cora, Cora_ML, DBLP, PubMed **Description**: Full citation networks without sampling ```python from torch_geometric.datasets import CitationFull dataset = CitationFull(root='/tmp/Cora', name='Cora') ``` ## Graph Classification ### TUDataset **Usage**: Graph classification, graph kernel benchmarks **Description**: Collection of 120+ graph classification datasets - **MUTAG**: 188 graphs, 2 classes (molecular compounds) - **PROTEINS**: 1,113 graphs, 2 classes (protein structures) - **ENZYMES**: 600 graphs, 6 classes (protein enzymes) - **IMDB-BINARY**: 1,000 graphs, 2 classes (social networks) - **REDDIT-BINARY**: 2,000 graphs, 2 classes (discussion threads) - **COLLAB**: 5,000 graphs, 3 classes (scientific collaborations) - **NCI1**: 4,110 graphs, 2 classes (chemical compounds) - **DD**: 1,178 graphs, 2 classes (protein structures) ```python from torch_geometric.datasets import TUDataset dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES') ``` ### MoleculeNet **Usage**: Molecular property prediction **Datasets**: Over 10 molecular benchmark datasets **Description**: Comprehensive molecular machine learning benchmarks - **ESOL**: Aqueous solubility (regression) - **FreeSolv**: Hydration free energy (regression) - **Lipophilicity**: Octanol/water distribution (regression) - **BACE**: Binding results (classification) - **BBBP**: Blood-brain barrier penetration (classification) - **HIV**: HIV inhibition (classification) - **Tox21**: Toxicity prediction (multi-task classification) - **ToxCast**: Toxicology forecasting (multi-task classification) - **SIDER**: Side effects (multi-task classification) - **ClinTox**: Clinical trial toxicity (multi-task classification) ```python from torch_geometric.datasets import MoleculeNet dataset = MoleculeNet(root='/tmp/ESOL', name='ESOL') ``` ## Molecular and Chemical Datasets ### QM7b **Usage**: Molecular property prediction (quantum mechanics) **Description**: 7,211 molecules with up to 7 heavy atoms - Properties: Atomization energies, electronic properties ```python from torch_geometric.datasets import QM7b dataset = QM7b(root='/tmp/QM7b') ``` ### QM9 **Usage**: Molecular property prediction (quantum mechanics) **Description**: ~130,000 molecules with up to 9 heavy atoms (C, O, N, F) - Properties: 19 quantum chemical properties including HOMO, LUMO, gap, energy ```python from torch_geometric.datasets import QM9 dataset = QM9(root='/tmp/QM9') ``` ### ZINC **Usage**: Molecular generation, property prediction **Description**: ~250,000 drug-like molecular graphs - Properties: Constrained solubility, molecular weight ```python from torch_geometric.datasets import ZINC dataset = ZINC(root='/tmp/ZINC', subset=True) ``` ### AQSOL **Usage**: Aqueous solubility prediction **Description**: ~10,000 molecules with solubility measurements ```python from torch_geometric.datasets import AQSOL dataset = AQSOL(root='/tmp/AQSOL') ``` ### MD17 **Usage**: Molecular dynamics, force field learning **Description**: Molecular dynamics trajectories for small molecules - Molecules: Benzene, Uracil, Naphthalene, Aspirin, Salicylic acid, etc. ```python from torch_geometric.datasets import MD17 dataset = MD17(root='/tmp/MD17', name='benzene') ``` ### PCQM4Mv2 **Usage**: Large-scale molecular property prediction **Description**: 3.8M molecules from PubChem for quantum chemistry - Part of OGB Large-Scale Challenge ```python from torch_geometric.datasets import PCQM4Mv2 dataset = PCQM4Mv2(root='/tmp/PCQM4Mv2') ``` ## Social Networks ### Reddit **Usage**: Large-scale node classification **Description**: Reddit posts from September 2014 - 232,965 nodes, 11,606,919 edges, 41 classes - Features: TF-IDF of post content ```python from torch_geometric.datasets import Reddit dataset = Reddit(root='/tmp/Reddit') ``` ### Reddit2 **Usage**: Large-scale node classification **Description**: Updated Reddit dataset with more posts ```python from torch_geometric.datasets import Reddit2 dataset = Reddit2(root='/tmp/Reddit2') ``` ### Twitch **Usage**: Node classification, social network analysis **Networks**: DE, EN, ES, FR, PT, RU **Description**: Twitch user networks by language ```python from torch_geometric.datasets import Twitch dataset = Twitch(root='/tmp/Twitch', name='DE') ``` ### Facebook **Usage**: Social network analysis, node classification **Description**: Facebook page-page networks ```python from torch_geometric.datasets import FacebookPagePage dataset = FacebookPagePage(root='/tmp/Facebook') ``` ### GitHub **Usage**: Social network analysis **Description**: GitHub developer networks ```python from torch_geometric.datasets import GitHub dataset = GitHub(root='/tmp/GitHub') ``` ## Knowledge Graphs ### Entities **Usage**: Link prediction, knowledge graph embeddings **Datasets**: AIFB, MUTAG, BGS, AM **Description**: RDF knowledge graphs with typed relations ```python from torch_geometric.datasets import Entities dataset = Entities(root='/tmp/AIFB', name='AIFB') ``` ### WordNet18 **Usage**: Link prediction on semantic networks **Description**: Subset of WordNet with 18 relations - 40,943 entities, 151,442 triplets ```python from torch_geometric.datasets import WordNet18 dataset = WordNet18(root='/tmp/WordNet18') ``` ### WordNet18RR **Usage**: Link prediction (no inverse relations) **Description**: Refined version without inverse relations ```python from torch_geometric.datasets import WordNet18RR dataset = WordNet18RR(root='/tmp/WordNet18RR') ``` ### FB15k-237 **Usage**: Link prediction on Freebase **Description**: Subset of Freebase with 237 relations - 14,541 entities, 310,116 triplets ```python from torch_geometric.datasets import FB15k_237 dataset = FB15k_237(root='/tmp/FB15k') ``` ## Heterogeneous Graphs ### OGB_MAG **Usage**: Heterogeneous graph learning, node classification **Description**: Microsoft Academic Graph with multiple node/edge types - Node types: paper, author, institution, field of study - 1M+ nodes, 21M+ edges ```python from torch_geometric.datasets import OGB_MAG dataset = OGB_MAG(root='/tmp/OGB_MAG') ``` ### MovieLens **Usage**: Recommendation systems, link prediction **Description**: User-movie rating network (MovieLens). `model_name` is the sentence-transformers model used to embed movie titles, not a dataset version. Separate `MovieLens100K` and `MovieLens1M` classes exist for those specific sizes. - Node types: user, movie - Edge types: rates ```python from torch_geometric.datasets import MovieLens dataset = MovieLens(root='/tmp/MovieLens', model_name='all-MiniLM-L6-v2') ``` ### IMDB **Usage**: Heterogeneous graph learning **Description**: IMDB movie network - Node types: movie, actor, director ```python from torch_geometric.datasets import IMDB dataset = IMDB(root='/tmp/IMDB') ``` ### DBLP **Usage**: Heterogeneous graph learning, node classification **Description**: DBLP bibliography network - Node types: author, paper, term, conference ```python from torch_geometric.datasets import DBLP dataset = DBLP(root='/tmp/DBLP') ``` ### LastFM **Usage**: Heterogeneous recommendation **Description**: LastFM music network - Node types: user, artist, tag ```python from torch_geometric.datasets import LastFM dataset = LastFM(root='/tmp/LastFM') ``` ## Temporal Graphs ### BitcoinOTC **Usage**: Temporal link prediction, trust networks **Description**: Bitcoin OTC trust network over time ```python from torch_geometric.datasets import BitcoinOTC dataset = BitcoinOTC(root='/tmp/BitcoinOTC') ``` ### ICEWS18 **Usage**: Temporal knowledge graph completion **Description**: Integrated Crisis Early Warning System events ```python from torch_geometric.datasets import ICEWS18 dataset = ICEWS18(root='/tmp/ICEWS18') ``` ### GDELT **Usage**: Temporal event forecasting **Description**: Global Database of Events, Language, and Tone ```python from torch_geometric.datasets import GDELT dataset = GDELT(root='/tmp/GDELT') ``` ### JODIEDataset **Usage**: Dynamic graph learning **Datasets**: Reddit, Wikipedia, MOOC, LastFM **Description**: Temporal interaction networks ```python from torch_geometric.datasets import JODIEDataset dataset = JODIEDataset(root='/tmp/JODIE', name='Reddit') ``` ## 3D Meshes and Point Clouds ### ShapeNet **Usage**: 3D shape classification and segmentation **Description**: Large-scale 3D CAD model dataset - 16,881 models across 16 categories - Part-level segmentation labels ```python from torch_geometric.datasets import ShapeNet dataset = ShapeNet(root='/tmp/ShapeNet', categories=['Airplane']) ``` ### ModelNet **Usage**: 3D shape classification **Versions**: ModelNet10, ModelNet40 **Description**: CAD models for 3D object classification - ModelNet10: 4,899 models, 10 categories - ModelNet40: 12,311 models, 40 categories ```python from torch_geometric.datasets import ModelNet dataset = ModelNet(root='/tmp/ModelNet', name='10') ``` ### FAUST **Usage**: 3D shape matching, correspondence **Description**: Human body scans for shape analysis - 100 meshes of 10 people in 10 poses ```python from torch_geometric.datasets import FAUST dataset = FAUST(root='/tmp/FAUST') ``` ### CoMA **Usage**: 3D mesh deformation **Description**: Facial expression meshes - 20,466 3D face scans with expressions ```python from torch_geometric.datasets import CoMA dataset = CoMA(root='/tmp/CoMA') ``` ### S3DIS **Usage**: 3D semantic segmentation **Description**: Stanford Large-Scale 3D Indoor Spaces - 6 areas, 271 rooms, point cloud data ```python from torch_geometric.datasets import S3DIS dataset = S3DIS(root='/tmp/S3DIS', test_area=6) ``` ## Image and Vision Datasets ### MNISTSuperpixels **Usage**: Graph-based image classification **Description**: MNIST images as superpixel graphs - 70,000 graphs (60k train, 10k test) ```python from torch_geometric.datasets import MNISTSuperpixels dataset = MNISTSuperpixels(root='/tmp/MNIST') ``` ### Flickr **Usage**: Image description, node classification **Description**: Flickr image network - 89,250 nodes, 899,756 edges ```python from torch_geometric.datasets import Flickr dataset = Flickr(root='/tmp/Flickr') ``` ### PPI **Usage**: Protein-protein interaction prediction **Description**: Multi-graph protein interaction networks - 24 graphs, 2,373 nodes total ```python from torch_geometric.datasets import PPI dataset = PPI(root='/tmp/PPI', split='train') ``` ## Small Classic Graphs ### KarateClub **Usage**: Community detection, visualization **Description**: Zachary's karate club network - 34 nodes, 78 edges, 2 communities ```python from torch_geometric.datasets import KarateClub dataset = KarateClub() ``` ## Open Graph Benchmark (OGB) PyG integrates seamlessly with OGB datasets: ### Node Property Prediction - **ogbn-products**: Amazon product network (2.4M nodes) - **ogbn-proteins**: Protein association network (132K nodes) - **ogbn-arxiv**: Citation network (169K nodes) - **ogbn-papers100M**: Large citation network (111M nodes) - **ogbn-mag**: Heterogeneous academic graph ### Link Property Prediction - **ogbl-ppa**: Protein association networks - **ogbl-collab**: Collaboration networks - **ogbl-ddi**: Drug-drug interaction network - **ogbl-citation2**: Citation network - **ogbl-wikikg2**: Wikidata knowledge graph ### Graph Property Prediction - **ogbg-molhiv**: Molecular HIV activity prediction - **ogbg-molpcba**: Molecular bioassays (multi-task) - **ogbg-ppa**: Protein function prediction - **ogbg-code2**: Code abstract syntax trees ```python # OGB datasets are provided by the separate `ogb` package, not torch_geometric.datasets. # Install: uv pip install ogb from ogb.nodeproppred import PygNodePropPredDataset dataset = PygNodePropPredDataset(name='ogbn-arxiv') # (PyG also ships a convenience torch_geometric.datasets.OGB_MAG wrapper.) ``` ## Synthetic Datasets ### FakeDataset **Usage**: Testing, debugging **Description**: Generates random graph data ```python from torch_geometric.datasets import FakeDataset dataset = FakeDataset(num_graphs=100, avg_num_nodes=50) ``` ### StochasticBlockModelDataset **Usage**: Community detection benchmarks **Description**: Graphs generated from stochastic block models ```python from torch_geometric.datasets import StochasticBlockModelDataset dataset = StochasticBlockModelDataset(root='/tmp/SBM', num_graphs=1000) ``` ### ExplainerDataset **Usage**: Testing explainability methods **Description**: Synthetic graphs with known explanation ground truth. Requires a `graph_generator`, a `motif_generator`, and `num_motifs` (it plants motifs into a base graph). ```python from torch_geometric.datasets import ExplainerDataset from torch_geometric.datasets.graph_generator import BAGraph dataset = ExplainerDataset( graph_generator=BAGraph(num_nodes=300, num_edges=5), motif_generator='house', num_motifs=80, num_graphs=1000, ) ``` ## Materials Science ### QM8 (not in PyG) PyG ships no `QM8` dataset class (its quantum-chemistry sets are `QM7b` and `QM9`, listed above). Load QM8 through DeepChem's MoleculeNet loaders (`alterlab-deepchem`) and convert the molecules to `Data` objects if needed. ## Biological Networks ### PPI (Protein-Protein Interaction) Already listed above under Image and Vision Datasets ### STRING **Usage**: Protein interaction networks **Description**: Known and predicted protein-protein interactions ```python # Available through external sources or custom loading ``` ## Usage Tips 1. **Start with small datasets**: Use Cora, KarateClub, or ENZYMES for prototyping 2. **Citation networks**: Planetoid datasets are perfect for node classification 3. **Graph classification**: TUDataset provides diverse benchmarks 4. **Molecular**: QM9, ZINC, MoleculeNet for chemistry applications 5. **Large-scale**: Use Reddit, OGB datasets with NeighborLoader 6. **Heterogeneous**: OGB_MAG, MovieLens, IMDB for multi-type graphs 7. **Temporal**: JODIE, ICEWS for dynamic graph learning 8. **3D**: ShapeNet, ModelNet, S3DIS for geometric learning ## Common Patterns ### Loading with Transforms ```python from torch_geometric.datasets import Planetoid from torch_geometric.transforms import NormalizeFeatures dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=NormalizeFeatures()) ``` ### Train/Val/Test Splits ```python # Node classification: a single graph carries boolean node masks. Don't slice the # Data object with them — apply the mask to the model output in the loss instead: data = dataset[0] loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) acc_nodes = data.val_mask # evaluate on out[data.val_mask], out[data.test_mask] # Graph classification: split the dataset of graphs, then batch with DataLoader. from torch_geometric.loader import DataLoader dataset = dataset.shuffle() train_dataset = dataset[:int(len(dataset) * 0.8)] test_dataset = dataset[int(len(dataset) * 0.8):] train_loader = DataLoader(train_dataset, batch_size=32) ``` ### Custom Data Loading ```python from torch_geometric.data import Data, Dataset class MyCustomDataset(Dataset): def __init__(self, root, transform=None): super().__init__(root, transform) # Your initialization def len(self): return len(self.data_list) def get(self, idx): # Load and return data object return self.data_list[idx] ``` -
getting_started.md 3.4 KB
# PyG: Getting Started and Core Concepts Installation, basic graph creation, the `Data` structure, edge-index format, and mini-batching. ## Installation ```bash uv pip install torch_geometric ``` PyG 2.8 (current 2.8.0.post1) needs Python >= 3.10; its release notes list PyTorch 2.9–2.12. Optional compiled extensions — `pyg-lib` provides neighbor sampling (`NeighborLoader`), `knn_graph`/`radius_graph`, `fps`, and segment ops. Since PyG 2.8 it also replaces `torch-cluster` and `torch-spline-conv`, which are no longer used: ```bash # TORCH = your torch version (e.g. 2.12.0), CUDA = cpu | cu126 | cu128 | cu130 | cu132 uv pip install pyg-lib -f https://data.pyg.org/whl/torch-${TORCH}+${CUDA}.html # torch-scatter / torch-sparse are optional and only published for older torch builds # (up to 2.12 as of 2026-09): uv pip install torch-scatter torch-sparse -f https://data.pyg.org/whl/torch-${TORCH}+${CUDA}.html ``` Check what was picked up with `torch_geometric.typing.WITH_PYG_LIB` (and `WITH_TORCH_SPARSE`). ## Basic Graph Creation ```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}") ``` ## Loading a Benchmark Dataset ```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}") ``` ## Data Structure PyG represents graphs using the `torch_geometric.data.Data` class with these key attributes: - **`data.x`**: Node feature matrix `[num_nodes, num_node_features]` - **`data.edge_index`**: Graph connectivity in COO format `[2, num_edges]` - **`data.edge_attr`**: Edge feature matrix `[num_edges, num_edge_features]` (optional) - **`data.y`**: Target labels for nodes or graphs - **`data.pos`**: Node spatial positions `[num_nodes, num_dimensions]` (optional) - **Custom attributes**: Can add any attribute (e.g., `data.train_mask`, `data.batch`) **Important**: These attributes are not mandatory—extend Data objects with custom attributes as needed. ## Edge Index Format Edges are stored in COO (coordinate) format as a `[2, num_edges]` tensor: - First row: source node indices - Second row: target node indices ```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) ``` ## Mini-Batch Processing PyG handles batching by creating block-diagonal adjacency matrices, concatenating multiple graphs into one large disconnected graph: - Adjacency matrices are stacked diagonally - Node features are concatenated along the node dimension - A `batch` vector maps each node to its source graph - No padding needed—computationally efficient ```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 ``` -
layers_reference.md 16.7 KB
# PyTorch Geometric Neural Network Layers Reference This document provides a comprehensive reference of all neural network layers available in `torch_geometric.nn`. ## Layer Capability Flags When selecting layers, consider these capability flags: - **SparseTensor**: Supports `torch_sparse.SparseTensor` format for efficient sparse operations - **edge_weight**: Handles one-dimensional edge weight data - **edge_attr**: Processes multi-dimensional edge feature information - **Bipartite**: Works with bipartite graphs (different source/target node dimensions) - **Static**: Operates on static graphs with batched node features - **Lazy**: Enables initialization without specifying input channel dimensions ## Convolutional Layers ### Standard Graph Convolutions **GCNConv** - Graph Convolutional Network layer - Implements spectral graph convolution with symmetric normalization - Supports: SparseTensor, edge_weight, Bipartite, Lazy - Use for: Citation networks, social networks, general graph learning - Example: `GCNConv(in_channels, out_channels, improved=False, cached=True)` **SAGEConv** - GraphSAGE layer - Inductive learning via neighborhood sampling and aggregation - Supports: SparseTensor, Bipartite, Lazy - Use for: Large graphs, inductive learning, heterogeneous features - Example: `SAGEConv(in_channels, out_channels, aggr='mean')` **GATConv** - Graph Attention Network layer - Multi-head attention mechanism for adaptive neighbor weighting - Supports: SparseTensor, edge_attr, Bipartite, Static, Lazy - Use for: Tasks requiring variable neighbor importance - Example: `GATConv(in_channels, out_channels, heads=8, dropout=0.6)` **GraphConv** - Simple graph convolution (Morris et al.) - Basic message passing with optional edge weights - Supports: SparseTensor, edge_weight, Bipartite, Lazy - Use for: Baseline models, simple graph structures - Example: `GraphConv(in_channels, out_channels, aggr='add')` **GINConv** - Graph Isomorphism Network layer - Maximally powerful GNN for graph isomorphism testing - Supports: Bipartite - Use for: Graph classification, molecular property prediction - Example: `GINConv(nn.Sequential(nn.Linear(in_channels, out_channels), nn.ReLU()))` **TransformerConv** - Graph Transformer layer - Combines graph structure with transformer attention - Supports: SparseTensor, Bipartite, Lazy - Use for: Long-range dependencies, complex graphs - Example: `TransformerConv(in_channels, out_channels, heads=8, beta=True)` **ChebConv** - Chebyshev spectral graph convolution - Uses Chebyshev polynomials for efficient spectral filtering - Supports: SparseTensor, edge_weight, Bipartite, Lazy - Use for: Spectral graph learning, efficient convolutions - Example: `ChebConv(in_channels, out_channels, K=3)` **SGConv** - Simplified Graph Convolution - Pre-computes fixed number of propagation steps - Supports: SparseTensor, edge_weight, Bipartite, Lazy - Use for: Fast training, shallow models - Example: `SGConv(in_channels, out_channels, K=2)` **APPNP** - Approximate Personalized Propagation of Neural Predictions - Separates feature transformation from propagation - Supports: SparseTensor, edge_weight, Lazy - Use for: Deep propagation without oversmoothing - Example: `APPNP(K=10, alpha=0.1)` **ARMAConv** - ARMA graph convolution - Uses ARMA filters for graph filtering - Supports: SparseTensor, edge_weight, Bipartite, Lazy - Use for: Advanced spectral methods - Example: `ARMAConv(in_channels, out_channels, num_stacks=3, num_layers=2)` **GATv2Conv** - Improved Graph Attention Network - Fixes static attention computation issue in GAT - Supports: SparseTensor, edge_attr, Bipartite, Static, Lazy - Use for: Better attention learning than original GAT - Example: `GATv2Conv(in_channels, out_channels, heads=8)` **SuperGATConv** - Self-supervised Graph Attention - Adds self-supervised attention mechanism - Supports: SparseTensor, edge_attr, Bipartite, Static, Lazy - Use for: Self-supervised learning, limited labels - Example: `SuperGATConv(in_channels, out_channels, heads=8)` **GMMConv** - Gaussian Mixture Model Convolution - Uses Gaussian kernels in pseudo-coordinate space - Supports: Bipartite - Use for: Point clouds, spatial data - Example: `GMMConv(in_channels, out_channels, dim=3, kernel_size=5)` **SplineConv** - Spline-based convolution - B-spline basis functions for spatial filtering - Supports: Bipartite - Use for: Irregular grids, continuous spaces - Example: `SplineConv(in_channels, out_channels, dim=2, kernel_size=5)` **NNConv** - Neural Network Convolution - Edge features processed by neural networks - Supports: edge_attr, Bipartite - Use for: Rich edge features, molecular graphs - Example: `NNConv(in_channels, out_channels, nn=edge_nn, aggr='mean')` **CGConv** - Crystal Graph Convolution - Designed for crystalline materials - Supports: Bipartite - Use for: Materials science, crystal structures - Example: `CGConv(in_channels, dim=3, batch_norm=True)` **EdgeConv** - Edge Convolution (Dynamic Graph CNN) - Dynamically computes edges based on feature space - Supports: Static - Use for: Point clouds, dynamic graphs - Example: `EdgeConv(nn=edge_nn, aggr='max')` **PointNetConv** - PointNet++ convolution - Local and global feature learning for point clouds - Use for: 3D point cloud processing - Example: `PointNetConv(local_nn, global_nn)` **ResGatedGraphConv** - Residual Gated Graph Convolution - Gating mechanism with residual connections - Supports: edge_attr, Bipartite, Lazy - Use for: Deep GNNs, complex features - Example: `ResGatedGraphConv(in_channels, out_channels)` **GENConv** - Generalized Graph Convolution - Generalizes multiple GNN variants - Supports: SparseTensor, edge_weight, edge_attr, Bipartite, Lazy - Use for: Flexible architecture exploration - Example: `GENConv(in_channels, out_channels, aggr='softmax', num_layers=2)` **FiLMConv** - Feature-wise Linear Modulation - Conditions on global features - Supports: Bipartite, Lazy - Use for: Conditional generation, multi-task learning - Example: `FiLMConv(in_channels, out_channels, num_relations=5)` **PANConv** - Path Attention Network - Attention over multi-hop paths - Supports: SparseTensor, Lazy - Use for: Complex connectivity patterns - Example: `PANConv(in_channels, out_channels, filter_size=3)` **ClusterGCNConv** - Cluster-GCN convolution - Efficient training via graph clustering - Supports: edge_attr, Lazy - Use for: Very large graphs - Example: `ClusterGCNConv(in_channels, out_channels)` **MFConv** - Multi-scale Feature Convolution - Aggregates features at multiple scales - Supports: SparseTensor, Lazy - Use for: Multi-scale patterns - Example: `MFConv(in_channels, out_channels)` **RGCNConv** - Relational Graph Convolution - Handles multiple edge types - Supports: SparseTensor, edge_weight, Lazy - Use for: Knowledge graphs, heterogeneous graphs - Example: `RGCNConv(in_channels, out_channels, num_relations=10)` **FAConv** - Frequency Adaptive Convolution - Adaptive filtering in spectral domain - Supports: SparseTensor, Lazy - Use for: Spectral graph learning - Example: `FAConv(in_channels, eps=0.1, dropout=0.5)` ### Molecular and 3D Convolutions **SchNet** - Continuous-filter convolutional layer - Designed for molecular dynamics - Use for: Molecular property prediction, 3D molecules - Example: `SchNet(hidden_channels=128, num_filters=64, num_interactions=6)` **DimeNet** - Directional Message Passing - Uses directional information and angles - Use for: 3D molecular structures, chemical properties - Example: `DimeNet(hidden_channels=128, out_channels=1, num_blocks=6)` **PointTransformerConv** - Point cloud transformer - Transformer for 3D point clouds - Use for: 3D vision, point cloud segmentation - Example: `PointTransformerConv(in_channels, out_channels)` ### Hypergraph Convolutions **HypergraphConv** - Hypergraph convolution - Operates on hyperedges (edges connecting multiple nodes) - Supports: Lazy - Use for: Multi-way relationships, chemical reactions - Example: `HypergraphConv(in_channels, out_channels)` **HGTConv** - Heterogeneous Graph Transformer - Transformer for heterogeneous graphs with multiple types - Supports: Lazy - Use for: Heterogeneous networks, knowledge graphs - Example: `HGTConv(in_channels, out_channels, metadata, heads=8)` ## Aggregation Operators **Aggr** - Base aggregation class - Flexible aggregation across nodes **SumAggregation** - Sum aggregation - Example: `SumAggregation()` **MeanAggregation** - Mean aggregation - Example: `MeanAggregation()` **MaxAggregation** - Max aggregation - Example: `MaxAggregation()` **SoftmaxAggregation** - Softmax-weighted aggregation - Learnable attention weights - Example: `SoftmaxAggregation(learn=True)` **PowerMeanAggregation** - Power mean aggregation - Learnable power parameter - Example: `PowerMeanAggregation(learn=True)` **LSTMAggregation** - LSTM-based aggregation - Sequential processing of neighbors - Example: `LSTMAggregation(in_channels, out_channels)` **SetTransformerAggregation** - Set Transformer aggregation - Transformer for permutation-invariant aggregation - Example: `SetTransformerAggregation(in_channels, out_channels)` **MultiAggregation** - Multiple aggregations - Combines multiple aggregation methods - Example: `MultiAggregation(['mean', 'max', 'std'])` ## Pooling Layers ### Global Pooling **global_mean_pool** - Global mean pooling - Averages node features per graph - Example: `global_mean_pool(x, batch)` **global_max_pool** - Global max pooling - Max over node features per graph - Example: `global_max_pool(x, batch)` **global_add_pool** - Global sum pooling - Sums node features per graph - Example: `global_add_pool(x, batch)` **global_sort_pool** - Global sort pooling - Sorts and concatenates top-k nodes - Example: `global_sort_pool(x, batch, k=30)` **GlobalAttention** - Global attention pooling - Learnable attention weights for aggregation - Example: `GlobalAttention(gate_nn)` **Set2Set** - Set2Set pooling - LSTM-based attention mechanism - Example: `Set2Set(in_channels, processing_steps=3)` ### Hierarchical Pooling **TopKPooling** - Top-k pooling - Keeps top-k nodes based on projection scores - Example: `TopKPooling(in_channels, ratio=0.5)` **SAGPooling** - Self-Attention Graph Pooling - Uses self-attention for node selection - Example: `SAGPooling(in_channels, ratio=0.5)` **ASAPooling** - Adaptive Structure Aware Pooling - Structure-aware node selection - Example: `ASAPooling(in_channels, ratio=0.5)` **PANPooling** - Path Attention Pooling - Attention over paths for pooling - Example: `PANPooling(in_channels, ratio=0.5)` **EdgePooling** - Edge contraction pooling - Pools by contracting edges - Example: `EdgePooling(in_channels)` **MemPooling** - Memory-based pooling - Learnable cluster assignments - Example: `MemPooling(in_channels, out_channels, heads=4, num_clusters=10)` **avg_pool** / **max_pool** - Average/Max pool with clustering - Pools nodes within clusters - Example: `avg_pool(cluster, data)` ## Normalization Layers **BatchNorm** - Batch normalization - Normalizes features across batch - Example: `BatchNorm(in_channels)` **LayerNorm** - Layer normalization - Normalizes features per sample - Example: `LayerNorm(in_channels)` **InstanceNorm** - Instance normalization - Normalizes per sample and graph - Example: `InstanceNorm(in_channels)` **GraphNorm** - Graph normalization - Graph-specific normalization - Example: `GraphNorm(in_channels)` **PairNorm** - Pair normalization - Prevents oversmoothing in deep GNNs - Example: `PairNorm(scale_individually=False)` **MessageNorm** - Message normalization - Normalizes messages during passing - Example: `MessageNorm(learn_scale=True)` **DiffGroupNorm** - Differentiable Group Normalization - Learnable grouping for normalization - Example: `DiffGroupNorm(in_channels, groups=10)` ## Model Architectures ### Pre-Built Models **GCN** - Complete Graph Convolutional Network - Multi-layer GCN with dropout - Example: `GCN(in_channels, hidden_channels, num_layers, out_channels)` **GraphSAGE** - Complete GraphSAGE model - Multi-layer SAGE with dropout - Example: `GraphSAGE(in_channels, hidden_channels, num_layers, out_channels)` **GIN** - Complete Graph Isomorphism Network - Multi-layer GIN for graph classification - Example: `GIN(in_channels, hidden_channels, num_layers, out_channels)` **GAT** - Complete Graph Attention Network - Multi-layer GAT with attention - Example: `GAT(in_channels, hidden_channels, num_layers, out_channels, heads=8)` **PNA** - Principal Neighbourhood Aggregation - Combines multiple aggregators and scalers - Example: `PNA(in_channels, hidden_channels, num_layers, out_channels)` **EdgeCNN** - Edge Convolution CNN - Dynamic graph CNN for point clouds - Example: `EdgeCNN(out_channels, num_layers=3, k=20)` ### Auto-Encoders **GAE** - Graph Auto-Encoder - Encodes graphs into latent space - Example: `GAE(encoder)` **VGAE** - Variational Graph Auto-Encoder - Probabilistic graph encoding - Example: `VGAE(encoder)` **ARGA** - Adversarially Regularized Graph Auto-Encoder - GAE with adversarial regularization - Example: `ARGA(encoder, discriminator)` **ARGVA** - Adversarially Regularized Variational Graph Auto-Encoder - VGAE with adversarial regularization - Example: `ARGVA(encoder, discriminator)` ### Knowledge Graph Embeddings **TransE** - Translating embeddings - Learns entity and relation embeddings - Example: `TransE(num_nodes, num_relations, hidden_channels)` **RotatE** - Rotational embeddings - Embeddings in complex space - Example: `RotatE(num_nodes, num_relations, hidden_channels)` **ComplEx** - Complex embeddings - Complex-valued embeddings - Example: `ComplEx(num_nodes, num_relations, hidden_channels)` **DistMult** - Bilinear diagonal model - Simplified bilinear model - Example: `DistMult(num_nodes, num_relations, hidden_channels)` ## Utility Layers **Sequential** - Sequential container - Chains multiple layers - Example: `Sequential('x, edge_index', [(GCNConv(16, 64), 'x, edge_index -> x'), nn.ReLU()])` **JumpingKnowledge** - Jumping knowledge connections - Combines representations from all layers - Modes: 'cat', 'max', 'lstm' - Example: `JumpingKnowledge(mode='cat')` **DeepGCNLayer** - Deep GCN layer wrapper - Enables very deep GNNs with skip connections - Example: `DeepGCNLayer(conv, norm, act, block='res+', dropout=0.1)` **MLP** - Multi-layer perceptron - Standard feedforward network - Example: `MLP([in_channels, 64, 64, out_channels], dropout=0.5)` **Linear** - Lazy linear layer - Linear transformation with lazy initialization - Example: `Linear(in_channels, out_channels, bias=True)` ## Dense Layers For dense (non-sparse) graph representations: **DenseGCNConv** - Dense GCN layer **DenseSAGEConv** - Dense SAGE layer **DenseGINConv** - Dense GIN layer **DenseGraphConv** - Dense graph convolution These are useful when working with small, fully-connected, or densely represented graphs. ## Usage Tips 1. **Start simple**: Begin with GCNConv or GATConv for most tasks 2. **Consider data type**: Use molecular layers (SchNet, DimeNet) for 3D structures 3. **Check capabilities**: Match layer capabilities to your data (edge features, bipartite, etc.) 4. **Deep networks**: Use normalization (PairNorm, LayerNorm) and JumpingKnowledge for deep GNNs 5. **Large graphs**: Use scalable layers (SAGE, Cluster-GCN) with neighbor sampling 6. **Heterogeneous**: Use RGCNConv, HGTConv, or to_hetero() conversion 7. **Lazy initialization**: Use lazy layers when input dimensions vary or are unknown ## Common Patterns ### Basic GNN ```python from torch_geometric.nn import GCNConv, global_mean_pool class GNN(torch.nn.Module): def __init__(self, in_channels, hidden_channels, out_channels): super().__init__() self.conv1 = GCNConv(in_channels, hidden_channels) self.conv2 = GCNConv(hidden_channels, out_channels) def forward(self, x, edge_index, batch): x = self.conv1(x, edge_index).relu() x = self.conv2(x, edge_index) return global_mean_pool(x, batch) ``` ### Deep GNN with Normalization ```python class DeepGNN(torch.nn.Module): def __init__(self, in_channels, hidden_channels, num_layers, out_channels): super().__init__() self.convs = torch.nn.ModuleList() self.norms = torch.nn.ModuleList() self.convs.append(GCNConv(in_channels, hidden_channels)) self.norms.append(LayerNorm(hidden_channels)) for _ in range(num_layers - 2): self.convs.append(GCNConv(hidden_channels, hidden_channels)) self.norms.append(LayerNorm(hidden_channels)) self.convs.append(GCNConv(hidden_channels, out_channels)) self.jk = JumpingKnowledge(mode='cat') def forward(self, x, edge_index, batch): xs = [] for conv, norm in zip(self.convs[:-1], self.norms): x = conv(x, edge_index) x = norm(x) x = F.relu(x) xs.append(x) x = self.convs[-1](x, edge_index) xs.append(x) x = self.jk(xs) return global_mean_pool(x, batch) ``` -
training_workflows.md 3.4 KB
# Training Workflows Complete training loops for node classification, graph classification, and large-scale neighbor sampling. ## Node Classification (Single Graph) ```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}') ``` ## Graph Classification (Multiple Graphs) ```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}') ``` ## Large-Scale Graphs with Neighbor Sampling For large graphs, use `NeighborLoader` to sample subgraphs (requires the optional `pyg-lib` or `torch-sparse` package; without either it raises `ImportError`): ```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() ``` **Important**: - Output subgraphs are directed - Node indices are relabeled (0 to batch.num_nodes - 1) - Only use seed node predictions for loss computation - Sampling beyond 2-3 hops is generally not feasible -
transforms_reference.md 17.5 KB
# PyTorch Geometric Transforms Reference This document provides a comprehensive reference of all transforms available in `torch_geometric.transforms`. ## Overview Transforms modify `Data` or `HeteroData` objects before or during training. Apply them via: ```python # During dataset loading dataset = MyDataset(root='/tmp', transform=MyTransform()) # Apply to individual data transform = MyTransform() data = transform(data) # Compose multiple transforms from torch_geometric.transforms import Compose transform = Compose([Transform1(), Transform2(), Transform3()]) ``` ## General Transforms ### NormalizeFeatures **Purpose**: Row-normalizes node features to sum to 1 **Use case**: Feature scaling, probability-like features ```python from torch_geometric.transforms import NormalizeFeatures transform = NormalizeFeatures() ``` ### ToDevice **Purpose**: Transfers data to specified device (CPU/GPU) **Use case**: GPU training, device management ```python from torch_geometric.transforms import ToDevice transform = ToDevice('cuda') ``` ### RandomNodeSplit **Purpose**: Creates train/val/test node masks **Use case**: Node classification splits **Parameters**: `split='train_rest'`, `num_splits`, `num_val`, `num_test` ```python from torch_geometric.transforms import RandomNodeSplit transform = RandomNodeSplit(num_val=0.1, num_test=0.2) ``` ### RandomLinkSplit **Purpose**: Creates train/val/test edge splits **Use case**: Link prediction **Parameters**: `num_val`, `num_test`, `is_undirected`, `split_labels` ```python from torch_geometric.transforms import RandomLinkSplit transform = RandomLinkSplit(num_val=0.1, num_test=0.2) ``` ### IndexToMask **Purpose**: Converts indices to boolean masks **Use case**: Data preprocessing ```python from torch_geometric.transforms import IndexToMask transform = IndexToMask() ``` ### MaskToIndex **Purpose**: Converts boolean masks to indices **Use case**: Data preprocessing ```python from torch_geometric.transforms import MaskToIndex transform = MaskToIndex() ``` ### FixedPoints **Purpose**: Samples a fixed number of points **Use case**: Point cloud subsampling **Parameters**: `num`, `replace`, `allow_duplicates` ```python from torch_geometric.transforms import FixedPoints transform = FixedPoints(1024) ``` ### ToDense **Purpose**: Converts to dense adjacency matrices **Use case**: Small graphs, dense operations ```python from torch_geometric.transforms import ToDense transform = ToDense(num_nodes=100) ``` ### ToSparseTensor **Purpose**: Converts edge_index to SparseTensor **Use case**: Efficient sparse operations **Parameters**: `remove_edge_index`, `fill_cache` ```python from torch_geometric.transforms import ToSparseTensor transform = ToSparseTensor() ``` ## Graph Structure Transforms ### ToUndirected **Purpose**: Converts directed graph to undirected **Use case**: Undirected graph algorithms **Parameters**: `reduce='add'` (how to handle duplicate edges) ```python from torch_geometric.transforms import ToUndirected transform = ToUndirected() ``` ### AddSelfLoops **Purpose**: Adds self-loops to all nodes **Use case**: GCN-style convolutions **Parameters**: `fill_value` (edge attribute for self-loops) ```python from torch_geometric.transforms import AddSelfLoops transform = AddSelfLoops() ``` ### RemoveSelfLoops **Purpose**: Removes all self-loops **Use case**: Cleaning graph structure ```python from torch_geometric.transforms import RemoveSelfLoops transform = RemoveSelfLoops() ``` ### RemoveIsolatedNodes **Purpose**: Removes nodes without edges **Use case**: Graph cleaning ```python from torch_geometric.transforms import RemoveIsolatedNodes transform = RemoveIsolatedNodes() ``` ### RemoveDuplicatedEdges **Purpose**: Removes duplicate edges **Use case**: Graph cleaning ```python from torch_geometric.transforms import RemoveDuplicatedEdges transform = RemoveDuplicatedEdges() ``` ### LargestConnectedComponents **Purpose**: Keeps only the largest connected component **Use case**: Focus on main graph structure **Parameters**: `num_components` (how many components to keep) ```python from torch_geometric.transforms import LargestConnectedComponents transform = LargestConnectedComponents(num_components=1) ``` ### KNNGraph **Purpose**: Creates edges based on k-nearest neighbors **Use case**: Point clouds, spatial data **Parameters**: `k`, `loop`, `force_undirected`, `flow` ```python from torch_geometric.transforms import KNNGraph transform = KNNGraph(k=6) ``` ### RadiusGraph **Purpose**: Creates edges within a radius **Use case**: Point clouds, spatial data **Parameters**: `r`, `loop`, `max_num_neighbors`, `flow` ```python from torch_geometric.transforms import RadiusGraph transform = RadiusGraph(r=0.1) ``` ### Delaunay **Purpose**: Computes Delaunay triangulation **Use case**: 2D/3D spatial graphs ```python from torch_geometric.transforms import Delaunay transform = Delaunay() ``` ### FaceToEdge **Purpose**: Converts mesh faces to edges **Use case**: Mesh processing ```python from torch_geometric.transforms import FaceToEdge transform = FaceToEdge() ``` ### LineGraph **Purpose**: Converts graph to its line graph **Use case**: Edge-centric analysis **Parameters**: `force_directed` ```python from torch_geometric.transforms import LineGraph transform = LineGraph() ``` ### GDC **Purpose**: Graph Diffusion Convolution preprocessing **Use case**: Improved message passing **Parameters**: `self_loop_weight`, `normalization_in`, `normalization_out`, `diffusion_kwargs` ```python from torch_geometric.transforms import GDC transform = GDC(self_loop_weight=1, normalization_in='sym', diffusion_kwargs=dict(method='ppr', alpha=0.15)) ``` ### SIGN **Purpose**: Scalable Inception Graph Neural Networks preprocessing **Use case**: Efficient multi-scale features **Parameters**: `K` (number of hops) ```python from torch_geometric.transforms import SIGN transform = SIGN(K=3) ``` ## Feature Transforms ### OneHotDegree **Purpose**: One-hot encodes node degree **Use case**: Degree as feature **Parameters**: `max_degree`, `cat` (concatenate with existing features) ```python from torch_geometric.transforms import OneHotDegree transform = OneHotDegree(max_degree=100) ``` ### LocalDegreeProfile **Purpose**: Appends local degree profile **Use case**: Structural node features ```python from torch_geometric.transforms import LocalDegreeProfile transform = LocalDegreeProfile() ``` ### Constant **Purpose**: Adds constant features to nodes **Use case**: Featureless graphs **Parameters**: `value`, `cat` ```python from torch_geometric.transforms import Constant transform = Constant(value=1.0) ``` ### TargetIndegree **Purpose**: Saves in-degree as target **Use case**: Degree prediction **Parameters**: `norm`, `max_value` ```python from torch_geometric.transforms import TargetIndegree transform = TargetIndegree(norm=False) ``` ### AddRandomWalkPE **Purpose**: Adds random walk positional encoding **Use case**: Positional information **Parameters**: `walk_length`, `attr_name` ```python from torch_geometric.transforms import AddRandomWalkPE transform = AddRandomWalkPE(walk_length=20) ``` ### AddLaplacianEigenvectorPE **Purpose**: Adds Laplacian eigenvector positional encoding **Use case**: Spectral positional information **Parameters**: `k` (number of eigenvectors), `attr_name` ```python from torch_geometric.transforms import AddLaplacianEigenvectorPE transform = AddLaplacianEigenvectorPE(k=10) ``` ### AddMetaPaths **Purpose**: Adds meta-path induced edges **Use case**: Heterogeneous graphs **Parameters**: `metapaths`, `drop_orig_edges`, `drop_unconnected_nodes` ```python from torch_geometric.transforms import AddMetaPaths metapaths = [[('author', 'paper'), ('paper', 'author')]] # Co-authorship transform = AddMetaPaths(metapaths) ``` ### SVDFeatureReduction **Purpose**: Reduces feature dimensionality via SVD **Use case**: Dimensionality reduction **Parameters**: `out_channels` ```python from torch_geometric.transforms import SVDFeatureReduction transform = SVDFeatureReduction(out_channels=64) ``` ## Vision/Spatial Transforms ### Center **Purpose**: Centers node positions **Use case**: Point cloud preprocessing ```python from torch_geometric.transforms import Center transform = Center() ``` ### NormalizeScale **Purpose**: Normalizes positions to unit sphere **Use case**: Point cloud normalization ```python from torch_geometric.transforms import NormalizeScale transform = NormalizeScale() ``` ### NormalizeRotation **Purpose**: Rotates to principal components **Use case**: Rotation-invariant learning **Parameters**: `max_points` ```python from torch_geometric.transforms import NormalizeRotation transform = NormalizeRotation() ``` ### Distance **Purpose**: Saves Euclidean distance as edge attribute **Use case**: Spatial graphs **Parameters**: `norm`, `max_value`, `cat` ```python from torch_geometric.transforms import Distance transform = Distance(norm=False, cat=False) ``` ### Cartesian **Purpose**: Saves relative Cartesian coordinates as edge attributes **Use case**: Spatial relationships **Parameters**: `norm`, `max_value`, `cat` ```python from torch_geometric.transforms import Cartesian transform = Cartesian(norm=False) ``` ### Polar **Purpose**: Saves polar coordinates as edge attributes **Use case**: 2D spatial graphs **Parameters**: `norm`, `max_value`, `cat` ```python from torch_geometric.transforms import Polar transform = Polar(norm=False) ``` ### Spherical **Purpose**: Saves spherical coordinates as edge attributes **Use case**: 3D spatial graphs **Parameters**: `norm`, `max_value`, `cat` ```python from torch_geometric.transforms import Spherical transform = Spherical(norm=False) ``` ### LocalCartesian **Purpose**: Saves coordinates in local coordinate system **Use case**: Local spatial features **Parameters**: `norm`, `cat` ```python from torch_geometric.transforms import LocalCartesian transform = LocalCartesian() ``` ### PointPairFeatures **Purpose**: Computes point pair features **Use case**: 3D registration, correspondence **Parameters**: `cat` ```python from torch_geometric.transforms import PointPairFeatures transform = PointPairFeatures() ``` ## Data Augmentation ### RandomJitter **Purpose**: Randomly jitters node positions **Use case**: Point cloud augmentation **Parameters**: `translate`, `scale` ```python from torch_geometric.transforms import RandomJitter transform = RandomJitter(0.01) ``` ### RandomFlip **Purpose**: Randomly flips positions along axis **Use case**: Geometric augmentation **Parameters**: `axis`, `p` (probability) ```python from torch_geometric.transforms import RandomFlip transform = RandomFlip(axis=0, p=0.5) ``` ### RandomScale **Purpose**: Randomly scales positions **Use case**: Scale augmentation **Parameters**: `scales` (min, max) ```python from torch_geometric.transforms import RandomScale transform = RandomScale((0.9, 1.1)) ``` ### RandomRotate **Purpose**: Randomly rotates positions **Use case**: Rotation augmentation **Parameters**: `degrees` (range), `axis` (rotation axis) ```python from torch_geometric.transforms import RandomRotate transform = RandomRotate(degrees=15, axis=2) ``` ### RandomShear **Purpose**: Randomly shears positions **Use case**: Geometric augmentation **Parameters**: `shear` (range) ```python from torch_geometric.transforms import RandomShear transform = RandomShear(0.1) ``` ### RandomTranslate **Purpose**: Randomly translates positions **Use case**: Translation augmentation **Parameters**: `translate` (range) ```python from torch_geometric.transforms import RandomTranslate transform = RandomTranslate(0.1) ``` ### LinearTransformation **Purpose**: Applies linear transformation matrix **Use case**: Custom geometric transforms **Parameters**: `matrix` ```python from torch_geometric.transforms import LinearTransformation import torch matrix = torch.eye(3) transform = LinearTransformation(matrix) ``` ## Mesh Processing ### SamplePoints **Purpose**: Samples points uniformly from mesh **Use case**: Mesh to point cloud conversion **Parameters**: `num`, `remove_faces`, `include_normals` ```python from torch_geometric.transforms import SamplePoints transform = SamplePoints(num=1024) ``` ### GenerateMeshNormals **Purpose**: Generates face/vertex normals **Use case**: Mesh processing ```python from torch_geometric.transforms import GenerateMeshNormals transform = GenerateMeshNormals() ``` ### FaceToEdge **Purpose**: Converts mesh faces to edges **Use case**: Mesh to graph conversion **Parameters**: `remove_faces` ```python from torch_geometric.transforms import FaceToEdge transform = FaceToEdge() ``` ## Sampling and Splitting ### GridSampling **Purpose**: Clusters points in voxel grid **Use case**: Point cloud downsampling **Parameters**: `size` (voxel size), `start`, `end` ```python from torch_geometric.transforms import GridSampling transform = GridSampling(size=0.1) ``` ### FixedPoints **Purpose**: Samples fixed number of points **Use case**: Uniform point cloud size **Parameters**: `num`, `replace`, `allow_duplicates` ```python from torch_geometric.transforms import FixedPoints transform = FixedPoints(num=2048, replace=False) ``` ### RandomScale **Purpose**: Randomly scales by sampling from range **Use case**: Scale augmentation (already listed above) ### VirtualNode **Purpose**: Adds a virtual node connected to all nodes **Use case**: Global information propagation ```python from torch_geometric.transforms import VirtualNode transform = VirtualNode() ``` ## Specialized Transforms ### ToSLIC **Purpose**: Converts images to superpixel graphs (SLIC algorithm) **Use case**: Image as graph **Parameters**: `num_segments`, `compactness`, `add_seg`, `add_img` ```python from torch_geometric.transforms import ToSLIC transform = ToSLIC(num_segments=75) ``` ### GCNNorm **Purpose**: Applies GCN-style normalization to edges **Use case**: Preprocessing for GCN **Parameters**: `add_self_loops` ```python from torch_geometric.transforms import GCNNorm transform = GCNNorm(add_self_loops=True) ``` ### LaplacianLambdaMax **Purpose**: Computes largest Laplacian eigenvalue **Use case**: ChebConv preprocessing **Parameters**: `normalization`, `is_undirected` ```python from torch_geometric.transforms import LaplacianLambdaMax transform = LaplacianLambdaMax(normalization='sym') ``` ### NormalizeRotation **Purpose**: Rotates mesh/point cloud to align with principal axes **Use case**: Canonical orientation **Parameters**: `max_points` ```python from torch_geometric.transforms import NormalizeRotation transform = NormalizeRotation() ``` ## Compose and Apply ### Compose **Purpose**: Chains multiple transforms **Use case**: Complex preprocessing pipelines ```python from torch_geometric.transforms import Compose transform = Compose([ Center(), NormalizeScale(), KNNGraph(k=6), Distance(norm=False), ]) ``` ### BaseTransform **Purpose**: Base class for custom transforms **Use case**: Implementing custom transforms ```python from torch_geometric.transforms import BaseTransform class MyTransform(BaseTransform): def __init__(self, param): self.param = param def __call__(self, data): # Modify data data.x = data.x * self.param return data ``` ## Common Transform Combinations ### Node Classification Preprocessing ```python transform = Compose([ NormalizeFeatures(), RandomNodeSplit(num_val=0.1, num_test=0.2), ]) ``` ### Point Cloud Processing ```python transform = Compose([ Center(), NormalizeScale(), RandomRotate(degrees=15, axis=2), RandomJitter(0.01), KNNGraph(k=6), Distance(norm=False), ]) ``` ### Mesh to Graph ```python transform = Compose([ FaceToEdge(remove_faces=True), GenerateMeshNormals(), Distance(norm=True), ]) ``` ### Graph Structure Enhancement ```python transform = Compose([ ToUndirected(), AddSelfLoops(), RemoveIsolatedNodes(), GCNNorm(), ]) ``` ### Heterogeneous Graph Preprocessing ```python transform = Compose([ AddMetaPaths(metapaths=[ [('author', 'paper'), ('paper', 'author')], [('author', 'paper'), ('paper', 'conference'), ('conference', 'paper'), ('paper', 'author')] ]), RandomNodeSplit(split='train_rest', num_val=0.1, num_test=0.2), ]) ``` ### Link Prediction ```python transform = Compose([ NormalizeFeatures(), RandomLinkSplit(num_val=0.1, num_test=0.2, is_undirected=True), ]) ``` ## Usage Tips 1. **Order matters**: Apply structural transforms before feature transforms 2. **Caching**: Some transforms (like GDC) are expensive—apply once 3. **Augmentation**: Use Random* transforms during training only 4. **Compose sparingly**: Too many transforms slow down data loading 5. **Custom transforms**: Inherit from `BaseTransform` for custom logic 6. **Pre-transforms**: Apply expensive transforms once during dataset processing: ```python dataset = MyDataset(root='/tmp', pre_transform=ExpensiveTransform()) ``` 7. **Dynamic transforms**: Apply cheap transforms during training: ```python dataset = MyDataset(root='/tmp', transform=CheapTransform()) ``` ## Performance Considerations **Expensive transforms** (apply as pre_transform): - GDC - SIGN - KNNGraph (for large point clouds) - AddLaplacianEigenvectorPE - SVDFeatureReduction **Cheap transforms** (apply as transform): - NormalizeFeatures - ToUndirected - AddSelfLoops - Random* augmentations - ToDevice **Example**: ```python from torch_geometric.datasets import Planetoid from torch_geometric.transforms import Compose, GDC, NormalizeFeatures # Expensive preprocessing done once pre_transform = GDC( self_loop_weight=1, normalization_in='sym', diffusion_kwargs=dict(method='ppr', alpha=0.15) ) # Cheap transform applied each time transform = NormalizeFeatures() dataset = Planetoid( root='/tmp/Cora', name='Cora', pre_transform=pre_transform, transform=transform ) ```
-
-
scripts
-
benchmark_model.py 9.8 KB
#!/usr/bin/env python3 """ Benchmark GNN models on standard datasets. This script provides a simple way to benchmark different GNN architectures on common datasets and compare their performance. Usage: python benchmark_model.py --models gcn gat --dataset Cora python benchmark_model.py --models gcn --dataset Cora --epochs 200 --runs 10 """ import argparse import torch import torch.nn.functional as F from torch_geometric.nn import GCNConv, GATConv, SAGEConv from torch_geometric.datasets import Planetoid, TUDataset from torch_geometric.loader import DataLoader from torch_geometric.nn import global_mean_pool import time import numpy as np class GCN(torch.nn.Module): def __init__(self, num_features, hidden_channels, num_classes, dropout=0.5): super().__init__() self.conv1 = GCNConv(num_features, hidden_channels) self.conv2 = GCNConv(hidden_channels, num_classes) self.dropout = dropout def forward(self, x, edge_index, batch=None): x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) x = self.conv2(x, edge_index) if batch is not None: x = global_mean_pool(x, batch) return F.log_softmax(x, dim=1) class GAT(torch.nn.Module): def __init__(self, num_features, hidden_channels, num_classes, heads=8, dropout=0.6): super().__init__() self.conv1 = GATConv(num_features, hidden_channels, heads=heads, dropout=dropout) self.conv2 = GATConv(hidden_channels * heads, num_classes, heads=1, concat=False, dropout=dropout) self.dropout = dropout def forward(self, x, edge_index, batch=None): x = F.dropout(x, p=self.dropout, training=self.training) x = F.elu(self.conv1(x, edge_index)) x = F.dropout(x, p=self.dropout, training=self.training) x = self.conv2(x, edge_index) if batch is not None: x = global_mean_pool(x, batch) return F.log_softmax(x, dim=1) class GraphSAGE(torch.nn.Module): def __init__(self, num_features, hidden_channels, num_classes, dropout=0.5): super().__init__() self.conv1 = SAGEConv(num_features, hidden_channels) self.conv2 = SAGEConv(hidden_channels, num_classes) self.dropout = dropout def forward(self, x, edge_index, batch=None): x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) x = self.conv2(x, edge_index) if batch is not None: x = global_mean_pool(x, batch) return F.log_softmax(x, dim=1) MODELS = { 'gcn': GCN, 'gat': GAT, 'graphsage': GraphSAGE, } def train_node_classification(model, data, optimizer): """Train for node classification.""" model.train() optimizer.zero_grad() out = model(data.x, data.edge_index) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() return loss.item() @torch.no_grad() def test_node_classification(model, data): """Test for node classification.""" model.eval() out = model(data.x, data.edge_index) pred = out.argmax(dim=1) accs = [] for mask in [data.train_mask, data.val_mask, data.test_mask]: correct = (pred[mask] == data.y[mask]).sum() accs.append(float(correct) / int(mask.sum())) return accs def train_graph_classification(model, loader, optimizer, device): """Train for graph classification.""" model.train() total_loss = 0 for data in loader: data = data.to(device) optimizer.zero_grad() out = model(data.x, data.edge_index, data.batch) loss = F.nll_loss(out, data.y) loss.backward() optimizer.step() total_loss += loss.item() * data.num_graphs return total_loss / len(loader.dataset) @torch.no_grad() def test_graph_classification(model, loader, device): """Test for graph classification.""" model.eval() correct = 0 for data in loader: data = data.to(device) out = model(data.x, data.edge_index, data.batch) pred = out.argmax(dim=1) correct += (pred == data.y).sum().item() return correct / len(loader.dataset) def benchmark_node_classification(model_name, dataset_name, epochs, lr, weight_decay, device): """Benchmark a model on node classification.""" # Load dataset dataset = Planetoid(root=f'/tmp/{dataset_name}', name=dataset_name) data = dataset[0].to(device) # Create model model_class = MODELS[model_name] model = model_class( num_features=dataset.num_features, hidden_channels=64, num_classes=dataset.num_classes ).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay) # Training start_time = time.time() best_val_acc = 0 best_test_acc = 0 for epoch in range(1, epochs + 1): _loss = train_node_classification(model, data, optimizer) train_acc, val_acc, test_acc = test_node_classification(model, data) if val_acc > best_val_acc: best_val_acc = val_acc best_test_acc = test_acc train_time = time.time() - start_time return { 'train_acc': train_acc, 'val_acc': best_val_acc, 'test_acc': best_test_acc, 'train_time': train_time, } def benchmark_graph_classification(model_name, dataset_name, epochs, lr, device): """Benchmark a model on graph classification.""" # Load dataset dataset = TUDataset(root=f'/tmp/{dataset_name}', name=dataset_name) # Split dataset dataset = dataset.shuffle() train_dataset = dataset[:int(len(dataset) * 0.8)] test_dataset = dataset[int(len(dataset) * 0.8):] train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=32) # Create model model_class = MODELS[model_name] model = model_class( num_features=dataset.num_features, hidden_channels=64, num_classes=dataset.num_classes ).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=lr) # Training start_time = time.time() for epoch in range(1, epochs + 1): _loss = train_graph_classification(model, train_loader, optimizer, device) # Final evaluation train_acc = test_graph_classification(model, train_loader, device) test_acc = test_graph_classification(model, test_loader, device) train_time = time.time() - start_time return { 'train_acc': train_acc, 'test_acc': test_acc, 'train_time': train_time, } def run_benchmark(args): """Run benchmark experiments.""" device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f"Using device: {device}") # Determine task type if args.dataset in ['Cora', 'CiteSeer', 'PubMed']: task = 'node_classification' else: task = 'graph_classification' print(f"\nDataset: {args.dataset}") print(f"Task: {task}") print(f"Models: {', '.join(args.models)}") print(f"Epochs: {args.epochs}") print(f"Runs: {args.runs}") print("=" * 60) results = {model: [] for model in args.models} # Run experiments for run in range(args.runs): print(f"\nRun {run + 1}/{args.runs}") print("-" * 60) for model_name in args.models: if model_name not in MODELS: print(f"Unknown model: {model_name}") continue print(f" Training {model_name.upper()}...", end=" ") try: if task == 'node_classification': result = benchmark_node_classification( model_name, args.dataset, args.epochs, args.lr, args.weight_decay, device ) print(f"Test Acc: {result['test_acc']:.4f}, " f"Time: {result['train_time']:.2f}s") else: result = benchmark_graph_classification( model_name, args.dataset, args.epochs, args.lr, device ) print(f"Test Acc: {result['test_acc']:.4f}, " f"Time: {result['train_time']:.2f}s") results[model_name].append(result) except Exception as e: print(f"Error: {e}") # Print summary print("\n" + "=" * 60) print("BENCHMARK RESULTS") print("=" * 60) for model_name in args.models: if not results[model_name]: continue test_accs = [r['test_acc'] for r in results[model_name]] times = [r['train_time'] for r in results[model_name]] print(f"\n{model_name.upper()}") print(f" Test Accuracy: {np.mean(test_accs):.4f} ± {np.std(test_accs):.4f}") print(f" Training Time: {np.mean(times):.2f} ± {np.std(times):.2f}s") def main(): parser = argparse.ArgumentParser(description="Benchmark GNN models") parser.add_argument('--models', nargs='+', default=['gcn'], help='Model types to benchmark (gcn, gat, graphsage)') parser.add_argument('--dataset', type=str, default='Cora', help='Dataset name (Cora, CiteSeer, PubMed, ENZYMES, PROTEINS)') parser.add_argument('--epochs', type=int, default=200, help='Number of training epochs') parser.add_argument('--runs', type=int, default=5, help='Number of runs to average over') parser.add_argument('--lr', type=float, default=0.01, help='Learning rate') parser.add_argument('--weight-decay', type=float, default=5e-4, help='Weight decay for node classification') args = parser.parse_args() run_benchmark(args) if __name__ == '__main__': main() -
create_gnn_template.py 15 KB
#!/usr/bin/env python3 """ Generate boilerplate code for common GNN architectures in PyTorch Geometric. This script creates ready-to-use GNN model templates with training loops, evaluation metrics, and proper data handling. Usage: python create_gnn_template.py --model gcn --task node_classification --output my_model.py python create_gnn_template.py --model gat --task graph_classification --output graph_classifier.py """ import argparse from pathlib import Path TEMPLATES = { 'node_classification': { 'gcn': '''import torch import torch.nn.functional as F from torch_geometric.nn import GCNConv from torch_geometric.datasets import Planetoid class GCN(torch.nn.Module): """Graph Convolutional Network for node classification.""" def __init__(self, num_features, hidden_channels, num_classes, num_layers=2, dropout=0.5): super().__init__() self.convs = torch.nn.ModuleList() # First layer self.convs.append(GCNConv(num_features, hidden_channels)) # Hidden layers for _ in range(num_layers - 2): self.convs.append(GCNConv(hidden_channels, hidden_channels)) # Output layer self.convs.append(GCNConv(hidden_channels, num_classes)) self.dropout = dropout def forward(self, data): x, edge_index = data.x, data.edge_index # Apply conv layers with ReLU and dropout for conv in self.convs[:-1]: x = conv(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) # Final layer without activation x = self.convs[-1](x, edge_index) return F.log_softmax(x, dim=1) def train(model, data, optimizer): """Train the model for one epoch.""" model.train() optimizer.zero_grad() out = model(data) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() return loss.item() @torch.no_grad() def test(model, data): """Evaluate the model.""" model.eval() out = model(data) pred = out.argmax(dim=1) accs = [] for mask in [data.train_mask, data.val_mask, data.test_mask]: correct = (pred[mask] == data.y[mask]).sum() accs.append(int(correct) / int(mask.sum())) return accs def main(): # Load dataset dataset = Planetoid(root='/tmp/Cora', name='Cora') data = dataset[0] # Create model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GCN( num_features=dataset.num_features, hidden_channels=64, num_classes=dataset.num_classes, num_layers=3, dropout=0.5 ).to(device) data = data.to(device) # Setup optimizer optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) # Training loop print("Training GCN model...") best_val_acc = 0 for epoch in range(1, 201): loss = train(model, data, optimizer) train_acc, val_acc, test_acc = test(model, data) if val_acc > best_val_acc: best_val_acc = val_acc best_test_acc = test_acc if epoch % 10 == 0: print(f'Epoch {epoch:03d}, Loss: {loss:.4f}, ' f'Train: {train_acc:.4f}, Val: {val_acc:.4f}, Test: {test_acc:.4f}') print(f'\\nBest Test Accuracy: {best_test_acc:.4f}') if __name__ == '__main__': main() ''', 'gat': '''import torch import torch.nn.functional as F from torch_geometric.nn import GATConv from torch_geometric.datasets import Planetoid class GAT(torch.nn.Module): """Graph Attention Network for node classification.""" def __init__(self, num_features, hidden_channels, num_classes, heads=8, dropout=0.6): super().__init__() self.conv1 = GATConv(num_features, hidden_channels, heads=heads, dropout=dropout) self.conv2 = GATConv(hidden_channels * heads, num_classes, heads=1, concat=False, dropout=dropout) self.dropout = dropout def forward(self, data): x, edge_index = data.x, data.edge_index x = F.dropout(x, p=self.dropout, training=self.training) x = F.elu(self.conv1(x, edge_index)) x = F.dropout(x, p=self.dropout, training=self.training) x = self.conv2(x, edge_index) return F.log_softmax(x, dim=1) def train(model, data, optimizer): """Train the model for one epoch.""" model.train() optimizer.zero_grad() out = model(data) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() return loss.item() @torch.no_grad() def test(model, data): """Evaluate the model.""" model.eval() out = model(data) pred = out.argmax(dim=1) accs = [] for mask in [data.train_mask, data.val_mask, data.test_mask]: correct = (pred[mask] == data.y[mask]).sum() accs.append(int(correct) / int(mask.sum())) return accs def main(): # Load dataset dataset = Planetoid(root='/tmp/Cora', name='Cora') data = dataset[0] # Create model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GAT( num_features=dataset.num_features, hidden_channels=8, num_classes=dataset.num_classes, heads=8, dropout=0.6 ).to(device) data = data.to(device) # Setup optimizer optimizer = torch.optim.Adam(model.parameters(), lr=0.005, weight_decay=5e-4) # Training loop print("Training GAT model...") best_val_acc = 0 for epoch in range(1, 201): loss = train(model, data, optimizer) train_acc, val_acc, test_acc = test(model, data) if val_acc > best_val_acc: best_val_acc = val_acc best_test_acc = test_acc if epoch % 10 == 0: print(f'Epoch {epoch:03d}, Loss: {loss:.4f}, ' f'Train: {train_acc:.4f}, Val: {val_acc:.4f}, Test: {test_acc:.4f}') print(f'\\nBest Test Accuracy: {best_test_acc:.4f}') if __name__ == '__main__': main() ''', 'graphsage': '''import torch import torch.nn.functional as F from torch_geometric.nn import SAGEConv from torch_geometric.datasets import Planetoid class GraphSAGE(torch.nn.Module): """GraphSAGE for node classification.""" def __init__(self, num_features, hidden_channels, num_classes, num_layers=2, dropout=0.5): super().__init__() self.convs = torch.nn.ModuleList() self.convs.append(SAGEConv(num_features, hidden_channels)) for _ in range(num_layers - 2): self.convs.append(SAGEConv(hidden_channels, hidden_channels)) self.convs.append(SAGEConv(hidden_channels, num_classes)) self.dropout = dropout def forward(self, data): x, edge_index = data.x, data.edge_index for conv in self.convs[:-1]: x = conv(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) x = self.convs[-1](x, edge_index) return F.log_softmax(x, dim=1) def train(model, data, optimizer): model.train() optimizer.zero_grad() out = model(data) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() return loss.item() @torch.no_grad() def test(model, data): model.eval() out = model(data) pred = out.argmax(dim=1) accs = [] for mask in [data.train_mask, data.val_mask, data.test_mask]: correct = (pred[mask] == data.y[mask]).sum() accs.append(int(correct) / int(mask.sum())) return accs def main(): dataset = Planetoid(root='/tmp/Cora', name='Cora') data = dataset[0] device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GraphSAGE( num_features=dataset.num_features, hidden_channels=64, num_classes=dataset.num_classes, num_layers=2, dropout=0.5 ).to(device) data = data.to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) print("Training GraphSAGE model...") best_val_acc = 0 for epoch in range(1, 201): loss = train(model, data, optimizer) train_acc, val_acc, test_acc = test(model, data) if val_acc > best_val_acc: best_val_acc = val_acc best_test_acc = test_acc if epoch % 10 == 0: print(f'Epoch {epoch:03d}, Loss: {loss:.4f}, ' f'Train: {train_acc:.4f}, Val: {val_acc:.4f}, Test: {test_acc:.4f}') print(f'\\nBest Test Accuracy: {best_test_acc:.4f}') if __name__ == '__main__': main() ''', }, 'graph_classification': { 'gin': '''import torch import torch.nn.functional as F from torch_geometric.nn import GINConv, global_add_pool from torch_geometric.datasets import TUDataset from torch_geometric.loader import DataLoader class GIN(torch.nn.Module): """Graph Isomorphism Network for graph classification.""" def __init__(self, num_features, hidden_channels, num_classes, num_layers=3, dropout=0.5): super().__init__() self.convs = torch.nn.ModuleList() self.batch_norms = torch.nn.ModuleList() # Create MLP for first layer nn = torch.nn.Sequential( torch.nn.Linear(num_features, hidden_channels), torch.nn.ReLU(), torch.nn.Linear(hidden_channels, hidden_channels) ) self.convs.append(GINConv(nn)) self.batch_norms.append(torch.nn.BatchNorm1d(hidden_channels)) # Hidden layers for _ in range(num_layers - 2): nn = torch.nn.Sequential( torch.nn.Linear(hidden_channels, hidden_channels), torch.nn.ReLU(), torch.nn.Linear(hidden_channels, hidden_channels) ) self.convs.append(GINConv(nn)) self.batch_norms.append(torch.nn.BatchNorm1d(hidden_channels)) # Output MLP self.lin = torch.nn.Linear(hidden_channels, num_classes) self.dropout = dropout def forward(self, data): x, edge_index, batch = data.x, data.edge_index, data.batch for conv, batch_norm in zip(self.convs, self.batch_norms): x = conv(x, edge_index) x = batch_norm(x) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) # Global pooling x = global_add_pool(x, batch) # Output layer x = self.lin(x) return F.log_softmax(x, dim=1) def train(model, loader, optimizer, device): """Train the model for one epoch.""" model.train() total_loss = 0 for data in loader: data = data.to(device) optimizer.zero_grad() out = model(data) loss = F.nll_loss(out, data.y) loss.backward() optimizer.step() total_loss += loss.item() * data.num_graphs return total_loss / len(loader.dataset) @torch.no_grad() def test(model, loader, device): """Evaluate the model.""" model.eval() correct = 0 for data in loader: data = data.to(device) out = model(data) pred = out.argmax(dim=1) correct += (pred == data.y).sum().item() return correct / len(loader.dataset) def main(): # Load dataset dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES') print(f"Dataset: {dataset}") print(f"Number of graphs: {len(dataset)}") print(f"Number of features: {dataset.num_features}") print(f"Number of classes: {dataset.num_classes}") # Shuffle and split dataset = dataset.shuffle() train_dataset = dataset[:int(len(dataset) * 0.8)] test_dataset = dataset[int(len(dataset) * 0.8):] # Create data loaders train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=32) # Create model device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GIN( num_features=dataset.num_features, hidden_channels=64, num_classes=dataset.num_classes, num_layers=3, dropout=0.5 ).to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # Training loop print("\\nTraining GIN model...") for epoch in range(1, 101): loss = train(model, train_loader, optimizer, device) train_acc = test(model, train_loader, device) test_acc = test(model, test_loader, device) if epoch % 10 == 0: print(f'Epoch {epoch:03d}, Loss: {loss:.4f}, ' f'Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}') if __name__ == '__main__': main() ''', }, } def generate_template(model_type: str, task: str, output_path: str): """Generate a GNN template file.""" if task not in TEMPLATES: raise ValueError(f"Unknown task: {task}. Available: {list(TEMPLATES.keys())}") if model_type not in TEMPLATES[task]: raise ValueError(f"Model {model_type} not available for task {task}. " f"Available: {list(TEMPLATES[task].keys())}") template = TEMPLATES[task][model_type] # Write to file output_file = Path(output_path) output_file.parent.mkdir(parents=True, exist_ok=True) with open(output_file, 'w') as f: f.write(template) print(f"✓ Generated {model_type.upper()} template for {task}") print(f" Saved to: {output_path}") print("\nTo run the template:") print(f" python {output_path}") def list_templates(): """List all available templates.""" print("Available GNN Templates") print("=" * 50) for task, models in TEMPLATES.items(): print(f"\n{task.upper()}") print("-" * 50) for model in models.keys(): print(f" - {model}") print() def main(): parser = argparse.ArgumentParser( description="Generate GNN model templates", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python create_gnn_template.py --model gcn --task node_classification --output gcn_model.py python create_gnn_template.py --model gin --task graph_classification --output gin_model.py python create_gnn_template.py --list """ ) parser.add_argument('--model', type=str, help='Model type (gcn, gat, graphsage, gin)') parser.add_argument('--task', type=str, help='Task type (node_classification, graph_classification)') parser.add_argument('--output', type=str, default='gnn_model.py', help='Output file path (default: gnn_model.py)') parser.add_argument('--list', action='store_true', help='List all available templates') args = parser.parse_args() if args.list: list_templates() return if not args.model or not args.task: parser.print_help() print("\n" + "=" * 50) list_templates() return try: generate_template(args.model, args.task, args.output) except ValueError as e: print(f"Error: {e}") print("\nUse --list to see available templates") if __name__ == '__main__': main() -
visualize_graph.py 10 KB
#!/usr/bin/env python3 """ Visualize PyTorch Geometric graph structures using networkx and matplotlib. This script provides utilities to visualize Data objects, including: - Graph structure (nodes and edges) - Node features (as colors) - Edge attributes (as edge colors/widths) - Community/cluster assignments Usage: python visualize_graph.py --dataset Cora --output graph.png Or import and use: from scripts.visualize_graph import visualize_data visualize_data(data, title="My Graph", show_labels=True) """ import argparse import matplotlib.pyplot as plt import networkx as nx import torch from typing import Optional import numpy as np def visualize_data( data, title: str = "Graph Visualization", node_color_attr: Optional[str] = None, edge_color_attr: Optional[str] = None, show_labels: bool = False, node_size: int = 300, figsize: tuple = (12, 10), layout: str = "spring", output_path: Optional[str] = None, max_nodes: Optional[int] = None, ): """ Visualize a PyTorch Geometric Data object. Args: data: PyTorch Geometric Data object title: Plot title node_color_attr: Data attribute to use for node colors (e.g., 'y', 'train_mask') edge_color_attr: Data attribute to use for edge colors show_labels: Whether to show node labels node_size: Size of nodes in visualization figsize: Figure size (width, height) layout: Graph layout algorithm ('spring', 'circular', 'kamada_kawai', 'spectral') output_path: Path to save figure (if None, displays interactively) max_nodes: Maximum number of nodes to visualize (samples if exceeded) """ # Sample nodes if graph is too large if max_nodes and data.num_nodes > max_nodes: print(f"Graph has {data.num_nodes} nodes. Sampling {max_nodes} nodes for visualization.") node_indices = torch.randperm(data.num_nodes)[:max_nodes] data = data.subgraph(node_indices) # Convert to networkx graph G = nx.Graph() if is_undirected(data.edge_index) else nx.DiGraph() # Add nodes G.add_nodes_from(range(data.num_nodes)) # Add edges edge_index = data.edge_index.cpu().numpy() edges = list(zip(edge_index[0], edge_index[1])) G.add_edges_from(edges) # Setup figure fig, ax = plt.subplots(figsize=figsize) # Choose layout if layout == "spring": pos = nx.spring_layout(G, k=0.5, iterations=50) elif layout == "circular": pos = nx.circular_layout(G) elif layout == "kamada_kawai": pos = nx.kamada_kawai_layout(G) elif layout == "spectral": pos = nx.spectral_layout(G) else: raise ValueError(f"Unknown layout: {layout}") # Determine node colors if node_color_attr and hasattr(data, node_color_attr): node_colors = getattr(data, node_color_attr).cpu().numpy() if node_colors.dtype == bool: node_colors = node_colors.astype(int) if len(node_colors.shape) > 1: # Multi-dimensional features - use first dimension node_colors = node_colors[:, 0] else: node_colors = 'skyblue' # Determine edge colors if edge_color_attr and hasattr(data, edge_color_attr): edge_colors = getattr(data, edge_color_attr).cpu().numpy() if len(edge_colors.shape) > 1: edge_colors = edge_colors[:, 0] else: edge_colors = 'gray' # Draw graph nx.draw_networkx_nodes( G, pos, node_color=node_colors, node_size=node_size, cmap=plt.cm.viridis, ax=ax ) nx.draw_networkx_edges( G, pos, edge_color=edge_colors, alpha=0.3, arrows=isinstance(G, nx.DiGraph), arrowsize=10, ax=ax ) if show_labels: nx.draw_networkx_labels(G, pos, font_size=8, ax=ax) ax.set_title(title, fontsize=16, fontweight='bold') ax.axis('off') # Add colorbar if using numeric node colors if node_color_attr and isinstance(node_colors, np.ndarray): sm = plt.cm.ScalarMappable( cmap=plt.cm.viridis, norm=plt.Normalize(vmin=node_colors.min(), vmax=node_colors.max()) ) sm.set_array([]) cbar = plt.colorbar(sm, ax=ax, fraction=0.046, pad=0.04) cbar.set_label(node_color_attr, rotation=270, labelpad=20) plt.tight_layout() if output_path: plt.savefig(output_path, dpi=300, bbox_inches='tight') print(f"Figure saved to {output_path}") else: plt.show() plt.close() def is_undirected(edge_index): """Check if graph is undirected.""" row, col = edge_index _num_edges = edge_index.size(1) # Create a set of edges and reverse edges edges = set(zip(row.tolist(), col.tolist())) reverse_edges = set(zip(col.tolist(), row.tolist())) # Check if all edges have their reverse return edges == reverse_edges def plot_degree_distribution(data, output_path: Optional[str] = None): """Plot the degree distribution of the graph.""" from torch_geometric.utils import degree row, col = data.edge_index deg = degree(col, data.num_nodes).cpu().numpy() fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) # Histogram ax1.hist(deg, bins=50, edgecolor='black', alpha=0.7) ax1.set_xlabel('Degree', fontsize=12) ax1.set_ylabel('Frequency', fontsize=12) ax1.set_title('Degree Distribution', fontsize=14, fontweight='bold') ax1.grid(alpha=0.3) # Log-log plot unique_degrees, counts = np.unique(deg, return_counts=True) ax2.loglog(unique_degrees, counts, 'o-', alpha=0.7) ax2.set_xlabel('Degree (log scale)', fontsize=12) ax2.set_ylabel('Frequency (log scale)', fontsize=12) ax2.set_title('Degree Distribution (Log-Log)', fontsize=14, fontweight='bold') ax2.grid(alpha=0.3) plt.tight_layout() if output_path: plt.savefig(output_path, dpi=300, bbox_inches='tight') print(f"Degree distribution saved to {output_path}") else: plt.show() plt.close() def plot_graph_statistics(data, output_path: Optional[str] = None): """Plot various graph statistics.""" from torch_geometric.utils import degree, contains_self_loops, is_undirected as check_undirected # Compute statistics row, col = data.edge_index deg = degree(col, data.num_nodes).cpu().numpy() stats = { 'Nodes': data.num_nodes, 'Edges': data.num_edges, 'Avg Degree': deg.mean(), 'Max Degree': deg.max(), 'Self-loops': contains_self_loops(data.edge_index), 'Undirected': check_undirected(data.edge_index), } if hasattr(data, 'num_node_features'): stats['Node Features'] = data.num_node_features if hasattr(data, 'num_edge_features') and data.edge_attr is not None: stats['Edge Features'] = data.num_edge_features if hasattr(data, 'y'): if data.y.dim() == 1: stats['Classes'] = int(data.y.max().item()) + 1 # Create text plot fig, ax = plt.subplots(figsize=(8, 6)) ax.axis('off') text = "Graph Statistics\n" + "=" * 40 + "\n\n" for key, value in stats.items(): text += f"{key:20s}: {value}\n" ax.text(0.1, 0.5, text, fontsize=14, family='monospace', verticalalignment='center', transform=ax.transAxes) plt.tight_layout() if output_path: plt.savefig(output_path, dpi=300, bbox_inches='tight') print(f"Statistics saved to {output_path}") else: plt.show() plt.close() # Print to console as well print("\n" + text) def main(): parser = argparse.ArgumentParser(description="Visualize PyTorch Geometric graphs") parser.add_argument('--dataset', type=str, default='Cora', help='Dataset name (e.g., Cora, CiteSeer, ENZYMES)') parser.add_argument('--output', type=str, default=None, help='Output file path for visualization') parser.add_argument('--node-color', type=str, default='y', help='Attribute to use for node colors') parser.add_argument('--layout', type=str, default='spring', choices=['spring', 'circular', 'kamada_kawai', 'spectral'], help='Graph layout algorithm') parser.add_argument('--show-labels', action='store_true', help='Show node labels') parser.add_argument('--max-nodes', type=int, default=500, help='Maximum nodes to visualize') parser.add_argument('--stats', action='store_true', help='Show graph statistics') parser.add_argument('--degree', action='store_true', help='Show degree distribution') args = parser.parse_args() # Load dataset print(f"Loading dataset: {args.dataset}") try: # Try Planetoid datasets from torch_geometric.datasets import Planetoid dataset = Planetoid(root=f'/tmp/{args.dataset}', name=args.dataset) data = dataset[0] except Exception: try: # Try TUDataset from torch_geometric.datasets import TUDataset dataset = TUDataset(root=f'/tmp/{args.dataset}', name=args.dataset) data = dataset[0] except Exception as e: print(f"Error loading dataset: {e}") print("Supported datasets: Cora, CiteSeer, PubMed, ENZYMES, PROTEINS, etc.") return print(f"Loaded {args.dataset}: {data.num_nodes} nodes, {data.num_edges} edges") # Generate visualizations if args.stats: stats_output = args.output.replace('.png', '_stats.png') if args.output else None plot_graph_statistics(data, stats_output) if args.degree: degree_output = args.output.replace('.png', '_degree.png') if args.output else None plot_degree_distribution(data, degree_output) # Main visualization visualize_data( data, title=f"{args.dataset} Graph", node_color_attr=args.node_color, show_labels=args.show_labels, layout=args.layout, output_path=args.output, max_nodes=args.max_nodes ) if __name__ == '__main__': main()
-
-
SKILL.md 6.4 KB
--- name: alterlab-torch-geometric description: Graph Neural Networks with PyTorch Geometric (PyG) — node and graph classification, link prediction, GCN, GAT, and GraphSAGE layers, heterogeneous graphs, and molecular property prediction. Use when building or training GNNs for geometric deep learning on graph-structured data. Part of the AlterLab Academic Skills suite. license: MIT allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*) compatibility: No API key required. Runs locally via `uv run python`; requires torch and torch-geometric (current 2.8.0.post1 as of 2026-09; Python >= 3.10; release notes list PyTorch 2.9-2.12). Neighbor sampling and knn/radius graph ops additionally need the optional pyg-lib wheel. GPU optional. metadata: skill-author: AlterLab version: "1.0.1" last_updated: "2026-09-23" --- # PyTorch Geometric (PyG) ## Overview PyTorch Geometric is a library built on PyTorch for developing and training Graph Neural Networks (GNNs). Apply this skill for deep learning on graphs and irregular structures, including mini-batch processing, multi-GPU training, and geometric deep learning applications. ## When to Use This Skill This skill should be used when working with: - **Graph-based machine learning**: Node classification, graph classification, link prediction - **Molecular property prediction**: Drug discovery, chemical property prediction - **Social network analysis**: Community detection, influence prediction - **Citation networks**: Paper classification, recommendation systems - **3D geometric data**: Point clouds, meshes, molecular structures - **Heterogeneous graphs**: Multi-type nodes and edges (e.g., knowledge graphs) - **Large-scale graph learning**: Neighbor sampling, distributed training ### Does NOT Trigger | Scenario | Use Instead | |----------|-------------| | Classical graph algorithms, centrality, shortest paths, or network visualization without learning | `alterlab-networkx` | | Social-network-analysis designs (centrality, community detection, ERGMs) for substantive social-science claims | `alterlab-sna` | | End-to-end molecular property prediction with MoleculeNet featurizers and pretrained chemistry models | `alterlab-deepchem` | | Projects built on TorchDrug's datasets and tasks (GearNet, retrosynthesis, molecule generation) | `alterlab-torchdrug` | ## Quick Start ```bash uv pip install torch_geometric # Optional compiled ops (neighbor sampling, knn/radius graphs, fps): pick TORCH/CUDA to match your install uv pip install pyg-lib -f https://data.pyg.org/whl/torch-${TORCH}+${CUDA}.html ``` PyG 2.8 folded `torch-cluster` and `torch-spline-conv` into `pyg-lib` (>= 0.7), so those two packages are no longer needed. `NeighborLoader` raises `ImportError` unless `pyg-lib` (or `torch-sparse`) is installed; for current PyTorch (2.13/2.14) data.pyg.org publishes only `pyg-lib` wheels, not `torch-scatter`/`torch-sparse`. Graphs are `torch_geometric.data.Data` objects: `x` (node features `[N, F]`), `edge_index` (connectivity in COO `[2, E]`), optional `edge_attr`, `y`, `pos`, and any custom attribute (`train_mask`, etc.). `DataLoader` batches multiple graphs into one block-diagonal graph (no padding); a `batch` vector maps nodes back to their source graph. Full install/sparse-deps, basic graph creation, benchmark loading, edge-index format, and mini-batching details: `references/getting_started.md`. ## Core Workflow 1. **Load or build data** — benchmark datasets, custom `InMemoryDataset`, or from CSV (`references/datasets_and_loading.md`; full catalog in `references/datasets_reference.md`). 2. **Define a GNN** — stack pre-built conv layers (GCNConv, GATConv, SAGEConv) or subclass `MessagePassing` for custom layers (`references/building_gnns.md`; full layer list in `references/layers_reference.md`). 3. **Train** — node classification (single graph, train/test masks), graph classification (`DataLoader` + global pooling), or large-scale via `NeighborLoader` neighbor sampling (`references/training_workflows.md`). 4. **Go advanced if needed** — `HeteroData`/`to_hetero` for heterogeneous graphs, transforms, `GNNExplainer` explainability, hierarchical pooling, GPU, save/load (`references/advanced_features.md`; transforms catalog in `references/transforms_reference.md`). ## Building GNNs (at a glance) GNNs follow neighborhood aggregation: transform node features → propagate messages along edges → aggregate from neighbors → update representations. PyG ships 40+ conv layers. When choosing one, check its capabilities: SparseTensor support, `edge_weight`, `edge_attr`, bipartite, and lazy (`-1` channel) initialization. Code for GCN/GAT/GraphSAGE and custom `MessagePassing` layers (including the `_i`/`_j` target/source naming convention) is in `references/building_gnns.md`. ## Resources ### Bundled References This skill includes detailed reference documentation: - **`references/getting_started.md`**: Install, basic graph creation, `Data` structure, edge-index format, mini-batching - **`references/building_gnns.md`**: Message passing, GCN/GAT/GraphSAGE code, custom `MessagePassing` layers, layer capabilities - **`references/datasets_and_loading.md`**: Built-in datasets, custom `InMemoryDataset`, loading graphs from CSV - **`references/training_workflows.md`**: Node classification, graph classification, large-scale neighbor sampling - **`references/advanced_features.md`**: Heterogeneous graphs, transforms, explainability, pooling, GPU, save/load - **`references/layers_reference.md`**: Complete listing of all 40+ GNN layers with descriptions and capabilities - **`references/datasets_reference.md`**: Comprehensive dataset catalog organized by category - **`references/transforms_reference.md`**: All available transforms and their use cases ### Scripts Utility scripts are provided in `scripts/`: - **`scripts/visualize_graph.py`**: Visualize graph structure using networkx and matplotlib - **`scripts/create_gnn_template.py`**: Generate boilerplate code for common GNN architectures - **`scripts/benchmark_model.py`**: Benchmark model performance on standard datasets Execute scripts directly or read them for implementation patterns. ### Official Resources - **Documentation**: 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 Part of the AlterLab Academic Skills suite.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.