加载图数据集

Ascend GPU CPU 数据准备

在线运行下载Notebook下载样例代码查看源文件

概述

MindSpore提供的mindspore.dataset模块可以帮助用户构建数据集对象,分批次地读取文本数据。同时,在各个数据集类中还内置了数据处理和数据分词算子,使得数据在训练过程中能够像经过pipeline管道的水一样源源不断地流向训练系统,提升数据训练效果。

此外,MindSpore还支持分布式场景数据加载,用户可以在加载数据集时指定分片数目,具体用法参见数据并行模式加载数据集

下面,本文将简要演示如何使用MindSpore加载和处理图数据。

图的概念

在介绍图数据的读取及增强之前,先介绍图的基本概念进行,有助于后续内容更好地理解。通常一个图(graph) G是由一系列的节点(vertices) V以及边(eage)E组成的,每条边都连接着图中的两个节点,用公式可表述为:G = F(V, E),简单的图如下所示。

basicGraph.png

图中包含节点V = {a, b, c, d},和边E = {(a, b), (b, c), (c, d), (d, b)},针对图中的连接关系通常需借助数学的方式进行描述,如常用的基于邻接矩阵的方式,用于描述上述图连接关系的矩阵C如下,其中a、 b、c、d对应为第1、2、 3、4个节点。

\[\begin{split}C=\begin{bmatrix} 1&1&0&0\\ 1&1&1&1\\ 0&1&1&1\\ 0&1&1&1\\ \end{bmatrix}\end{split}\]

数据集准备环节

  1. 数据集介绍:常用的图数据集包含Cora、Citeseer、PubMed等,在本文中我们基于Cora数据集进行介绍。

  • 原始数据集可以从ucsc网站进行下载,本文采用在github提供的预处理后的版本[1]

  • Cora数据集主体部分(cora.content)包含2708条样本,每条样本描述1篇科学论文的信息,论文都属于7个类别中的一个。每条样本数据包含三部分,依次为论文编号、论文的词向量(一个1433位的二进制)、论文的类别;引用数据集部分(cora.cites)包含5429行,每行包含两个论文编号,表示第二篇论文对第一篇论文进行了引用。

  1. 数据集下载:下载预处理后的cora数据集目录如下所示。

.
└── cora
    ├── ind.cora.allx
    ├── ind.cora.ally
    ├── ind.cora.graph
    ├── ind.cora.test.index
    ├── ind.cora.tx
    ├── ind.cora.ty
    ├── ind.cora.x
    ├── ind.cora.y
    ├── trans.cora.graph
    ├── trans.cora.tx
    ├── trans.cora.ty
    ├── trans.cora.x
    └── trans.cora.y

以下示例代码将cora数据集下载并解压到指定位置。

