0
点赞
收藏
分享

微信扫一扫

图神经网络基础知识——初识图学习


初识图学习

  • ​​一、简单的图基础​​
  • ​​什么是图?​​
  • ​​生活中的图​​
  • ​​图的分类​​
  • ​​同构图、异构图举例​​
  • ​​图的度和邻居​​
  • ​​图的表示​​
  • ​​邻接矩阵​​
  • ​​邻接表​​
  • ​​边集​​
  • ​​图的特征​​
  • ​​二、图学习初印象​​
  • ​​图学习的应用​​
  • ​​节点级别任务​​
  • ​​金融诈骗检测​​
  • ​​自动驾驶​​
  • ​​边级别任务​​
  • ​​推荐系统​​
  • ​​图级别任务​​
  • ​​气味识别​​
  • ​​图学习算法分类​​
  • ​​图游走类算法​​
  • ​​图神经网络算法​​
  • ​​三、PGL 图学习库初体验​​
  • ​​1. 环境安装​​
  • ​​2. 使用 PGL 来创建一张图​​
  • ​​3. 定义图模型​​
  • ​​4. 模型定义​​
  • ​​5. 训练前准备​​
  • ​​6. 开始训练​​
  • ​​7. 模型测试​​

一、简单的图基础

在上一篇文章中,我整理了​​数据结构与算法中的图​​,讲解了与图有关的基本概念。

这里再做一个小小的回顾。

什么是图?

图神经网络基础知识——初识图学习_图


七桥问题的定义是:一个步行者怎样才能不重复、不遗漏地一次走完七座桥,最后回到出发点。

当年,大数学家欧拉在解答七桥问题的同时,也开创了数学的一个新分支——图论。

可以毫不夸张地说,七桥图是我们真正意义上的第一张图。对七桥图上的各个地点和桥做一个抽象,我们可以将七桥图抽象为一个由点和边构成的图。

生活中的图

事实上,图是一种统一描述复杂事物的语言。在我们的实际生活中,存在着许许多多的复杂事物,而这些事物都可以抽象成图。

图神经网络基础知识——初识图学习_python_02

我们生活在一个巨大的社交网络里面,这个社交网络里有着许许多多的人,从而抽象为了图中的点;而人与人之间的各种联系,包括父母关系、朋友关系、以及其他各种复杂的联系,则构成了图中的边。

又比如,我们每天都在网上冲浪,而网页与网页之间存在着超链接关系,这也构成了一张图。

图神经网络基础知识——初识图学习_PGL_03


我们经常会在淘宝等购物APP上买东西,在界面上经常会有各种好物推荐。而其实,这个推荐系统本身也是一张图,在这张图里面,用户和商品都是图中的节点,而用户与商品的点击、浏览、购买等行为则构成了图中的边。

在化学这门学科上,绝大多数化学分子就是由单个或多个原子组成的,原子就是节点,而原子之间的相互作用力,也就是化学键,它构成了图中的边。

图的分类

图神经网络基础知识——初识图学习_机器学习_04

  • 根据图的节点间是否有方向,可将图分为无向图与有向图
  • 根据图的边是否有权重,可以将图分为无权图和有权图
  • 根据图的边和点是否具有多种类型,可以将图分为同构图和异构图

同构图、异构图举例

图神经网络基础知识——初识图学习_机器学习_05

图的度和邻居

图神经网络基础知识——初识图学习_神经网络_06

  • 是图上一节点,其边的条数
  • 邻居指的是图上一节点的相邻节点

对于上面这两张图来说:

  1. 这张无向图有4个节点,每条边都是双向的,所以有8条边;
  • 拿节点4举例,其有3条边,所以度为3;
  • 3个节点与节点4相连,因此它有3个邻居节点。
  1. 这张有向图有4个节点,4条边
  • 拿节点4举例,它有3条边,所以度为3,根据箭头的指向,可以分为出度和入度,其中入度为1,出度为2
  • 有3个节点与节点4相连,其中,指向节点4的节点称为前继节点,反之则称为后继节点

图的表示

邻接矩阵

邻接矩阵是用0和1表示节点间关系的矩阵

图神经网络基础知识——初识图学习_神经网络_07


可以看出,无向图的临界矩阵就是对称矩阵

