Datawhale组队学习-图神经网络(一)

Datawhale组队学习-图神经网络(一)

Taks 01

学习书籍、工具及软件

  • 本次学习资料为Datawhale社区在Github上的开源项目官方文档
  • 电脑配置为Macbook m1,操作系统为MacOS 11.4 Big Sur。所用编程环境为Anaconda 下的 Jupyter Notebook,python 3.8.8。

学习图神经网络的原因

在过去的深度学习应用中,我们接触的数据形式主要是这四种:矩阵、张量、序列(sequence)和时间序列(time series)。然而来自现实世界应用的数据更多地是图的结构,如社交网络、交通网络、蛋白质与蛋白质相互作用网络、知识图谱和大脑网络等。图提供了一种通用的数据表示方法,众多其他类型的数据也可以转化为图的形式。此外,大量的现实世界的问题可以作为图上的一组小的计算任务来解决。推断节点属性、检测异常节点(如垃圾邮件发送者)、识别与疾病相关的基因、向病人推荐药物等,都可以概括为节点分类问题。推荐、药物副作用预测、药物与目标的相互作用识别和知识图谱的完成(knowledge graph completion)等,本质上都是边预测问题

同一图的节点存在连接关系,这表明节点不是独立的。然而,传统的机器学习技术假设样本是独立且同分布的,因此传统机器学习方法不适用于图计算任务。图机器学习研究如何构建节点表征,节点表征要求同时包含节点自身的信息和节点邻接的信息,从而我们可以在节点表征上应用传统的分类技术实现节点分类。图机器学习成功的关键在于如何为节点构建表征。深度学习已经被证明在表征学习中具有强大的能力,它大大推动了计算机视觉、语音识别和自然语言处理等各个领域的发展。因此,将深度学习与图连接起来,利用神经网络来学习节点表征,将带来前所未有的机会。

然而,如何将神经网络应用于图,这一问题面临着巨大的挑战。首先,传统的深度学习是为规则且结构化的数据设计的,图像、文本、语音和时间序列等都是规则且结构化的数据。但图是不规则的,节点是无序的,节点可以有不同的邻居节点。其次,规则数据的结构信息是简单的,而图的结构信息是复杂的,特别是在考虑到各种类型的复杂图,它们的节点和边可以关联丰富的信息,这些丰富的信息无法被传统的深度学习方法捕获。

图深度学习是一个新兴的研究领域,它将深度学习技术与图数据连接起来,推动了现实中的图预测应用的发展。然而,此研究领域也面临着前所未有的挑战。

注:以上内容整理自“Deep Learning on Graphs: An Introduction”!!!

PyG 环境配置

1.安装Anaconda

因为本人已有Anaconda环境,这里不再赘述。

2.创建新环境

conda create -n pytorch python=3.8.8
#在shell中输入python查看到自己python版本

激活环境
conda activate pytorch
修改镜像源

因为Anaconda的默认源下载速度缓慢甚至导致报错,需要换成国内源,这里我换到了清华源,国内其它源也可:

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
conda config --set show_channel_urls yes

3.安装PyTorch和PyG

安装PyTorch:

首先进入官网找到自己对应的PyTorch安装命令:

conda install pytorch torchvision torchaudio -c pytorch

检查是否安装成功

安装PyG:

本次电脑系统和配置为MacOS m1芯片,直接安装CPU版本:

pip install torch-scatter -f https://pytorch-geometric.com/whl/torch-1.8.0+cpu.html
pip install torch-sparse -f https://pytorch-geometric.com/whl/torch-1.8.0+cpu.html
pip install torch-cluster -f https://pytorch-geometric.com/whl/torch-1.8.0+cpu.html
pip install torch-spline-conv -f https://pytorch-geometric.com/whl/torch-1.8.0+cpu.html
pip install torch-geometric

其他版本的安装方法以及安装过程中出现的大部分问题的解决方案可以在Installation of of PyTorch Geometric 页面找到。

至此,本次学习所需环境全部安装完毕。

一、简单图论