[2]:
!mkdir -p ./cora
!git clone https://github.com/kimiyoung/planetoid
!cp planetoid/data/*.cora.* ./cora
!rm -rf planetoid
Cloning into 'planetoid'...
remote: Enumerating objects: 86, done.
remote: Total 86 (delta 0), reused 0 (delta 0), pack-reused 86
Unpacking objects: 100% (86/86), done.
  1. 数据集格式转换:将数据集转换为MindRecord格式,可借助models仓库提供的转换脚本进行转换,生成的MindRecord文件在./cora_mindrecord路径下。

[3]:
!git clone https://gitee.com/mindspore/models.git
SRC_PATH = "./cora"
MINDRECORD_PATH = "./cora_mindrecord"

!rm -rf $MINDRECORD_PATH
!mkdir $MINDRECORD_PATH

!python models/utils/graph_to_mindrecord/writer.py --mindrecord_script cora --mindrecord_file "$MINDRECORD_PATH/cora_mr" --mindrecord_partitions 1 --mindrecord_header_size_by_bit 18 --mindrecord_page_size_by_bit 20 --graph_api_args "$SRC_PATH"
Cloning into 'models'...
remote: Enumerating objects: 3475, done.
remote: Counting objects: 100% (3475/3475), done.
remote: Compressing objects: 100% (1276/1276), done.
remote: Total 16325 (delta 2612), reused 2429 (delta 2195), pack-reused 12850
Receiving objects: 100% (16325/16325), 79.56 MiB | 6.66 MiB/s, done.
Resolving deltas: 100% (9993/9993), done.
Checking out files: 100% (7152/7152), done.
Namespace(graph_api_args='./cora', mindrecord_file='./cora_mindrecord/cora_mr', mindrecord_header_size_by_bit=18, mindrecord_page_size_by_bit=20, mindrecord_partitions=1, mindrecord_script='cora', mindrecord_workers=8, num_edge_tasks=1, num_node_tasks=1)
Init writer  ...
exec task 0, parallel: False ...
Node task is 0
transformed 512 record...
transformed 1024 record...
transformed 1536 record...
transformed 2048 record...
transformed 2560 record...
Processed 2708 lines for nodes.
transformed 2708 record...
exec task 0, parallel: False ...
Edge task is 0
transformed 512 record...
transformed 1024 record...
transformed 1536 record...
transformed 2048 record...
transformed 2560 record...
transformed 3072 record...
transformed 3584 record...
transformed 4096 record...
transformed 4608 record...
transformed 5120 record...
transformed 5632 record...
transformed 6144 record...
transformed 6656 record...
transformed 7168 record...
transformed 7680 record...
transformed 8192 record...
transformed 8704 record...
transformed 9216 record...
transformed 9728 record...
transformed 10240 record...
transformed 10752 record...
Processed 10858 lines for edges.
transformed 10858 record...
--------------------------------------------
END. Total time: 4.108929634094238
--------------------------------------------

加载数据集

MindSpore目前支持加载文本领域常用的经典数据集和多种数据存储格式下的数据集,用户也可以通过构建自定义数据集类实现自定义方式的数据加载。各种数据集的详细加载方法,可参考编程指南中数据集加载章节。

下面演示使用MindSpore.dataset模块中的MindDataset类加载上述已转换成mindrecord格式的cora数据集。

  1. 配置数据集目录,创建数据集对象。

[4]:
import mindspore.dataset as ds
import numpy as np

data_file = "./cora_mindrecord/cora_mr"
dataset = ds.GraphData(data_file)
  1. 访问对应的接口,获取图信息及特性、标签内容。

[5]:
# 查看图中结构信息
graph = dataset.graph_info()
print("graph info:", graph)

# 获取所有的节点信息
nodes = dataset.get_all_nodes(0)
nodes_list = nodes.tolist()
print("node shape:", len(nodes_list))

# 获取特征和标签信息,总共2708条数据
# 每条数据中特征信息是用于描述论文i,长度为1433的二进制表示,标签信息指的是论文所属的种类
raw_tensor = dataset.get_node_feature(nodes_list, [1, 2])
features, labels = raw_tensor[0], raw_tensor[1]

print("features shape:", features.shape)
print("labels shape:", labels.shape)
print("labels:", labels)
graph info: {'node_type': [0], 'edge_type': [0], 'node_num': {0: 2708}, 'edge_num': {0: 10858}, 'node_feature_type': [1, 2], 'edge_feature_type': []}
node shape: 2708
features shape: (2708, 1433)
labels shape: (2708,)
labels: [3 4 4 ... 3 3 3]

数据处理

MindSpore目前支持的数据处理算子及其详细使用方法,可参考编程指南中数据处理章节。

下面演示构建pipeline,对节点进行采样等操作。

  1. 获取节点的邻居节点,构造邻接矩阵。

[6]:
neighbor = dataset.get_all_neighbors(nodes_list, 0)
# neighbor的第一列是node_id,第二列到最后一列存储的是第一列的邻居节点,如果不存在这么多,则用-1补齐。
print("neighbor:\n", neighbor)
neighbor:
 [[   0  633 1862 ...   -1   -1   -1]
 [   1    2  652 ...   -1   -1   -1]
 [   2 1986  332 ...   -1   -1   -1]
 ...
 [2705  287   -1 ...   -1   -1   -1]
 [2706  165 2707 ...   -1   -1   -1]
 [2707  598  165 ...   -1   -1   -1]]
  1. 依据节点的邻居节点信息,构造邻接矩阵。

[7]:
nodes_num = labels.shape[0]
node_map = {node_id: index for index, node_id in enumerate(nodes_list)}
adj = np.zeros([nodes_num, nodes_num], dtype=np.float32)

for index, value in np.ndenumerate(neighbor):
    # neighbor的第一列是node_id,第二列到最后一列存储的是第一列的邻居节点,如果不存在这么多,则用-1补齐。
    if value >= 0 and index[1] > 0:
        adj[node_map[neighbor[index[0], 0]], node_map[value]] = 1

print("adj:\n", adj)
adj:
 [[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 1. ... 0. 0. 0.]
 [0. 1. 0. ... 0. 0. 0.]
 ...
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 1.]
 [0. 0. 0. ... 0. 1. 0.]]
  1. 节点采样,支持常见的多次跳跃采样与随机游走采样方法等。

  • 多跳邻接点采样如(a)图所示,当次采样的节点将作为下次采样的起始点;随机游走方式如(b)图所示,随机选择一条路径依次遍历相邻的节点,对应图中则选择了从Vi到Vj的游走路径。

graphNodeSample.png

[8]:
# 基于多次跳跃进行节点采样
neighbor = dataset.get_sampled_neighbors(nodes_list[0:21], [2], [0])
print("neighbor:\n", neighbor)

# 基于随机游走进行节点采样
meta_path = [0]
walks = dataset.random_walk(nodes_list[0:21], meta_path)
print("walks:\n", walks)
neighbor:
 [[   0  633 2582]
 [   1  654    2]
 [   2    1 1986]
 [   3 2544 2544]
 [   4 1256 2175]
 [   5 1629 2546]
 [   6 1042 1602]
 [   7  208  208]
 [   8 1996  269]
 [   9  723 2614]
 [  10 2545  476]
 [  11 1839 1655]
 [  12 2661 1001]
 [  13 1810 1701]
 [  14 2034 2075]
 [  15 1093 1090]
 [  16 2642 2444]
 [  17 1316  927]
 [  18 2082 2145]
 [  19 1939 1939]
 [  20 2269 2374]]
walks:
 [[   0 2582]
 [   1    2]
 [   2 1454]
 [   3 2544]
 [   4 1016]
 [   5 1629]
 [   6 1042]
 [   7  208]
 [   8 1996]
 [   9  723]
 [  10  476]
 [  11 1839]
 [  12 1318]
 [  13 1701]
 [  14  158]
 [  15 1093]
 [  16 2642]
 [  17 1315]
 [  18 1560]
 [  19 1939]
 [  20 2374]]
  1. 通过节点获取边/通过边获取节点。

[9]:
# 通过边获取节点
part_edges = dataset.get_all_edges(0)[:10]
nodes = dataset.get_nodes_from_edges(part_edges)
print("part edges:", part_edges)
print("nodes:", nodes)

# 通过节点获取边
# nodes_pair_list = [(0, 1), (1, 2), (1, 3), (1, 4)]
# edges = dataset.get_edges_from_nodes(nodes_pair_list)
# print("edges:", edges)
part edges: [0 1 2 3 4 5 6 7 8 9]
nodes: [[   0  633]
 [   0 1862]
 [   0 2582]
 [   1    2]
 [   1  652]
 [   1  654]
 [   2 1986]
 [   2  332]
 [   2 1666]
 [   2    1]]

参考文献

[1] Yang Z, Cohen W, Salakhudinov R. Revisiting semi-supervised learning with graph embeddings.