邻接表

邻接表其实就是直接记录着每个节点的邻居信息

图神经网络基础知识——初识图学习_机器学习_08

边集

图神经网络基础知识——初识图学习_PGL_09

图的特征

对于一张图来说,每个节点、每条边可能都有各自的特征

图神经网络基础知识——初识图学习_神经网络_10

二、图学习初印象

图学习(Graph Learning)是深度学习中的一个子领域,强调处理的数据对象为图。

与一般深度学习的区别是图学习能够方便地处理不规则数据(树、图),同时也可以处理规则数据(如图像)。

图神经网络基础知识——初识图学习_机器学习_11

图学习的应用

图神经网络基础知识——初识图学习_神经网络_12

图神经网络基础知识——初识图学习_图_13


可以将基于图能做的任务进行一个分类。对于一张图:

  • 我们希望预测这个点的类别或者其他的特性,那么这就是一个节点级别的任务;
  • 又比如我们希望预测这条边的权值,或者预测这条边是否存在,等等,那么这就是一个边级别的任务;
  • 再比如,我们想要预测整张图的一个类别,或者想比较两张图之间的相似性等等,这就是一个图级别的任务了。

节点级别任务

金融诈骗检测

图神经网络基础知识——初识图学习_PGL_14


在建图的时候,它的节点是用户和商家,同时还包含了各自共有的信息作为节点。

其中,每个用户或者商家都有着各自的特征,也具备着某些相同的特征,同时也有着与他人的交互。传统方法通常是直接利用用户和商家的特征来训练一个分类网络,而没有利用节点与节点之间的交互,因此使用图学习,我们可以同时学习图结构以及节点特征,更好的进行分类,从而更好地找到金融诈骗分子。

自动驾驶

图神经网络基础知识——初识图学习_PGL_15


图神经网络基础知识——初识图学习_图_16


点云是通过激光扫描等来获得的点数据,而3D点云这个结构可以建模为图结构。

在点云中构建好图之后,将图结构和图特征经过这个叫 Point-GNN 的模型,从而预测出点云中每个点所对应的 object,也就是目标对象,同时要预测出对应目标的所在三维边界,也就是 bounding box。

由于预测对象是每个点,因此这是一个节点级别的任务。

边级别任务

推荐系统

图神经网络基础知识——初识图学习_python_17


推荐系统可以表示成图

比如,我们想要向用户推荐新闻,以左边这个图为例,我们已经知道了用户 ABC的历史点击行为,那么接下来,想要预测用户B会不会点击某条广告,其实就相当于预测这条边是否存在,因此这就是一个边预测的任务。

具体实现的时候,会把用户行为图关系通过图表示学习后,得到用户、商品或内容的向量表示;得到对应这些节点的 Embeddings 之后, 就可以利用这些 embeddings 来做各种的推荐任务。

图级别任务

气味识别

图神经网络基础知识——初识图学习_神经网络_18


气味识别其实是一个非常典型的图识别任务了,而且对于实际生活也很有帮助。

假设这样的一个场景,我们有两种花,蒙住眼睛,只能用鼻子来分辨花。如果我们光靠鼻子搞不定,那么这时候就可以派图学习上场了。

图神经网络基础知识——初识图学习_python_19

图学习算法分类

图神经网络基础知识——初识图学习_图_20


这里分为了三大类:游走类算法图神经网络算法、以及知识图谱嵌入算法

因为知识图谱也是一种典型的图,因此把它也加入到了这个分类里面。

其中,图神经网络算法还可以进行更加具体的划分,比如分为卷积网络和递归网络,等等。

图游走类算法

图神经网络基础知识——初识图学习_机器学习_21

图游走类算法就有点像我们去旅游一样,任意选择一个出发点,然后随机地选择下一个目的地,不断地走,直到我们累了。

通过不断地旅游,我们得到了多个序列,而游走类算法就是在得到这些序列之后,对它们应用图表示学习,再进行接下来的其他操作。

图神经网络算法

图神经网络基础知识——初识图学习_神经网络_22


图神经网络算法相对来说则复杂一点,它的一种实现方式是消息传递

消息传递,其实质就是把当前节点的邻居发送到自身,将这些信息聚合后,再利用这些信息更新自身的表示。