学习资料来源于DataWhale,这里列出我在学习中感到重要的,容易混淆和困惑的知识点:

  • 在图的计算任务中,节点(node)一定含有信息(至少含有节点的度的信息),边(edge)可能含有信息。

  • 因为定义,无向图的邻接矩阵是对称的。我们默认无权图的各条边权重为1,边不存在则为0,所以无权图的矩阵元素只有0和1.

  • 对于有向有权图,结点 v i v_i vi的出度(out degree)等于从 v i v_i vi出发的边的权重之和,结点 v i v_i vi的入度(in degree)等于从连向 v i v_i vi的边的权重之和。结点 v i v_i vi的度记为 d ( v i ) d(v_i) d(vi),入度记为 d i n ( v i ) d_{in}(v_i) din(vi),出度记为 d o u t ( v i ) d_{out}(v_i) dout(vi)

  • 无向图,无权图和无向无权图都为有向有权图的特殊情况。无向图的结点的出度与入度相等。有权图各边的权重为 1 1 1,那么结点 v i v_i vi的出度(out degree)等于从 v i v_i vi出发的边的数量,结点 v i v_i vi的入度(in degree)等于从连向 v i v_i vi的边的数量。

  • 领接节点:

    • 结点 v i v_i vi的邻接结点为与结点 v i v_i vi直接相连的结点,其被记为** N ( v i ) \mathcal{N(v_i)} N(vi)**。
    • **结点 v i v_i vi k k k跳远的邻接节点(neighbors with k k k-hop)**指的是到结点 v i v_i vi要走 k k k步的节点(一个节点的 2 2 2跳远的邻接节点包含了自身)。
  • 有一图,其邻接矩阵为 L \mathbf{L} L, L n \mathbf{L}^{n} Ln为邻接矩阵的 n n n次方,那么 L n [ i , j ] \mathbf{L}^{n}[i,j] Ln[i,j]等于从结点 v i v_i vi到结点 v j v_j vj的长度为 n n n的行走的个数。

    这个概念当时我难以理解,查阅资料后在此补充我的理解:

    我们需要从数学的计算角度去考虑此定义:

    我们先构造一个图:

    该图的邻接矩阵 L = ( 0 1 0 1 1 0 1 1 0 1 0 1 1 1 1 0 ) \mathbf{L}=\left(\begin{array}{lllll} 0 & 1 & 0 & 1 \\ 1 & 0 & 1 & 1 \\ 0 & 1 & 0 & 1 \\ 1 & 1 & 1 & 0 \\ \end{array}\right) L=0101101101011110,则 L 2 = A = ( 2 1 2 1 1 3 1 2 2 1 2 1 1 2 1 3 ) \mathbf{L^2}=\bf A=\left(\begin{array}{lllll} 2 & 1 & 2 & 1 \\ 1 & 3 & 1 & 2 \\ 2 & 1 & 2 & 1 \\ 1 & 2 & 1 & 3 \\ \end{array}\right) L2=A=2121131221211213,我们对B的第一行进行展开:

    a 11 = a 11 ∗ a 11 + a 12 ∗ a 21 + a 13 ∗ a 31 + a 14 ∗ a 41 = 2 a 12 = a 11 ∗ a 12 + a 12 ∗ a 22 + a 13 ∗ a 32 + a 12 ∗ a 42 = 1 a 13 = a 11 ∗ a 13 + a 12 ∗ a 23 + a 13 ∗ a 33 + a 14 ∗ a 43 = 2 a 14 = a 11 ∗ a 14 + a 12 ∗ a 24 + a 13 ∗ a 34 + a 14 ∗ a 44 = 1 a_{11} = a_{11}*a_{11} + a_{12}*a_{21} + a_{13}*a_{31} + a_{14}*a_{41} = 2\\ a_{12} = a_{11}*a_{12} + a_{12}*a_{22} + a_{13}*a_{32} + a_{12}*a_{42} = 1\\ a_{13} = a_{11}*a_{13} + a_{12}*a_{23} + a_{13}*a_{33} + a_{14}*a_{43} = 2\\ a_{14} = a_{11}*a_{14} + a_{12}*a_{24} + a_{13}*a_{34} + a_{14}*a_{44} = 1 a11=a11a11+a12a21+a13a31+a14a41=2a12=a11a12+a12a22+a13a32+a12a42=1a13=a11a13+a12a23+a13a33+a14a43=2a14=a11a14+a12a24+a13a34+a14a44=1

    不难看出,因为矩阵乘法的缘故,两个相乘时,第一个的纵坐标等于第二个的横坐标。例如 a 12 ∗ a 21 a_{12}*a_{21} a12a21就相当于从1“走”到2,再从2“走”到1,并且只有当两者都为1,即存在这两条路径的时候这个乘积才会为1,那么就表示从1出发到达1路径长度为2的路径+1。其实矩阵 L L L的含义可以这样解释, l i j l_{ij} lij表示的是,从点i出发走一步到点j有多少条路径,这时 l i j l_{ij} lij要么为1,要么为0(意为没有边)。而乘上一个矩阵 L L L就相当于步数+1。现在我们来分析 A A A这个矩阵的含义, a i j a_{ij} aij表示的是,从点i出发走2步到达点j有多少条路径。

    我们设 L 3 = B \bf L^3 = \bf B L3=B,则:
    b 11 = a 11 ∗ l 11 + a 12 ∗ l 21 + a 13 ∗ l 31 + a 14 ∗ l 41 = 2 ∗ 0 + 1 ∗ 1 + 2 ∗ 0 + 1 ∗ 1 = 2 b 12 = a 11 ∗ l 12 + a 12 ∗ l 22 + a 13 ∗ l 32 + a 14 ∗ l 42 = 2 ∗ 1 + 1 ∗ 0 + 2 ∗ 1 + 1 ∗ 1 = 5 b_{11} = a_{11}*l_{11} + a_{12}*l_{21} + a_{13}*l_{31} + a_{14}*l_{41} = 2*0 + 1*1 + 2*0 +1*1 = 2\\ b_{12} = a_{11}*l_{12} + a_{12}*l_{22} + a_{13}*l_{32} + a_{14}*l_{42} = 2*1 + 1*0 + 2*1 + 1*1 = 5 b11=a11l11+a12l21+a13l31+a14l41=20+11+20+11=2b12=a11l12+a12l22+a13l32+a14l42=21+10+21+11=5
    这个则为三步的数量。以此类推,若 G = A n \bf G=\bf A^n G=An g i j g_{ij} gij表示的是从i出发走到点j走n步,有多少种走法。

  • 直径(diameter):给定一个连通图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其直径为其所有节点对之间的最短路径的最大值,形式化定义为

    diameter ⁡ ( G ) = max ⁡ v s , v t ∈ V min ⁡ p ∈ P s t ∣ p ∣ \operatorname{diameter}(\mathcal{G})=\max _{v_{s}, v_{t} \in \mathcal{V}} \min _{p \in \mathcal{P}_{s t}}|p| diameter(G)=vs,vtVmaxpPstminp

  • 拉普拉斯矩阵(Laplacian Matrix):给定一个图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其邻接矩阵为 A A A,其拉普拉斯矩阵定义为 L = D − A \mathbf{L=D-A} L=DA,其中 D = d i a g ( d ( v 1 ) , ⋯   , d ( v N ) ) \mathbf{D=diag(d(v_1), \cdots, d(v_N))} D=diag(d(v1),,d(vN))

  • 对称归一化的拉普拉斯矩阵(Symmetric normalized Laplacian):给定一个图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其邻接矩阵为 A A A,其规范化的拉普拉斯矩阵定义为:

    L = D − 1 2 ( D − A ) D − 1 2 = I − D − 1 2 A D − 1 2 \mathbf{L=D^{-\frac{1}{2}}(D-A)D^{-\frac{1}{2}}=I-D^{-\frac{1}{2}}AD^{-\frac{1}{2}}} L=D21(DA)D21=ID21AD21

    这里的 − 1 2 -\frac{1}{2} 21是矩阵的二分之一次方再取逆。

    D是图KaTeX parse error: Undefined control sequence: \cal at position 1: \̲c̲a̲l̲{G}的度矩阵,是一个对角阵,对角上的元素为各顶点的的度

    I是指单位阵

    具体关于拉普拉斯矩阵和对称归一化拉普拉斯矩阵查看此处

  • 二部图(Bipartite Graphs):节点分为两类,只有不同类的节点之间存在边。

  • 同质图(Homogeneous Graph):只有一种类型的节点和一种类型的边的图。

  • 异质图(Heterogeneous Graph):存在多种类型的节点和多种类型的边的图。

