Deep Graph Library (DGL)
This page summarizes the core concepts of the Deep Graph Library (DGL), focusing on graph construction, feature handling, and the message-passing paradigm used to implement architectures like GCN and GAT.
Based on the official Deep Graph Library Documentation
1. Creating a Graph (DGLGraph)
In DGL, a graph is defined by a set of edges connecting nodes. Nodes are identified by integers starting from 0.
Basic Construction
You define edges using two arrays: Source Nodes (u) and Destination Nodes (v).
- PyTorch Tensors
- NetworkX
import dgl
import torch
# Define a graph with 4 nodes (0, 1, 2, 3)
# Edges: 0->1, 1->2, 2->3, 3->0 (A cycle)
u = torch.tensor([0, 1, 2, 3]) # Source nodes
v = torch.tensor([1, 2, 3, 0]) # Destination nodes
# Create the graph
g = dgl.graph((u, v))
print(f"Number of nodes: {g.num_nodes()}")
print(f"Number of edges: {g.num_edges()}")
import dgl
import networkx as nx
# Create using NetworkX first
nx_g = nx.cycle_graph(4)
# Convert to DGL
g = dgl.from_networkx(nx_g)
print(f"Number of nodes: {g.num_nodes()}")
DGL graphs are directed by default. To make them undirected (bi-directional), you must explicitly add reverse edges or use dgl.add_reverse_edges(g).
2. Node and Edge Features
Graphs in DGL are not just structure; they store data. You can store feature vectors (embeddings) directly on the nodes and edges using the ndata and edata interfaces.
# Assign a 5-dimensional random vector to each node
# 'feat' is just a name we choose. You can name it 'h', 'attr', 'x', etc.
g.ndata['feat'] = torch.randn(4, 5)
# Assign a 2-dimensional vector to each edge (e.g., edge type embeddings)
g.edata['weight'] = torch.randn(4, 2)
# Accessing features of Node 0
print(g.ndata['feat'][0])
3. Message Passing (The Core Engine)
This is the most critical concept in DGL. It abstracts the mathematical formula of GNNs.
Mathematical Formulation
DGL uses a specific syntax for this: update_all(message_func, reduce_func).
Built-in Functions (dgl.function)
DGL provides optimized C++ kernels for common operations, accessed via dgl.function (usually imported as fn).
| Function Name | Description | Notation |
|---|---|---|
fn.copy_u | Copy source node features to be the message. | |
fn.u_add_e | Add source feature and edge feature. | |
fn.u_mul_e | Multiply source feature by edge feature (Attention). | |
fn.sum | Sum up all incoming messages. | |
fn.mean | Average all incoming messages. |
Implementations
A Simple Graph Convolution
Let's implement a basic layer where every node calculates the sum of its neighbors' features.
import dgl.function as fn
# 1. Define the Message: "copy_u" means "Copy the neighbor's 'feat' vector"
# 2. Define the Reduce: "sum" means "Sum up those vectors"
g.update_all(fn.copy_u('feat', 'm'), fn.sum('m', 'h_new'))
# The result is stored in 'h_new' in ndata
print(g.ndata['h_new'])
Edge-Weighted Message Passing (GAT Style)
In architectures like Graph Attention Networks, we care about the edge data.
# 1. Message: Multiply Source Feature ('feat') by Edge Weight ('weight')
# This is analogous to the Attention Mechanism
g.update_all(fn.u_mul_e('feat', 'weight', 'm'), fn.sum('m', 'h_weighted'))
4. Building a GNN Module
To use DGL in a real project, you wrap the message passing logic inside a standard PyTorch nn.Module.
Here is a simple Graph Convolutional Layer (GCN) implementation:
import torch.nn as nn
class GCNLayer(nn.Module):
def __init__(self, in_feats, out_feats):
super(GCNLayer, self).__init__()
# A linear layer to transform features before aggregation
self.linear = nn.Linear(in_feats, out_feats)
def forward(self, g, feature):
# 1. Create a local scope to avoid dirtying the graph
with g.local_scope():
# 2. Add the current features to the graph
g.ndata['h'] = feature
# 3. Message Passing:
# Send copy of 'h', Sum them up, store as 'h_agg'
g.update_all(fn.copy_u('h', 'm'), fn.sum('m', 'h_agg'))
# 4. Retrieve the aggregated result
h_agg = g.ndata['h_agg']
# 5. Pass through Linear Layer and return
return self.linear(h_agg)
5. Training Loop
Training a DGL model looks almost identical to training a standard image or text model in PyTorch.
In the forward pass, you must pass both the graph object (g) and the features (features).
# Initialize graph and features
g = ...
features = g.ndata['feat']
labels = ...
# Initialize Model, Loss, and Optimizer
model = GCNLayer(in_feats=5, out_feats=2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# Training Loop
for epoch in range(100):
model.train()
# Forward Pass
logits = model(g, features)
# Calculate Loss (e.g., CrossEntropy)
loss = F.cross_entropy(logits, labels)
# Backward Pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"Epoch {epoch} | Loss: {loss.item()}")
6. Advanced: Batching Graphs
When processing multiple small graphs (e.g., in a dataset of chemical molecules or system logs), DGL handles this by merging many small graphs into one giant Batched Graph.
# List of individual graphs
graphs = [g1, g2, g3, ...]
# Batch them into one object
batched_g = dgl.batch(graphs)
# You can run the EXACT same model code on this batched graph!
output = model(batched_g, batched_g.ndata['feat'])
# To get separate outputs again (Readout/Pooling)
# This sums up all nodes belonging to Graph 1, all nodes for Graph 2, etc.
graph_level_embeddings = dgl.sum_nodes(batched_g, output)
Notebook
You can find a simple example of using DGL for GCN here on Colab.