三、PGL 图学习库初体验

  • Github 链接:​​https://github.com/PaddlePaddle/PGL​​
  • API文档: ​​https://pgl.readthedocs.io/en/latest/​​

1. 环境安装

# 安装 PaddlePaddle 框架
!pip install paddlepaddle==1.8.5

# 安装 PGL 学习库
!pip install pgl

2. 使用 PGL 来创建一张图

假设我们有下面的这一张图,其中包含了10个节点以及14条边。

图神经网络基础知识——初识图学习_图_23

我们的目的是,训练一个图模型,使得该图模型可以区分图上的黄色节点和绿色节点。我们可以使用以下代码来构图。

import pgl
from pgl import graph # 导入 PGL 中的图模块
import paddle.fluid as fluid # 导入飞桨框架
import numpy as np

def build_graph():
# 定义图中的节点数目,我们使用数字来表示图中的每个节点
num_nodes = 10

# 定义图中的边集
edge_list = [(2, 0), (2, 1), (3, 1),(4, 0), (5, 0),
(6, 0), (6, 4), (6, 5), (7, 0), (7, 1),
(7, 2), (7, 3), (8, 0), (9, 7)]

# 随机初始化节点特征,特征维度为 d
d = 16
feature = np.random.randn(num_nodes, d).astype("float32")

# 随机地为每条边赋值一个权重
edge_feature = np.random.randn(len(edge_list), 1).astype("float32")

# 创建图对象,最多四个输入
g = graph.Graph(num_nodes = num_nodes,
edges = edge_list,
node_feat = {'feature':feature},
edge_feat ={'edge_feature': edge_feature})

return g

g = build_graph()

图创建完毕后,我们可以打印出图中的一些信息。

print('图中共计 %d 个节点' % g.num_nodes)
print('图中共计 %d 条边' % g.num_edges)

图中共计 10 个节点
图中共计 14 条边

3. 定义图模型

我们可以定义下面的一个简单图模型层,这里的结构是添加了边权重信息的类 GCN 层。

# 定义一个同时传递节点特征和边权重的简单模型层。
def model_layer(gw, nfeat, efeat, hidden_size, name, activation):
'''
gw: GraphWrapper 图数据容器,用于在定义模型的时候使用,后续训练时再feed入真实数据
nfeat: 节点特征
efeat: 边权重
hidden_size: 模型隐藏层维度
activation: 使用的激活函数
'''

# 定义 send 函数
def send_func(src_feat, dst_feat, edge_feat):
# 将源节点的节点特征和边权重共同作为消息发送
return src_feat['h'] * edge_feat['e']

# 定义 recv 函数
def recv_func(feat):
# 目标节点接收源节点消息,采用 sum 的聚合方式
return fluid.layers.sequence_pool(feat, pool_type='sum')

# 触发消息传递机制
msg = gw.send(send_func, nfeat_list=[('h', nfeat)], efeat_list=[('e', efeat)])
output = gw.recv(msg, recv_func)
output = fluid.layers.fc(output,
size=hidden_size,
bias_attr=False,
act=activation,
name=name)
return output

4. 模型定义

这里我们简单的把上述定义好的模型层堆叠两层,作为我们的最终模型。

class Model(object):
def __init__(self, graph):
"""
graph: 我们前面创建好的图
"""
# 创建 GraphWrapper 图数据容器,用于在定义模型的时候使用,后续训练时再feed入真实数据
self.gw = pgl.graph_wrapper.GraphWrapper(name='graph',
node_feat=graph.node_feat_info(),
edge_feat=graph.edge_feat_info())
# 作用同 GraphWrapper,此处用作节点标签的容器
self.node_label = fluid.layers.data("node_label", shape=[None, 1],
dtype="float32", append_batch_size=False)

def build_model(self):
# 定义两层model_layer
output = model_layer(self.gw,
self.gw.node_feat['feature'],
self.gw.edge_feat['edge_feature'],
hidden_size=8,
name='layer_1',
activation='relu')
output = model_layer(self.gw,
output,
self.gw.edge_feat['edge_feature'],
hidden_size=1,
name='layer_2',
activation=None)

# 对于二分类任务,可以使用以下 API 计算损失
loss = fluid.layers.sigmoid_cross_entropy_with_logits(x=output,
label=self.node_label)
# 计算平均损失
loss = fluid.layers.mean(loss)

