From 2db8ccb4875e025398c6abb1ff1e1f7473ca712a Mon Sep 17 00:00:00 2001 From: zhjwy9343 <6593865@qq.com> Date: Thu, 19 Nov 2020 13:40:33 +0800 Subject: [PATCH] [Doc] Chinese User Guide chapter 1 - 4 (#2351) * [Feature] Add full graph training with dgl built-in dataset. * [Feature] Add full graph training with dgl built-in dataset. * [Feature] Add full graph training with dgl built-in dataset. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Bug] fix model to cuda. * [Feature] Add test loss and accuracy * [Feature] Add test loss and accuracy * [Feature] Add test loss and accuracy * [Feature] Add test loss and accuracy * [Feature] Add test loss and accuracy * [Feature] Add test loss and accuracy * [Fix] Add random * [Bug] Fix batch norm error * [Doc] Test with CN in Sphinx * [Doc] Test with CN in Sphinx * [Doc] Remove the test CN docs. * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Feature] Add input embedding layer * [Doc] fill readme with new performance results * [Doc] Add Chinese User Guide, graph and 1.5 * [Doc] Add Chinese User Guide, graph and 1.5 * Update README.md * [Fix] Temporary remove compgcn * [Doc] Add CN user guide chapter2 * [Test] Tunning format * [Test] Tunning format * [Test] Tunning format * [Test] Tunning format * [Test] Tunning format * [Test] Section headers * [Fix] Fix format errors * [Fix] Fix format errors * [Fix] Fix format errors * [Doc] Add CN-EN EN-CN links * [Doc] Add CN-EN EN-CN links * [Doc] Copyedit chapter2 * [Doc] Copyedit chapter2 * [Doc] Remove EN in 2.1 * [Doc] Remove EN in chapter 2 * [Doc] Copyedit first 2 sections * [Doc] Copyedit first 2 sections * [Doc] copyedited chapter 2 CN * [Doc] Add chapter 3 raw texts * [Doc] Add chapter 3 preface and 3.1 * [Doc] Add chapter 3.2 and 3.3 * [Doc] Add chapter 3.2 and 3.3 * [Doc] Add chapter 3.2 and 3.3 * [Doc] Remove EN parts * [Doc] Copyediting 3.1 * [Doc] Copyediting 3.2 and 3.3 * [Doc] Proofreading 3.1 and 3.2 * [Doc] Proofreading 3.2 and 3.3 * [Doc] Add chapter 4 CN raw text. * [Clean] Remove codes in other branches * [Doc] Start to copyedit chapter 4 preface * [Doc] copyedit CN section 4.1 * [Doc] Remove EN in User Guide Chapter 4 * [Doc] Copyedit chapter 4.1 * [Doc] copyedit cn chapter 4.2, 4.3, 4.4, and 4.5. * [Doc] Fix errors in EN user guide graph feature and heterograph * [Doc] 2nd round copyediting with Murph's comments * [Doc] 3rd round copyediting with Murph's comments * [Doc] 3rd round copyediting with Murph's comments * [Doc] 3rd round copyediting with Murph's comments * [Sync] syncronize with the dgl master * [Doc] edited after Minjie's comments, 1st round * update cub Co-authored-by: Minjie Wang --- docs/source/guide/data-dataset.rst | 2 + docs/source/guide/data-download.rst | 2 + docs/source/guide/data-loadogb.rst | 4 +- docs/source/guide/data-process.rst | 2 + docs/source/guide/data-savenload.rst | 9 +- docs/source/guide/data.rst | 2 + docs/source/guide/graph-feature.rst | 1 + docs/source/guide/message-api.rst | 2 + docs/source/guide/message-edge.rst | 2 + docs/source/guide/message-efficient.rst | 2 + docs/source/guide/message-heterograph.rst | 2 + docs/source/guide/message-part.rst | 2 + docs/source/guide/message.rst | 2 + docs/source/guide/nn-construction.rst | 2 + docs/source/guide/nn-forward.rst | 10 +- docs/source/guide/nn-heterograph.rst | 6 +- docs/source/guide/nn.rst | 2 + docs/source/guide_cn/data-dataset.rst | 89 ++++++ docs/source/guide_cn/data-download.rst | 50 +++ docs/source/guide_cn/data-loadogb.rst | 76 +++++ docs/source/guide_cn/data-process.rst | 301 +++++++++++++++++++ docs/source/guide_cn/data-savenload.rst | 45 +++ docs/source/guide_cn/data.rst | 31 ++ docs/source/guide_cn/graph.rst | 4 +- docs/source/guide_cn/message-api.rst | 76 +++++ docs/source/guide_cn/message-edge.rst | 25 ++ docs/source/guide_cn/message-efficient.rst | 63 ++++ docs/source/guide_cn/message-heterograph.rst | 38 +++ docs/source/guide_cn/message-part.rst | 17 ++ docs/source/guide_cn/message.rst | 43 +++ docs/source/guide_cn/nn-construction.rst | 73 +++++ docs/source/guide_cn/nn-forward.rst | 138 +++++++++ docs/source/guide_cn/nn-heterograph.rst | 91 ++++++ docs/source/guide_cn/nn.rst | 35 +++ 34 files changed, 1233 insertions(+), 16 deletions(-) create mode 100644 docs/source/guide_cn/data-dataset.rst create mode 100644 docs/source/guide_cn/data-download.rst create mode 100644 docs/source/guide_cn/data-loadogb.rst create mode 100644 docs/source/guide_cn/data-process.rst create mode 100644 docs/source/guide_cn/data-savenload.rst create mode 100644 docs/source/guide_cn/data.rst create mode 100644 docs/source/guide_cn/message-api.rst create mode 100644 docs/source/guide_cn/message-edge.rst create mode 100644 docs/source/guide_cn/message-efficient.rst create mode 100644 docs/source/guide_cn/message-heterograph.rst create mode 100644 docs/source/guide_cn/message-part.rst create mode 100644 docs/source/guide_cn/message.rst create mode 100644 docs/source/guide_cn/nn-construction.rst create mode 100644 docs/source/guide_cn/nn-forward.rst create mode 100644 docs/source/guide_cn/nn-heterograph.rst create mode 100644 docs/source/guide_cn/nn.rst diff --git a/docs/source/guide/data-dataset.rst b/docs/source/guide/data-dataset.rst index b140a7013510..b2c3f87ef2de 100644 --- a/docs/source/guide/data-dataset.rst +++ b/docs/source/guide/data-dataset.rst @@ -3,6 +3,8 @@ 4.1 DGLDataset class -------------------- +:ref:`(中文版) ` + :class:`~dgl.data.DGLDataset` is the base class for processing, loading and saving graph datasets defined in :ref:`apidata`. It implements the basic pipeline for processing graph data. The following flow chart shows how the diff --git a/docs/source/guide/data-download.rst b/docs/source/guide/data-download.rst index e1f920359531..309eeb6a9c65 100644 --- a/docs/source/guide/data-download.rst +++ b/docs/source/guide/data-download.rst @@ -3,6 +3,8 @@ 4.2 Download raw data (optional) -------------------------------- +:ref:`(中文版) ` + If a dataset is already in local disk, make sure it’s in directory ``raw_dir``. If one wants to run the code anywhere without bothering to download and move data to the right directory, one can do it diff --git a/docs/source/guide/data-loadogb.rst b/docs/source/guide/data-loadogb.rst index 976085d62c4c..8e704e7c92fb 100644 --- a/docs/source/guide/data-loadogb.rst +++ b/docs/source/guide/data-loadogb.rst @@ -3,6 +3,8 @@ 4.5 Loading OGB datasets using ``ogb`` package ---------------------------------------------- +:ref:`(中文版) ` + `Open Graph Benchmark (OGB) `__ is a collection of benchmark datasets. The official OGB package `ogb `__ provides APIs for @@ -61,7 +63,7 @@ there is only one graph object in this kind of dataset. valid_label = dataset.labels[split_idx['valid']] test_label = dataset.labels[split_idx['test']] -*Link Property Prediction* datasets also contain one graph per dataset: +*Link Property Prediction* datasets also contain one graph per dataset. .. code:: diff --git a/docs/source/guide/data-process.rst b/docs/source/guide/data-process.rst index 18b0f66634bd..85dcbaa70dc7 100644 --- a/docs/source/guide/data-process.rst +++ b/docs/source/guide/data-process.rst @@ -3,6 +3,8 @@ 4.3 Process data ---------------- +:ref:`(中文版) ` + One can implement the data processing code in function ``process()``, and it assumes that the raw data is located in ``self.raw_dir`` already. There are typically three types of tasks in machine learning on graphs: graph diff --git a/docs/source/guide/data-savenload.rst b/docs/source/guide/data-savenload.rst index 868858250d49..8a84b00d3d75 100644 --- a/docs/source/guide/data-savenload.rst +++ b/docs/source/guide/data-savenload.rst @@ -3,6 +3,8 @@ 4.4 Save and load data ---------------------- +:ref:`(中文版) ` + DGL recommends implementing saving and loading functions to cache the processed data in local disk. This saves a lot of data processing time in most cases. DGL provides four functions to make things simple: @@ -44,9 +46,4 @@ dataset information. Note that there are cases not suitable to save processed data. For example, in the builtin dataset :class:`~dgl.data.GDELTDataset`, the processed data is quite large, so it’s more effective to process -each data example in ``__getitem__(idx)``. - -.. code:: - - print(split_edge['valid'].keys()) - print(split_edge['test'].keys()) +each data example in ``__getitem__(idx)``. \ No newline at end of file diff --git a/docs/source/guide/data.rst b/docs/source/guide/data.rst index 7020c2dc26a2..034798a2d97a 100644 --- a/docs/source/guide/data.rst +++ b/docs/source/guide/data.rst @@ -3,6 +3,8 @@ Chapter 4: Graph Data Pipeline ============================== +:ref:`(中文版) ` + DGL implements many commonly used graph datasets in :ref:`apidata`. They follow a standard pipeline defined in class :class:`dgl.data.DGLDataset`. DGL highly recommends processing graph data into a :class:`dgl.data.DGLDataset` subclass, as the diff --git a/docs/source/guide/graph-feature.rst b/docs/source/guide/graph-feature.rst index 53110926431b..b30a68631834 100644 --- a/docs/source/guide/graph-feature.rst +++ b/docs/source/guide/graph-feature.rst @@ -61,4 +61,5 @@ For weighted graphs, one can store the weights as an edge feature as below. ndata_schemes={} edata_schemes={'w' : Scheme(shape=(,), dtype=torch.float32)}) + See APIs: :py:attr:`~dgl.DGLGraph.ndata`, :py:attr:`~dgl.DGLGraph.edata`. diff --git a/docs/source/guide/message-api.rst b/docs/source/guide/message-api.rst index 493b0d4c3e58..e10b97aa1e5f 100644 --- a/docs/source/guide/message-api.rst +++ b/docs/source/guide/message-api.rst @@ -3,6 +3,8 @@ 2.1 Built-in Functions and Message Passing APIs ----------------------------------------------- +:ref:`(中文版) ` + In DGL, **message function** takes a single argument ``edges``, which is an :class:`~dgl.udf.EdgeBatch` instance. During message passing, DGL generates it internally to represent a batch of edges. It has three diff --git a/docs/source/guide/message-edge.rst b/docs/source/guide/message-edge.rst index 8d1df4f4920d..58336bf4f166 100644 --- a/docs/source/guide/message-edge.rst +++ b/docs/source/guide/message-edge.rst @@ -3,6 +3,8 @@ 2.4 Apply Edge Weight In Message Passing ---------------------------------------- +:ref:`(中文版) ` + A commonly seen practice in GNN modeling is to apply edge weight on the message before message aggregation, for examples, in `GAT `__ and some `GCN diff --git a/docs/source/guide/message-efficient.rst b/docs/source/guide/message-efficient.rst index 29838ee99a62..6f0c12f9edd4 100644 --- a/docs/source/guide/message-efficient.rst +++ b/docs/source/guide/message-efficient.rst @@ -3,6 +3,8 @@ 2.2 Writing Efficient Message Passing Code ------------------------------------------ +:ref:`(中文版) ` + DGL optimizes memory consumption and computing speed for message passing. The optimization includes: diff --git a/docs/source/guide/message-heterograph.rst b/docs/source/guide/message-heterograph.rst index 013f7a14e316..9b5feee81a02 100644 --- a/docs/source/guide/message-heterograph.rst +++ b/docs/source/guide/message-heterograph.rst @@ -3,6 +3,8 @@ 2.5 Message Passing on Heterogeneous Graph ------------------------------------------ +:ref:`(中文版) ` + Heterogeneous graphs (:ref:`guide-graph-heterogeneous`), or heterographs for short, are graphs that contain different types of nodes and edges. The different types of nodes and edges tend to have different diff --git a/docs/source/guide/message-part.rst b/docs/source/guide/message-part.rst index 8225b688e772..7daf78719d26 100644 --- a/docs/source/guide/message-part.rst +++ b/docs/source/guide/message-part.rst @@ -3,6 +3,8 @@ 2.3 Apply Message Passing On Part Of The Graph ---------------------------------------------- +:ref:`(中文版) ` + If one only wants to update part of the nodes in the graph, the practice is to create a subgraph by providing the IDs for the nodes to include in the update, then call :meth:`~dgl.DGLGraph.update_all` on the diff --git a/docs/source/guide/message.rst b/docs/source/guide/message.rst index 924be0a53cdf..1dd0d8d0a136 100644 --- a/docs/source/guide/message.rst +++ b/docs/source/guide/message.rst @@ -3,6 +3,8 @@ Chapter 2: Message Passing ========================== +:ref:`(中文版) ` + Message Passing Paradigm ------------------------ diff --git a/docs/source/guide/nn-construction.rst b/docs/source/guide/nn-construction.rst index 7543bb54686f..5884bc62c96e 100644 --- a/docs/source/guide/nn-construction.rst +++ b/docs/source/guide/nn-construction.rst @@ -3,6 +3,8 @@ 3.1 DGL NN Module Construction Function --------------------------------------- +:ref:`(中文版) ` + The construction function performs the following steps: 1. Set options. diff --git a/docs/source/guide/nn-forward.rst b/docs/source/guide/nn-forward.rst index 2a9f375945e8..8a0d28c46fd7 100644 --- a/docs/source/guide/nn-forward.rst +++ b/docs/source/guide/nn-forward.rst @@ -3,6 +3,8 @@ 3.2 DGL NN Module Forward Function ---------------------------------- +:ref:`(中文版) ` + In NN module, ``forward()`` function does the actual message passing and computation. Compared with PyTorch’s NN module which usually takes tensors as the parameters, DGL NN module takes an additional parameter @@ -60,7 +62,7 @@ The math formulas for SAGEConv are: One needs to specify the source node feature ``feat_src`` and destination node feature ``feat_dst`` according to the graph type. -:meth:``~dgl.utils.expand_as_pair`` is a function that specifies the graph +:meth:`~dgl.utils.expand_as_pair` is a function that specifies the graph type and expand ``feat`` into ``feat_src`` and ``feat_dst``. The detail of this function is shown below. @@ -95,9 +97,7 @@ element will be the destination node feature. In mini-batch training, the computing is applied on a subgraph sampled based on a bunch of destination nodes. The subgraph is called as -``block`` in DGL. After message passing, only those destination nodes -will be updated since they have the same neighborhood as the one they -have in the original full graph. In the block creation phase, +``block`` in DGL. In the block creation phase, ``dst nodes`` are in the front of the node list. One can find the ``feat_dst`` by the index ``[0:g.number_of_dst_nodes()]``. @@ -120,7 +120,7 @@ Message passing and reducing elif self._aggre_type == 'gcn': check_eq_shape(feat) graph.srcdata['h'] = feat_src - graph.dstdata['h'] = feat_dst # same as above if homogeneous + graph.dstdata['h'] = feat_dst graph.update_all(fn.copy_u('h', 'm'), fn.sum('m', 'neigh')) # divide in_degrees degs = graph.in_degrees().to(feat_dst) diff --git a/docs/source/guide/nn-heterograph.rst b/docs/source/guide/nn-heterograph.rst index 2f3ca81006ff..0da482561bf6 100644 --- a/docs/source/guide/nn-heterograph.rst +++ b/docs/source/guide/nn-heterograph.rst @@ -1,12 +1,14 @@ .. _guide-nn-heterograph: 3.3 Heterogeneous GraphConv Module ----------------------------------- +------------------------------------ + +:ref:`(中文版) ` :class:`~dgl.nn.pytorch.HeteroGraphConv` is a module-level encapsulation to run DGL NN module on heterogeneous graphs. The implementation logic is the same as message passing level API -:meth:`~dgl.DGLGraph.multi_update_all`: +:meth:`~dgl.DGLGraph.multi_update_all`, including: - DGL NN module within each relation :math:`r`. - Reduction that merges the results on the same node type from multiple diff --git a/docs/source/guide/nn.rst b/docs/source/guide/nn.rst index c52901e1d2e2..d4eb292e73da 100644 --- a/docs/source/guide/nn.rst +++ b/docs/source/guide/nn.rst @@ -3,6 +3,8 @@ Chapter 3: Building GNN Modules =============================== +:ref:`(中文版) ` + DGL NN module consists of building blocks for GNN models. An NN module inherits from `Pytorch’s NN Module `__, `MXNet Gluon’s NN Block `__ and `TensorFlow’s Keras Layer `__, depending on the DNN framework backend in use. In a DGL NN diff --git a/docs/source/guide_cn/data-dataset.rst b/docs/source/guide_cn/data-dataset.rst new file mode 100644 index 000000000000..f976a3678856 --- /dev/null +++ b/docs/source/guide_cn/data-dataset.rst @@ -0,0 +1,89 @@ +.. _guide_cn-data-pipeline-dataset: + +4.1 DGLDataset类 +-------------------- + +:ref:`(English Version) ` + +:class:`~dgl.data.DGLDataset` 是处理、导入和保存 :ref:`apidata` 中定义的图数据集的基类。 +它实现了用于处理图数据的基本模版。下面的流程图展示了这个模版的工作方式。 + +.. figure:: https://data.dgl.ai/asset/image/userguide_data_flow.png + :align: center + + 在类DGLDataset中定义的图数据处理模版的流程图。 + +为了处理位于远程服务器或本地磁盘上的图数据集,下面的例子中定义了一个类,称为 ``MyDataset``, +它继承自 :class:`dgl.data.DGLDataset`。 + +.. code:: + + from dgl.data import DGLDataset + + class MyDataset(DGLDataset): + """ 用于在DGL中自定义图数据集的模板: + + Parameters + ---------- + url : str + 下载原始数据集的url。 + raw_dir : str + 指定下载数据的存储目录或已下载数据的存储目录。默认: ~/.dgl/ + save_dir : str + 处理完成的数据集的保存目录。默认:raw_dir指定的值 + force_reload : bool + 是否重新导入数据集。默认:False + verbose : bool + 是否打印进度信息。 + """ + def __init__(self, + url=None, + raw_dir=None, + save_dir=None, + force_reload=False, + verbose=False): + super(MyDataset, self).__init__(name='dataset_name', + url=url, + raw_dir=raw_dir, + save_dir=save_dir, + force_reload=force_reload, + verbose=verbose) + + def download(self): + # 将原始数据下载到本地磁盘 + pass + + def process(self): + # 将原始数据处理为图、标签和数据集划分的掩码 + pass + + def __getitem__(self, idx): + # 通过idx得到与之对应的一个样本 + pass + + def __len__(self): + # 数据样本的数量 + pass + + def save(self): + # 将处理后的数据保存至 `self.save_path` + pass + + def load(self): + # 从 `self.save_path` 导入处理后的数据 + pass + + def has_cache(self): + # 检查在 `self.save_path` 中是否存有处理后的数据 + pass + +:class:`~dgl.data.DGLDataset` 类有抽象函数 ``process()``, +``__getitem__(idx)`` 和 ``__len__()``。子类必须实现这些函数。同时DGL也建议实现保存和导入函数, +因为对于处理后的大型数据集,这么做可以节省大量的时间, +并且有多个已有的API可以简化此操作(请参阅 :ref:`guide_cn-data-pipeline-savenload`)。 + +请注意, :class:`~dgl.data.DGLDataset` 的目的是提供一种标准且方便的方式来导入图数据。 +用户可以存储有关数据集的图、特征、标签、掩码,以及诸如类别数、标签数等基本信息。 +诸如采样、划分或特征归一化等操作建议在 :class:`~dgl.data.DGLDataset` 子类之外完成。 + +本章的后续部分展示了实现这些函数的最佳实践。 \ No newline at end of file diff --git a/docs/source/guide_cn/data-download.rst b/docs/source/guide_cn/data-download.rst new file mode 100644 index 000000000000..c41f765aecf4 --- /dev/null +++ b/docs/source/guide_cn/data-download.rst @@ -0,0 +1,50 @@ +.. _guide_cn-data-pipeline-download: + +4.2 下载原始数据(可选) +-------------------------------- + +:ref:`(English Version) ` + +如果用户的数据集已经在本地磁盘中,请确保它被存放在目录 ``raw_dir`` 中。 +如果用户想在任何地方运行代码而又不想自己下载数据并将其移动到正确的目录中,则可以通过实现函数 ``download()`` 来自动完成。 + +如果数据集是一个zip文件,可以直接继承 :class:`dgl.data.DGLBuiltinDataset` 类。后者支持解压缩zip文件。 +否则用户需要自己实现 ``download()``,具体可以参考 :class:`~dgl.data.QM7bDataset` 类: + +.. code:: + + import os + from dgl.data.utils import download + + def download(self): + # 存储文件的路径 + file_path = os.path.join(self.raw_dir, self.name + '.mat') + # 下载文件 + download(self.url, path=file_path) + +上面的代码将一个.mat文件下载到目录 ``self.raw_dir``。如果文件是.gz、.tar、.tar.gz或.tgz文件,请使用 +:func:`~dgl.data.utils.extract_archive` 函数进行解压缩。以下代码展示了如何在 +:class:`~dgl.data.BitcoinOTCDataset` 类中下载一个.gz文件: + +.. code:: + + from dgl.data.utils import download, check_sha1 + + def download(self): + # 存储文件的路径,请确保使用与原始文件名相同的后缀 + gz_file_path = os.path.join(self.raw_dir, self.name + '.csv.gz') + # 下载文件 + download(self.url, path=gz_file_path) + # 检查 SHA-1 + if not check_sha1(gz_file_path, self._sha1_str): + raise UserWarning('File {} is downloaded but the content hash does not match.' + 'The repo may be outdated or download may be incomplete. ' + 'Otherwise you can create an issue for it.'.format(self.name + '.csv.gz')) + # 将文件解压缩到目录self.raw_dir下的self.name目录中 + self._extract_gz(gz_file_path, self.raw_path) + +上面的代码会将文件解压缩到 ``self.raw_dir`` 下的目录 ``self.name`` 中。 +如果该类继承自 :class:`dgl.data.DGLBuiltinDataset` 来处理zip文件, +则它也会将文件解压缩到目录 ``self.name`` 中。 + +一个可选项是用户可以按照上面的示例检查下载后文件的SHA-1字符串,以防作者在远程服务器上更改了文件。 \ No newline at end of file diff --git a/docs/source/guide_cn/data-loadogb.rst b/docs/source/guide_cn/data-loadogb.rst new file mode 100644 index 000000000000..b9a203380bc3 --- /dev/null +++ b/docs/source/guide_cn/data-loadogb.rst @@ -0,0 +1,76 @@ +.. _guide_cn-data-pipeline-loadogb: + +4.5 使用ogb包导入OGB数据集 +---------------------------------------------- + +:ref:`(English Version) ` + +`Open Graph Benchmark (OGB) `__ 是一个图深度学习的基准数据集。 +官方的 `ogb `__ 包提供了用于下载和处理OGB数据集到 +:class:`dgl.data.DGLGraph` 对象的API。本节会介绍它们的基本用法。 + +首先使用pip安装ogb包: + +.. code:: + + pip install ogb + + +以下代码显示了如何为 *Graph Property Prediction* 任务加载数据集。 + +.. code:: + + # 载入OGB的Graph Property Prediction数据集 + import dgl + import torch + from ogb.graphproppred import DglGraphPropPredDataset + from torch.utils.data import DataLoader + + def _collate_fn(batch): + # 小批次是一个元组(graph, label)列表 + graphs = [e[0] for e in batch] + g = dgl.batch(graphs) + labels = [e[1] for e in batch] + labels = torch.stack(labels, 0) + return g, labels + + # 载入数据集 + dataset = DglGraphPropPredDataset(name='ogbg-molhiv') + split_idx = dataset.get_idx_split() + # dataloader + train_loader = DataLoader(dataset[split_idx["train"]], batch_size=32, shuffle=True, collate_fn=_collate_fn) + valid_loader = DataLoader(dataset[split_idx["valid"]], batch_size=32, shuffle=False, collate_fn=_collate_fn) + test_loader = DataLoader(dataset[split_idx["test"]], batch_size=32, shuffle=False, collate_fn=_collate_fn) + +加载 *Node Property Prediction* 数据集类似,但要注意的是这种数据集只有一个图对象。 + +.. code:: + + # 载入OGB的Node Property Prediction数据集 + from ogb.nodeproppred import DglNodePropPredDataset + + dataset = DglNodePropPredDataset(name='ogbn-proteins') + split_idx = dataset.get_idx_split() + + # there is only one graph in Node Property Prediction datasets + # 在Node Property Prediction数据集里只有一个图 + g, labels = dataset[0] + # 获取划分的标签 + train_label = dataset.labels[split_idx['train']] + valid_label = dataset.labels[split_idx['valid']] + test_label = dataset.labels[split_idx['test']] + +每个 *Link Property Prediction* 数据集也只包括一个图。 + +.. code:: + + # 载入OGB的Link Property Prediction数据集 + from ogb.linkproppred import DglLinkPropPredDataset + + dataset = DglLinkPropPredDataset(name='ogbl-ppa') + split_edge = dataset.get_edge_split() + + graph = dataset[0] + print(split_edge['train'].keys()) + print(split_edge['valid'].keys()) + print(split_edge['test'].keys()) diff --git a/docs/source/guide_cn/data-process.rst b/docs/source/guide_cn/data-process.rst new file mode 100644 index 000000000000..996d5639cf2d --- /dev/null +++ b/docs/source/guide_cn/data-process.rst @@ -0,0 +1,301 @@ +.. _guide_cn-data-pipeline-process: + +4.3 处理数据 +---------------- + +:ref:`(English Version) ` + +用户可以在 ``process()`` 函数中实现数据处理。该函数假定原始数据已经位于 ``self.raw_dir`` 目录中。 + +图上的机器学习任务通常有三种类型:整图分类、节点分类和链接预测。本节将展示如何处理与这些任务相关的数据集。 + +本节重点介绍了处理图、特征和划分掩码的标准方法。用户指南将以内置数据集为例,并跳过从文件构建图的实现。 +用户可以参考 :ref:`guide_cn-graph-external` 以查看如何从外部数据源构建图的完整指南。 + +处理整图分类数据集 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +整图分类数据集与用小批次训练的典型机器学习任务中的大多数数据集类似。 +因此,需要将原始数据处理为 :class:`dgl.DGLGraph` 对象的列表和标签张量的列表。 +此外,如果原始数据已被拆分为多个文件,则可以添加参数 ``split`` 以导入数据的特定部分。 + +下面以 :class:`~dgl.data.QM7bDataset` 为例: + +.. code:: + + from dgl.data import DGLDataset + + class QM7bDataset(DGLDataset): + _url = 'http://deepchem.io.s3-website-us-west-1.amazonaws.com/' \ + 'datasets/qm7b.mat' + _sha1_str = '4102c744bb9d6fd7b40ac67a300e49cd87e28392' + + def __init__(self, raw_dir=None, force_reload=False, verbose=False): + super(QM7bDataset, self).__init__(name='qm7b', + url=self._url, + raw_dir=raw_dir, + force_reload=force_reload, + verbose=verbose) + + def process(self): + mat_path = self.raw_path + '.mat' + # 将数据处理为图列表和标签列表 + self.graphs, self.label = self._load_graph(mat_path) + + def __getitem__(self, idx): + """ 通过idx获取对应的图和标签 + + Parameters + ---------- + idx : int + Item index + + Returns + ------- + (dgl.DGLGraph, Tensor) + """ + return self.graphs[idx], self.label[idx] + + def __len__(self): + """数据集中图的数量""" + return len(self.graphs) + +函数 ``process()`` 将原始数据处理为图列表和标签列表。用户必须实现 ``__getitem__(idx)`` 和 ``__len__()`` 以进行迭代。 +DGL建议让 ``__getitem__(idx)`` 返回如上面代码所示的元组 ``(图,标签)``。 +用户可以参考 `QM7bDataset源代码 `__ +以获得 ``self._load_graph()`` 和 ``__getitem__`` 的详细信息。 + +用户还可以向类添加属性以指示一些有用的数据集信息。在 :class:`~dgl.data.QM7bDataset` 中, +用户可以添加属性 ``num_labels`` 来指示此多任务数据集中的预测任务总数: + +.. code:: + + @property + def num_labels(self): + """每个图的标签数,即预测任务数。""" + return 14 + +在编写完这些代码之后,用户可以按如下所示的方式来使用 :class:`~dgl.data.QM7bDataset`: + +.. code:: + + import dgl + import torch + + from torch.utils.data import DataLoader + + # 数据导入 + dataset = QM7bDataset() + num_labels = dataset.num_labels + + # 创建collate_fn函数 + def _collate_fn(batch): + graphs, labels = batch + g = dgl.batch(graphs) + labels = torch.tensor(labels, dtype=torch.long) + return g, labels + + # 创建 dataloaders + dataloader = DataLoader(dataset, batch_size=1, shuffle=True, collate_fn=_collate_fn) + + # 训练 + for epoch in range(100): + for g, labels in dataloader: + # 用户自己的训练代码 + pass + +训练整图分类模型的完整指南可以在 :ref:`guide-training-graph-classification` 中找到。 + +有关整图分类数据集的更多示例,用户可以参考 :ref:`guide-training-graph-classification`: + +* :ref:`gindataset` + +* :ref:`minigcdataset` + +* :ref:`qm7bdata` + +* :ref:`tudata` + +处理节点分类数据集 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +与整图分类不同,节点分类通常在单个图上进行。因此数据集的划分是在图的节点集上进行。 +DGL建议使用节点掩码来指定数据集的划分。 +本节以内置数据集 `CitationGraphDataset `__ 为例: + +.. code:: + + from dgl.data import DGLBuiltinDataset + from dgl.data.utils import _get_dgl_url, generate_mask_tensor + + class CitationGraphDataset(DGLBuiltinDataset): + _urls = { + 'cora_v2' : 'dataset/cora_v2.zip', + 'citeseer' : 'dataset/citeseer.zip', + 'pubmed' : 'dataset/pubmed.zip', + } + + def __init__(self, name, raw_dir=None, force_reload=False, verbose=True): + assert name.lower() in ['cora', 'citeseer', 'pubmed'] + if name.lower() == 'cora': + name = 'cora_v2' + url = _get_dgl_url(self._urls[name]) + super(CitationGraphDataset, self).__init__(name, + url=url, + raw_dir=raw_dir, + force_reload=force_reload, + verbose=verbose) + + def process(self): + # 跳过一些处理的代码 + # === 跳过数据处理 === + + # 构建图 + g = dgl.graph(graph) + + # 划分掩码 + g.ndata['train_mask'] = generate_mask_tensor(train_mask) + g.ndata['val_mask'] = generate_mask_tensor(val_mask) + g.ndata['test_mask'] = generate_mask_tensor(test_mask) + + # 节点的标签 + g.ndata['label'] = torch.tensor(labels) + + # 节点的特征 + g.ndata['feat'] = torch.tensor(_preprocess_features(features), + dtype=F.data_type_dict['float32']) + self._num_labels = onehot_labels.shape[1] + self._labels = labels + self._g = g + + def __getitem__(self, idx): + assert idx == 0, "这个数据集里只有一个图" + return self._g + + def __len__(self): + return 1 + +为简便起见,这里省略了 ``process()`` 中的一些代码,以突出展示用于处理节点分类数据集的关键部分:划分掩码。 +节点特征和节点的标签被存储在 ``g.ndata`` 中。详细的实现请参考 +`CitationGraphDataset源代码 `__ 。 + +请注意,这里 ``__getitem__(idx)`` 和 ``__len__()`` 的实现也发生了变化, +这是因为节点分类任务通常只用一个图。掩码在PyTorch和TensorFlow中是bool张量,在MXNet中是float张量。 + +下面中使用 :class:`dgl.data.CitationGraphDataset` 的子类 :class:`dgl.data.CiteseerGraphDataset` +来演示如何使用用于节点分类的数据集: + +.. code:: + + # 导入数据 + dataset = CiteseerGraphDataset(raw_dir='') + graph = dataset[0] + + # 获取划分的掩码 + train_mask = graph.ndata['train_mask'] + val_mask = graph.ndata['val_mask'] + test_mask = graph.ndata['test_mask'] + + # 获取节点特征 + feats = graph.ndata['feat'] + + # 获取标签 + labels = graph.ndata['label'] + +:ref:`guide-training-node-classification` 提供了训练节点分类模型的完整指南。 + +有关节点分类数据集的更多示例,用户可以参考以下内置数据集: + +* :ref:`citationdata` + +* :ref:`corafulldata` + +* :ref:`amazoncobuydata` + +* :ref:`coauthordata` + +* :ref:`karateclubdata` + +* :ref:`ppidata` + +* :ref:`redditdata` + +* :ref:`sbmdata` + +* :ref:`sstdata` + +* :ref:`rdfdata` + +处理链接预测数据集 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +链接预测数据集的处理与节点分类相似,数据集中通常只有一个图。 + +本节以内置的数据集 `KnowledgeGraphDataset `__ +为例,同时省略了详细的数据处理代码以突出展示处理链接预测数据集的关键部分: + +.. code:: + + # 创建链接预测数据集示例 + class KnowledgeGraphDataset(DGLBuiltinDataset): + def __init__(self, name, reverse=True, raw_dir=None, force_reload=False, verbose=True): + self._name = name + self.reverse = reverse + url = _get_dgl_url('dataset/') + '{}.tgz'.format(name) + super(KnowledgeGraphDataset, self).__init__(name, + url=url, + raw_dir=raw_dir, + force_reload=force_reload, + verbose=verbose) + + def process(self): + # 跳过一些处理的代码 + # === 跳过数据处理 === + + # 划分掩码 + g.edata['train_mask'] = train_mask + g.edata['val_mask'] = val_mask + g.edata['test_mask'] = test_mask + + # 边类型 + g.edata['etype'] = etype + + # 节点类型 + g.ndata['ntype'] = ntype + self._g = g + + def __getitem__(self, idx): + assert idx == 0, "这个数据集只有一个图" + return self._g + + def __len__(self): + return 1 + + +如代码所示,图的 ``edata`` 存储了划分掩码。在 +`KnowledgeGraphDataset 源代码 `__ +中可以查看完整的代码。下面使用 ``KnowledgeGraphDataset``的子类 :class:`dgl.data.FB15k237Dataset` 来做演示如何使用用于链路预测的数据集: + +.. code:: + + from dgl.data import FB15k237Dataset + + # 导入数据 + dataset = FB15k237Dataset() + graph = dataset[0] + + # 获取训练集掩码 + train_mask = graph.edata['train_mask'] + train_idx = torch.nonzero(train_mask).squeeze() + src, dst = graph.edges(train_idx) + + # 获取训练集中的边类型 + rel = graph.edata['etype'][train_idx] + +有关训练链接预测模型的完整指南,请参见 :ref:`guide-training-link-prediction`。 + +有关链接预测数据集的更多示例,请参考DGL的内置数据集: + +* :ref:`kgdata` + +* :ref:`bitcoinotcdata` diff --git a/docs/source/guide_cn/data-savenload.rst b/docs/source/guide_cn/data-savenload.rst new file mode 100644 index 000000000000..6804540caaed --- /dev/null +++ b/docs/source/guide_cn/data-savenload.rst @@ -0,0 +1,45 @@ +.. _guide_cn-data-pipeline-savenload: + +4.4 保存和加载数据 +---------------------- + +:ref:`(English Version) ` + +DGL建议用户实现保存和加载数据的函数,将处理后的数据缓存在本地磁盘中。 +这样在多数情况下可以帮用户节省大量的数据处理时间。DGL提供了4个函数让任务变得简单。 + +- :func:`dgl.save_graphs` 和 :func:`dgl.load_graphs`: 保存DGLGraph对象和标签到本地磁盘和从本地磁盘读取它们。 +- :func:`dgl.data.utils.save_info` 和 :func:`dgl.data.utils.load_info`: 将数据集的有用信息(python dict对象)保存到本地磁盘和从本地磁盘读取它们。 + +下面的示例显示了如何保存和读取图和数据集信息的列表。 + +.. code:: + + import os + from dgl import save_graphs, load_graphs + from dgl.data.utils import makedirs, save_info, load_info + + def save(self): + # 保存图和标签 + graph_path = os.path.join(self.save_path, self.mode + '_dgl_graph.bin') + save_graphs(graph_path, self.graphs, {'labels': self.labels}) + # 在Python字典里保存其他信息 + info_path = os.path.join(self.save_path, self.mode + '_info.pkl') + save_info(info_path, {'num_classes': self.num_classes}) + + def load(self): + # 从目录 `self.save_path` 里读取处理过的数据 + graph_path = os.path.join(self.save_path, self.mode + '_dgl_graph.bin') + self.graphs, label_dict = load_graphs(graph_path) + self.labels = label_dict['labels'] + info_path = os.path.join(self.save_path, self.mode + '_info.pkl') + self.num_classes = load_info(info_path)['num_classes'] + + def has_cache(self): + # 检查在 `self.save_path` 里是否有处理过的数据文件 + graph_path = os.path.join(self.save_path, self.mode + '_dgl_graph.bin') + info_path = os.path.join(self.save_path, self.mode + '_info.pkl') + return os.path.exists(graph_path) and os.path.exists(info_path) + +请注意:有些情况下不适合保存处理过的数据。例如,在内置数据集 :class:`~dgl.data.GDELTDataset` 中, +处理过的数据比较大。所以这个时候,在 ``__getitem__(idx)`` 中处理每个数据实例是更高效的方法。 diff --git a/docs/source/guide_cn/data.rst b/docs/source/guide_cn/data.rst new file mode 100644 index 000000000000..b06aaff71f06 --- /dev/null +++ b/docs/source/guide_cn/data.rst @@ -0,0 +1,31 @@ +.. _guide_cn-data-pipeline: + +第4章:图数据处理管道 +============================== + +:ref:`(English Version) ` + +DGL在 :ref:`apidata` 里实现了很多常用的图数据集。它们遵循了由 :class:`dgl.data.DGLDataset` 类定义的标准的数据处理管道。 +DGL推荐用户将图数据处理为 :class:`dgl.data.DGLDataset` 的子类。该类为导入、处理和保存图数据提供了简单而干净的解决方案。 + +本章路线图 +----------- + +本章介绍了如何为用户自己的图数据创建一个DGL数据集。以下内容说明了管道的工作方式,并展示了如何实现管道的每个组件。 + +* :ref:`guide_cn-data-pipeline-dataset` +* :ref:`guide_cn-data-pipeline-download` +* :ref:`guide_cn-data-pipeline-process` +* :ref:`guide_cn-data-pipeline-savenload` +* :ref:`guide_cn-data-pipeline-loadogb` + +.. toctree:: + :maxdepth: 1 + :hidden: + :glob: + + data-dataset + data-download + data-process + data-savenload + data-loadogb \ No newline at end of file diff --git a/docs/source/guide_cn/graph.rst b/docs/source/guide_cn/graph.rst index 12e23d982a0a..b6045acecc21 100644 --- a/docs/source/guide_cn/graph.rst +++ b/docs/source/guide_cn/graph.rst @@ -1,7 +1,7 @@ .. _guide_cn-graph: 第1章:图 -======== +============= :ref:`(English Version)` @@ -11,7 +11,7 @@ DGL通过其核心数据结构 :class:`~dgl.DGLGraph` 提供了一个以图为 本章路线图 --------- +-------------- 本章首先简要介绍了图的定义(见1.1节),然后介绍了一些 :class:`~dgl.DGLGraph` 相关的核心概念: diff --git a/docs/source/guide_cn/message-api.rst b/docs/source/guide_cn/message-api.rst new file mode 100644 index 000000000000..9db62e513c30 --- /dev/null +++ b/docs/source/guide_cn/message-api.rst @@ -0,0 +1,76 @@ +.. _guide_cn-message-passing-api: + +2.1 内置函数和消息传递API +---------------------- + +:ref:`(English Version) ` + +在DGL中,**消息函数** 接受一个参数 ``edges``,这是一个 :class:`~dgl.udf.EdgeBatch` 的实例, +在消息传递时,它被DGL在内部生成以表示一批边。 ``edges`` 有 ``src``、 ``dst`` 和 ``data`` 共3个成员属性, +分别用于访问源节点、目标节点和边的特征。 + +**聚合函数** 接受一个参数 ``nodes``,这是一个 :class:`~dgl.udf.NodeBatch` 的实例, +在消息传递时,它被DGL在内部生成以表示一批节点。 ``nodes`` 的成员属性 ``mailbox`` 可以用来访问节点收到的消息。 +一些最常见的聚合操作包括 ``sum``、``max``、``min`` 等。 + +**更新函数** 接受一个如上所述的参数 ``nodes``。此函数对 ``聚合函数`` 的聚合结果进行操作, +通常在消息传递的最后一步将其与节点的特征相结合,并将输出作为节点的新特征。 + +DGL在命名空间 ``dgl.function`` 中实现了常用的消息函数和聚合函数作为 **内置函数**。 +一般来说,DGL建议 **尽可能** 使用内置函数,因为它们经过了大量优化,并且可以自动处理维度广播。 + +如果用户的消息传递函数无法用内置函数实现,则可以实现自己的消息或聚合函数(也称为 **用户定义函数** )。 + +内置消息函数可以是一元函数或二元函数。对于一元函数,DGL支持 ``copy`` 函数。对于二元函数, +DGL现在支持 ``add``、 ``sub``、 ``mul``、 ``div``、 ``dot`` 函数。消息的内置函数的命名约定是 ``u`` 表示 ``源`` 节点, +``v`` 表示 ``目标`` 节点,``e`` 表示 ``边``。这些函数的参数是字符串,指示相应节点和边的输入和输出特征字段名。 +关于内置函数的列表,请参见 :ref:`api-built-in`。例如,要对源节点的 ``hu`` 特征和目标节点的 ``hv`` 特征求和, +然后将结果保存在边的 ``he`` 特征上,用户可以使用内置函数 ``dgl.function.u_add_v('hu', 'hv', 'he')``。 +而以下用户定义消息函数与此内置函数等价。 + +.. code:: + + def message_func(edges): + return {'he': edges.src['hu'] + edges.dst['hv']} + +DGL支持内置的聚合函数 ``sum``、 ``max``、 ``min`` 和 ``mean`` 操作。 +聚合函数通常有两个参数,它们的类型都是字符串。一个用于指定 ``mailbox`` 中的字段名,一个用于指示目标节点特征的字段名, +例如, ``dgl.function.sum('m', 'h')`` 等价于如下所示的对接收到消息求和的用户定义函数: + +.. code:: + + import torch + def reduce_func(nodes): + return {'h': torch.sum(nodes.mailbox['m'], dim=1)} + +在DGL中,也可以在不涉及消息传递的情况下,通过 :meth:`~dgl.DGLGraph.apply_edges` 单独调用逐边计算。 +:meth:`~dgl.DGLGraph.apply_edges` 的参数是一个消息函数。并且在默认情况下,这个接口将更新所有的边。例如: + +.. code:: + + import dgl.function as fn + graph.apply_edges(fn.u_add_v('el', 'er', 'e')) + +对于消息传递, :meth:`~dgl.DGLGraph.update_all` 是一个高级API。它在单个API调用里合并了消息生成、 +消息聚合和节点特征更新,这为从整体上进行系统优化提供了空间。 + +:meth:`~dgl.DGLGraph.update_all` 的参数是一个消息函数、一个聚合函数和一个更新函数。 +更新函数是一个可选择的参数,用户也可以不使用它,而是在 ``update_all`` 执行完后直接对节点特征进行操作。 +由于更新函数通常可以用纯张量操作实现,所以DGL不推荐在 ``update_all`` 中指定更新函数。例如: + +.. code:: + + def updata_all_example(graph): + # 在graph.ndata['ft']中存储结果 + graph.update_all(fn.u_mul_e('ft', 'a', 'm'), + fn.sum('m', 'ft')) + # 在update_all外调用更新函数 + final_ft = graph.ndata['ft'] * 2 + return final_ft + +此调用通过将源节点特征 ``ft`` 与边特征 ``a`` 相乘生成消息 ``m``, +然后对所有消息求和来更新节点特征 ``ft``,再将 ``ft`` 乘以2得到最终结果 ``final_ft``。 + +调用后,中间消息 ``m`` 将被清除。上述函数的数学公式为: + +.. math:: {final\_ft}_i = 2 * \sum_{j\in\mathcal{N}(i)} ({ft}_j * a_{ij}) diff --git a/docs/source/guide_cn/message-edge.rst b/docs/source/guide_cn/message-edge.rst new file mode 100644 index 000000000000..edd7e6873af7 --- /dev/null +++ b/docs/source/guide_cn/message-edge.rst @@ -0,0 +1,25 @@ +.. _guide_cn-message-passing-edge: + +2.4 在消息传递中使用边的权重 +----------------------- + +:ref:`(English Version) ` + +一类常见的图神经网络建模的做法是在消息聚合前使用边的权重, +比如在 `图注意力网络(GAT) `__ 和一些 `GCN的变种 `__ 。 +DGL的处理方法是: + +- 将权重存为边的特征。 +- 在消息函数中用边的特征与源节点的特征相乘。 + +例如: + +.. code:: + + import dgl.function as fn + + graph.edata['a'] = affinity + graph.update_all(fn.u_mul_e('ft', 'a', 'm'), + fn.sum('m', 'ft')) + +在以上代码中,affinity被用作边的权重。边权重通常是一个标量。 \ No newline at end of file diff --git a/docs/source/guide_cn/message-efficient.rst b/docs/source/guide_cn/message-efficient.rst new file mode 100644 index 000000000000..40b4a52696e7 --- /dev/null +++ b/docs/source/guide_cn/message-efficient.rst @@ -0,0 +1,63 @@ +.. _guide_cn-message-passing-efficient: + +2.2 编写高效的消息传递代码 +---------------------- + +:ref:`(English Version) ` + +DGL优化了消息传递的内存消耗和计算速度,这包括: + +- 将多个内核合并到一个内核中:这是通过使用 :meth:`~dgl.DGLGraph.update_all` 一次调用多个内置函数来实现的。(速度优化) + +- 节点和边上的并行计算:DGL抽象了逐边计算,将 :meth:`~dgl.DGLGraph.apply_edges` 作为一种广义抽样稠密-稠密矩阵乘法 + **(gSDDMM)** 运算,并实现了跨边并行计算。同样,DGL将逐节点计算 :meth:`~dgl.DGLGraph.update_all` 抽象为广义稀疏-稠密矩阵乘法(gSPMM)运算, + 并实现了跨节点并行计算。(速度优化) + +- 避免不必要的从点到边的内存拷贝:想要生成带有源节点和目标节点特征的消息,一个选项是将源节点和目标节点的特征拷贝到边上。 + 对于某些图,边的数量远远大于节点的数量。这个拷贝的代价会很大。DGL内置的消息函数通过使用条目索引对节点特征进行采集来避免这种内存拷贝。 + (内存和速度优化) + +- 避免具体化边上的特征向量:完整的消息传递过程包括消息生成、消息聚合和节点更新。 + 在调用 :meth:`~dgl.DGLGraph.update_all` 时,如果消息函数和聚合函数是内置的,则它们会被合并到一个内核中, + 从而避免存储消息对象。(内存优化) + +根据以上所述,利用这些优化的一个常见实践是通过基于内置函数的 :meth:`~dgl.DGLGraph.update_all` 来开发消息传递功能。 + +对于某些情况,比如 :class:`~dgl.nn.pytorch.conv.GATConv`,计算必须在边上保存消息, +那么用户就需要调用基于内置函数的 :meth:`~dgl.DGLGraph.apply_edges`。有时边上的消息可能是高维的,这会非常消耗内存。 +DGL建议用户尽量减少边的特征维数。 + +下面是一个如何通过对节点特征降维来减少消息维度的示例。该做法执行以下操作:拼接 ``源`` 节点和 ``目标`` 节点特征, +然后应用一个线性层,即 :math:`W\times (u || v)`。 ``源`` 节点和 ``目标`` 节点特征维数较高,而线性层输出维数较低。 +一个直截了当的实现方式如下: + +.. code:: + + import torch + import torch.nn as nn + + linear = nn.Parameter(torch.FloatTensor(size=(1, node_feat_dim * 2))) + def concat_message_function(edges): + return {'cat_feat': torch.cat([edges.src.ndata['feat'], edges.dst.ndata['feat']])} + g.apply_edges(concat_message_function) + g.edata['out'] = g.edata['cat_feat'] * linear + +建议的实现是将线性操作分成两部分,一个应用于 ``源`` 节点特征,另一个应用于 ``目标`` 节点特征。 +在最后一个阶段,在边上将以上两部分线性操作的结果相加,即执行 :math:`W_l\times u + W_r \times v`, +因为 :math:`W \times (u||v) = W_l \times u + W_r \times v`,其中 :math:`W_l` 和 :math:`W_r` 分别是矩阵 +:math:`W` 的左半部分和右半部分: + +.. code:: + + import dgl.function as fn + + linear_src = nn.Parameter(torch.FloatTensor(size=(1, node_feat_dim))) + linear_dst = nn.Parameter(torch.FloatTensor(size=(1, node_feat_dim))) + out_src = g.ndata['feat'] * linear_src + out_dst = g.ndata['feat'] * linear_dst + g.srcdata.update({'out_src': out_src}) + g.dstdata.update({'out_dst': out_dst}) + g.apply_edges(fn.u_add_v('out_src', 'out_dst', 'out')) + +以上两个实现在数学上是等价的。后一种方法效率高得多,因为不需要在边上保存feat_src和feat_dst, +从内存角度来说是高效的。另外,加法可以通过DGL的内置函数 ``u_add_v`` 进行优化,从而进一步加快计算速度并节省内存占用。 diff --git a/docs/source/guide_cn/message-heterograph.rst b/docs/source/guide_cn/message-heterograph.rst new file mode 100644 index 000000000000..517f7f1c491b --- /dev/null +++ b/docs/source/guide_cn/message-heterograph.rst @@ -0,0 +1,38 @@ +.. _guide_cn-message-passing-heterograph: + +2.5 在异构图上进行消息传递 +---------------------- + +:ref:`(English Version) ` + +异构图(参考用户指南 :ref:`1.5 异构图 ` )是包含不同类型的节点和边的图。 +不同类型的节点和边常常具有不同类型的属性。这些属性旨在刻画每一种节点和边的特征。在使用图神经网络时,根据其复杂性, +可能需要使用不同维度的表示来对不同类型的节点和边进行建模。 + +异构图上的消息传递可以分为两个部分: + +1. 对每个关系计算和聚合消息。 +2. 对每个结点聚合来自不同关系的消息。 + +在DGL中,对异构图进行消息传递的接口是 :meth:`~dgl.DGLGraph.multi_update_all`。 +:meth:`~dgl.DGLGraph.multi_update_all` 接受一个字典。这个字典的每一个键值对里,键是一种关系, +值是这种关系对应 :meth:`~dgl.DGLGraph.update_all` 的参数。 +:meth:`~dgl.DGLGraph.multi_update_all` 还接受一个字符串来表示跨类型整合函数,来指定整合不同关系聚合结果的方式。 +这个整合方式可以是 ``sum``、 ``min``、 ``max``、 ``mean`` 和 ``stack`` 中的一个。以下是一个例子: + +.. code:: + + import dgl.function as fn + + for c_etype in G.canonical_etypes: + srctype, etype, dsttype = c_etype + Wh = self.weight[etype](feat_dict[srctype]) + # 把它存在图中用来做消息传递 + G.nodes[srctype].data['Wh_%s' % etype] = Wh + # 指定每个关系的消息传递函数:(message_func, reduce_func). + # 注意结果保存在同一个目标特征“h”,说明聚合是逐类进行的。 + funcs[etype] = (fn.copy_u('Wh_%s' % etype, 'm'), fn.mean('m', 'h')) + # 将每个类型消息聚合的结果相加。 + G.multi_update_all(funcs, 'sum') + # 返回更新过的节点特征字典 + return {ntype : G.nodes[ntype].data['h'] for ntype in G.ntypes} diff --git a/docs/source/guide_cn/message-part.rst b/docs/source/guide_cn/message-part.rst new file mode 100644 index 000000000000..dbdc6c319883 --- /dev/null +++ b/docs/source/guide_cn/message-part.rst @@ -0,0 +1,17 @@ +.. _guide_cn-message-passing-part: + +2.3 在图的一部分上进行消息传递 +------------------------- + +:ref:`(English Version) ` + +如果用户只想更新图中的部分节点,可以先通过想要囊括的节点编号创建一个子图, +然后在子图上调用 :meth:`~dgl.DGLGraph.update_all` 方法。例如: + +.. code:: + + nid = [0, 2, 3, 6, 7, 9] + sg = g.subgraph(nid) + sg.update_all(message_func, reduce_func, apply_node_func) + +这是小批量训练中的常见用法。更多详细用法请参考用户指南 :ref:`第6章:在大图上的随机(批次)训练 `。 \ No newline at end of file diff --git a/docs/source/guide_cn/message.rst b/docs/source/guide_cn/message.rst new file mode 100644 index 000000000000..b99bd6c2c313 --- /dev/null +++ b/docs/source/guide_cn/message.rst @@ -0,0 +1,43 @@ +.. _guide_cn-message-passing: + +第2章:消息传递范式 +================ + +:ref:`(English Version) ` + +消息传递是实现GNN的一种通用框架和编程范式。它从聚合与更新的角度归纳总结了多种GNN模型的实现。 + +消息传递范式 +---------- + +假设节点 :math:`v` 上的的特征为 :math:`x_v\in\mathbb{R}^{d_1}`,边 :math:`({u}, {v})` 上的特征为 :math:`w_{e}\in\mathbb{R}^{d_2}`。 +**消息传递范式** 定义了以下逐节点和边上的计算: + +.. math:: \text{边上计算: } m_{e}^{(t+1)} = \phi \left( x_v^{(t)}, x_u^{(t)}, w_{e}^{(t)} \right) , ({u}, {v},{e}) \in \mathcal{E}. + +.. math:: \text{点上计算: } x_v^{(t+1)} = \psi \left(x_v^{(t)}, \rho\left(\left\lbrace m_{e}^{(t+1)} : ({u}, {v},{e}) \in \mathcal{E} \right\rbrace \right) \right). + +在上面的等式中, :math:`\phi` 是定义在每条边上的消息函数,它通过将边上特征与其两端节点的特征相结合来生成消息。 +**聚合函数** :math:`\rho` 会聚合节点接受到的消息。 **更新函数** :math:`\psi` 会结合聚合后的消息和节点本身的特征来更新节点的特征。 + +本章路线图 +-------- + +本章首先介绍了DGL的消息传递API。然后讲解了如何高效地在点和边上使用这些API。本章的最后一节解释了如何在异构图上实现消息传递。 + +* :ref:`guide_cn-message-passing-api` +* :ref:`guide_cn-message-passing-efficient` +* :ref:`guide_cn-message-passing-part` +* :ref:`guide_cn-message-passing-edge` +* :ref:`guide_cn-message-passing-heterograph` + +.. toctree:: + :maxdepth: 1 + :hidden: + :glob: + + message-api + message-efficient + message-part + message-edge + message-heterograph diff --git a/docs/source/guide_cn/nn-construction.rst b/docs/source/guide_cn/nn-construction.rst new file mode 100644 index 000000000000..0677c3d54534 --- /dev/null +++ b/docs/source/guide_cn/nn-construction.rst @@ -0,0 +1,73 @@ +.. _guide_cn-nn-construction: + +3.1 DGL NN模块的构造函数 +----------------------------- + +:ref:`(English Version) ` + +构造函数完成以下几个任务: + +1. 设置选项。 +2. 注册可学习的参数或者子模块。 +3. 初始化参数。 + +.. code:: + + import torch.nn as nn + + from dgl.utils import expand_as_pair + + class SAGEConv(nn.Module): + def __init__(self, + in_feats, + out_feats, + aggregator_type, + bias=True, + norm=None, + activation=None): + super(SAGEConv, self).__init__() + + self._in_src_feats, self._in_dst_feats = expand_as_pair(in_feats) + self._out_feats = out_feats + self._aggre_type = aggregator_type + self.norm = norm + self.activation = activation + +在构造函数中,用户首先需要设置数据的维度。对于一般的PyTorch模块,维度通常包括输入的维度、输出的维度和隐层的维度。 +对于图神经网络,输入维度可被分为源节点特征维度和目标节点特征维度。 + +除了数据维度,图神经网络的一个典型选项是聚合类型(``self._aggre_type``)。对于特定目标节点,聚合类型决定了如何聚合不同边上的信息。 +常用的聚合类型包括 ``mean``、 ``sum``、 ``max`` 和 ``min``。一些模块可能会使用更加复杂的聚合函数,比如 ``lstm``。 + +上面代码里的 ``norm`` 是用于特征归一化的可调用函数。在SAGEConv论文里,归一化可以是L2归一化: +:math:`h_v = h_v / \lVert h_v \rVert_2`。 + +.. code:: + + # 聚合类型:mean、max_pool、lstm、gcn + if aggregator_type not in ['mean', 'max_pool', 'lstm', 'gcn']: + raise KeyError('Aggregator type {} not supported.'.format(aggregator_type)) + if aggregator_type == 'max_pool': + self.fc_pool = nn.Linear(self._in_src_feats, self._in_src_feats) + if aggregator_type == 'lstm': + self.lstm = nn.LSTM(self._in_src_feats, self._in_src_feats, batch_first=True) + if aggregator_type in ['mean', 'max_pool', 'lstm']: + self.fc_self = nn.Linear(self._in_dst_feats, out_feats, bias=bias) + self.fc_neigh = nn.Linear(self._in_src_feats, out_feats, bias=bias) + self.reset_parameters() + +注册参数和子模块。在SAGEConv中,子模块根据聚合类型而有所不同。这些模块是纯PyTorch NN模块,例如 ``nn.Linear``、 ``nn.LSTM`` 等。 +构造函数的最后调用了 ``reset_parameters()`` 进行权重初始化。 + +.. code:: + + def reset_parameters(self): + """重新初始化可学习的参数""" + gain = nn.init.calculate_gain('relu') + if self._aggre_type == 'max_pool': + nn.init.xavier_uniform_(self.fc_pool.weight, gain=gain) + if self._aggre_type == 'lstm': + self.lstm.reset_parameters() + if self._aggre_type != 'gcn': + nn.init.xavier_uniform_(self.fc_self.weight, gain=gain) + nn.init.xavier_uniform_(self.fc_neigh.weight, gain=gain) diff --git a/docs/source/guide_cn/nn-forward.rst b/docs/source/guide_cn/nn-forward.rst new file mode 100644 index 000000000000..716bdb0aea03 --- /dev/null +++ b/docs/source/guide_cn/nn-forward.rst @@ -0,0 +1,138 @@ +.. _guide_cn-nn-forward: + +3.2 编写DGL NN模块的forward函数 +--------------------------------- + +:ref:`(English Version) ` + +在NN模块中, ``forward()`` 函数执行了实际的消息传递和计算。与通常以张量为参数的PyTorch NN模块相比, +DGL NN模块额外增加了1个参数 :class:`dgl.DGLGraph`。``forward()`` 函数的内容一般可以分为3项操作: + +- 检测输入图对象是否符合规范。 + +- 消息传递和聚合。 + +- 聚合后,更新特征作为输出。 + +下文展示了SAGEConv示例中的 ``forward()`` 函数。 + +输入图对象的规范检测 +~~~~~~~~~~~~~~~~~~~~~ + +.. code:: + + def forward(self, graph, feat): + with graph.local_scope(): + # 指定图类型,然后根据图类型扩展输入特征 + feat_src, feat_dst = expand_as_pair(feat, graph) + +``forward()`` 函数需要处理输入的许多极端情况,这些情况可能导致计算和消息传递中的值无效。 +比如在 :class:`~dgl.nn.pytorch.conv.GraphConv` 等conv模块中,DGL会检查输入图中是否有入度为0的节点。 +当1个节点入度为0时, ``mailbox`` 将为空,并且聚合函数的输出值全为0, +这可能会导致模型性能不佳。但是,在 :class:`~dgl.nn.pytorch.conv.SAGEConv` 模块中,被聚合的特征将会与节点的初始特征拼接起来, +``forward()`` 函数的输出不会全为0。在这种情况下,无需进行此类检验。 + +DGL NN模块可在不同类型的图输入中重复使用,包括:同构图、异构图(:ref:`guide_cn-graph-heterogeneous`)和子图区块(:ref:`guide-minibatch`)。 + +SAGEConv的数学公式如下: + +.. math:: + + + h_{\mathcal{N}(dst)}^{(l+1)} = \mathrm{aggregate} + \left(\{h_{src}^{l}, \forall src \in \mathcal{N}(dst) \}\right) + +.. math:: + + h_{dst}^{(l+1)} = \sigma \left(W \cdot \mathrm{concat} + (h_{dst}^{l}, h_{\mathcal{N}(dst)}^{l+1}) + b \right) + +.. math:: + + h_{dst}^{(l+1)} = \mathrm{norm}(h_{dst}^{l}) + +源节点特征 ``feat_src`` 和目标节点特征 ``feat_dst`` 需要根据图类型被指定。 +用于指定图类型并将 ``feat`` 扩展为 ``feat_src`` 和 ``feat_dst`` 的函数是 :meth:`~dgl.utils.expand_as_pair`。 +该函数的细节如下所示。 + +.. code:: + + def expand_as_pair(input_, g=None): + if isinstance(input_, tuple): + # 二分图的情况 + return input_ + elif g is not None and g.is_block: + # 子图块的情况 + if isinstance(input_, Mapping): + input_dst = { + k: F.narrow_row(v, 0, g.number_of_dst_nodes(k)) + for k, v in input_.items()} + else: + input_dst = F.narrow_row(input_, 0, g.number_of_dst_nodes()) + return input_, input_dst + else: + # 同构图的情况 + return input_, input_ + +对于同构图上的全图训练,源节点和目标节点相同,它们都是图中的所有节点。 + +在异构图的情况下,图可以分为几个二分图,每种关系对应一个。关系表示为 ``(src_type, edge_type, dst_dtype)``。 +当输入特征 ``feat`` 是1个元组时,图将会被视为二分图。元组中的第1个元素为源节点特征,第2个元素为目标节点特征。 + +在小批次训练中,计算应用于给定的一堆目标节点所采样的子图。子图在DGL中称为区块(``block``)。 +在区块创建的阶段,``dst nodes`` 位于节点列表的最前面。通过索引 ``[0:g.number_of_dst_nodes()]`` 可以找到 ``feat_dst``。 + +确定 ``feat_src`` 和 ``feat_dst`` 之后,以上3种图类型的计算方法是相同的。 + +消息传递和聚合 +~~~~~~~~~~~~~~~~~ + +.. code:: + + import dgl.function as fn + import torch.nn.functional as F + from dgl.utils import check_eq_shape + + if self._aggre_type == 'mean': + graph.srcdata['h'] = feat_src + graph.update_all(fn.copy_u('h', 'm'), fn.mean('m', 'neigh')) + h_neigh = graph.dstdata['neigh'] + elif self._aggre_type == 'gcn': + check_eq_shape(feat) + graph.srcdata['h'] = feat_src + graph.dstdata['h'] = feat_dst + graph.update_all(fn.copy_u('h', 'm'), fn.sum('m', 'neigh')) + # 除以入度 + degs = graph.in_degrees().to(feat_dst) + h_neigh = (graph.dstdata['neigh'] + graph.dstdata['h']) / (degs.unsqueeze(-1) + 1) + elif self._aggre_type == 'max_pool': + graph.srcdata['h'] = F.relu(self.fc_pool(feat_src)) + graph.update_all(fn.copy_u('h', 'm'), fn.max('m', 'neigh')) + h_neigh = graph.dstdata['neigh'] + else: + raise KeyError('Aggregator type {} not recognized.'.format(self._aggre_type)) + + # GraphSAGE中gcn聚合不需要fc_self + if self._aggre_type == 'gcn': + rst = self.fc_neigh(h_neigh) + else: + rst = self.fc_self(h_self) + self.fc_neigh(h_neigh) + +上面的代码执行了消息传递和聚合的计算。这部分代码会因模块而异。请注意,代码中的所有消息传递均使用 :meth:`~dgl.DGLGraph.update_all` API和 +DGL内置的消息/聚合函数来实现,以充分利用 :ref:`guide_cn-message-passing-efficient` 里所介绍的性能优化。 + +聚合后,更新特征作为输出 +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: + + # 激活函数 + if self.activation is not None: + rst = self.activation(rst) + # 归一化 + if self.norm is not None: + rst = self.norm(rst) + return rst + +``forward()`` 函数的最后一部分是在完成消息聚合后更新节点的特征。 +常见的更新操作是根据构造函数中设置的选项来应用激活函数和进行归一化。 diff --git a/docs/source/guide_cn/nn-heterograph.rst b/docs/source/guide_cn/nn-heterograph.rst new file mode 100644 index 000000000000..6eb9937962de --- /dev/null +++ b/docs/source/guide_cn/nn-heterograph.rst @@ -0,0 +1,91 @@ +.. _guide_cn-nn-heterograph: + +3.3 异构图上的GraphConv模块 +-------------------------------- + +:ref:`(English Version) ` + +DGL提供了 :class:`~dgl.nn.pytorch.HeteroGraphConv`,用于定义异构图上GNN模块。 +实现逻辑与消息传递级别的API :meth:`~dgl.DGLGraph.multi_update_all` 相同,它包括: + +- 每个关系上的DGL NN模块。 +- 聚合来自不同关系上的结果。 + +其数学定义为: + +.. math:: h_{dst}^{(l+1)} = \underset{r\in\mathcal{R}, r_{dst}=dst}{AGG} (f_r(g_r, h_{r_{src}}^l, h_{r_{dst}}^l)) + +其中 :math:`f_r` 是对应每个关系 :math:`r` 的NN模块,:math:`AGG` 是聚合函数。 + +HeteroGraphConv的实现逻辑 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: + + import torch.nn as nn + + class HeteroGraphConv(nn.Module): + def __init__(self, mods, aggregate='sum'): + super(HeteroGraphConv, self).__init__() + self.mods = nn.ModuleDict(mods) + if isinstance(aggregate, str): + # 获取聚合函数的内部函数 + self.agg_fn = get_aggregate_fn(aggregate) + else: + self.agg_fn = aggregate + +异构图的卷积操作接受一个字典类型参数 ``mods``。这个字典的键为关系名,值为作用在该关系上NN模块对象。参数 ``aggregate`` +则指定了如何聚合来自不同关系的结果。 + +.. code:: + + def forward(self, g, inputs, mod_args=None, mod_kwargs=None): + if mod_args is None: + mod_args = {} + if mod_kwargs is None: + mod_kwargs = {} + outputs = {nty : [] for nty in g.dsttypes} + +除了输入图和输入张量,``forward()`` 函数还使用2个额外的字典参数 ``mod_args`` 和 ``mod_kwargs``。 +这2个字典与 ``self.mods`` 具有相同的键,值则为对应NN模块的自定义参数。 + +``forward()`` 函数的输出结果也是一个字典类型的对象。其键为 ``nty``,其值为每个目标节点类型 ``nty`` 的输出张量的列表, +表示来自不同关系的计算结果。``HeteroGraphConv`` 会对这个列表进一步聚合,并将结果返回给用户。 + +.. code:: + + if g.is_block: + src_inputs = inputs + dst_inputs = {k: v[:g.number_of_dst_nodes(k)] for k, v in inputs.items()} + else: + src_inputs = dst_inputs = inputs + + for stype, etype, dtype in g.canonical_etypes: + rel_graph = g[stype, etype, dtype] + if rel_graph.num_edges() == 0: + continue + if stype not in src_inputs or dtype not in dst_inputs: + continue + dstdata = self.mods[etype]( + rel_graph, + (src_inputs[stype], dst_inputs[dtype]), + *mod_args.get(etype, ()), + **mod_kwargs.get(etype, {})) + outputs[dtype].append(dstdata) + +输入 ``g`` 可以是异构图或来自异构图的子图区块。和普通的NN模块一样,``forward()`` 函数需要分别处理不同的输入图类型。 + +上述代码中的for循环为处理异构图计算的主要逻辑。首先我们遍历图中所有的关系(通过调用 ``canonical_etypes``)。 +通过关系名,我们可以使用g[ ``stype, etype, dtype`` ]的语法将只包含该关系的子图( ``rel_graph`` )抽取出来。 +对于二部图,输入特征将被组织为元组 ``(src_inputs[stype], dst_inputs[dtype])``。 +接着调用用户预先注册在该关系上的NN模块,并将结果保存在outputs字典中。 + +.. code:: + + rsts = {} + for nty, alist in outputs.items(): + if len(alist) != 0: + rsts[nty] = self.agg_fn(alist, nty) + +最后,``HeteroGraphConv`` 会调用用户注册的 ``self.agg_fn`` 函数聚合来自多个关系的结果。 +读者可以在API文档中找到 :class:~dgl.nn.pytorch.HeteroGraphConv 的示例。 \ No newline at end of file diff --git a/docs/source/guide_cn/nn.rst b/docs/source/guide_cn/nn.rst new file mode 100644 index 000000000000..055f2307aa11 --- /dev/null +++ b/docs/source/guide_cn/nn.rst @@ -0,0 +1,35 @@ +.. _guide_cn-nn: + +第3章:构建图神经网络(GNN)模块 +=================================== + +:ref:`(English Version) ` + +DGL NN模块是用户构建GNN模型的基本模块。根据DGL所使用的后端深度神经网络框架, +DGL NN模块的父类取决于后端所使用的深度神经网络框架。对于PyTorch后端, +它应该继承 `PyTorch的NN模块 `__;对于MXNet后端,它应该继承 +`MXNet Gluon的NN块 `__; +对于TensorFlow后端,它应该继承 `Tensorflow的Keras层 `__。 +在DGL NN模块中,构造函数中的参数注册和前向传播函数中使用的张量操作与后端框架一样。这种方式使得DGL的代码可以无缝嵌入到后端框架的代码中。 +DGL和这些深度神经网络框架的主要差异是其独有的消息传递操作。 + +DGL已经集成了很多常用的 :ref:`apinn-pytorch-conv`、 :ref:`apinn-pytorch-dense-conv`、 +:ref:`apinn-pytorch-pooling` 和 :ref:`apinn-pytorch-util`。欢迎给DGL贡献更多的模块! + +本章将使用PyTorch作为后端,用 :class:`~dgl.nn.pytorch.conv.SAGEConv` 作为例子来介绍如何构建用户自己的DGL NN模块。 + +本章路线图 +------------ + +* :ref:`guide_cn-nn-construction` +* :ref:`guide_cn-nn-forward` +* :ref:`guide_cn-nn-heterograph` + +.. toctree:: + :maxdepth: 1 + :hidden: + :glob: + + nn-construction + nn-forward + nn-heterograph