二、Data类——PyG中图的表示及其使用

Data对象的创建

Data类的官方文档为torch_geometric.data.Data

class Data(object):

    def __init__(self, x=None, edge_index=None, edge_attr=None, y=None, **kwargs):
        r"""
        Args:
            x (Tensor, optional): 节点属性矩阵,大小为`[num_nodes, num_node_features]`
            edge_index (LongTensor, optional): 边索引矩阵,大小为`[2, num_edges]`,第0行为尾节点,第1行为头节点,头指向尾
            edge_attr (Tensor, optional): 边属性矩阵,大小为`[num_edges, num_edge_features]`
            y (Tensor, optional): 节点或图的标签,任意大小(,其实也可以是边的标签)

        """
        self.x = x
        self.edge_index = edge_index
        self.edge_attr = edge_attr
        self.y = y

    for key, item in kwargs.items():
        if key == 'num_nodes':
            self.__num_nodes__ = item
        else:
            self[key] = item

这里方括号里的值的是指该属性的维度,其中edge_index的对应顺序是不影响结果的,只是用来计算邻接矩阵。

[[0,1,2],[3,4,5]][[1,0,2],[4,3,5]]的定义是等价的。

其中简单复习COO的知识,另有CSR和CRC这俩互为转置的数据格式。

我们用一个简单例子简要看一下data数据的具体细节:

 
import torch
from torch_geometric.data import Data
 
 
x = torch.tensor([[2,1], [5,6], [3,7], [12,0]], dtype=torch.float)
y = torch.tensor([0, 1, 0, 1], dtype=torch.float)
 