# 计算准确率
prob = fluid.layers.sigmoid(output)
pred = prob > 0.5
pred = fluid.layers.cast(prob > 0.5, dtype="float32")
correct = fluid.layers.equal(pred, self.node_label)
correct = fluid.layers.cast(correct, dtype="float32")
acc = fluid.layers.reduce_mean(correct)

return loss, acc

5. 训练前准备

# 是否在 GPU 或 CPU 环境运行
use_cuda = True
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()

# 定义程序,也就是我们的 Program
startup_program = fluid.Program() # 用于初始化模型参数
train_program = fluid.Program() # 训练时使用的主程序,包含前向计算和反向梯度计算
test_program = fluid.Program() # 测试时使用的程序,只包含前向计算

with fluid.program_guard(train_program, startup_program):
model = Model(g)
# 创建模型和计算 Loss
loss, acc = model.build_model()
# 选择Adam优化器,学习率设置为0.01
adam = fluid.optimizer.Adam(learning_rate=0.01)
adam.minimize(loss) # 计算梯度和执行梯度反向传播过程

# 复制构造 test_program,与 train_program的区别在于不需要梯度计算和反向过程。
test_program = train_program.clone(for_test=True)

# 定义一个在 place(CPU)上的Executor来执行program
exe = fluid.Executor(place)
# 参数初始化
exe.run(startup_program)

# 获取真实图数据
feed_dict = model.gw.to_feed(g)
# 获取真实标签数据
# 由于我们是做节点分类任务,因此可以简单的用0、1表示节点类别。其中,黄色点标签为0,绿色点标签为1。
y = [0,1,1,1,0,0,0,1,0,1]
label = np.array(y, dtype="float32")
label = np.expand_dims(label, -1)
feed_dict['node_label'] = label

打印输入的数据:

print(feed_dict)

{'graph/num_edges': array([14]), 'graph/edges_src': array([2, 4, 5, 6, 7, 8, 2, 3, 7, 7, 7, 6, 6, 9]), 'graph/edges_dst': array([0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 7]), 'graph/num_nodes': array([10]), 'graph/uniq_dst': array([0, 1, 2, 3, 4, 5, 7]), 'graph/uniq_dst_count': array([ 0,  6,  9, 10, 11, 12, 13, 14], dtype=int32), 'graph/indegree': array([6, 3, 1, 1, 1, 1, 0, 1, 0, 0]), 'graph/graph_lod': array([ 0, 10], dtype=int32), 'graph/num_graph': array([1]), 'graph/node_feat/feature': array([[ 0.58795387,  1.2630547 ,  0.15662637,  1.3688753 ,  1.1675597 ,
0.04462763, 0.18476363, -0.25253114, -0.21759783, 0.8734884 ,
0.13879131, 1.0323961 , 0.03691616, 0.10940287, -1.792123 ,
0.04989915],
[-0.1444743 , -1.2530277 , 0.15299311, -1.6170429 , 0.64711034,
-2.7449965 , -1.3882436 , 0.51445365, -1.2793802 , -0.25707176,
-1.0026166 , 0.5339329 , -0.2335032 , -1.8645428 , 1.8710616 ,
1.0047083 ],
[-2.0020528 , 0.21761087, 1.333866 , -1.306326 , -0.01795861,
1.0073777 , -0.253056 , -0.2271674 , 2.1695845 , -0.96707493,
-0.76226276, 0.16730066, 0.54326695, 1.3340389 , 0.28255144,
0.10114926],
[-1.9666518 , -1.9607522 , -0.43653187, 1.4989506 , -0.3806762 ,
0.9109142 , 0.6621056 , 0.27713525, 0.3436323 , 0.6689543 ,
-0.4054542 , 0.7418638 , 0.09361912, -0.51458985, 0.56043166,
0.6162147 ],
[-1.1269506 , -1.1637336 , -1.3745695 , -0.17090996, -0.52521807,
1.1339206 , 0.36089063, 1.1876916 , -0.4320988 , 0.18111283,
-0.9603039 , -1.542511 , -0.5449157 , 0.0152637 , 1.2007928 ,
-0.74825037],
[-1.3099471 , 1.2409917 , 0.51250875, -1.0624666 , -0.02053989,
2.0178044 , -0.59536004, 0.3934452 , 0.98477745, -0.1810182 ,
0.65636057, 2.3335536 , -0.42765683, 0.09425509, -0.4363421 ,
-1.1370741 ],
[-0.48036537, -0.44820678, -1.1542974 , -0.6099145 , -1.2221897 ,
1.9440795 , -1.2649409 , -0.301374 , -0.42991576, -0.6353119 ,
-0.32859164, 1.0071183 , 0.5237574 , -1.0096937 , -0.544537 ,
-0.99841434],
[-1.8510556 , 0.8686978 , 1.4590447 , -1.9817588 , -1.2099521 ,
0.40852544, 0.52567303, 0.89494723, 0.03127011, -0.4033486 ,
0.60143316, 0.19032563, 0.6081489 , -0.297795 , 0.9398178 ,
-0.32976308],
[-0.7306164 , -0.1550996 , 1.2815462 , -0.20480168, 0.21323843,
-0.80160755, -1.155071 , 1.8388264 , 0.5788635 , 0.01409068,
0.57363164, -1.3481847 , 0.33940238, 0.9301134 , 0.9973273 ,
0.3144489 ],
[ 0.03115364, 0.4957952 , -1.2972994 , -0.42183682, 1.939209 ,
-0.6846291 , 0.4093842 , 0.7665659 , 1.5198345 , -0.94440025,
0.57780015, 1.2090318 , 0.33736354, 0.34547713, -1.1328425 ,
-0.96179086]], dtype=float32), 'graph/edge_feat/edge_feature': array([[ 0.49790657],
[-0.44967642],
[ 0.92381525],
[-0.0052013 ],
[-0.22613016],
[-0.927129 ],
[ 0.8273991 ],
[-0.5968252 ],
[-0.37763336],
[-0.39886096],
[ 0.55989164],
[ 0.12900583],
[-1.9236063 ],
[ 0.15244085]], dtype=float32), 'node_label': array([[0.],
[1.],
[1.],
[1.],
[0.],
[0.],
[0.],
[1.],
[0.],
[1.]], dtype=float32)}

