dmlc--dgl
863c249568
* Update graph-heterogeneous.rst `tensor([0, 1, 2, 0, 1, 2])` should be output instead of code * Update message-api.rst `updata_all_example()` should be `update_all_example()` * Update message-efficient.rst `cat_feat` need to concatenate with `dim=1` for the # edge features to match # edges * Update nn-construction.rst all `max_pool` in the aggregator type of `SAGEConv` should be `pool` instead * Update graph-heterogeneous.rst `tensor([0, 1, 2, 0, 1, 2])` should be output instead of code * Update message-api.rst `updata_all_example()` should be `update_all_example()` * Update message-efficient.rst `cat_feat` need to concatenate with `dim=1` for the # edge features to match # edges * Update nn-construction.rst all `max_pool` in the aggregator type of `SAGEConv` should be `pool` instead * Update nn-forward.rst all `max_pool` in the aggregator type of `SAGEConv` should be `pool` instead * Update nn-forward.rst all `max_pool` in the aggregator type of `SAGEConv` should be `pool` instead Co-authored-by: zhjwy9343 <6593865@qq.com>
283 行
11 KiB
ReStructuredText
283 行
11 KiB
ReStructuredText
.. _guide-graph-heterogeneous:
|
|
|
|
1.5 Heterogeneous Graphs
|
|
------------------------
|
|
|
|
:ref:`(中文版)<guide_cn-graph-heterogeneous>`
|
|
|
|
A heterogeneous graph can have nodes and edges of different types. Nodes/Edges of
|
|
different types have independent ID space and feature storage. For example in the figure below, the
|
|
user and game node IDs both start from zero and they have different features.
|
|
|
|
.. figure:: https://data.dgl.ai/asset/image/user_guide_graphch_2.png
|
|
|
|
An example heterogeneous graph with two types of nodes (user and game) and two types of edges (follows and plays).
|
|
|
|
Creating a Heterogeneous Graph
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
In DGL, a heterogeneous graph (heterograph for short) is specified with a series of graphs as below, one per
|
|
relation. Each relation is a string triplet ``(source node type, edge type, destination node type)``.
|
|
Since relations disambiguate the edge types, DGL calls them canonical edge types.
|
|
|
|
The following code snippet is an example for creating a heterogeneous graph in DGL.
|
|
|
|
.. code::
|
|
|
|
>>> import dgl
|
|
>>> import torch as th
|
|
|
|
>>> # Create a heterograph with 3 node types and 3 edges types.
|
|
>>> graph_data = {
|
|
... ('drug', 'interacts', 'drug'): (th.tensor([0, 1]), th.tensor([1, 2])),
|
|
... ('drug', 'interacts', 'gene'): (th.tensor([0, 1]), th.tensor([2, 3])),
|
|
... ('drug', 'treats', 'disease'): (th.tensor([1]), th.tensor([2]))
|
|
... }
|
|
>>> g = dgl.heterograph(graph_data)
|
|
>>> g.ntypes
|
|
['disease', 'drug', 'gene']
|
|
>>> g.etypes
|
|
['interacts', 'interacts', 'treats']
|
|
>>> g.canonical_etypes
|
|
[('drug', 'interacts', 'drug'),
|
|
('drug', 'interacts', 'gene'),
|
|
('drug', 'treats', 'disease')]
|
|
|
|
Note that homogeneous and bipartite graphs are just special heterogeneous graphs with one
|
|
relation.
|
|
|
|
.. code::
|
|
|
|
>>> # A homogeneous graph
|
|
>>> dgl.heterograph({('node_type', 'edge_type', 'node_type'): (u, v)})
|
|
>>> # A bipartite graph
|
|
>>> dgl.heterograph({('source_type', 'edge_type', 'destination_type'): (u, v)})
|
|
|
|
The *metagraph* associated with a heterogeneous graph is the schema of the graph. It specifies
|
|
type constraints on the sets of nodes and edges between the nodes. A node :math:`u` in a metagraph
|
|
corresponds to a node type in the associated heterograph. An edge :math:`(u, v)` in a metagraph indicates that
|
|
there are edges from nodes of type :math:`u` to nodes of type :math:`v` in the associated heterograph.
|
|
|
|
.. code::
|
|
|
|
>>> g
|
|
Graph(num_nodes={'disease': 3, 'drug': 3, 'gene': 4},
|
|
num_edges={('drug', 'interacts', 'drug'): 2,
|
|
('drug', 'interacts', 'gene'): 2,
|
|
('drug', 'treats', 'disease'): 1},
|
|
metagraph=[('drug', 'drug', 'interacts'),
|
|
('drug', 'gene', 'interacts'),
|
|
('drug', 'disease', 'treats')])
|
|
>>> g.metagraph().edges()
|
|
OutMultiEdgeDataView([('drug', 'drug'), ('drug', 'gene'), ('drug', 'disease')])
|
|
|
|
See APIs: :func:`dgl.heterograph`, :py:attr:`~dgl.DGLGraph.ntypes`, :py:attr:`~dgl.DGLGraph.etypes`,
|
|
:py:attr:`~dgl.DGLGraph.canonical_etypes`, :py:attr:`~dgl.DGLGraph.metagraph`.
|
|
|
|
Working with Multiple Types
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
When multiple node/edge types are introduced, users need to specify the particular
|
|
node/edge type when invoking a DGLGraph API for type-specific information. In addition,
|
|
nodes/edges of different types have separate IDs.
|
|
|
|
.. code::
|
|
|
|
>>> # Get the number of all nodes in the graph
|
|
>>> g.num_nodes()
|
|
10
|
|
>>> # Get the number of drug nodes
|
|
>>> g.num_nodes('drug')
|
|
3
|
|
>>> # Nodes of different types have separate IDs,
|
|
>>> # hence not well-defined without a type specified
|
|
>>> g.nodes()
|
|
DGLError: Node type name must be specified if there are more than one node types.
|
|
>>> g.nodes('drug')
|
|
tensor([0, 1, 2])
|
|
|
|
To set/get features for a specific node/edge type, DGL provides two new types of syntax --
|
|
`g.nodes['node_type'].data['feat_name']` and `g.edges['edge_type'].data['feat_name']`.
|
|
|
|
.. code::
|
|
|
|
>>> # Set/get feature 'hv' for nodes of type 'drug'
|
|
>>> g.nodes['drug'].data['hv'] = th.ones(3, 1)
|
|
>>> g.nodes['drug'].data['hv']
|
|
tensor([[1.],
|
|
[1.],
|
|
[1.]])
|
|
>>> # Set/get feature 'he' for edge of type 'treats'
|
|
>>> g.edges['treats'].data['he'] = th.zeros(1, 1)
|
|
>>> g.edges['treats'].data['he']
|
|
tensor([[0.]])
|
|
|
|
If the graph only has one node/edge type, there is no need to specify the node/edge type.
|
|
|
|
.. code::
|
|
|
|
>>> g = dgl.heterograph({
|
|
... ('drug', 'interacts', 'drug'): (th.tensor([0, 1]), th.tensor([1, 2])),
|
|
... ('drug', 'is similar', 'drug'): (th.tensor([0, 1]), th.tensor([2, 3]))
|
|
... })
|
|
>>> g.nodes()
|
|
tensor([0, 1, 2, 3])
|
|
>>> # To set/get feature with a single type, no need to use the new syntax
|
|
>>> g.ndata['hv'] = th.ones(4, 1)
|
|
|
|
.. note::
|
|
|
|
When the edge type uniquely determines the types of source and destination nodes, one
|
|
can just use one string instead of a string triplet to specify the edge type. For example, for a
|
|
heterograph with two relations ``('user', 'plays', 'game')`` and ``('user', 'likes', 'game')``, it
|
|
is safe to just use ``'plays'`` or ``'likes'`` to refer to the two relations.
|
|
|
|
Loading Heterographs from Disk
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Comma Separated Values (CSV)
|
|
""""""""""""""""""""""""""""
|
|
|
|
A common way to store a heterograph is to store nodes and edges of different types in different CSV files.
|
|
An example is as follows.
|
|
|
|
.. code::
|
|
|
|
# data folder
|
|
data/
|
|
|-- drug.csv # drug nodes
|
|
|-- gene.csv # gene nodes
|
|
|-- disease.csv # disease nodes
|
|
|-- drug-interact-drug.csv # drug-drug interaction edges
|
|
|-- drug-interact-gene.csv # drug-gene interaction edges
|
|
|-- drug-treat-disease.csv # drug-treat-disease edges
|
|
|
|
Similar to the case of homogeneous graphs, one can use packages like Pandas to parse
|
|
CSV files into numpy arrays or framework tensors, build a relation dictionary and
|
|
construct a heterograph from that. The approach also applies to other popular formats like
|
|
GML/JSON.
|
|
|
|
DGL Binary Format
|
|
"""""""""""""""""
|
|
|
|
DGL provides :func:`dgl.save_graphs` and :func:`dgl.load_graphs` respectively for saving
|
|
heterogeneous graphs in binary format and loading them from binary format.
|
|
|
|
Edge Type Subgraph
|
|
^^^^^^^^^^^^^^^^^^
|
|
|
|
One can create a subgraph of a heterogeneous graph by specifying the relations to retain, with
|
|
features copied if any.
|
|
|
|
.. code::
|
|
|
|
>>> g = dgl.heterograph({
|
|
... ('drug', 'interacts', 'drug'): (th.tensor([0, 1]), th.tensor([1, 2])),
|
|
... ('drug', 'interacts', 'gene'): (th.tensor([0, 1]), th.tensor([2, 3])),
|
|
... ('drug', 'treats', 'disease'): (th.tensor([1]), th.tensor([2]))
|
|
... })
|
|
>>> g.nodes['drug'].data['hv'] = th.ones(3, 1)
|
|
|
|
>>> # Retain relations ('drug', 'interacts', 'drug') and ('drug', 'treats', 'disease')
|
|
>>> # All nodes for 'drug' and 'disease' will be retained
|
|
>>> eg = dgl.edge_type_subgraph(g, [('drug', 'interacts', 'drug'),
|
|
... ('drug', 'treats', 'disease')])
|
|
>>> eg
|
|
Graph(num_nodes={'disease': 3, 'drug': 3},
|
|
num_edges={('drug', 'interacts', 'drug'): 2, ('drug', 'treats', 'disease'): 1},
|
|
metagraph=[('drug', 'drug', 'interacts'), ('drug', 'disease', 'treats')])
|
|
>>> # The associated features will be copied as well
|
|
>>> eg.nodes['drug'].data['hv']
|
|
tensor([[1.],
|
|
[1.],
|
|
[1.]])
|
|
|
|
Converting Heterogeneous Graphs to Homogeneous Graphs
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
Heterographs provide a clean interface for managing nodes/edges of different types and
|
|
their associated features. This is particularly helpful when:
|
|
|
|
1. The features for nodes/edges of different types have different data types or sizes.
|
|
2. We want to apply different operations to nodes/edges of different types.
|
|
|
|
If the above conditions do not hold and one does not want to distinguish node/edge types in
|
|
modeling, then DGL allows converting a heterogeneous graph to a homogeneous graph with :func:`dgl.DGLGraph.to_homogeneous` API.
|
|
It proceeds as follows:
|
|
|
|
1. Relabels nodes/edges of all types using consecutive integers starting from 0
|
|
2. Merges the features across node/edge types specified by the user.
|
|
|
|
.. code::
|
|
|
|
>>> g = dgl.heterograph({
|
|
... ('drug', 'interacts', 'drug'): (th.tensor([0, 1]), th.tensor([1, 2])),
|
|
... ('drug', 'treats', 'disease'): (th.tensor([1]), th.tensor([2]))})
|
|
>>> g.nodes['drug'].data['hv'] = th.zeros(3, 1)
|
|
>>> g.nodes['disease'].data['hv'] = th.ones(3, 1)
|
|
>>> g.edges['interacts'].data['he'] = th.zeros(2, 1)
|
|
>>> g.edges['treats'].data['he'] = th.zeros(1, 2)
|
|
|
|
>>> # By default, it does not merge any features
|
|
>>> hg = dgl.to_homogeneous(g)
|
|
>>> 'hv' in hg.ndata
|
|
False
|
|
|
|
>>> # Copy edge features
|
|
>>> # For feature copy, it expects features to have
|
|
>>> # the same size and dtype across node/edge types
|
|
>>> hg = dgl.to_homogeneous(g, edata=['he'])
|
|
DGLError: Cannot concatenate column ‘he’ with shape Scheme(shape=(2,), dtype=torch.float32) and shape Scheme(shape=(1,), dtype=torch.float32)
|
|
|
|
>>> # Copy node features
|
|
>>> hg = dgl.to_homogeneous(g, ndata=['hv'])
|
|
>>> hg.ndata['hv']
|
|
tensor([[1.],
|
|
[1.],
|
|
[1.],
|
|
[0.],
|
|
[0.],
|
|
[0.]])
|
|
|
|
The original node/edge types and type-specific IDs are stored in :py:attr:`~dgl.DGLGraph.ndata` and :py:attr:`~dgl.DGLGraph.edata`.
|
|
|
|
.. code::
|
|
|
|
>>> # Order of node types in the heterograph
|
|
>>> g.ntypes
|
|
['disease', 'drug']
|
|
>>> # Original node types
|
|
>>> hg.ndata[dgl.NTYPE]
|
|
tensor([0, 0, 0, 1, 1, 1])
|
|
>>> # Original type-specific node IDs
|
|
>>> hg.ndata[dgl.NID]
|
|
tensor([0, 1, 2, 0, 1, 2])
|
|
|
|
>>> # Order of edge types in the heterograph
|
|
>>> g.etypes
|
|
['interacts', 'treats']
|
|
>>> # Original edge types
|
|
>>> hg.edata[dgl.ETYPE]
|
|
tensor([0, 0, 1])
|
|
>>> # Original type-specific edge IDs
|
|
>>> hg.edata[dgl.EID]
|
|
tensor([0, 1, 0])
|
|
|
|
For modeling purposes, one may want to group some relations together and apply the same
|
|
operation to them. To address this need, one can first take an edge type subgraph of the
|
|
heterograph and then convert the subgraph to a homogeneous graph.
|
|
|
|
.. code::
|
|
|
|
>>> g = dgl.heterograph({
|
|
... ('drug', 'interacts', 'drug'): (th.tensor([0, 1]), th.tensor([1, 2])),
|
|
... ('drug', 'interacts', 'gene'): (th.tensor([0, 1]), th.tensor([2, 3])),
|
|
... ('drug', 'treats', 'disease'): (th.tensor([1]), th.tensor([2]))
|
|
... })
|
|
>>> sub_g = dgl.edge_type_subgraph(g, [('drug', 'interacts', 'drug'),
|
|
... ('drug', 'interacts', 'gene')])
|
|
>>> h_sub_g = dgl.to_homogeneous(sub_g)
|
|
>>> h_sub_g
|
|
Graph(num_nodes=7, num_edges=4,
|
|
...)
|