DGL 0.5ユーザガイド : 4 章 グラフ・データパイプライン (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 09/20/2020 (0.5.1)
* 本ページは、DGL の以下のドキュメントを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
ユーザガイド : 4 章 グラフ・データパイプライン
DGL は dgl.data で多くの一般的に利用されるグラフ・データセットを実装しています。それらはクラス dgl.data.DGLDataset で定義された標準パイプラインに従っています。グラフデータを dgl.data.DGLDataset サブクラス内に処理することを強く勧めます、何故ならばパイプラインはグラフデータをロード、処理してセーブするための単純でクリーンな解を提供するからです。
この章は私達自身のグラフデータのために DGL-データセットをどのように作成するかを紹介します。以下の内容はパイプラインがどのように動作するかを説明し、そしてその各コンポーネントをどのように実装するかを示します。
DGLDataset クラス
dgl.data.DGLDataset は dgl.data で定義されたグラフデータセットを処理し、ロードしてセーブするための基底クラスです。それはグラフデータを処理するために基本的なパイプラインを実装します。下のフローチャートはパイプラインがどのように動作するかを示します。
遠隔サーバかローカルディスクにあるグラフ・データセットを処理するため、dgl.data.DGLDataset から継承したクラス、例えば MyDataset を定義します。MyDataset のテンプレートは次のようなものです。
from dgl.data import DGLDataset class MyDataset(DGLDataset): """ Template for customizing graph datasets in DGL. Parameters ---------- url : str URL to download the raw dataset raw_dir : str Specifying the directory that will store the downloaded data or the directory that already stores the input data. Default: ~/.dgl/ save_dir : str Directory to save the processed dataset. Default: the value of `raw_dir` force_reload : bool Whether to reload the dataset. Default: False verbose : bool Whether to print out progress information """ 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): # download raw data to local disk pass def process(self): # process raw data to graphs, labels, splitting masks pass def __getitem__(self, idx): # get one example by index pass def __len__(self): # number of data examples pass def save(self): # save processed data to directory `self.save_path` pass def load(self): # load processed data from directory `self.save_path` pass def has_cache(self): # check whether there are processed data in `self.save_path` pass
dgl.data.DGLDataset クラスは抽象関数 process(), __getitem__(idx) と __len__() を持ちます、これらはサブクラスで実装されなければなりません。しかしセーブとロードを実装することも勧めます、何故ならばそれらは巨大なデータセットを処理するための多大な時間をセーブできるからです、そしてそれを容易にする幾つかの API があります (Save and load data 参照)。
dgl.data.DGLDataset の目的はグラフデータをロードするための標準的で便利な方法を提供することです。グラフ、特徴、ラベル、マスクとクラス数、ラベル数等のようなデータセットについての基本的な情報をストアできます。サンプリング、分割や特徴正規化のような演算は dgl.data.DGLDataset の外側で成されます。
この章の残りはパイプラインの関数を実装するためのベストプラクティスを示します。
raw データをダウンロードする (オプション)
データセットが既にローカルディスクにあるならば、それがディレクトリ raw_dir にあることを確実にしてください。データをダウンロードして正しいディレクトリに移動する手間なしにコードをどこでも実行することを望むのであれば、関数 download() を実装することにより自動的にそれを成すことができます。
データセットが zip ファイルであれば、MyDataset を dgl.data.DGLBuiltinDataset クラスから継承させます、これは zip file 展開を処理します。さもなければ、dgl.data.QM7bDataset でのように download() を実装します :
import os from dgl.data.utils import download def download(self): # path to store the file file_path = os.path.join(self.raw_dir, self.name + '.mat') # download file download(self.url, path=file_path)
上のコードは .mat ファイルをディレクトリ self.raw_dir にダウンロードします。ファイルが .gz, .tar, .tar.gz か .tgz file であれば、展開するために dgl.data.utils.extract_archive() 関数を使用します。次のコードは dgl.data.BitcoinOTCDataset で .gz ファイルをどのようにダウンロードするかを示します :
from dgl.data.utils import download, extract_archive def download(self): # path to store the file # make sure to use the same suffix as the original file name's gz_file_path = os.path.join(self.raw_dir, self.name + '.csv.gz') # download file download(self.url, path=gz_file_path) # check 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')) # extract file to directory `self.name` under `self.raw_dir` self._extract_gz(gz_file_path, self.raw_path)
上のコードはファイルを self.raw_dir 下のディレクトリ self.name に展開します。クラスが zip ファイルを扱うために dgl.data.DGLBuiltinDataset から継承しているのであれば、それはファイルをディレクトリ self.name にまた展開します。
オプションで、上のサンプルが行なっているようにダウンロードされたファイルの SHA-1 文字列を確認できます、作者が遠隔サーバのファイルをいつの日にか変更した場合に。
データを処理する
関数 process() でデータ処理コードを実装します、そして raw データが既に self.raw_dir にあることを仮定します。グラフ上の機械学習では典型的には 3 つのタイプのタスクがあります : グラフ分類、ノード分類、そして リンク予測 です。これらのタスクに関連するデータセットをどのように処理するかを示します。
ここではグラフ、特徴とマスクを処理する標準的な方法にフォーカスします。サンプルとして組込みデータセットを使用してファイルからグラフを構築するための実装はスキップしますが、詳細な実装へのリンクを追加します。外部ソースからどのようにグラフを構築するかの完全なガイドを見るには 1.4 外部ソースからグラフを作成する を参照してください。
グラフ分類データセットを処理する
グラフ分類データセットは典型的な機械学習タスクの大半のデータセットとと殆ど同じで、そこではミニバッチ訓練が利用されます。そして raw データを dgl.DGLGraph オブジェクトのリストとラベル tensor のリストに処理します。加えて、raw データが幾つかのファイルに分割されている場合、データの特定のパートをロードするためにパラメータ split を追加できます。
サンプルとして dgl.data.QM7bDataset を取ります :
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' # process data to a list of graphs and a list of labels self.graphs, self.label = self._load_graph(mat_path) def __getitem__(self, idx): """ Get graph and label by index Parameters ---------- idx : int Item index Returns ------- (dgl.DGLGraph, Tensor) """ return self.graphs[idx], self.label[idx] def __len__(self): """Number of graphs in the dataset""" return len(self.graphs)
process() では、raw データはグラフのリストとラベルのリストに処理されます。反復のために __getitem__(idx) と __len__() を実装しなければなりません。__getitem__(idx) は上のようにタプル (graph, label) を返すようにすることを勧めます。self._load_graph() and __getitem__ の詳細については QM7bDataset ソースコード を確認してください。
データセットの幾つかの有用な情報を示すためにクラスにプロパティを追加することもできます。dgl.data.QM7bDataset では、このマルチタスク・データセットで予測タスクの総数示すためにプロパティ num_labels を追加できます :
@property def num_labels(self): """Number of labels for each graph, i.e. number of prediction tasks.""" return 14
これら総てのコーディングの後、最後に次のように dgl.data.QM7bDataset を使用できます :
from torch.utils.data import DataLoader # load data dataset = QM7bDataset() num_labels = dataset.num_labels # create collate_fn def _collate_fn(batch): graphs, labels = batch g = dgl.batch(graphs) labels = torch.tensor(labels, dtype=torch.long) return g, labels # create dataloaders dataloader = DataLoader(dataset, batch_size=1, shuffle=True, collate_fn=_collate_fn) # training for epoch in range(100): for g, labels in dataloader: # your training code here pass
グラフ分類モデルを訓練するための完全なガイドは 5.4 Graph Classification で見つけられます。
グラフ分類データセットのより多くのサンプルについては、組込みグラフ分類データセットを参照してください :
ノード分類データセットを処理する
グラフ分類とは異なり、ノード分類は典型的には単一グラフ上です。そのようなものとして、データセットの分割はグラフのノード上です。分割を指定するためにノードマスクを使用することを勧めます。サンプルとして組込みデータセット CitationGraphDataset を使用します :
import dgl from dgl.data import DGLBuiltinDataset 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): # Skip some processing code # === data processing skipped === # build graph g = dgl.graph(graph) # splitting masks 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) # node labels g.ndata['label'] = F.tensor(labels) # node features g.ndata['feat'] = F.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, "This dataset has only one graph" return self._g def __len__(self): return 1
簡潔さのため、ノード分類データセットを処理するための主要パートをハイライトするために process() のあるコードはスキップします : マスク、ノード特徴とノードラベルの分割は g.ndata にストアされます。詳細な実装については、CitationGraphDataset ソースコード を参照してください。
__getitem__(idx) と __len__() の実装もまた変えられたことに気付いてください、ノード分類タスクのためにはしばしば一つのグラフだけがあるためです。マスクは PyTorch と TensorFlow では bool tensor で、MXNet では float tensor です。
その使用方法を示すため、CitationGraphDataset, dgl.data.CiteseerGraphDataset のサブクラスを使用します。
# load data dataset = CiteseerGraphDataset(raw_dir='') graph = dataset[0] # get split masks train_mask = graph.ndata['train_mask'] val_mask = graph.ndata['val_mask'] test_mask = graph.ndata['test_mask'] # get node features feats = graph.ndata['feat'] # get labels labels = graph.ndata['label']
ノード分類モデルを訓練するための完全なガイドは 5.1 Node Classification/Regression で見つけられます。
ノード分類データセットのより多くのサンプルについては、組込みデータセットを参照してください :
- Citation ネットワーク・データセット
- CoraFull データセット
- Amazon Co-Purchase データセット
- Coauthor データセット
- Karate クラブ・データセット
- Protein-Protein Interaction データセット
- Reddit データセット
- Symmetric Stochastic ブロックモデル混合データセット
- Stanford センチメント treebank データセット
- RDF データセット
リンク予測データセットのためのデータセットを処理する
リンク予測データセットの処理はノード分類のそれに類似していて、データセットにしばしば一つのグラフがあります。
サンプルとして組込みデータセット KnowledgeGraphDataset を使用して、そしてまたリンク予測データセットを処理するために主要パートをハイライトするために詳細なデータ処理コードはスキップします :
# Example for creating Link Prediction datasets 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): # Skip some processing code # === data processing skipped === # splitting mask g.edata['train_mask'] = train_mask g.edata['val_mask'] = val_mask g.edata['test_mask'] = test_mask # edge type g.edata['etype'] = etype # node type g.ndata['ntype'] = ntype self._g = g def __getitem__(self, idx): assert idx == 0, "This dataset has only one graph" return self._g def __len__(self): return 1
コードで示されるように、splitting マスクをグラフの edata フィールドに追加します。完全なコードを見るためには KnowledgeGraphDataset ソースコード を確認してください。その使用方法を示すために KnowledgeGraphDataset, dgl.data.FB15k237Dataset のサブクラスを使用します :
import torch # load data dataset = FB15k237Dataset() graph = dataset[0] # get training mask train_mask = graph.edata['train_mask'] train_idx = torch.nonzero(train_mask).squeeze() src, dst = graph.edges(train_idx) # get edge types in training set rel = graph.edata['etype'][train_idx]
リンク予測モデルを訓練するための完全なガイドは 5.3 リンク予測 で見つけられます。
リンク予測データセットのより多くのサンプルについては、組込みデータセットを参照してください :
データをセーブしてロードする
処理されたデータをローカルディスクにキャッシュするためにセーブとロード関数を実装することを勧めます。これは殆どの場合多くのデータ処理時間を節約します。物事を単純にするため 4 つの関数を提供します :
- dgl.save_graphs() と dgl.load_graphs() : DGLGraph オブジェクトとラベルをローカルディスクにセーブ / からロードする。
- dgl.data.utils.save_info() と dgl.data.utils.load_info() : データセットの有用な情報 (python dict オブジェクト) をローカルディスクにセーブ / からロードする。
次のサンプルはグラフのリストとデータセット情報をどのようにセーブしてロードするかを示します。
import os from dgl import save_graphs, load_graphs from dgl.data.utils import makedirs, save_info, load_info def save(self): # save graphs and labels graph_path = os.path.join(self.save_path, self.mode + '_dgl_graph.bin') save_graphs(graph_path, self.graphs, {'labels': self.labels}) # save other information in python dict info_path = os.path.join(self.save_path, self.mode + '_info.pkl') save_info(info_path, {'num_classes': self.num_classes}) def load(self): # load processed data from directory `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): # check whether there are processed data in `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)
処理されたデータをセーブするに適さないケースがあることに注意してください。例えば、組込みデータセット dgl.data.GDELTDataset では、処理されたデータは非常に巨大ですので、__getitem__(idx) で各データサンプルを処理することがより効果的です。
ogb パッケージを使用して OGB データセットをロードする
Open グラフ・ベンチマーク (OGB) はベンチマーク・データセットのコレクションです。公式 OGB パッケージ ogb は OGB データセットを dgl.data.DGLGraph オブジェクトにダウンロードして処理するための API を提供します。ここではそれらの基本的な使用方法を紹介します。
最初に pip を使用して ogb パッケージをインストールします :
pip install ogb
次のコードはグラフ特性 (= property) 予測タスクのためのデータセットをどのようにロードするかを示します。
# Load Graph Property Prediction datasets in OGB import dgl import torch from ogb.graphproppred import DglGraphPropPredDataset from torch.utils.data import DataLoader def _collate_fn(batch): # batch is a list of tuple (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 # load dataset 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)
グラフ特性予測データセットをロードすることも同様ですが、この種類のデータセットには一つのグラフオブジェクトだけがあることに注意してください。
# Load Node Property Prediction datasets in OGB 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 g, labels = dataset[0] # get split labels train_label = dataset.labels[split_idx['train']] valid_label = dataset.labels[split_idx['valid']] test_label = dataset.labels[split_idx['test']]
リンク特性予測データセットもまたデータセット毎に一つのグラフを含みます :
# Load Link Property Prediction datasets in OGB 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())
以上