edge_index = torch.tensor([[0, 2, 1, 0, 3],
                           [3, 1, 0, 1, 2]], dtype=torch.long)
 
 
data = Data(x=x, y=y, edge_index=edge_index)
>>> Data(edge_index=[2, 5], x=[4, 2], y=[4])

通常,一个图至少包含x, edge_index, edge_attr, y, num_nodes5个属性,当图包含其他属性时,我们可以通过指定额外的参数使Data对象包含其他的属性

graph = Data(x=x, edge_index=edge_index, edge_attr=edge_attr, y=y, num_nodes=num_nodes, other_attr=other_attr)

data类的简单调用:

from torch_geometric.datasets import KarateClub
dataset = KarateClub()
data = dataset[0]  # Get the first graph object.

print(data)
# Data(edge_index=[2, 156], train_mask=[34], x=[34, 34], y=[34])  
	
# 获取图的一些信息  
print(f'Number of nodes: {data.num_nodes}') # 节点数量
# Number of nodes: 34 
	
print(f'Number of edges: {data.num_edges}') # 边数量
# Number of edges: 156
	
print(f'Number of node features: {data.num_node_features}') # 节点属性的维度
# Number of node features: 34
	
print(f'Number of node features: {data.num_features}') # 同样是节点属性的维度
# Number of node features: 34
	
print(f'Number of edge features: {data.num_edge_features}') # 边属性的维度
# Number of edge features: 0
	
print(f'Average node degree: {data.num_edges / data.num_nodes:.2f}') # 平均节点度
# Average node degree: 4.59
	
print(f'if edge indices are ordered and do not contain duplicate entries.: {data.is_coalesced()}') # 是否边是有序的同时不含有重复的边
# if edge indices are ordered and do not contain duplicate entries.: True
	
print(f'Number of training nodes: {data.train_mask.sum()}') # 用作训练集的节点
# Number of training nodes: 4
	
print(f'Training node label rate: {int(data.train_mask.sum()) / data.num_nodes:.2f}') # 用作训练集的节点的数量
# Training node label rate: 0.12
	
print(f'Contains isolated nodes: {data.contains_isolated_nodes()}') # 此图是否包含孤立的节点
# Contains isolated nodes: False
	
print(f'Contains self-loops: {data.contains_self_loops()}')  # 此图是否包含自环的边
# Contains self-loops: False
	
