Skip to content

Commit 3b1ee3c

Browse files
authored
Merge pull request #334 from FluxML/new_features
Replace GraphMLDatasets in favor of MLDatasets and update docs
2 parents 2a4430c + db60d93 commit 3b1ee3c

30 files changed

+534
-298
lines changed

Project.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
1010
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
1111
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
1212
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
13-
GraphMLDatasets = "21828b05-d3b3-40ad-870e-a4bc2f52d5e8"
1413
GraphSignals = "3ebe565e-a4b5-49c6-aed2-300248c3a9c1"
1514
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
1615
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
16+
MLDatasets = "eb30cadb-4394-5ae3-aed4-317e484a6458"
1717
NNlib = "872c559c-99b0-510c-b3b7-b6c96a88d5cd"
1818
NNlibCUDA = "a00861dc-f156-4864-bf3c-e6376f28a68d"
1919
Optimisers = "3bd65402-5787-11e9-1adc-39752487f4e2"
@@ -26,17 +26,17 @@ Word2Vec = "c64b6f0f-98cd-51d1-af78-58ae84944834"
2626

2727
[compat]
2828
CUDA = "3"
29-
ChainRulesCore = "1.7"
29+
ChainRulesCore = "1"
3030
DataStructures = "0.18"
3131
FillArrays = "0.13"
3232
Flux = "0.12 - 0.13"
33-
GraphMLDatasets = "0.1"
3433
GraphSignals = "0.7"
3534
Graphs = "1"
35+
MLDatasets = "0.7"
3636
NNlib = "0.8"
3737
NNlibCUDA = "0.2"
3838
Optimisers = "0.2"
39-
Reexport = "1.1"
39+
Reexport = "1"
4040
StatsBase = "0.33"
4141
Word2Vec = "0.5"
4242
julia = "1.6"

data/cora_features.jld2

-796 KB
Binary file not shown.

data/cora_graph.jld2

-260 KB
Binary file not shown.

data/cora_label2onehot.jld2

-7.11 KB
Binary file not shown.

data/cora_labels.jld2

-68.9 KB
Binary file not shown.

data/cora_paper2idx.jld2

-47.8 KB
Binary file not shown.

docs/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
[deps]
2+
DemoCards = "311a05b2-6137-4a5a-b473-18580a3d38b5"
23
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
34
DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
45
Flux = "587475ba-b771-5e3f-ad9e-33799f191a9c"
6+
GeometricFlux = "7e08b658-56d3-11e9-2997-919d5b31e4ea"
57

68
[compat]
79
Documenter = "0.27"

