Heterogeneous Graphs
GNNHeteroGraph
Documentation page for the type GNNHeteroGraph
representing heterogeneous graphs, where nodes and edges can have different types.
GNNGraphs.GNNHeteroGraph
— TypeGNNHeteroGraph(data; [ndata, edata, gdata, num_nodes])
GNNHeteroGraph(pairs...; [ndata, edata, gdata, num_nodes])
A type representing a heterogeneous graph structure. It is similar to GNNGraph
but nodes and edges are of different types.
Constructor Arguments
data
: A dictionary or an iterable object that maps(source_type, edge_type, target_type)
triples to(source, target)
index vectors (or to(source, target, weight)
if also edge weights are present).pairs
: Passing multiple relations as pairs is equivalent to passingdata=Dict(pairs...)
.ndata
: Node features. A dictionary of arrays or named tuple of arrays. The size of the last dimension of each array must be given byg.num_nodes
.edata
: Edge features. A dictionary of arrays or named tuple of arrays. Defaultnothing
. The size of the last dimension of each array must be given byg.num_edges
. Defaultnothing
.gdata
: Graph features. An array or named tuple of arrays whose last dimension has sizenum_graphs
. Defaultnothing
.num_nodes
: The number of nodes for each type. If not specified, inferred fromdata
. Defaultnothing
.
Fields
graph
: A dictionary that maps (sourcetype, edgetype, target_type) triples to (source, target) index vectors.num_nodes
: The number of nodes for each type.num_edges
: The number of edges for each type.ndata
: Node features.edata
: Edge features.gdata
: Graph features.ntypes
: The node types.etypes
: The edge types.
Examples
julia> using GNNGraphs
julia> nA, nB = 10, 20;
julia> num_nodes = Dict(:A => nA, :B => nB);
julia> edges1 = (rand(1:nA, 20), rand(1:nB, 20))
([4, 8, 6, 3, 4, 7, 2, 7, 3, 2, 3, 4, 9, 4, 2, 9, 10, 1, 3, 9], [6, 4, 20, 8, 16, 7, 12, 16, 5, 4, 6, 20, 11, 19, 17, 9, 12, 2, 18, 12])
julia> edges2 = (rand(1:nB, 30), rand(1:nA, 30))
([17, 5, 2, 4, 5, 3, 8, 7, 9, 7 … 19, 8, 20, 7, 16, 2, 9, 15, 8, 13], [1, 1, 3, 1, 1, 3, 2, 7, 4, 4 … 7, 10, 6, 3, 4, 9, 1, 5, 8, 5])
julia> data = ((:A, :rel1, :B) => edges1, (:B, :rel2, :A) => edges2);
julia> hg = GNNHeteroGraph(data; num_nodes)
GNNHeteroGraph:
num_nodes: (:A => 10, :B => 20)
num_edges: ((:A, :rel1, :B) => 20, (:B, :rel2, :A) => 30)
julia> hg.num_edges
Dict{Tuple{Symbol, Symbol, Symbol}, Int64} with 2 entries:
(:A, :rel1, :B) => 20
(:B, :rel2, :A) => 30
# Let's add some node features
julia> ndata = Dict(:A => (x = rand(2, nA), y = rand(3, num_nodes[:A])),
:B => rand(10, nB));
julia> hg = GNNHeteroGraph(data; num_nodes, ndata)
GNNHeteroGraph:
num_nodes: (:A => 10, :B => 20)
num_edges: ((:A, :rel1, :B) => 20, (:B, :rel2, :A) => 30)
ndata:
:A => (x = 2×10 Matrix{Float64}, y = 3×10 Matrix{Float64})
:B => x = 10×20 Matrix{Float64}
# Access features of nodes of type :A
julia> hg.ndata[:A].x
2×10 Matrix{Float64}:
0.825882 0.0797502 0.245813 0.142281 0.231253 0.685025 0.821457 0.888838 0.571347 0.53165
0.631286 0.316292 0.705325 0.239211 0.533007 0.249233 0.473736 0.595475 0.0623298 0.159307
See also GNNGraph
for a homogeneous graph type and rand_heterograph
for a function to generate random heterographs.
GNNGraphs.edge_type_subgraph
— Methodedge_type_subgraph(g::GNNHeteroGraph, edge_ts)
Return a subgraph of g
that contains only the edges of type edge_ts
. Edge types can be specified as a single edge type (i.e. a tuple containing 3 symbols) or a vector of edge types.
GNNGraphs.num_edge_types
— Methodnum_edge_types(g)
Return the number of edge types in the graph. For GNNGraph
s, this is always 1. For GNNHeteroGraph
s, this is the number of unique edge types.
GNNGraphs.num_node_types
— Methodnum_node_types(g)
Return the number of node types in the graph. For GNNGraph
s, this is always 1. For GNNHeteroGraph
s, this is the number of unique node types.
Query
GNNGraphs.edge_index
— Methodedge_index(g::GNNHeteroGraph, [edge_t])
Return a tuple containing two vectors, respectively storing the source and target nodes for each edges in g
of type edge_t = (src_t, rel_t, trg_t)
.
If edge_t
is not provided, it will error if g
has more than one edge type.
GNNGraphs.graph_indicator
— Methodgraph_indicator(g::GNNHeteroGraph, [node_t])
Return a Dict of vectors containing the graph membership (an integer from 1
to g.num_graphs
) of each node in the graph for each node type. If node_t
is provided, return the graph membership of each node of type node_t
instead.
See also batch
.
Graphs.degree
— Methoddegree(g::GNNHeteroGraph, edge_type::EType; dir = :in)
Return a vector containing the degrees of the nodes in g
GNNHeteroGraph given edge_type
.
Arguments
g
: A graph.edge_type
: A tuple of symbols(source_t, edge_t, target_t)
representing the edge type.T
: Element type of the returned vector. Ifnothing
, is chosen based on the graph type. Defaultnothing
.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. Defaultdir = :out
.
Graphs.has_edge
— Methodhas_edge(g::GNNHeteroGraph, edge_t, i, j)
Return true
if there is an edge of type edge_t
from node i
to node j
in g
.
Examples
julia> g = rand_bipartite_heterograph((2, 2), (4, 0), bidirected=false)
GNNHeteroGraph:
num_nodes: Dict(:A => 2, :B => 2)
num_edges: Dict((:A, :to, :B) => 4, (:B, :to, :A) => 0)
julia> has_edge(g, (:A,:to,:B), 1, 1)
true
julia> has_edge(g, (:B,:to,:A), 1, 1)
false
Transform
GNNGraphs.add_edges
— Methodadd_edges(g::GNNHeteroGraph, edge_t, s, t; [edata, num_nodes])
add_edges(g::GNNHeteroGraph, edge_t => (s, t); [edata, num_nodes])
add_edges(g::GNNHeteroGraph, edge_t => (s, t, w); [edata, num_nodes])
Add to heterograph g
edges of type edge_t
with source node vector s
and target node vector t
. Optionally, pass the edge weights w
or the features edata
for the new edges. edge_t
is a triplet of symbols (src_t, rel_t, dst_t)
.
If the edge type is not already present in the graph, it is added. If it involves new node types, they are added to the graph as well. In this case, a dictionary or named tuple of num_nodes
can be passed to specify the number of nodes of the new types, otherwise the number of nodes is inferred from the maximum node id in s
and t
.
GNNGraphs.add_self_loops
— Methodadd_self_loops(g::GNNHeteroGraph, edge_t::EType)
add_self_loops(g::GNNHeteroGraph)
If the source node type is the same as the destination node type in edge_t
, return a graph with the same features as g
but also add self-loops of the specified type, edge_t
. Otherwise, it returns g
unchanged.
Nodes with already existing self-loops of type edge_t
will obtain a second set of self-loops of the same type.
If the graph has edge weights for edges of type edge_t
, the new edges will have weight 1.
If no edges of type edge_t
exist, or all existing edges have no weight, then all new self loops will have no weight.
If edge_t
is not passed as argument, for the entire graph self-loop is added to each node for every edge type in the graph where the source and destination node types are the same. This iterates over all edge types present in the graph, applying the self-loop addition logic to each applicable edge type.
Generate
GNNGraphs.rand_bipartite_heterograph
— Methodrand_bipartite_heterograph([rng,]
(n1, n2), (m12, m21);
bidirected = true,
node_t = (:A, :B),
edge_t = :to,
kws...)
Construct an GNNHeteroGraph
with random edges representing a bipartite graph. The graph will have two types of nodes, and edges will only connect nodes of different types.
The first argument is a tuple (n1, n2)
specifying the number of nodes of each type. The second argument is a tuple (m12, m21)
specifying the number of edges connecting nodes of type 1
to nodes of type 2
and vice versa.
The type of nodes and edges can be specified with the node_t
and edge_t
keyword arguments, which default to (:A, :B)
and :to
respectively.
If bidirected=true
(default), the reverse edge of each edge will be present. In this case m12 == m21
is required.
A random number generator can be passed as the first argument to make the generation reproducible.
Additional keyword arguments will be passed to the GNNHeteroGraph
constructor.
See rand_heterograph
for a more general version.
Examples
julia> g = rand_bipartite_heterograph((10, 15), 20)
GNNHeteroGraph:
num_nodes: (:A => 10, :B => 15)
num_edges: ((:A, :to, :B) => 20, (:B, :to, :A) => 20)
julia> g = rand_bipartite_heterograph((10, 15), (20, 0), node_t=(:user, :item), edge_t=:-, bidirected=false)
GNNHeteroGraph:
num_nodes: Dict(:item => 15, :user => 10)
num_edges: Dict((:item, :-, :user) => 0, (:user, :-, :item) => 20)
GNNGraphs.rand_heterograph
— Functionrand_heterograph([rng,] n, m; bidirected=false, kws...)
Construct an GNNHeteroGraph
with random edges and with number of nodes and edges specified by n
and m
respectively. n
and m
can be any iterable of pairs specifing node/edge types and their numbers.
Pass a random number generator as a first argument to make the generation reproducible.
Setting bidirected=true
will generate a bidirected graph, i.e. each edge will have a reverse edge. Therefore, for each edge type (:A, :rel, :B)
a corresponding reverse edge type (:B, :rel, :A)
will be generated.
Additional keyword arguments will be passed to the GNNHeteroGraph
constructor.
Examples
julia> g = rand_heterograph((:user => 10, :movie => 20),
(:user, :rate, :movie) => 30)
GNNHeteroGraph:
num_nodes: Dict(:movie => 20, :user => 10)
num_edges: Dict((:user, :rate, :movie) => 30)