print(f'Is undirected: {data.is_undirected()}')  # 此图是否是无向图
# Is undirected: True #

这里需要注意的是print(data)得出的结果是它的各属性的shape。如果我们想要看到data的具体细节的话我们需要用到接下来的知识。

dict对象为Data对象

Data的内置from_dict方法:

graph_dict = {
    'x': x,
    'edge_index': edge_index,
    'edge_attr': edge_attr,
    'y': y,
    'num_nodes': num_nodes,
    'other_attr': other_attr
}
graph_data = Data.from_dict(graph_dict)

查看from_dict的源码:

@classmethod
def from_dict(cls, dictionary):
    r"""Creates a data object from a python dictionary."""
    data = cls()
    for key, item in dictionary.items():
        data[key] = item

    return data

它其实构造了一个数据结构类似于dict的这么一个类方法,这里需要注意的是graph_dict中属性值的类型与大小的要求与Data类的构造函数的要求相同。

Data对象为其他对象

  1. Data内置的to_dict方法将Data对象转换为dict对象:

    def to_dict(self):
        return {key: item for key, item in self}
    
  2. Data内置的to_nametuple方法将Data对象转换为nametuple对象:

    def to_namedtuple(self):
        keys = self.keys
        DataTuple = collections.namedtuple('DataTuple', keys)
        return DataTuple(*[self[key] for key in keys])
    

    这里需要补充一下关于nametuple知识

    import collections
    User = collections.namedtuple('User', 'name age id')
    user = User('tester', '22', '464643123')
    print(user)
    # User(name='tester', age='22', id='464643123')
    