docs/bibliography.bib

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ @inproceedings{Satorras2021
201201
@article{Dwivedi2021,
202202
abstract = {Graph neural networks (GNNs) have become the standard learning architectures for graphs. GNNs have been applied to numerous domains ranging from quantum chemistry, recommender systems to knowledge graphs and natural language processing. A major issue with arbitrary graphs is the absence of canonical positional information of nodes, which decreases the representation power of GNNs to distinguish e.g. isomorphic nodes and other graph symmetries. An approach to tackle this issue is to introduce Positional Encoding (PE) of nodes, and inject it into the input layer, like in Transformers. Possible graph PE are Laplacian eigenvectors. In this work, we propose to decouple structural and positional representations to make easy for the network to learn these two essential properties. We introduce a novel generic architecture which we call LSPE (Learnable Structural and Positional Encodings). We investigate several sparse and fully-connected (Transformer-like) GNNs, and observe a performance increase for molecular datasets, from 2.87% up to 64.14% when considering learnable PE for both GNN classes.},
203203
author = {Vijay Prakash Dwivedi and Anh Tuan Luu and Thomas Laurent and Yoshua Bengio and Xavier Bresson},
204+
journal = {ArXiv},
204205
month = {10},
205206
title = {Graph Neural Networks with Learnable Structural and Positional Representations},
206207
url = {http://arxiv.org/abs/2110.07875},

docs/make.jl

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
using Documenter
22
using DocumenterCitations
3+
using DemoCards
34
using GeometricFlux
45

6+
const ASSETS = ["assets/flux.css", "assets/favicon.ico"]
7+
58
bib = CitationBibliography(joinpath(@__DIR__, "bibliography.bib"), sorting=:nyt)
69

710
DocMeta.setdocmeta!(GeometricFlux, :DocTestSetup, :(using GeometricFlux, Flux); recursive=true)
811

12+
# DemoCards
13+
demopage, postprocess_cb, demo_assets = makedemos("tutorials")
14+
isnothing(demo_assets) || (push!(ASSETS, demo_assets))
15+
916
makedocs(
1017
bib,
1118
sitename = "GeometricFlux.jl",
1219
format = Documenter.HTML(
13-
assets = ["assets/flux.css", "assets/favicon.ico"],
20+
assets = ASSETS,
1421
canonical = "https://fluxml.ai/GeometricFlux.jl/stable/",
1522
analytics = "G-M61P0B2Y8E",
23+
edit_link = "master",
1624
),
1725
clean = false,
1826
modules = [GeometricFlux,GraphSignals],
1927
pages = ["Home" => "index.md",
20-
"Tutorials" => [
21-
"Semi-Supervised Learning with GCN" => "tutorials/semisupervised_gcn.md",
22-
"GCN with Fixed Graph" => "tutorials/gcn_fixed_graph.md",
23-
"Graph Attention Network" => "tutorials/gat.md",
24-
"DeepSet for Digit Sum" => "tutorials/deepset.md",
25-
"Variational Graph Autoencoder" => "tutorials/vgae.md",
26-
"Graph Embedding" => "tutorials/graph_embedding.md",
27-
],
28+
demopage,
2829
"Introduction" => "introduction.md",
2930
"Basics" => [
3031
"Graph Convolutions" => "basics/conv.md",
@@ -55,6 +56,9 @@ makedocs(
5556
]
5657
)
5758

59+
# callbacks of DemoCards
60+
postprocess_cb()
61+
5862
deploydocs(
5963
repo = "github.com/FluxML/GeometricFlux.jl.git",
6064
target = "build",

docs/src/tutorials/graph_embedding.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/tutorials/config.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"theme": "grid"
3+
}

docs/tutorials/examples/assets/logo.svg

Lines changed: 251 additions & 0 deletions
Loading

docs/tutorials/examples/config.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"theme": "grid",
3+
"description": "To begin with GeometricFlux, it is recommended to learn with following examples.",
4+
"order": [
5+
"semisupervised_gcn.md",
6+
"gcn_static_graph.md",
7+
"gat.md",
8+
"deepset.md",
9+
"vgae.md",
10+
"graph_embedding.md"
11+
]
12+
}

docs/src/tutorials/deepset.md renamed to docs/tutorials/examples/deepset.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# Predicting Digits Sum from DeepSet model
1+
---
2+
title: Predicting Digits Sum from DeepSet Model
3+
cover: assets/logo.svg
4+
id: deepset
5+
---
6+
7+
# Predicting Digits Sum from DeepSet Model
28

39
Digits sum is a task of summing up digits in images or text. This example demonstrates summing up digits in arbitrary number of MNIST images. To accomplish such task, DeepSet model is suitable for this task. DeepSet model is excellent at the task which takes a set of objects and reduces them into single object.
410

@@ -9,8 +15,9 @@ Since a DeepSet model predicts the summation from a set of images, we have to pr
915
First, the whole dataset is loaded from MLDatasets.jl and then shuffled before generating training dataset.
1016

1117
```julia
12-
train_X, train_y = MLDatasets.MNIST.traindata(Float32)
13-
train_X, train_y = shuffle_data(train_X, train_y)
18+
train_data, test_data = MNIST(:train), MNIST(:test)
19+
train_X, train_y = shuffle_data(train_data.features, train_data.targets)
20+
test_X, test_y = shuffle_data(test_data.features, test_data.targets)
1421
```
1522

1623
The `generate_featuredgraphs` here generates a set of pairs which contains a `FeaturedGraph` and a summed number for prediction target. In a `FeaturedGraph`, an arbitrary number of MNIST images are collected as node features and corresponding nodes are collected in a graph without edges.
@@ -68,9 +75,8 @@ for epoch = 1:args.epochs
6875
@info "Epoch $(epoch)"
6976

7077
for batch in train_loader
71-
train_loss, back = Flux.pullback(ps) do
72-
model_loss(model, batch |> device)
73-
end
78+
batch = batch |> device
79+
train_loss, back = Flux.pullback(() -> model_loss(model, batch), ps)
7480
test_loss = model_loss(model, test_loader, device)
7581
grad = back(1f0)
7682
Flux.Optimise.update!(opt, ps, grad)

docs/src/tutorials/gat.md renamed to docs/tutorials/examples/gat.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
---
2+
title: Graph Attention Network
3+
cover: assets/logo.svg
4+
id: gat
5+
---
6+
17
# Graph Attention Network
28

39
Graph attention network (GAT) belongs to the message-passing network family, and it queries node feature over its neighbor features and generates result as layer output.
@@ -7,18 +13,26 @@ Graph attention network (GAT) belongs to the message-passing network family, and
713
We load dataset from Planetoid dataset. Here cora dataset is used.
814

915
```julia
10-
train_X, train_y = map(x -> Matrix(x), alldata(Planetoid(), dataset, padding=true))
16+
data = dataset[1].node_data
17+
X, y = data.features, onehotbatch(data.targets, 1:7)
18+
train_idx, test_idx = data.train_mask, data.val_mask
1119
```
1220

1321
## Step 2: Batch up Features and Labels
1422

1523
Just batch up features as usual.
1624

1725
```julia
26+
s, t = dataset[1].edge_index
27+
g = Graphs.Graph(dataset[1].num_nodes)
28+
for (i, j) in zip(s, t)
29+
Graphs.add_edge!(g, i, j)
30+
end
31+
1832
add_all_self_loops!(g)
1933
fg = FeaturedGraph(g)
20-
train_data = (repeat(train_X, outer=(1,1,train_repeats)), repeat(train_y, outer=(1,1,train_repeats)))
21-
train_loader = DataLoader(train_data, batchsize=batch_size, shuffle=true)
34+
train_X, train_y = repeat(X, outer=(1,1,train_repeats)), repeat(y, outer=(1,1,train_repeats))
35+
train_loader = DataLoader((train_X, train_y), batchsize=batch_size, shuffle=true)
2236
```
2337

2438
Notably, self loop for all nodes are needed for GAT model.
@@ -66,9 +80,8 @@ for epoch = 1:args.epochs
6680
@info "Epoch $(epoch)"
6781

6882
for (X, y) in train_loader
69-
loss, back = Flux.pullback(ps) do
70-
model_loss(model, X |> device, y |> device, train_idx |> device)
71-
end
83+
X, y, device_idx = X |> device, y |> device, train_idx |> device
84+
loss, back = Flux.pullback(() -> model_loss(model, X, y, device_idx), ps)
7285
train_acc = accuracy(model, train_loader, device, train_idx)
7386
test_acc = accuracy(model, test_loader, device, test_idx)
7487
grad = back(1f0)

docs/src/tutorials/gcn_fixed_graph.md renamed to docs/tutorials/examples/gcn_static_graph.md

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
# GCN with Fixed Graph
1+
---
2+
title: GCN with Static Graph
3+
cover: assets/logo.svg
4+
id: gcn_static_graph
5+
---
26

3-
In the tutorial for semi-supervised learning with GCN, variable graphs are provided to GNN from `FeaturedGraph`, which contains a graph and node features. Each `FeaturedGraph` object can contain different graph and different node features, and can be train on the same GNN model. However, variable graph doesn't have the proper form of graph structure with respect to GNN layers and this lead to inefficient training/inference process. Fixed graph strategy can be used to train a GNN model with the same graph structure in GeometricFlux.
7+
# GCN with Static Graph
48

5-
## Fixed Graph
9+
In the tutorial for semi-supervised learning with GCN, variable graphs are provided to GNN from `FeaturedGraph`, which contains a graph and node features. Each `FeaturedGraph` object can contain different graph and different node features, and can be train on the same GNN model. However, variable graph doesn't have the proper form of graph structure with respect to GNN layers and this lead to inefficient training/inference process. Static graph strategy can be used to train a GNN model with the same graph structure in GeometricFlux.
610

7-
A fixed graph is given to a layer by `WithGraph` syntax. `WithGraph` wrap a `FeaturedGraph` object and a GNN layer as first and second arguments, respectively.
11+
## Static Graph
12+
13+
A static graph is given to a layer by `WithGraph` syntax. `WithGraph` wrap a `FeaturedGraph` object and a GNN layer as first and second arguments, respectively.
814

915
```julia
1016
fg = FeaturedGraph(graph)
@@ -26,23 +32,29 @@ Since features are in the form of array, they can be batched up for batched lear
2632
Different from loading datasets in semi-supervised learning example, we use `alldata` for supervised learning here and `padding=true` is added in order to padding features from partial nodes to pseudo-full nodes. A padded features contains zeros in the nodes that are not supposed to be train on.
2733

2834
```julia
29-
train_X, train_y = map(x -> Matrix(x), alldata(Planetoid(), dataset, padding=true))
35+
data = dataset[1].node_data
36+
X, y = data.features, onehotbatch(data.targets, 1:7)
37+
train_idx, test_idx = data.train_mask, data.val_mask
38+
train_X, train_y = repeat(X, outer=(1,1,train_repeats)), repeat(y, outer=(1,1,train_repeats))
3039
```
3140

3241
We need graph and node indices for training as well.
3342

3443
```julia
35-
g = graphdata(Planetoid(), dataset)
36-
train_idx = 1:size(train_X, 2)
44+
s, t = dataset[1].edge_index
45+
g = Graphs.Graph(dataset[1].num_nodes)
46+
for (i, j) in zip(s, t)
47+
Graphs.add_edge!(g, i, j)
48+
end
49+
fg = FeaturedGraph(g)
3750
```
3851

3952
## Step 2: Batch up Features and Labels
4053

4154
In order to make batch learning available, we separate graph and node features. We don't subgraph here. Node features are batched up by repeating node features here for demonstration, since planetoid dataset doesn't have batched settings. Different repeat numbers can be specified by `train_repeats` and `train_repeats`.
4255

4356
```julia
44-
fg = FeaturedGraph(g)
45-
train_data = (repeat(train_X, outer=(1,1,train_repeats)), repeat(train_y, outer=(1,1,train_repeats)))
57+
train_loader = DataLoader((train_X, train_y), batchsize=batch_size, shuffle=true)
4658
```
4759

4860
## Step 3: Build a GCN model
@@ -99,7 +111,8 @@ for epoch = 1:args.epochs
99111
@info "Epoch $(epoch)"
100112

101113
for (X, y) in train_loader
102-
grad = gradient(() -> model_loss(model, args.λ, X |> device, y |> device, train_idx |> device), ps)
114+
X, y, device_idx = X |> device, y |> device, train_idx |> device
115+
grad = gradient(() -> model_loss(model, args.λ, X, y, device_idx), ps)
103116
Flux.Optimise.update!(opt, ps, grad)
104117
train_steps += 1
105118
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Graph Embedding Through Node2vec Model
3+
cover: assets/logo.svg
4+
id: graph_embedding
5+
---
6+
7+
# Graph Embedding Through Node2vec Model

docs/src/tutorials/semisupervised_gcn.md renamed to docs/tutorials/examples/semisupervised_gcn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
---
2+
title: Semi-supervised Learning with Graph Convolution Networks (GCN)
3+
cover: assets/logo.svg
4+
id: semisupervised_gcn
5+
---
6+
17
# Semi-supervised Learning with Graph Convolution Networks (GCN)
28

39
Graph convolution networks (GCN) have been considered as the first step to graph neural networks (GNN). This example will go through how to train a vanilla GCN.

docs/src/tutorials/vgae.md renamed to docs/tutorials/examples/vgae.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
---
2+
title: Variational Graph Autoencoder
3+
cover: assets/logo.svg
4+
id: vgae
5+
---
6+
17
# Variational Graph Autoencoder
28

39
Variational Graph Autoencoder (VGAE) is a unsupervised generative model. It takes node features and graph structure and predicts the edge link in the graph. A link preidction task is defined for this model.
@@ -7,13 +13,19 @@ Variational Graph Autoencoder (VGAE) is a unsupervised generative model. It take
713
We load dataset from Planetoid dataset. Here cora dataset is used.
814

915
```julia
10-
train_X, _ = map(x -> Matrix(x), alldata(Planetoid(), dataset))
16+
data = dataset[1].node_data
17+
X = data.features
18+
train_X = repeat(X, outer=(1, 1, train_repeats))
1119
```
1220

1321
Notably, a link prediction task will output a graph in the form of adjacency matrix, so an adjacency matrix is needed as label for this task.
1422

1523
```julia
16-
g = graphdata(Planetoid(), dataset)
24+
s, t = dataset[1].edge_index
25+
g = Graphs.Graph(dataset[1].num_nodes)
26+
for (i, j) in zip(s, t)
27+
Graphs.add_edge!(g, i, j)
28+
end
1729
fg = FeaturedGraph(g)
1830
A = GraphSignals.adjacency_matrix(fg)
1931
```
@@ -23,8 +35,7 @@ A = GraphSignals.adjacency_matrix(fg)
2335
Just batch up features as usual.
2436

2537
```julia
26-
data = (repeat(X, outer=(1,1,train_repeats)), repeat(A, outer=(1,1,train_repeats)))
27-
loader = DataLoader(data, batchsize=batch_size, shuffle=true)
38+
loader = DataLoader((train_X, train_y), batchsize=batch_size, shuffle=true)
2839
```
2940

3041
## Step 3: Build a VGAE model
@@ -90,10 +101,9 @@ ps = Flux.params(model)
90101
for epoch = 1:args.epochs
91102
@info "Epoch $(epoch)"
92103

93-
for (X, A) in loader
94-
loss, back = Flux.pullback(ps) do
95-
model_loss(model, X |> device, A |> device, args.β)
96-
end
104+
for (X, Â) in loader
105+
X, Â = X |> device, Â |> device
106+
loss, back = Flux.pullback(() -> model_loss(model, X, Â, args.β), ps)
97107
prec = precision(model, loader, device)
98108
grad = back(1f0)
99109
Flux.Optimise.update!(opt, ps, grad)

docs/tutorials/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# [Tutorials](@id tutorials)
2+
3+
{{{democards}}}

examples/digitsum_deepsets.jl

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,15 @@ function load_data(
2020
test_min_length,
2121
test_max_length
2222
)
23-
train_X, train_y = MLDatasets.MNIST.traindata(Float32)
24-
test_X, test_y = MLDatasets.MNIST.testdata(Float32)
25-
26-
train_X, train_y = shuffle_data(train_X, train_y)
27-
test_X, test_y = shuffle_data(test_X, test_y)
23+
train_data, test_data = MNIST(:train), MNIST(:test)
24+
train_X, train_y = shuffle_data(train_data.features, train_data.targets)
25+
test_X, test_y = shuffle_data(test_data.features, test_data.targets)
2826

2927
train_data = generate_featuredgraphs(train_X, train_y, num_train_examples, 1:train_max_length)
3028
test_data = generate_featuredgraphs(test_X, test_y, num_test_examples, test_min_length:test_max_length)
31-
train_batch = Flux.batch(train_data)
32-
test_batch = Flux.batch(test_data)
3329

34-
train_loader = DataLoader(train_batch, batchsize=batch_size)
35-
test_loader = DataLoader(test_batch, batchsize=batch_size)
30+
train_loader = DataLoader(train_data, batchsize=batch_size)
31+
test_loader = DataLoader(test_data, batchsize=batch_size)
3632
return train_loader, test_loader
3733
end
3834

@@ -92,6 +88,7 @@ function train(; kws...)
9288
# GPU config
9389
if args.cuda && CUDA.has_cuda()
9490
device = gpu
91+
CUDA.allowscalar(false)
9592
@info "Training on GPU"
9693
else
9794
device = cpu
@@ -131,9 +128,8 @@ function train(; kws...)
131128
progress = Progress(length(train_loader))
132129

133130
for batch in train_loader
134-
train_loss, back = Flux.pullback(ps) do
135-
model_loss(model, batch |> device)
136-
end
131+
batch = batch |> device
132+
train_loss, back = Flux.pullback(() -> model_loss(model, batch), ps)
137133
test_loss = model_loss(model, test_loader, device)
138134
grad = back(1f0)
139135
Flux.Optimise.update!(opt, ps, grad)

0 commit comments

Comments
 (0)