6. 开始训练

for epoch in range(30):
train_loss = exe.run(train_program,
feed=feed_dict, # feed入真实训练数据
fetch_list=[loss], # fetch出需要的计算结果
return_numpy=True)[0]
print('Epoch %d | Loss: %f' % (epoch, train_loss))

Epoch 0 | Loss: 0.706203
Epoch 1 | Loss: 0.689478
Epoch 2 | Loss: 0.677695
Epoch 3 | Loss: 0.667933
Epoch 4 | Loss: 0.659103
Epoch 5 | Loss: 0.650838
Epoch 6 | Loss: 0.642839
Epoch 7 | Loss: 0.635119
Epoch 8 | Loss: 0.627715
Epoch 9 | Loss: 0.620718
Epoch 10 | Loss: 0.614087
Epoch 11 | Loss: 0.607863
Epoch 12 | Loss: 0.602069
Epoch 13 | Loss: 0.596725
Epoch 14 | Loss: 0.591840
Epoch 15 | Loss: 0.587411
Epoch 16 | Loss: 0.583429
Epoch 17 | Loss: 0.579876
Epoch 18 | Loss: 0.576725
Epoch 19 | Loss: 0.573948
Epoch 20 | Loss: 0.571514
Epoch 21 | Loss: 0.569391
Epoch 22 | Loss: 0.567547
Epoch 23 | Loss: 0.565950
Epoch 24 | Loss: 0.564570
Epoch 25 | Loss: 0.563379
Epoch 26 | Loss: 0.562358
Epoch 27 | Loss: 0.561476
Epoch 28 | Loss: 0.560711
Epoch 29 | Loss: 0.560047

7. 模型测试

test_acc = exe.run(test_program, feed=feed_dict, fetch_list=[acc], return_numpy=True)[0]
print("Test Acc: %f" % test_acc)

Test Acc: 0.700000

result = exe.run(test_program, feed=feed_dict, fetch_list=[acc], return_numpy=True)
print(result)

[array([0.70000005], dtype=float32)]


举报

相关推荐

0 条评论