获取Data对象的具体内容

  1. 用上述两种方法,转Data对象为其他对象后,查看转换后的对象:

    print(data.to_namedtuple())
    """
    DataTuple(x=tensor([[1., 0., 0.,  ..., 0., 0., 0.],
            [0., 1., 0.,  ..., 0., 0., 0.],
            [0., 0., 1.,  ..., 0., 0., 0.],
            ...,
            [0., 0., 0.,  ..., 1., 0., 0.],
            [0., 0., 0.,  ..., 0., 1., 0.],
            [0., 0., 0.,  ..., 0., 0., 1.]]), edge_index=tensor([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,
              1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  3,
              3,  3,  3,  3,  3,  4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,
              7,  7,  8,  8,  8,  8,  8,  9,  9, 10, 10, 10, 11, 12, 12, 13, 13, 13,
             13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 20, 20, 21,
             21, 22, 22, 23, 23, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 27, 27,
             27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 31,
             31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33,
             33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33],
            [ 1,  2,  3,  4,  5,  6,  7,  8, 10, 11, 12, 13, 17, 19, 21, 31,  0,  2,
              3,  7, 13, 17, 19, 21, 30,  0,  1,  3,  7,  8,  9, 13, 27, 28, 32,  0,
              1,  2,  7, 12, 13,  0,  6, 10,  0,  6, 10, 16,  0,  4,  5, 16,  0,  1,
              2,  3,  0,  2, 30, 32, 33,  2, 33,  0,  4,  5,  0,  0,  3,  0,  1,  2,
              3, 33, 32, 33, 32, 33,  5,  6,  0,  1, 32, 33,  0,  1, 33, 32, 33,  0,
              1, 32, 33, 25, 27, 29, 32, 33, 25, 27, 31, 23, 24, 31, 29, 33,  2, 23,
             24, 33,  2, 31, 33, 23, 26, 32, 33,  1,  8, 32, 33,  0, 24, 25, 28, 32,
             33,  2,  8, 14, 15, 18, 20, 22, 23, 29, 30, 31, 33,  8,  9, 13, 14, 15,
             18, 19, 20, 22, 23, 26, 27, 28, 29, 30, 31, 32]]), y=tensor([0, 0, 0, 0, 3, 3, 3, 0, 1, 0, 3, 0, 0, 0, 1, 1, 3, 0, 1, 0, 1, 0, 1, 1,
            2, 2, 1, 1, 2, 1, 1, 2, 1, 1]), train_mask=tensor([ True, False, False, False,  True, False, False, False,  True, False,
            False, False, False, False, False, False, False, False, False, False,
            False, False, False, False,  True, False, False, False, False, False,
            False, False, False, False]))
    """
    print(data.to_dict())
    """
    {'edge_index': tensor([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,
              1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  3,
              3,  3,  3,  3,  3,  4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,
              7,  7,  8,  8,  8,  8,  8,  9,  9, 10, 10, 10, 11, 12, 12, 13, 13, 13,
             13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 20, 20, 21,
             21, 22, 22, 23, 23, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 27, 27,
             27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 31,
             31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33,
             33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33],
            [ 1,  2,  3,  4,  5,  6,  7,  8, 10, 11, 12, 13, 17, 19, 21, 31,  0,  2,
              3,  7, 13, 17, 19, 21, 30,  0,  1,  3,  7,  8,  9, 13, 27, 28, 32,  0,
              1,  2,  7, 12, 13,  0,  6, 10,  0,  6, 10, 16,  0,  4,  5, 16,  0,  1,
              2,  3,  0,  2, 30, 32, 33,  2, 33,  0,  4,  5,  0,  0,  3,  0,  1,  2,
              3, 33, 32, 33, 32, 33,  5,  6,  0,  1, 32, 33,  0,  1, 33, 32, 33,  0,
              1, 32, 33, 25, 27, 29, 32, 33, 25, 27, 31, 23, 24, 31, 29, 33,  2, 23,
             24, 33,  2, 31, 33, 23, 26, 32, 33,  1,  8, 32, 33,  0, 24, 25, 28, 32,
             33,  2,  8, 14, 15, 18, 20, 22, 23, 29, 30, 31, 33,  8,  9, 13, 14, 15,
             18, 19, 20, 22, 23, 26, 27, 28, 29, 30, 31, 32]]), 'train_mask': tensor([ True, False, False, False,  True, False, False, False,  True, False,
            False, False, False, False, False, False, False, False, False, False,
            False, False, False, False,  True, False, False, False, False, False,
            False, False, False, False]), 'x': tensor([[1., 0., 0.,  ..., 0., 0., 0.],
            [0., 1., 0.,  ..., 0., 0., 0.],
            [0., 0., 1.,  ..., 0., 0., 0.],
            ...,
            [0., 0., 0.,  ..., 1., 0., 0.],
            [0., 0., 0.,  ..., 0., 1., 0.],
            [0., 0., 0.,  ..., 0., 0., 1.]]), 'y': tensor([0, 0, 0, 0, 3, 3, 3, 0, 1, 0, 3, 0, 0, 0, 1, 1, 3, 0, 1, 0, 1, 0, 1, 1,
            2, 2, 1, 1, 2, 1, 1, 2, 1, 1])}
    """
    
  2. Data内置的__iter__方法查看:

    from torch_geometric.datasets import KarateClub
    dataset = KarateClub()
    data = dataset[0]
    __data__ = data.__iter__()
    print(__data__)
    # <generator object Data.__iter__ at 0x7fb924b21120>
    

    这里发现它生成了一个生成器对象,通过一些方法进行查看:

    print(list(__data__))
    print(tuple(__data__))
    for i in __data__:
    		print(i)
    """
    [('edge_index', tensor([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,
              1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  3,
              3,  3,  3,  3,  3,  4,  4,  4,  5,  5,  5,  5,  6,  6,  6,  6,  7,  7,
              7,  7,  8,  8,  8,  8,  8,  9,  9, 10, 10, 10, 11, 12, 12, 13, 13, 13,
             13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 20, 20, 21,
             21, 22, 22, 23, 23, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 27, 27,
             27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 31,
             31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33,
             33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33],
            [ 1,  2,  3,  4,  5,  6,  7,  8, 10, 11, 12, 13, 17, 19, 21, 31,  0,  2,
              3,  7, 13, 17, 19, 21, 30,  0,  1,  3,  7,  8,  9, 13, 27, 28, 32,  0,
              1,  2,  7, 12, 13,  0,  6, 10,  0,  6, 10, 16,  0,  4,  5, 16,  0,  1,
              2,  3,  0,  2, 30, 32, 33,  2, 33,  0,  4,  5,  0,  0,  3,  0,  1,  2,
              3, 33, 32, 33, 32, 33,  5,  6,  0,  1, 32, 33,  0,  1, 33, 32, 33,  0,
              1, 32, 33, 25, 27, 29, 32, 33, 25, 27, 31, 23, 24, 31, 29, 33,  2, 23,
             24, 33,  2, 31, 33, 23, 26, 32, 33,  1,  8, 32, 33,  0, 24, 25, 28, 32,
             33,  2,  8, 14, 15, 18, 20, 22, 23, 29, 30, 31, 33,  8,  9, 13, 14, 15,
             18, 19, 20, 22, 23, 26, 27, 28, 29, 30, 31, 32]])), ('train_mask', tensor([ True, False, False, False,  True, False, False, False,  True, False,
            False, False, False, False, False, False, False, False, False, False,
            False, False, False, False,  True, False, False, False, False, False,
            False, False, False, False])), ('x', tensor([[1., 0., 0.,  ..., 0., 0., 0.],
            [0., 1., 0.,  ..., 0., 0., 0.],
            [0., 0., 1.,  ..., 0., 0., 0.],
            ...,
            [0., 0., 0.,  ..., 1., 0., 0.],
            [0., 0., 0.,  ..., 0., 1., 0.],
            [0., 0., 0.,  ..., 0., 0., 1.]])), ('y', tensor([0, 0, 0, 0, 3, 3, 3, 0, 1, 0, 3, 0, 0, 0, 1, 1, 3, 0, 1, 0, 1, 0, 1, 1,
            2, 2, 1, 1, 2, 1, 1, 2, 1, 1]))]
    """
    # 其他两种方式只有最外围括号变了,这里不贴结果了
    

