GNNGraph
Documentation page for the graph type GNNGraph
provided by GraphNeuralNetworks.jl and related methods.
Besides the methods documented here, one can rely on the large set of functionalities given by Graphs.jl thanks to the fact that GNNGraph
inherits from Graphs.AbstractGraph
.
Index
GraphNeuralNetworks.GNNGraphs.GNNGraph
Base.intersect
GraphNeuralNetworks.GNNGraphs.add_edges
GraphNeuralNetworks.GNNGraphs.add_nodes
GraphNeuralNetworks.GNNGraphs.add_self_loops
GraphNeuralNetworks.GNNGraphs.adjacency_list
GraphNeuralNetworks.GNNGraphs.edge_index
GraphNeuralNetworks.GNNGraphs.getgraph
GraphNeuralNetworks.GNNGraphs.graph_indicator
GraphNeuralNetworks.GNNGraphs.has_multi_edges
GraphNeuralNetworks.GNNGraphs.is_bidirected
GraphNeuralNetworks.GNNGraphs.knn_graph
GraphNeuralNetworks.GNNGraphs.negative_sample
GraphNeuralNetworks.GNNGraphs.normalized_laplacian
GraphNeuralNetworks.GNNGraphs.rand_edge_split
GraphNeuralNetworks.GNNGraphs.rand_graph
GraphNeuralNetworks.GNNGraphs.remove_multi_edges
GraphNeuralNetworks.GNNGraphs.remove_self_loops
GraphNeuralNetworks.GNNGraphs.sample_neighbors
GraphNeuralNetworks.GNNGraphs.scaled_laplacian
GraphNeuralNetworks.GNNGraphs.set_edge_weight
GraphNeuralNetworks.GNNGraphs.to_bidirected
Graphs.LinAlg.adjacency_matrix
Graphs.LinAlg.adjacency_matrix
Graphs.degree
Graphs.degree
Graphs.has_self_loops
Graphs.inneighbors
Graphs.outneighbors
MLUtils.batch
MLUtils.unbatch
SparseArrays.blockdiag
GNNGraph type
GraphNeuralNetworks.GNNGraphs.GNNGraph
— TypeGNNGraph(data; [graph_type, ndata, edata, gdata, num_nodes, graph_indicator, dir])
GNNGraph(g::GNNGraph; [ndata, edata, gdata])
A type representing a graph structure that also stores feature arrays associated to nodes, edges, and the graph itself.
A GNNGraph
can be constructed out of different data
objects expressing the connections inside the graph. The internal representation type is determined by graph_type
.
When constructed from another GNNGraph
, the internal graph representation is preserved and shared. The node/edge/graph features are retained as well, unless explicitely set by the keyword arguments ndata
, edata
, and gdata
.
A GNNGraph
can also represent multiple graphs batched togheter (see Flux.batch
or SparseArrays.blockdiag
). The field g.graph_indicator
contains the graph membership of each node.
GNNGraph
s are always directed graphs, therefore each edge is defined by a source node and a target node (see edge_index
). Self loops (edges connecting a node to itself) and multiple edges (more than one edge between the same pair of nodes) are supported.
A GNNGraph
is a Graphs.jl's AbstractGraph
, therefore it supports most functionality from that library.
Arguments
data
: Some data representing the graph topology. Possible type are- An adjacency matrix
- An adjacency list.
- A tuple containing the source and target vectors (COO representation)
- A Graphs.jl' graph.
graph_type
: A keyword argument that specifies the underlying representation used by the GNNGraph. Currently supported values are:coo
. Graph represented as a tuple(source, target)
, such that thek
-th edge connects the nodesource[k]
to nodetarget[k]
. Optionally, also edge weights can be given:(source, target, weights)
.:sparse
. A sparse adjacency matrix representation.:dense
. A dense adjacency matrix representation.
:coo
, currently the most supported type.dir
: The assumed edge direction when given adjacency matrix or adjacency list input datag
. Possible values are:out
and:in
. Default:out
.num_nodes
: The number of nodes. If not specified, inferred fromg
. Defaultnothing
.graph_indicator
: For batched graphs, a vector containing the graph assigment of each node. Defaultnothing
.ndata
: Node features. An array or named tuple of arrays whose last dimension has sizenum_nodes
.edata
: Edge features. An array or named tuple of arrays whose last dimension has sizenum_edges
.gdata
: Graph features. An array or named tuple of arrays whose last dimension has sizenum_graphs
.
Examples
using Flux, GraphNeuralNetworks
# Construct from adjacency list representation
data = [[2,3], [1,4,5], [1], [2,5], [2,4]]
g = GNNGraph(data)
# Number of nodes, edges, and batched graphs
g.num_nodes # 5
g.num_edges # 10
g.num_graphs # 1
# Same graph in COO representation
s = [1,1,2,2,2,3,4,4,5,5]
t = [2,3,1,4,5,3,2,5,2,4]
g = GNNGraph(s, t)
# From a Graphs' graph
g = GNNGraph(erdos_renyi(100, 20))
# Add 2 node feature arrays
g = GNNGraph(g, ndata = (x=rand(100, g.num_nodes), y=rand(g.num_nodes)))
# Add node features and edge features with default names `x` and `e`
g = GNNGraph(g, ndata = rand(100, g.num_nodes), edata = rand(16, g.num_edges))
g.ndata.x
g.ndata.e
# Send to gpu
g = g |> gpu
# Collect edges' source and target nodes.
# Both source and target are vectors of length num_edges
source, target = edge_index(g)
Query
GraphNeuralNetworks.GNNGraphs.adjacency_list
— Methodadjacency_list(g; dir=:out)
adjacency_list(g, nodes; dir=:out)
Return the adjacency list representation (a vector of vectors) of the graph g
.
Calling a
the adjacency list, if dir=:out
than a[i]
will contain the neighbors of node i
through outgoing edges. If dir=:in
, it will contain neighbors from incoming edges instead.
If nodes
is given, return the neighborhood of the nodes in nodes
only.
GraphNeuralNetworks.GNNGraphs.edge_index
— Methodedge_index(g::GNNGraph)
Return a tuple containing two vectors, respectively storing the source and target nodes for each edges in g
.
s, t = edge_index(g)
GraphNeuralNetworks.GNNGraphs.graph_indicator
— Methodgraph_indicator(g)
Return a vector containing the graph membership (an integer from 1
to g.num_graphs
) of each node in the graph.
GraphNeuralNetworks.GNNGraphs.has_multi_edges
— Methodhas_multi_edges(g::GNNGraph)
Return true
if g
has any multiple edges.
GraphNeuralNetworks.GNNGraphs.is_bidirected
— Methodis_bidirected(g::GNNGraph)
Check if the directed graph g
essentially corresponds to an undirected graph, i.e. if for each edge it also contains the reverse edge.
GraphNeuralNetworks.GNNGraphs.normalized_laplacian
— Functionnormalized_laplacian(g, T=Float32; add_self_loops=false, dir=:out)
Normalized Laplacian matrix of graph g
.
Arguments
g
: AGNNGraph
.T
: result element type.add_self_loops
: add self-loops while calculating the matrix.dir
: the edge directionality considered (:out, :in, :both).
GraphNeuralNetworks.GNNGraphs.scaled_laplacian
— Functionscaled_laplacian(g, T=Float32; dir=:out)
Scaled Laplacian matrix of graph g
, defined as $\hat{L} = \frac{2}{\lambda_{max}} L - I$ where $L$ is the normalized Laplacian matrix.
Arguments
g
: AGNNGraph
.T
: result element type.dir
: the edge directionality considered (:out, :in, :both).
Graphs.LinAlg.adjacency_matrix
— Functionadjacency_matrix(g::GNNGraph, T=eltype(g); dir=:out, weighted=true)
Return the adjacency matrix A
for the graph g
.
If dir=:out
, A[i,j] > 0
denotes the presence of an edge from node i
to node j
. If dir=:in
instead, A[i,j] > 0
denotes the presence of an edge from node j
to node i
.
User may specify the eltype T
of the returned matrix.
If weighted=true
, the A
will contain the edge weigths if any, otherwise the elements of A
will be either 0 or 1.
Graphs.degree
— Methoddegree(g::GNNGraph, T=nothing; dir=:out, edge_weight=true)
Return a vector containing the degrees of the nodes in g
.
Arguments
g
: A graph.T
: Element type of the returned vector. Ifnothing
, is chosen based on the graph type and will be an integer ifedge_weight=false
.dir
: Fordir=:out
the degree of a node is counted based on the outgoing edges. Fordir=:in
, the ingoing edges are used. Ifdir=:both
we have the sum of the two.edge_weight
: Iftrue
and the graph contains weighted edges, the degree will be weighted. Set tofalse
instead to just count the number of outgoing/ingoing edges. In alternative, you can also pass a vector of weights to be used instead of the graph's own weights.
Graphs.has_self_loops
— Methodhas_self_loops(g::GNNGraph)
Return true
if g
has any self loops.
Graphs.LinAlg.adjacency_matrix
— Functionadjacency_matrix(g[, T=Int; dir=:out])
Return a sparse adjacency matrix for a graph, indexed by [u, v]
vertices. Non-zero values indicate an edge from u
to v
. Users may override the default data type (Int
) and specify an optional direction.
Optional Arguments
dir=:out
: :in
, :out
, or :both
are currently supported.
Implementation Notes
This function is optimized for speed and directly manipulates CSC sparse matrix fields.
adjacency_matrix(g::GNNGraph, T=eltype(g); dir=:out, weighted=true)
Return the adjacency matrix A
for the graph g
.
If dir=:out
, A[i,j] > 0
denotes the presence of an edge from node i
to node j
. If dir=:in
instead, A[i,j] > 0
denotes the presence of an edge from node j
to node i
.
User may specify the eltype T
of the returned matrix.
If weighted=true
, the A
will contain the edge weigths if any, otherwise the elements of A
will be either 0 or 1.
Graphs.degree
— Functiondegree(g[, v])
Return a vector corresponding to the number of edges which start or end at each vertex in graph g
. If v
is specified, only return degrees for vertices in v
. For directed graphs, this value equals the incoming plus outgoing edges. For undirected graphs, it equals the connected edges.
Examples
julia> using Graphs
julia> g = DiGraph(3);
julia> add_edge!(g, 2, 3);
julia> add_edge!(g, 3, 1);
julia> degree(g)
3-element Array{Int64,1}:
1
1
2
Graphs.outneighbors
— Functionoutneighbors(g, v)
Return a list of all neighbors connected to vertex v
by an outgoing edge.
Implementation Notes
Returns a reference to the current graph's internal structures, not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed.
Examples
julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]);
julia> outneighbors(g, 4)
1-element Array{Int64,1}:
5
Graphs.inneighbors
— Functioninneighbors(g, v)
Return a list of all neighbors connected to vertex v
by an incoming edge.
Implementation Notes
Returns a reference to the current graph's internal structures, not a copy. Do not modify result. If the graph is modified, the behavior is undefined: the array behind this reference may be modified too, but this is not guaranteed.
Examples
julia> g = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]);
julia> inneighbors(g, 4)
2-element Array{Int64,1}:
3
5
Transform
GraphNeuralNetworks.GNNGraphs.add_edges
— Methodadd_edges(g::GNNGraph, s::AbstractVector, t::AbstractVector; [edata])
Add to graph g
the edges with source nodes s
and target nodes t
. Optionally, pass the features edata
for the new edges.
GraphNeuralNetworks.GNNGraphs.add_nodes
— Methodadd_nodes(g::GNNGraph, n; [ndata])
Add n
new nodes to graph g
. In the new graph, these nodes will have indexes from g.num_nodes + 1
to g.num_nodes + n
.
GraphNeuralNetworks.GNNGraphs.add_self_loops
— Methodadd_self_loops(g::GNNGraph)
Return a graph with the same features as g
but also adding edges connecting the nodes to themselves.
Nodes with already existing self-loops will obtain a second self-loop.
If the graphs has edge weights, the new edges will have weight 1.
GraphNeuralNetworks.GNNGraphs.getgraph
— Methodgetgraph(g::GNNGraph, i; nmap=false)
Return the subgraph of g
induced by those nodes j
for which g.graph_indicator[j] == i
or, if i
is a collection, g.graph_indicator[j] ∈ i
. In other words, it extract the component graphs from a batched graph.
If nmap=true
, return also a vector v
mapping the new nodes to the old ones. The node i
in the subgraph will correspond to the node v[i]
in g
.
GraphNeuralNetworks.GNNGraphs.negative_sample
— Methodnegative_sample(g::GNNGraph;
num_neg_edges = g.num_edges,
bidirected = is_bidirected(g))
Return a graph containing random negative edges (i.e. non-edges) from graph g
as edges.
Is bidirected=true
, the output graph will be bidirected and there will be no leakage from the origin graph.
See also is_bidirected
.
GraphNeuralNetworks.GNNGraphs.rand_edge_split
— Methodrand_edge_split(g::GNNGraph, frac; bidirected=is_bidirected(g)) -> g1, g2
Randomly partition the edges in g
to form two graphs, g1
and g2
. Both will have the same number of nodes as g
. g1
will contain a fraction frac
of the original edges, while g2
wil contain the rest.
If bidirected = true
makes sure that an edge and its reverse go into the same split. This option is supported only for bidirected graphs with no self-loops and multi-edges.
rand_edge_split
is tipically used to create train/test splits in link prediction tasks.
GraphNeuralNetworks.GNNGraphs.remove_multi_edges
— Methodremove_multi_edges(g::GNNGraph; aggr=+)
Remove multiple edges (also called parallel edges or repeated edges) from graph g
. Possible edge features are aggregated according to aggr
, that can take value +
,min
, max
or mean
.
See also remove_self_loops
, has_multi_edges
, and to_bidirected
.
GraphNeuralNetworks.GNNGraphs.remove_self_loops
— Methodremove_self_loops(g::GNNGraph)
Return a graph constructed from g
where self-loops (edges from a node to itself) are removed.
See also add_self_loops
and remove_multi_edges
.
GraphNeuralNetworks.GNNGraphs.set_edge_weight
— Methodset_edge_weight(g::GNNGraph, w::AbstractVector)
Set w
as edge weights in the returned graph.
GraphNeuralNetworks.GNNGraphs.to_bidirected
— Methodto_bidirected(g)
Adds a reverse edge for each edge in the graph, then calls remove_multi_edges
with mean
aggregation to simplify the graph.
See also is_bidirected
.
Examples
julia> s, t = [1, 2, 3, 3, 4], [2, 3, 4, 4, 4];
julia> w = [1.0, 2.0, 3.0, 4.0, 5.0];
julia> e = [10.0, 20.0, 30.0, 40.0, 50.0];
julia> g = GNNGraph(s, t, w, edata = e)
GNNGraph:
num_nodes = 4
num_edges = 5
edata:
e => (5,)
julia> g2 = to_bidirected(g)
GNNGraph:
num_nodes = 4
num_edges = 7
edata:
e => (7,)
julia> edge_index(g2)
([1, 2, 2, 3, 3, 4, 4], [2, 1, 3, 2, 4, 3, 4])
julia> get_edge_weight(g2)
7-element Vector{Float64}:
1.0
1.0
2.0
2.0
3.5
3.5
5.0
julia> g2.edata.e
7-element Vector{Float64}:
10.0
10.0
20.0
20.0
35.0
35.0
50.0
MLUtils.batch
— Methodbatch(gs::Vector{<:GNNGraph})
Batch together multiple GNNGraph
s into a single one containing the total number of original nodes and edges.
Equivalent to SparseArrays.blockdiag
. See also Flux.unbatch
.
Examples
julia> g1 = rand_graph(4, 6, ndata=ones(8, 4))
GNNGraph:
num_nodes = 4
num_edges = 6
ndata:
x => (8, 4)
julia> g2 = rand_graph(7, 4, ndata=zeros(8, 7))
GNNGraph:
num_nodes = 7
num_edges = 4
ndata:
x => (8, 7)
julia> g12 = Flux.batch([g1, g2])
GNNGraph:
num_nodes = 11
num_edges = 10
num_graphs = 2
ndata:
x => (8, 11)
julia> g12.ndata.x
8×11 Matrix{Float64}:
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1.0 1.0 1.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
MLUtils.unbatch
— Methodunbatch(g::GNNGraph)
Opposite of the Flux.batch
operation, returns an array of the individual graphs batched together in g
.
See also Flux.batch
and getgraph
.
Examples
julia> gbatched = Flux.batch([rand_graph(5, 6), rand_graph(10, 8), rand_graph(4,2)])
GNNGraph:
num_nodes = 19
num_edges = 16
num_graphs = 3
julia> Flux.unbatch(gbatched)
3-element Vector{GNNGraph{Tuple{Vector{Int64}, Vector{Int64}, Nothing}}}:
GNNGraph:
num_nodes = 5
num_edges = 6
GNNGraph:
num_nodes = 10
num_edges = 8
GNNGraph:
num_nodes = 4
num_edges = 2
SparseArrays.blockdiag
— Methodblockdiag(xs::GNNGraph...)
Equivalent to Flux.batch
.
Generate
GraphNeuralNetworks.GNNGraphs.knn_graph
— Methodknn_graph(points::AbstractMatrix,
k::Int;
graph_indicator = nothing,
self_loops = false,
dir = :in,
kws...)
Create a k
-nearest neighbor graph where each node is linked to its k
closest points
.
Arguments
points
: A numfeatures × numnodes matrix storing the Euclidean positions of the nodes.k
: The number of neighbors considered in the kNN algorithm.graph_indicator
: Either nothing or a vector containing the graph assigment of each node, in which case the returned graph will be a batch of graphs.self_loops
: Iftrue
, consider the node itself among itsk
nearest neighbors, in which case the graph will contain self-loops.dir
: The direction of the edges. Ifdir=:in
edges go from thek
neighbors to the central node. Ifdir=:out
we have the opposite direction.kws
: Further keyword arguments will be passed to the `GNNGraph constructor.
Examples
julia> n, k = 10, 3;
julia> x = rand(3, n);
julia> g = knn_graph(x, k)
GNNGraph:
num_nodes = 10
num_edges = 30
julia> graph_indicator = [1,1,1,1,1,2,2,2,2,2];
julia> g = knn_graph(x, k; graph_indicator)
GNNGraph:
num_nodes = 10
num_edges = 30
num_graphs = 2
GraphNeuralNetworks.GNNGraphs.rand_graph
— Methodrand_graph(n, m; bidirected=true, seed=-1, kws...)
Generate a random (Erdós-Renyi) GNNGraph
with n
nodes and m
edges.
If bidirected=true
the reverse edge of each edge will be present. If bidirected=false
instead, m
unrelated edges are generated. In any case, the output graph will contain no self-loops or multi-edges.
Use a seed > 0
for reproducibility.
Additional keyword arguments will be passed to the GNNGraph
constructor.
Examples
julia> g = rand_graph(5, 4, bidirected=false)
GNNGraph:
num_nodes = 5
num_edges = 4
julia> edge_index(g)
([1, 3, 3, 4], [5, 4, 5, 2])
# In the bidirected case, edge data will be duplicated on the reverse edges if needed.
julia> g = rand_graph(5, 4, edata=rand(16, 2))
GNNGraph:
num_nodes = 5
num_edges = 4
edata:
e => (16, 4)
# Each edge has a reverse
julia> edge_index(g)
([1, 3, 3, 4], [3, 4, 1, 3])
Operators
Base.intersect
— Functionintersect(g, h)
Return a graph with edges that are only in both graph g
and graph h
.
Implementation Notes
This function may produce a graph with 0-degree vertices. Preserves the eltype of the input graph.
Examples
julia> g1 = SimpleDiGraph([0 1 0 0 0; 0 0 1 0 0; 1 0 0 1 0; 0 0 0 0 1; 0 0 0 1 0]);
julia> g2 = SimpleDiGraph([0 1 0; 0 0 1; 1 0 0]);
julia> foreach(println, edges(intersect(g1, g2)))
Edge 1 => 2
Edge 2 => 3
Edge 3 => 1
Sampling
GraphNeuralNetworks.GNNGraphs.sample_neighbors
— Functionsample_neighbors(g, nodes, K=-1; dir=:in, replace=false, dropnodes=false)
Sample neighboring edges of the given nodes and return the induced subgraph. For each node, a number of inbound (or outbound when dir = :out
) edges will be randomly chosen. If
dropnodes=false`, the graph returned will then contain all the nodes in the original graph, but only the sampled edges.
The returned graph will contain an edge feature EID
corresponding to the id of the edge in the original graph. If dropnodes=true
, it will also contain a node feature NID
with the node ids in the original graph.
Arguments
g
. The graph.nodes
. A list of node IDs to sample neighbors from.K
. The maximum number of edges to be sampled for each node. If -1, all the neighboring edges will be selected.dir
. Determines whether to sample inbound (:in
) or outbound (`:out
) edges (Default:in
).replace
. Iftrue
, sample with replacement.dropnodes
. Iftrue
, the resulting subgraph will contain only the nodes involved in the sampled edges.
Examples
julia> g = rand_graph(20, 100)
GNNGraph:
num_nodes = 20
num_edges = 100
julia> sample_neighbors(g, 2:3)
GNNGraph:
num_nodes = 20
num_edges = 9
edata:
EID => (9,)
julia> sg = sample_neighbors(g, 2:3, dropnodes=true)
GNNGraph:
num_nodes = 10
num_edges = 9
ndata:
NID => (10,)
edata:
EID => (9,)
julia> sg.ndata.NID
10-element Vector{Int64}:
2
3
17
14
18
15
16
20
7
10
julia> sample_neighbors(g, 2:3, 5, replace=true)
GNNGraph:
num_nodes = 20
num_edges = 10
edata:
EID => (10,)