查看输出可以看到,to_namedtuple()的顺序发生了改变。这里稍微深究一下原因:

查看to_namedtuple的源码:

 def to_namedtuple(self):
        keys = self.keys
        DataTuple = collections.namedtuple('DataTuple', keys)
        return DataTuple(*[self[key] for key in keys])

可以看到顺序跟key的顺序有关。
查看keys的源码:

def keys(self):
        r"""Returns all names of graph attributes."""
        keys = [key for key in self.__dict__.keys() if self[key] is not None]
        keys = [key for key in keys if key[:2] != '__' and key[-2:] != '__']
        return keys

跳转到__init__的源码:

def __init__(self, x=None, edge_index=None, edge_attr=None, y=None,
                 pos=None, normal=None, face=None, **kwargs):
        self.x = x
        self.edge_index = edge_index
        self.edge_attr = edge_attr
        self.y = y
        self.pos = pos
        self.normal = normal
        self.face = face
        for key, item in kwargs.items():
            if key == 'num_nodes':
                self.__num_nodes__ = item
            else:
                self[key] = item

        if edge_index is not None and edge_index.dtype != torch.long:
            raise ValueError(
                (f'Argument `edge_index` needs to be of type `torch.long` but '
                 f'found type `{edge_index.dtype}`.'))

        if face is not None and face.dtype != torch.long:
            raise ValueError(
                (f'Argument `face` needs to be of type `torch.long` but found '
                 f'type `{face.dtype}`.'))

        if torch_geometric.is_debug_enabled():
            self.debug()

综上可以看到顺序与__init__有关

获取Data对象属性

x = graph_data['x']
# 这里的x也可以是edge_index或者其他的之类的

设置Data对象属性

graph_data['x'] = x
# 同上

获取Data对象包含的属性的关键字

graph_data.keys()

对边排序并移除重复的边

graph_data.coalesce()

三、Dataset类——PyG中图数据集的表示及其使用

PyG内置了大量常用的基准数据集,接下来我们以PyG内置的Planetoid数据集为例,来学习PyG中图数据集的表示及使用

生成数据集对象并分析数据集

Planetoid数据集类的官方文档为torch_geometric.datasets.Planetoid

from torch_geometric.datasets import Planetoid

dataset = Planetoid(root='./dataset', name='Cora')
# Cora()

len(dataset)
# 1

dataset.num_classes
# 7

dataset.num_node_features
# 1433
# 意味着这个数据集只有一个图,包含7个分类任务,节点的属性为1433维度。

注意这里需要使用相对路径./表示目前所在目录。(/表示根目录,../表示上一层目录)

Cora数据集由机器学习论文组成。 这些论文分为以下七个类别之一:

  • 基于案例
  • 遗传算法
  • 神经网络
  • 概率方法
  • 强化学习
  • 规则学习
  • 理论

这些论文的选择方式是,在最终语料库中,每篇论文引用或被至少一篇其他论文引用。整个语料库中有 2708篇论文。

在词干堵塞和去除词尾后,只剩下 1433个 唯一的单词。文档频率小于10的所有单词都被删除。

现在我们看到该数据集包含的唯一的图,有2708个节点(2708篇论文),节点特征为1433维(1433个唯一的单词),有10556条边,有140个用作训练集的节点,有500个用作验证集的节点,有1000个用作测试集的节点。

数据集的使用

假设我们定义好了一个图神经网络模型,其名为Net。在下方的代码中,我们展示了节点分类图数据集在训练过程中的使用。

model = Net().to(device)
data = dataset[0].to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

model.train()
for epoch in range(200):
    optimizer.zero_grad()
    out = model(data)
    loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
    loss.backward()
    optimizer.step()

四、作业实践——“机构-作者-论文”网络的构建

请通过继承Data类实现一个类,专门用于表示“机构-作者-论文”的网络。该网络包含“机构“、”作者“和”论文”三类节点,以及“作者-机构“和“作者-论文“两类边。对要实现的类的要求:1)用不同的属性存储不同节点的属性;2)用不同的属性存储不同的边(边没有属性);3)逐一实现获取不同节点数量的方法。

解答:

我们先画个图:

class MyData(Data):

    def __init__(self, paper_x=None, author_x=None, institution_x=None,  write_edge_index=None, contribute_edge_index=None, write_edge_attr=None, contribute_edge_attr=None, y=None, **kwargs):
        self.paper_x = paper_x # 论文节点属性矩阵
        self.author_x = author_x # 作者节点属性矩阵
        self.institution_x = institution_x # 机构节点属性矩阵
        self.write_edge_index = write_edge_index # “作者-论文”边索引矩阵
        self.contribute_edge_index = contribute_edge_index # “作者-机构“边索引矩阵
        self.write_edge_attr = write_edge_attr # “作者-论文”边属性矩阵
        self.contribute_edge_attr = contribute_edge_attr # “作者-机构”边属性矩阵
        self.y = y # 

    def num_paper_x()
    		self.paper_x.shape[0] # 0为paper数量,1为paper 特征数量
    def num_author_x()
    		self.author_x.shape[0]
    def num_institution_x()
    		self.institution_x.shape[0]
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BP神经网络通用的注意事项包括以下几点: 1. 网络结构:BP神经网络通常包含输入层、隐藏层和输出层。在这个问题中,你提到的是一个3层网络,其中输入层有12个神经元,隐藏层的神经元数量可以根据需要进行调整,输出层有4个神经元。 2. 输入数据:在训练BP神经网络之前,需要准备好输入数据。对于姓名这种文本类型的数据,通常需要进行特征提取和编码,将姓名转换成数字或者向量表示形式作为网络的输入。 3. 输出数据:根据问题的描述,输出层有4个神经元,代表4个组队。对于分类问题,可以使用独热编码(one-hot encoding)的方式表示输出,即将每个组队对应的神经元置为1,其余神经元置为0。 4. 训练算法:BP神经网络通常使用反向传播算法进行训练。该算法通过计算网络输出与真实输出之间的误差,并将误差反向传播到网络的每一层,以更新权重和偏置值。 5. 数据预处理:在训练之前,需要对输入数据进行预处理。常见的预处理操作包括特征缩放、数据归一化、数据平衡等,以提高训练效果和网络的收敛速度。 6. 超参数调整:BP神经网络中存在一些超参数,如学习率、隐藏层神经元数量、迭代次数等。需要通过实验和交叉验证等方法来选择最优的超参数组合,以达到最佳的网络性能。 以上是一些关于BP神经网络通用的注意事项,希望对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值