原作者链接

基础知识
  1. Graph Neural Networks,简称GNN即图神经网络 是一种针对图数据结构的深度学习模型
  2. 什么是图?
    图是表示一些实体之间的一些关系。实体就是一个(node),关系就是一个边(edge)。
    图包括了V(vertex顶点) E(edge边) U(global全局)的属性(attributes)
    全局属性包括了图中有多少顶点,最长距离等
  3. V、E、U可以用什么表示(属性)?
    顶点可以用向量(embedding)表示。
    假如一共有六个值 他的高矮代表这个值的大小。
    边可以用向量(embedding)表示。
    假如一共有八个值 八个值的高矮代表每个值的大小。
    全局可以用向量(embedding)表示。
    假如一共有八个值 八个值的高矮代表每个值的大小。
  4. 图有两种 有方向的图和没有方向的图
数据如何表示图
  1. 图片表示成一张图
    一张2442443的图片 分别表示长宽和通道个数(RGB)
    把他表示成三个维度的一个tensor,每个像素相当于一个点,如果像素是邻接关系则连接成一条边
    邻接矩阵- 每一行代表一个顶点(所有顶点都有),每一列代表一个顶点(所有顶点都有),第i行和第j列对应的顶点是邻接关系则置1(蓝色)否则为0(白色)
  2. 文本表示成一张图
    文本当作一条序列,每个词看做一个顶点,一个词和下一个词之间有一条有向边
    邻接矩阵- 每一行代表一个顶点(所有顶点都有),每一列代表一个顶点(所有顶点都有),第i行和第j列对应的顶点是邻接关系则置1(蓝色)否则为0(白色)
  3. 社交网络表示成一张图
    每个人代表一个顶点,有交互的人之间画一条边
  4. 引用文献表示成一张图
    每篇文章作为一个顶点,引用是有向边
图层面的问题
  1. 对整个图进行识别
    分类,查看图中哪些分子是两个环状的
顶点层面的问题
  1. 对一个顶点的属性判断
    某个顶点上连接的顶点有哪些,假如两个老师分阵营了,可以从图中看出哪些学生跟了A老师这个阵营。
边层面的问题
  1. 对一个边的属性判断
    顶点之间的关系(属性),比如A和B顶点之间的关系是观众(watching)
图存储方法
  1. Nodes(顶点)
    eg:[0,0,1,0,0,1,1,1] 八个顶点,每个顶点的属性用一个标量表示(按照顶点编号顺序)
  2. Edges(边)
    eg: [1,1,1,1,2,1,2] 七条边,每条边的属性用一个标量表示

  3. Global(全局)
    eg: 0 一张图,每张图的属性用一个标量表示

  4. Adjacency List(邻接表)
    eg: [[1,0],[2,0],[4,3],[6,2],[7,3],[7,4],[7,5]] ,长度和边数一致,第i个项表示第i条边连接了哪两个节点(编号)
GNN-神经网络
  1. GNN是对图上所有的属性(顶点,边,全局)进行优化变换,这个变换可以保持住图的对称信息(symmetry)—-即对顶点进行其他排序后整个结果不变
  2. GNN输入和输出都是一个图,GNN改变的是点、边、全局的属性值,不改变输入的顶点连接性质(图的结构)
最简单的GNN
  1. 多层感知器(MLP)是由美国学者Frank Rosenblatt在1957年提出的,它是人工神经网络的一种基本形式。MLP由输入层、隐藏层和输出层组成,其中隐藏层可以有多个。每个节点都具有一个权重和偏置,节点之间的连接采用全连接方式。MLP的输入层接收原始数据,输出层产生最终结果。隐藏层的作用是提取输入数据的特征,并进行非线性变换。
  2. MLP的工作原理可以概括为以下几个步骤:
    1.输入层接收输入数据,将其传递给隐藏层。
    2.隐藏层对输入数据进行处理,通过激活函数产生输出。
    3.输出层对隐藏层的输出进行处理,通过激活函数产生最终结果。
    激活函数是MLP中的关键组成部分,它决定了神经元的激活状态。常⻅的激活函数有Sigmoid、ReLU、Tanh等。激活函数的引入使得MLP具备了学习非线性关系的能力。
  3. 最简单的GNN就是对于顶点向量V、边向量E、全局向量U分别构造一个MLP,一共只需三个MLP,所有顶点共用一个MLP,所有的边共用一个MLP
    三个MLP构成了一个GNN层。具体步骤如下:
    1.给一个输入的图
    2.输入的图进入GNN层,每一层都有三个MLP,输出保持了结构的输出但所有的属性发生了变化
    3.根据要对哪个属性做预测添加合适的输出层,如果确实信息需要先加入合适的汇聚层(池化层-pooling)
    4.得到预测值
  4. 为了使图的结构信息(不是属性信息)进入到GNN层使用信息传递(Passing messages)
    更新某个顶点时
    1.拿到顶点本身的向量以及邻居的向量相加到一起进入到MLP来得到这个点向量的更新—很像图像卷积而3*3卷积核都为
    更新顶点和边时
    1.第一种更新(Edge Then Node Learning)是把顶点的信息汇聚(池化)给边然后做更新,之后把更新过的信息汇聚到顶点再做更新
    2.第二种更新(Node Then Edge Learning)是先把边的信息汇聚到顶点然后做更新,然后把更新过的信息汇聚到边再做更新
    3.第三种更新(Weave Layer)-交替更新,把顶点的信息汇聚到边上的同时把边的信息汇聚到顶点上;此时再次把汇聚到边上的信息汇聚到顶点上的同时把汇聚到顶点上的信息汇聚到边上。
    更新全局信息时
    1.假设图比较大而且连接不紧密导致消息从一个点传递到一个很远的点需要走很长的步,解决方案是加入一个master node(context vector),这是一个虚拟的点,可以跟所有的顶点和所有的边相连。master node集合称为U,U中所有的节点和V中所有的点相连接,而且跟E中所有的节点相连。
    2.更新方式-把顶点的信息汇聚给边时也要把U汇聚给边,汇聚给顶点时把U以及E都汇聚过来,最后把所有的汇聚后的顶点信息和边信息都汇聚到U再更新。
GNN例子

第一种

  1. 想对每个顶点做预测,顶点的向量已知,对顶点二分类
    在输入后加入输出维度为2的全连接层再加一个softmax就可以得到一个二分类输出
  2. softmax就是归一化指数函数,将多分类的结果以概率形式展现,它可以将负无穷到正无穷上的预测结果按照两步转化为概率值
    1.将预测结果转化到指数上,保证概率的非负性
    2.归一化处理,将转化后的结果除以所有转化后的结果之和
    eg:下面为大家举一个例子,假如模型对一个三分类问题的预测结果为-3、1.5、2.7。我们要用softmax将模型结果转为概率。步骤如下:
    1)将预测结果转化为非负数
    y1 = exp(x1) = exp(-3) = 0.05
    y2 = exp(x2) = exp(1.5) = 4.48
    y3 = exp(x3) = exp(2.7) = 14.88
    2)各种预测结果概率之和等于1
    z1 = y1/(y1+y2+y3) = 0.05/(0.05+4.48+14.88) = 0.0026
    z2 = y2/(y1+y2+y3) = 4.48/(0.05+4.48+14.88) = 0.2308
    z3 = y3/(y1+y2+y3) = 14.88/(0.05+4.48+14.88) = 0.7666
  3. 全连接层指每一个节点都和下一层的所有节点相连
    第二种
  4. 只知道边的属性,但想知道未知节点的属性- -使用pooling(池化)
  5. 将需要预测点的连接的那些边的向量拿出来,把全局的向量拿出来
  6. 将上述的边的向量以及全局的向量全部相加就得到了代表预测点的向量
    第三种
  7. 只知道顶点的属性,但想知道未知边的属性- -使用pooling(池化)
  8. 将需要预测的边的两个顶点的向量相加再加上全局向量得到边的向量
  9. 进入边向量的输出层得到边的输出
    第四种
  10. 没有全局的向量,只有顶点的向量,对整个图进行预测
  11. 所有顶点向量加起来,得到一个全局的向量
  12. 然后进入全局的输出层得到全局的输出
实验

原作者链接
实验再该网页动态做
参数:

  1. Depth: 神经网络的层数
  2. Aggregation function: 汇聚的操作(池化)—(sum,mean,max)
  3. Node embedding size:顶点向量个数
  4. Edge embedding size: 边的向量个数
  5. Global embedding size: 全局的向量个数
    准确率:MODEL AUC越大越好
    右边的图:圆实心的颜色是预测值,圆边框的颜色是真实值 如果相同则预测准确
    左边的图:输入的分子
箱线图

本篇中的箱线图是顶点的向量(node dim)个数对准确性的影响

  1. 中间的白色横线代表中值
  2. 长方形的上下代表25%和75%的分位数
  3. 竖线最上方代表最小值,最下方代表最大值
  4. 中值越高越好,bar不要太长,太长表示很敏感
图基本模块定义
  1. 图由V(顶点)、E(边)、U(全局)组成,每一项都是由向量构成,向量代表提取的某些特征
  2. 图神经网络的目的是整合特征,重构特征,输入是特征,输出也是,邻接矩阵不会变。
  3. 邻接矩阵描述了哪些点是邻居
  4. GNN适合输入数据不规则
  5. 实际传入的邻接矩阵是一对一对的代表哪两个节点相连
  6. 节点更新时要考虑周围的邻接(有权重),还要考虑自身(有权重)eg:x1=w1x1+w2x2+w3*x3 权重是让神经网络学习出来的
  7. 更新方式有sum(求和,第六条就是)、Mean(求平均值)、Max、min
  8. 多次GNN能聚合更远的特征,经过第一层GNN后X1节点包含了邻居的特征,经过第二层后邻居节点也聚合了下面的邻居特征,因此x1间接的包含了邻居的邻居的特征。
  9. 图卷积(GCN)输入1.各节点输入特征 2.网络结构图(邻接矩阵),用损失函数去迭代更新参数。计算损失只用有标签的。卷积是拿一个小窗口对一张图片卷积。图卷积和卷积不一样。
  10. 计算节点的特征: 平均其邻居特征(包含自身)后传入神经网络
  11. 第一个图卷积层会对每一个节点都更新,经过激活函数(ReLU(x)=max(0,x)),进入第二层图卷积后经过激活函数
图中各种矩阵
  1. 图中基本组成:
    下面一张图假设有五个节点,节点名为A~E
    G矩阵:输入的图
    A矩阵:邻接矩阵(x,y是节点,两个节点相邻就置为1)
ABCDE
A00001
B00011
C00011
D01101
E11110

D矩阵:各个节点的度:某节点跟几个节点相连就写几

ABCDE
A10000
B02000
C00200
D00030
E00004

F矩阵:每个节点的特征:每一行代表一个节点的向量

A-1.13.24.2
B0.45.1-1.2
C1.21.32.1
D1.4-1.22.5
E1.42.54.5

更新后的节点F矩阵:等于AF (两个矩阵相乘)

ABCDE
A00001
B00011
C00011
D01101
E11110
     *
A-1.13.24.2
B0.45.1-1.2
C1.21.32.1
D1.4-1.22.5
E1.42.54.5
    =
1.42.54.5A
B
C
D
E

Ã矩阵(邻接矩阵的升级版): Ã=A+λE
(A矩阵的对角线上加了1,即邻接矩阵考虑自己,这是因为节点更新时候需要考虑自己,自己也应该在更新时有权重)如下:

ABCDE
A10001
B01O11
C00111
D01111
E11111

D⁻¹矩阵:度矩阵的升级版,在对角线上去倒数,可让结果数字小一些,按照关联程度大小对数据类似于平均化 归一化。关联性越大点的更新的值越大,所以为了让它的数据规范的更小一些,D⁻¹矩阵值更小,这样乘法后就会小很多。

ABCDE
A1/20000
B01/3000
C001/300
D0001/40
E00001/5

所以更新后的节点F矩阵变成了D⁻¹ÃF (F有时候写作X)。D⁻¹相当于对行进行了归一化
现在要进行列的归一化所以公式变为D⁻¹ÃD⁻¹F 但是这样每一个值进行了两次,为了避免这种情况改进为
D⁻⁰∙⁵ÃD⁻⁰∙⁵X(F有时候写作X)
所以更新后的节点F矩阵变成了D⁻⁰∙⁵ÃD⁻⁰∙⁵X

D⁻⁰∙⁵ÃD⁻⁰∙⁵矩阵:代表了怎么衡量邻接矩阵的权重
抽象理解为左边乘是考虑自身的度,右边乘是考虑别人的度

D⁻⁰∙⁵如下:

ABCDE
A1/√20000
B01/√3000
C001/√300
D0001/√40
E00001/√5

综上GCN在分类时的公式为
Z= f(X,A) = softmax(Â ReLU(ÂXW⁽⁰⁾) W⁽¹⁾)
解释:
1.Â=D⁻⁰∙⁵ÃD⁻⁰∙⁵即两边做归一化
2.ÂXW⁽⁰⁾是第一次对输入节点更新,乘上了第一层的一组权重参数
3.ReLU(ÂXW⁽⁰⁾)为第一层
4. ReLU(ÂXW⁽⁰⁾) W⁽¹⁾是经过了第二层,W⁽¹⁾是第二层权重参数。

pytorch_geometric基本使用
  1. 在pycharm中进入终端,在配置好的pytorch环境中直接pip可以安装,亲测可用
    1
    pip install torch_geometric
    安装不了,尝试其他的方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    # 离线安装
    # 查看torch版本
    import torch
    print(torch.__version__)
    print(torch.version.cuda)
    # 打开https://data.pyg.org/whl/
    # 如果torch为2.3.1,请安装低版本
    pip install torch==2.3.0
    # 找到对应版本下载轮子五个文件 python版本是3.9的选cp39,还要判断是win还是linux
    # 下载后将四个包放置在同一个文件夹
    cd D:/XXX/XX # 安装包所存的位置
    # 已经存在的使用强制重新安装
    # pip install --force-reinstall torch_scatter-2.1.2+pt23cpu-cp39-cp39-win_amd64.whl
    pip install torch_scatter-2.1.2+pt23cpu-cp39-cp39-win_amd64.whl
    pip install torch_sparse-0.6.18+pt23cpu-cp39-cp39-win_amd64.whl
    pip install torch_cluster-1.6.3+pt23cpu-cp39-cp39-win_amd64.whl
    pip install torch_spline_conv-1.2.2+pt23cpu-cp39-cp39-win_amd64.whl
    pip install pyg_lib-0.4.0+pt23cpu-cp39-cp39-win_amd64.whl
    # 最后安装pyg
    pip install torch-geometric


    # 在线安装轮子 --选择版本后一堆whl文件页面的网址
    pip install torch_scatter -f https://data.pyg.org/whl/torch-2.3.0%2Bcpu.html
    pip install torch_sparse -f https://data.pyg.org/whl/torch-2.3.0%2Bcpu.html
    pip install torch_cluster -f https://data.pyg.org/whl/torch-2.3.0%2Bcpu.html
    pip install torch_spline_conv -f https://data.pyg.org/whl/torch-2.3.0%2Bcpu.html
    pip install pyg_lib -f https://data.pyg.org/whl/torch-2.3.0%2Bcpu.html

  2. https://github.com/pyg-team/pytorch_geometric
  3. https://data.pyg.org/whl/
数据集与邻接矩阵格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import torch
import networkx as nx
import matplotlib.pyplot as plt

#画网络图的函数定义
def visualize_graph(G, color):
#画图的尺寸
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
#画网络图
#参数
#G这是一个 NetworkX 图对象,表示要绘制的图形数据
#pos 节点的位置布局。可以是一个字典,其中键是节点,值是节点的位置(二维坐标)
#spring_layout 是 NetworkX 提供的一种基于力导向的布局算法,用来模拟节点之间的吸引和排斥力,从而布置节点位置。seed=42 是一个随机种子,用于确保布局的可重现性(即相同的种子产生相同的布局结果)
#with_labels:布尔值,表示是否在节点上显示标签--节点的编号。
#node_color:节点的颜色。可以是单个颜色(字符串或 RGB 元组),也可以是一个颜色列表,与节点一一对应
#cmap: 这是颜色映射(colormap)的名称。在这里,设置为 "Set2",表示使用 Matplotlib 提供的 "Set2" 颜色映射来映射节点的颜色。
#颜色映射是一种从数据值到颜色的映射方式,通常用于将数据可视化中的数据范围映射到颜色空间,以便更直观地显示数据。
nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False,
node_color=color, cmap="Set2")
plt.show()

#画散点图的函数定义
def visualize_embedding(h, color, epoch=None, loss=None):
#创建一个大小为 7x7 英寸的图像窗口,并设置不显示任何刻度。
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
#将输入的向量 h 转换为 NumPy 数组,这一步通常是因为 Matplotlib 处理的输入必须是 NumPy 数组
#将输入的嵌入向量h从tensor转换为NumPy数组,并且将其从GPU移动到CPU上(如果在GPU上)
h = h.detach().cpu().numpy()
# h[:, 0]表示所有样本的第一个特征,h[:, 1]表示所有样本的第二个特征
# s=140表示散点的大小,c=color表示散点的颜色,cmap="Set2"表示使用Set2颜色映射
plt.scatter(h[:, 0], h[:, 1], s=140, c=color, cmap="Set2")
# 如果提供了epoch和loss参数,则显示在图像的x轴标签上
if epoch is not None and loss is not None:
plt.xlabel(f'Epoch: {epoch}, Loss: {loss.item():.4f}', fontsize=16)
plt.show()
# 导入数据集KarateClub ,这个数据集是一个俱乐部的数据集,有34个顶点,每个点有34维向量,每个点代表一个人
# 该图描述了一个空手道俱乐部会员的社交关系,以34名会员作为节点,如果两位会员在俱乐部之外仍保持社交关系,则在节点间增加一条边。
# 每个节点具有一个34维的特征向量,一共有78条边。
# 在收集数据的过程中,管理人员 John A 和 教练 Mr. Hi(化名)之间产生了冲突,
# 会员们选择了站队,一半会员跟随 Mr. Hi 成立了新俱乐部,剩下一半会员找了新教练或退出了俱乐部。
#下面将对点进行分类
#数据集网址https://pytorch-geometric.readthedocs.io/en/latest/modules/datasets.html#torch_geometric.datasets.KarateClub
from torch_geometric.datasets import KarateClub

dataset = KarateClub()

print(f'Dataset: {dataset}:')
print('======================')
# 打印数据集中图的数量 len(dataset)
print(f'Number of graphs: {len(dataset)}')
# 打印数据集中图中每个节点的特征向量的数量 dataset.num_features
print(f'Number of features: {dataset.num_features}')
# 打印数据集中图中节点分类的数量 dataset.num_classes
print(f'Number of classes: {dataset.num_classes}')
#查看数据集的格式
data = dataset[0] # Get the first graph object.
#数据集的格式
#这里的 [34, 34] 表示节点特征矩阵的维度是 34 x 34,即有34个节点,每个节点的特征是一个长度为34的向量。
#edge_index=[2,156] 是边有156条,2是两个节点为一组
#edge_index这是边索引。在图中,边可以用一个包含两行的索引列表来表示,每列对应一条边。这里的 [2, 156] 可能表示有156条边,并且每条边由两个索引值组成
# y=[34]是标签的数量
#train_mask=[34]指在训练模型时使用的一种标签或者掩码,用于指示模型在训练过程中需要关注的特定数据区域或样本。,这样的数据有34个
#train_mask是训练掩码。在图神经网络中,通常会使用掩码来标识哪些节点是用于训练的。这里的 [34] 可能表示有34个节点,每个节点都有一个掩码值来指示是否在训练过程中使用
print(data)
#打印节点之间边的关系,输出为一对一对的节点编号,
#.t()方法用于将张量的维度进行转置
#原来edge_index格式为上下对应,转置后为左右对应
edge_index = data.edge_index
print(edge_index.t())


#使用networkx可视化展示
#导入to_networkx,方便把数据转化成networkx的格式
from torch_geometric.utils import to_networkx
#把数据集的格式转变成networkx的格式 ,参数:数据集的格式,无向图
G = to_networkx(data, to_undirected=True)
#画网络的图,用上面定义的函数,传入数据集的networkx的格式,以及每个节点的分类编号(不同数字代表了不同颜色)--本来要传入每个节点的颜色
visualize_graph(G, color=data.y)




模型定义和训练方法
  1. 使用 torch.manual_seed(1234) 设置随机种子,以确保每次运行时结果一致性,请详细解释1234是什么,以及为什么设置随机种子,以及是怎么确保每次运行时结果一致性?
    在机器学习和深度学习中,随机性是非常常见的。许多算法和模型在训练过程中会使用随机数来初始化参数、打乱数据集或者引入噪声,这些随机因素可以帮助模型更好地泛化和学习。
    然而,有时候我们希望确保每次运行代码时得到的结果是一致的,这就需要使用随机种子。随机种子是一个整数,它作为随机数生成器的输入,决定了随机数的生成顺序。如果每次使用相同的种子,随机数生成器将会按照确定性的方式生成相同的随机数序列,从而保证结果的可重复性和一致性。
    具体到你提到的 torch.manual_seed(1234),这条命令使用了 PyTorch 深度学习框架中的函数 manual_seed,将随机数生成器的种子设置为 1234。1234 实际上是一个任意选定的整数种子值,只要它保证在不同的运行中使用相同的种子,就能够确保结果的一致性。
    为什么选择 1234 这个具体的数值呢?通常来说,种子的选择并没有严格的限制,只要是一个整数即可。选择常见的数值如 1234 或者 42 是为了方便记忆和使用。在实际应用中,你可以选择任何整数作为种子值,只要它在代码的多次运行中保持不变即可。
    总结起来,设置随机种子的目的是为了确保在不同运行中得到相同的随机数序列,从而使得代码的运行结果可以重现和验证。这对于调试代码、比较不同算法的性能或者进行科学研究都是非常重要的。
  2. self.conv3 = GCNConv(4, 2) # 输入特征维度为 4,输出特征维度为 2,为什么要进行特征维度的转换,为什么特征维度要减少,特征维度又是什么简单的举个例子,特征维度和类别有什么关系:

1.为什么要进行特征维度的转换?

特征维度的转换通常是为了适应模型的需求或者数据的特性。在你的例子中,GCNConv(4, 2) 表示使用了一个图卷积层(GCNConv),输入特征维度为 4,输出特征维度为 2。这意味着每个节点或对象在输入时有一个4维的特征表示,经过GCNConv后,输出为2维的特征表示。这种转换可以有多种原因,包括降低计算复杂度、提高模型泛化能力或者满足特定任务的需求。

2.为什么特征维度要减少?

特征维度减少可能是由于需要减少模型的参数量或计算复杂度,或者为了在输入特征的基础上提取更加抽象和有用的特征。通过特征维度的减少,模型可以更有效地学习数据中的模式和结构,从而提高性能或泛化能力。

3.特征维度是什么?简单举个例子。

特征维度指的是描述每个节点或对象的属性或特征的向量的长度。例如,如果我们要对社交网络中的用户进行建模,每个用户可以用一个包含性别、年龄、所在地区等特征的向量来表示。如果我们用一个长度为3的向量来表示每个用户(性别、年龄、地区),那么这个向量的长度3就是特征维度。

4.特征维度和类别有什么关系?

特征维度和类别之间没有直接的必然关系。特征维度是描述每个节点或对象的特征的向量的维度,而类别则通常是描述每个节点或对象所属的类别或标签。在监督学习任务中,通常会根据节点的特征来预测节点的类别或标签,但它们是两个不同的概念。

总结起来,特征维度的转换和减少是为了适应模型需求和数据特性,使得模型能够更有效地学习和表示数据中的模式和结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import torch
import networkx as nx
import matplotlib.pyplot as plt

#画网络图的函数定义
def visualize_graph(G, color):
#画图的尺寸
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])

nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False,
node_color=color, cmap="Set2")
plt.show()

#画散点图的函数定义
def visualize_embedding(h, color, epoch=None, loss=None):
#创建一个大小为 7x7 英寸的图像窗口,并设置不显示任何刻度。
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
#将输入的向量 h 转换为 NumPy 数组,这一步通常是因为 Matplotlib 处理的输入必须是 NumPy 数组
#将输入的嵌入向量h从tensor转换为NumPy数组,并且将其从GPU移动到CPU上(如果在GPU上)
h = h.detach().cpu().numpy()
# h[:, 0]表示所有样本的第一个特征,h[:, 1]表示所有样本的第二个特征
# s=140表示散点的大小,c=color表示散点的颜色,cmap="Set2"表示使用Set2颜色映射
plt.scatter(h[:, 0], h[:, 1], s=140, c=color, cmap="Set2")
# 如果提供了epoch和loss参数,则显示在图像的x轴标签上
if epoch is not None and loss is not None:
plt.xlabel(f'Epoch: {epoch}, Loss: {loss.item():.4f}', fontsize=16)
plt.show()

from torch_geometric.datasets import KarateClub

dataset = KarateClub()

#查看数据集的格式
data = dataset[0] # Get the first graph object.


import torch
#导入线性分类器
from torch.nn import Linear
#导入图卷积层
from torch_geometric.nn import GCNConv

#定义一个GCN类,该类继承自 torch.nn.Module
class GCN(torch.nn.Module):
#初始化
def __init__(self):
super().__init__()
# 设置随机种子以确保可重复性
#随机种子是一个整数,如果每次使用相同的种子,随机数生成器将会按照确定性的方式生成相同的随机数序列,从而保证结果的可重复性和一致性。
torch.manual_seed(1234)
#定义第一层卷积层---骨架
# 输入特征维度为 dataset.num_features,34维向量,输出特征维度为 4
self.conv1 = GCNConv(dataset.num_features, 4) # 只需定义好输入特征和输出特征即可
#定义第二层卷积层--骨架
#输入特征维度为 4,输出特征维度为 4
self.conv2 = GCNConv(4, 4)
# 定义第三层卷积层---骨架
# 输入特征维度为 4,输出特征维度为 2
self.conv3 = GCNConv(4, 2)
#定义分类器,---骨架
# 输入特征维度为 2(最终的GCN输出维度),输出特征维度为 dataset.num_classes(类别数量)
#线性层层通常用于学习输入特征与输出之间的线性映射关系
self.classifier = Linear(2, dataset.num_classes)
#定义前向传播函数:输入为图和邻接矩阵
def forward(self, x, edge_index):
#第一层卷积操作 self.conv1
#它将输入特征 x 和图的邻接矩阵 edge_index 作为输入,进行图卷积操作,生成新的特征表示 h
h = self.conv1(x, edge_index) # 输入特征与邻接矩阵(注意格式,上面那种)
#激活函数 对经过图卷积层得到的特征 h 应用双曲正切(tanh)激活函数,以增加网络的非线性表达能力
h = h.tanh()
#后续的图卷积层
h = self.conv2(h, edge_index)
h = h.tanh()
h = self.conv3(h, edge_index)
h = h.tanh()
# 将经过多层图卷积和激活函数处理后的特征 h 输入到 self.classifier 中进行分类
# 分类层
#根据之前提到的 self.classifier = Linear(2, dataset.num_classes),它是一个线性层,将输入特征映射到数据集的类别数目 dataset.num_classes
out = self.classifier(h)
#out 是分类层的输出,即最终的分类结果,四维向量;h 是最后一个图卷积层经过激活函数后的特征表示,二维向量
return out, h




model = GCN()
print(model)



_, h = model(data.x, data.edge_index)
print(_)
print("**************")
print(h)
print(f'Embedding shape: {list(h.shape)}')

visualize_embedding(h, color=data.y)

# 训练模型
# time.sleep(0.3) 的作用是在每次可视化更新之间增加一个0.3秒的延迟。
import time

# 创建一个图卷积网络模型实例
model = GCN()
# 定义损失函数 定义交叉熵损失函数作为损失标准
criterion = torch.nn.CrossEntropyLoss() # Define loss criterion.
# 定义优化器 使用Adam优化器来优化模型参数 学习率为0.01
optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # Define optimizer.


# 定义训练函数
def train(data):
# 每次迭代前清零梯度,以避免梯度累积
optimizer.zero_grad()
# 调用模型进行前向传播,计算输出和中间特征表示h
out, h = model(data.x, data.edge_index) # h是两维向量,主要是为了咱们画个图
# 计算损失,仅考虑有标签的数据(半监督学习)
loss = criterion(out[data.train_mask], data.y[data.train_mask]) # semi-supervised
# 执行反向传播,计算梯度
loss.backward()
# 根据计算出的梯度更新模型的参数
optimizer.step()
# 返回损失值和中间特征表示 h
return loss, h


# 训练循环 共进行401个epoch的训练
for epoch in range(401):
loss, h = train(data)
# 每隔10个epoch,调用散点图可视化
if epoch % 10 == 0:
#传入参数,二维特征向量,颜色,x轴上的标签有epoch、loss
#是将高维特征降维至二维,并着色以表示类别信息,相同类别的会聚在一起
visualize_embedding(h, color=data.y, epoch=epoch, loss=loss)
time.sleep(0.3)
点分类任务实战

Cora dataset数据集,论文分别归类成不同类别,论文之间存在引用关系,每个点代表一篇论文
论文引用数据集,每一个点有1433维向量
最终要对每个点进行7分类任务(每个类别只有20个点有标注)

  1. 错误:无法下载数据集 Cannot connect to host raw.githubusercontent.com:443 ssl:default [getaddrinfo failed]
  • 修改Pyg源码,右键放在Planetoid上,按下ctrl并点击进入Planetoid.py文件
  • ctrl+F搜索url 将
    1
    url = 'https://github.com/kimiyoung/planetoid/raw/master/data'
    修改为:
    1
    url = 'https://gitee.com/jiajiewu/planetoid/raw/master/data'
    论文引用数据集代码实战
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    #导入 PyTorch Geometric 库中的 Planetoid 数据集模块,该模块提供了几个常用的图数据集,包括 Cora。
    from torch_geometric.datasets import Planetoid#下载数据集用的
    #导入 PyTorch Geometric 库中的 NormalizeFeatures 变换,用于对图节点的特征进行归一化处理
    from torch_geometric.transforms import NormalizeFeatures
    #root='./data/Planetoid':指定数据集下载和存储的根目录。
    #name='Cora':指定要下载和处理的数据集名称,这里是 Cora 数据集。
    #transform=NormalizeFeatures():指定数据预处理的方式,这里使用了 NormalizeFeatures 变换,它将对图中节点的特征进行归一化处理,确保特征在相似的尺度上,有助于模型的训练和收敛
    dataset = Planetoid(root='./data/Planetoid', name='Cora', transform=NormalizeFeatures())#transform预处理

    print()

    # 输出数据集的基本信息
    #打印数据集名字
    print(f'Dataset: {dataset}:')
    print('======================')
    # 输出图的数量
    print(f'Number of graphs: {len(dataset)}')
    # 输出每个节点特征的维度
    print(f'Number of features: {dataset.num_features}')
    # 输出数据集中的类别数量
    print(f'Number of classes: {dataset.num_classes}')

    # 获取第一个图对象
    data = dataset[0]

    print()
    #输出图对象的详细信息
    print(data)
    print('===========================================================================================================')

    # 输出关于图结构的统计信息
    # 输出图中节点的数量
    print(f'Number of nodes: {data.num_nodes}')
    # 输出图中边的数量
    print(f'Number of edges: {data.num_edges}')
    # 输出平均节点度数 计算为总边数除以节点数
    print(f'Average node degree: {data.num_edges / data.num_nodes:.2f}')
    # 输出训练节点的数量(使用掩码标记) 通过训练节点的掩码(mask)求和得到
    print(f'Number of training nodes: {data.train_mask.sum()}')
    # 输出训练节点的标签比例 计算为训练节点数量除以总节点数量
    print(f'Training node label rate: {int(data.train_mask.sum()) / data.num_nodes:.2f}')
    # 输出图是否包含孤立节点
    print(f'Has isolated nodes: {data.has_isolated_nodes()}')
    # 输出图是否包含自环 起点和终点为同一个节点的边)
    print(f'Has self-loops: {data.has_self_loops()}')
    # 输出图是否为无向图
    print(f'Is undirected: {data.is_undirected()}')


    # 可视化部分,定义可视化散点函数
    # 用于在 notebook 环境中显示 matplotlib 绘制的图形,并将图形嵌入到 notebook 中
    # %matplotlib inline
    # 导入 matplotlib 库,用于绘制图形
    import matplotlib.pyplot as plt
    #从 scikit-learn 库中导入 t-SNE 算法,用于降低数据的维度。
    from sklearn.manifold import TSNE


    #定义一个名为 visualize 的函数,接受两个参数 h 和 color。
    # 参数 h 是一个 PyTorch 张量,这里会传入特征向量,color 是用于指定每个数据点颜色的数组。
    def visualize(h, color):
    #使用 t-SNE 将输入的高维数据 h 降至二维,并通过 .detach().cpu().numpy() 将 PyTorch 张量转换为 NumPy 数组,以便 t-SNE 可以处理。
    z = TSNE(n_components=2).fit_transform(h.detach().cpu().numpy())
    #创建一个新的图形窗口,并设置图形大小为 10x10 英寸。
    plt.figure(figsize=(10,10))
    plt.xticks([])
    plt.yticks([])
    #使用 plt.scatter 函数绘制散点图。z[:, 0] 和 z[:, 1] 分别是 t-SNE 转换后的二维数据的 x 和 y 坐标。
    # s=70 指定散点的大小为 70,c=color 设置散点的颜色为传入的颜色,cmap="Set2" 指定使用 "Set2" 颜色映射方案。
    plt.scatter(z[:, 0], z[:, 1], s=70, c=color, cmap="Set2")
    plt.show()



    # 这句语句导入了整个PyTorch库
    import torch
    #Linear类是PyTorch中用于定义线性变换(也称为全连接层)的类
    from torch.nn import Linear
    #导入了torch.nn.functional模块,并将其重命名为F
    #torch.nn.functional模块包含了许多神经网络的函数接口,如各种激活函数、损失函数、池化操作和卷积操作等
    import torch.nn.functional as F

    #对比实验,第一类传统的全连接层,第二类GNN
    ##第一类
    # 定义了一个简单的多层感知机(MLP)模型
    #MLP继承自torch.nn.Module
    class MLP(torch.nn.Module):
    # 实例化MLP对象就要传入参数hidden_channels,实例化就立即执行初始函数
    def __init__(self, hidden_channels):
    super().__init__()
    # 设置随机种子,保证每次运行结果一致性
    torch.manual_seed(12345)
    #定义了一个线性层 lin1,输入为 数据特征向量个数,输出为 hidden_channels,隐藏层个数,自己赋值
    self.lin1 = Linear(dataset.num_features, hidden_channels)
    #定义了第二个线性层 lin2,输入为 hidden_channels,隐藏层个数,输出为数据集的分类个数
    self.lin2 = Linear(hidden_channels, dataset.num_classes)
    #只有在实例化MLP的对象后,在对象中传入参数才自动执行该函数
    def forward(self, x):
    # 对输入 x 应用第一层线性变换 lin1
    x = self.lin1(x)
    #对第一层输出应用 ReLU 激活函数,将负值置零
    x = x.relu()
    #对经过激活函数后的输出进行 dropout 操作,防止过拟合
    #p=0.5 表示以 0.5 的概率将输入元素置零,self.training 表示当前模型是否处于训练模式
    #在训练阶段使用,而在评估或推理阶段则不使用 dropout
    x = F.dropout(x, p=0.5, training=self.training)
    #对 dropout 后的输出应用第二层线性变换 lin2
    x = self.lin2(x)
    #返回最后的特征向量
    return x

    # 创建了一个 MLP 类的实例名字为model,指定隐藏层的通道数为 16
    #MLP类在初始化时定义了两个线性层,这些层的参数会在训练过程中进行优化
    model = MLP(hidden_channels=16)
    #打印模型信息
    print(model)


    # 定义损失函数
    #定义了交叉熵损失函数作为模型的优化目标
    #在多分类问题中,交叉熵损失函数通常用于衡量模型预测输出与真实标签之间的差异
    criterion = torch.nn.CrossEntropyLoss() # Define loss criterion.
    # 定义了Adam优化器,用于更新模型的参数
    # 优化器将模型的参数(通过model.parameters()获取)与学习率(lr=0.01)和权重衰减(weight_decay=5e-4)结合
    optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) # Define optimizer.

    # 定义训练函数
    def train():
    # 将模型设置为训练模式,启用dropout等训练特定的行为
    model.train()
    # 清除之前的梯度,以避免梯度累积
    optimizer.zero_grad() # Clear gradients.
    # 通过模型进行一次前向传播
    out = model(data.x) # Perform a single forward pass.
    # 计算损失,仅基于训练节点的预测结果
    loss = criterion(out[data.train_mask], data.y[data.train_mask]) # Compute the loss solely based on the training nodes.
    # 计算损失相对于模型参数的梯度
    loss.backward() # Derive gradients.
    # 根据梯度更新模型的参数
    optimizer.step() # Update parameters based on gradients.
    # 返回损失值
    return loss

    # 定义测试函数
    def test():
    # 将模型设置为评估模式,关闭dropout等训练特定的行为
    model.eval()
    # 通过模型进行前向传播,返回模型的输出--经过模型预测的七维向量
    out = model(data.x)
    # 确定每个样本预测输出的类别
    # 首先计算每一行中的最大值,返回的是最大值所在的索引
    # dim=1按照行寻找 ,dim = 0 ,按照列寻找
    # 这样,pred 张量就包含了每个样本预测的类别索引
    pred = out.argmax(dim=1) # Use the class with highest probability.
    # 比较预测输出与真实标签,得出每个测试节点的正确性。
    test_correct = pred[data.test_mask] == data.y[data.test_mask] # Check against ground-truth labels.
    # 计算测试集上的准确率,即正确预测的比例。
    test_acc = int(test_correct.sum()) / int(data.test_mask.sum()) # Derive ratio of correct predictions.
    # 返回正确率
    return test_acc

    # 使用一个简单的for循环进行模型的训练,共进行200个epoch
    # 在每个epoch中,调用train()函数执行一次训练,并打印当前epoch的损失值。
    for epoch in range(1, 201):
    loss = train()
    print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')

    # 调用测试函数,测试准确率
    test_acc = test()
    print(f'Test Accuracy: {test_acc:.4f}')

    ##第二类,GNN模型
    # 导入图卷积层的依赖
    from torch_geometric.nn import GCNConv

    # 定义GNN类
    # GCN 类继承自 torch.nn.Module,表示这是一个PyTorch模型
    class GCN(torch.nn.Module):
    def __init__(self, hidden_channels):
    super().__init__()
    torch.manual_seed(1234567)
    self.conv1 = GCNConv(dataset.num_features, hidden_channels)
    self.conv2 = GCNConv(hidden_channels, dataset.num_classes)

    def forward(self, x, edge_index):
    x = self.conv1(x, edge_index)
    x = x.relu()
    x = F.dropout(x, p=0.5, training=self.training)
    x = self.conv2(x, edge_index)
    return x


    model = GCN(hidden_channels=16)
    print("---------GNN模型的结构------")
    print(model)

    #未训练时的GNN
    # 将模型设置为评估模式,关闭dropout等训练特定的行为
    model.eval()
    # 使用模型对输入数据 data.x 和图的边索引 data.edge_index 进行前向传播,得到输出 out
    out = model(data.x, data.edge_index)
    # 调用 visualize 函数来可视化模型的输出,其中 out 是节点特征表示(七维度),data.y 是节点的真实类别标签。
    # out 是七维,在可视化中映射为二维方便展示,data.y是数字,直接使用数字代表颜色,这样不同类型的点就是不同颜色
    visualize(out, color=data.y)

    # 训练GCN模型
    # 创建一个隐藏层特征数为16的GCN模型实例
    model = GCN(hidden_channels=16)
    # Adam优化器,学习率为0.01,权重衰减为5e-4
    optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
    # 交叉熵损失函数,用于多分类任务
    criterion = torch.nn.CrossEntropyLoss()

    #定义训练函数
    def train():
    # 将模型设置为训练模式
    model.train()
    # 梯度清零,防止梯度累加
    optimizer.zero_grad()
    # 进行一次前向传播
    out = model(data.x, data.edge_index)
    # 计算损失,仅考虑训练集掩码部分
    loss = criterion(out[data.train_mask], data.y[data.train_mask])
    # 反向传播计算梯度
    loss.backward()
    # 更新模型参数
    optimizer.step()
    # 返回损失值
    return loss
    # 定义测试函数
    def test():
    # 将模型设置为评估模式
    model.eval()
    # 进行前向传播
    out = model(data.x, data.edge_index)
    # 获取预测结果中每个节点的类别预测(取最大值对应的类别)
    pred = out.argmax(dim=1)
    # 比较预测结果与真实标签,得到预测正确的部分
    test_correct = pred[data.test_mask] == data.y[data.test_mask]
    # 计算测试集准确率
    test_acc = int(test_correct.sum()) / int(data.test_mask.sum())
    # 返回测试集准确率
    return test_acc

    # 开始训练,训练模型100轮
    for epoch in range(1, 101):
    # 每执行一次训练,并返回训练损失
    loss = train()
    # 打印当前epoch的训练损失值
    print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')

    # 准确率计算
    test_acc = test()
    print(f'Test Accuracy: {test_acc:.4f}')

    #可视化展示训练后的结果
    # 模型设置为评估模式
    model.eval()
    # 输出每个节点经过训练过的模型后的特征向量张量
    out = model(data.x, data.edge_index)
    #可视化每个节点,每个类别是不同颜色
    visualize(out, color=data.y)


    ##传统的的MLP模型不需要输入边,只需要输入节点特征向量,GNN模型需要


制作自己的图数据集
  1. 创建一个包含节点特征、节点标签和边索引的图数据对象,
  2. 关键数据为节点特征,节点标签,边索引—以下为代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import warnings
    # 设置忽略警告,这样在后续运行中不会显示警告信息
    warnings.filterwarnings("ignore")
    # 导入 PyTorch 库,用于张量操作和计算
    import torch
    # 定义节点特征 x 和标签 y--类别标签
    # x 是一个形状为 (4, 2) 的浮点型张量,表示图中每个节点的特征向量
    # 四个节点,,每个节点有两个特征
    x = torch.tensor([[2,1], [5,6], [3,7], [12,0]], dtype=torch.float)
    # y 是一个形状为 (4,) 的浮点型张量,表示每个节点的标签或类别,这里四个节点类别分别是0,1,0,1
    y = torch.tensor([0, 1, 0, 1], dtype=torch.float)

    # edge_index 是一个形状为 (2, 5) 的长整型张量,
    # 每一列表示一条边的起始节点和终止节点的索引。例如,第一条边连接节点 0 和节点 3
    edge_index = torch.tensor([[0, 1, 2, 0, 3],#起始点
    [1, 0, 1, 3, 2]], dtype=torch.long)#终止点

    # 使用 Data 类来创建图数据对象
    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 对象包含了节点特征 x、节点标签 y 和边的索引 edge_index
    # 实例化自己的数据-- 实例化Data类,传入自己数据的内容x,y,edge_index
    data = Data(x=x, y=y, edge_index=edge_index)

    print(data)

  3. 输出data的格式为Data(x=[4, 2], edge_index=[2, 5], y=[4])代表:
    1.数据类型为Data,这是图神经网络数据的pytorch格式
    2.x: 节点的特征矩阵,形状为 [num_nodes, num_node_features],这里有四个节点,每个节点有两个特征向量。
    3.edge_index: 边的索引矩阵,形状为 [2, num_edges],其中每一列表示一条边的连接关系,这里有5条边。
    4.edge_attr (可选): 边的属性,如果有的话,形状为 [num_edges, num_edge_features]。
    5.y (可选): 节点的标签或类别,形状为 [num_nodes],这里有四个节点有标签
电商购买预测实战
  1. 数据:yoochoose-clicks:国外电商用户浏览数据,每个人(session_id)是一张图,每一行表示一条浏览数据
    包含数据:
    session_id: 用户id
    timestamp: 浏览时间
    item_id: 用户浏览的商品的id
    category: 商品种类id
  2. 数据:yoochoose-buys.dat:用户购买数据,每一行代表用户的订单
    包含数据:
    session_id: 用户id
    timestamp: 下单时间
    item_id: 用户购买的商品的id
    price: 商品价格
    quantity: 该用户购买数量
    数据的基本处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    # 处理两个数据集 yoochoose-clicks.dat 和 yoochoose-buys.dat,然后使用 LabelEncoder 对 item_id 进行编码,并显示处理后的数据集的前几行
    # 导入 LabelEncoder 类,用于将数据编码--最小的数据编码为0,按照数据的大小依次增加
    from sklearn.preprocessing import LabelEncoder
    # 导入 pandas 库,用于数据处理和分析
    import pandas as pd

    # 读取 yoochoose-clicks.dat 数据集
    # 由于第三列包含混合类型数据,所以在读取时指定第三列的数据类型为字符串
    # 指定没有头部信息
    df = pd.read_csv('yoochoose-clicks.dat', header=None, dtype={3: str})
    # 指定列名
    df.columns = ['session_id', 'timestamp', 'item_id', 'category']

    # 读取 yoochoose-buys.dat 数据集
    buy_df = pd.read_csv('yoochoose-buys.dat', header=None, dtype={3: str})
    # 指定列名
    buy_df.columns = ['session_id', 'timestamp', 'item_id', 'price', 'quantity']

    # 使用 LabelEncoder 对 item_id 进行编码
    # 创建 LabelEncoder 对象
    item_encoder = LabelEncoder()
    # 对 df 数据框中的 item_id 列进行编码转换
    df['item_id'] = item_encoder.fit_transform(df.item_id)

    # 显示处理后的数据前五行
    print(df.head())

    # 导入 NumPy 库,用于生成随机数和进行数值操作
    import numpy as np
    #数据有点多,咱们只选择其中一小部分来建模
    # df.session_id.unique()获取 df 数据框中唯一的session_id 列
    # 从唯一的session_id 中随机选择 100,000 个id给sampled_session_id,replace=False 表示选择过程中不替换
    # 采样后的session_id
    sampled_session_id = np.random.choice(df.session_id.unique(), 100000, replace=False)
    # 根据抽样后的sampled_session_id 列表,筛选出包含在抽样列表中的行,从而得到抽样后的数据集 df
    df = df.loc[df.session_id.isin(sampled_session_id)]
    # 打印df中的信息(去重)
    print(df.nunique())

    # 添加一列来展示是否购买商品
    # 检查 df 数据框中的session_id是否存在于 buy_df 数据框(有就一定有购买商品)中的session_id 中,返回布尔类型的 Series
    # 将上述布尔 Series 赋值给 df 数据框的新列 label
    df['label'] = df.session_id.isin(buy_df.session_id)
    # 打印前五行
    print(df.head())



    构建图数据集
    1. 每一个session_id都当作一个图,每一个id对应一个人,每一个图具有多个点(会购买不同商品)和一个标签(是否购买)
    2. 图:session_id(人的编号)
      节点: sess_item_id(item_id编码后的结果) 从0开始,0,1,2,3,4…
      节点特征向量 :item_id,与sess_item_id是映射关系
      边:edge_index是sess_item_id组成的,按照浏览的顺序构成边
    3. 制作流程:
      1.首先遍历数据中每一组session_id,目的是将其制作成(from torch_geometric.data import Data)格式
      2.对每一组session_id中的所有item_id进行编码(例如15453,3651,15452)就按照数值大小编码成(2,0,1)
      3.这样编码的目的是制作edge_index,因为在edge_index中我们需要从0,1,2,3.。。开始
      4.点的特征就由其ID组成,edge_index是这样,因为咱们浏览的过程中是有顺序的比如(0,0,2,1)
      5.所以边就是0->0,0->2,2->1这样的,对应的索引就为target_nodes: [0 2 1],source_nodes: [0 0 2]
      6.最后转换格式data = Data(x=x, edge_index=edge_index, y=y)
      7.最后将数据集保存下来(以后就不用重复处理了)
    • 数据组合和提取,for循环中进行提取和制作图数据
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      #-------制作数据集-------#
      import torch
      # torch_geometric是PyTorch的一个扩展库,专门用于处理图神经网络(GNN)相关的任务和数据
      # torch_geometric.data模块提供了用于处理图数据的工具和类
      # 使用 Data 类来创建图数据对象
      from torch_geometric.data import Data
      import warnings
      # 设置忽略警告,这样在后续运行中不会显示警告信息
      warnings.filterwarnings("ignore")

      #InMemoryDataset是一个类,用于加载和处理图数据集,通常用于较小的数据集,将数据一次性加载到内存中进行处理和访问
      from torch_geometric.data import InMemoryDataset
      # tqdm是一个Python库,用于在命令行中显示进度条
      from tqdm import tqdm

      # 选择前100行作为测试数据
      df_test = df[:100]
      print("******************测试数据******************")
      print((df_test))
      # 根据'session_id'对测试数据进行分组
      grouped = df_test.groupby('session_id')
      print("******************测试数据分组后******************")
      # DataFrame类型不能直接读取所有信息,需要循环读取
      print(grouped)
      # 读取分组后的数据
      ## tqdm(grouped) 用于显示循环进度条
      ## 在分组后的grouped循环读取每个session_id的数据,数据暂时用group变量接收
      ## group 是当前session_id对应的子DataFrame
      for session_id, group in tqdm(grouped):
      print('session_id:', session_id)
      # 对每个session_id中的 item_id 进行编码,将其转换为整数形式并存储在 sess_item_id 中
      sess_item_id = LabelEncoder().fit_transform(group.item_id) # 使用LabelEncoder对item_id进行编码
      print('sess_item_id:', sess_item_id)
      # 重新设置 group 的索引,丢弃旧索引
      group = group.reset_index(drop=True)
      # group添加一列为sess_item_id
      group['sess_item_id'] = sess_item_id
      # 打印每个session_id的数据(group)
      print('group:', group)
      # 获取会话中的节点特征向量,即会话中不重复的item_id
      # 每次循环寻找组中对应的session_id的表,但是只保留两列--sess_item_id和item_id
      # 接着,选定的数据按照sess_item_id列进行排序 。sort_values('sess_item_id')
      # 然后只保留item_id列中唯一的值 .item_id.drop_duplicates()
      # 最后将结果转换为一个NumPy数组(.values)
      node_features = group.loc[group.session_id == session_id, ['sess_item_id', 'item_id']].sort_values('sess_item_id').item_id.drop_duplicates().values
      # 转换为PyTorch张量
      ## 将NumPy数组转换为PyTorch的LongTensor类型
      ## .unsqueeze(1)的作用是在张量的第一维度上增加一个维度
      node_features = torch.LongTensor(node_features).unsqueeze(1) # 转换为PyTorch的LongTensor并增加维度
      print('node_features:', node_features)

      # 构建边的索引,即会话中每个节点之间的连接关系
      # 目标节点为每个sess_item_id(除了第一个)
      target_nodes = group.sess_item_id.values[1:]
      # 源节点为每个sess_item_id(除了最后一个)
      source_nodes = group.sess_item_id.values[:-1]
      print('target_nodes:', target_nodes)
      print('source_nodes:', source_nodes)

      # 构建边索引的Tensor
      # 描述了节点之间的连接关系,源节点到目标节点
      edge_index = torch.tensor([source_nodes, target_nodes], dtype=torch.long)

      # 节点特征张量
      x = node_features
      # 标签值,这里标签在每一组DataFrame中为label列的第一个值--只有一个值
      y = torch.FloatTensor([group.label.values[0]])

      # 创建PyTorch Geometric中的Data对象,表示一个图数据
      data = Data(x=x, edge_index=edge_index, y=y)
      print('data:', data)
    • 转换为可用于训练的格式
    • 创建一个自定义的数据集类 YooChooseBinaryDataset,继承自 InMemoryDataset,用于处理和加载特定格式的数据
    • 只需要重写def process(self): 里面是我们对数据的处理,数据集路径保存在root参数中
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      # 处理原始数据并转换为可用于训练的格式
      # 为了创建一个自定义的数据集类 YooChooseBinaryDataset,继承自 InMemoryDataset,用于处理和加载特定格式的数据
      from torch_geometric.data import InMemoryDataset
      from tqdm import tqdm

      # 定义一个数据集类
      class YooChooseBinaryDataset(InMemoryDataset):
      def __init__(self, root, transform=None, pre_transform=None):
      super(YooChooseBinaryDataset, self).__init__(root, transform, pre_transform) # transform就是数据增强,对每一个数据都执行
      self.data, self.slices = torch.load(self.processed_paths[0])

      @property
      def raw_file_names(self): # 检查self.raw_dir目录下是否存在raw_file_names()属性方法返回的每个文件
      # 如有文件不存在,则调用download()方法执行原始文件下载
      return []

      @property
      def processed_file_names(self): # 检查self.processed_dir目录下是否存在self.processed_file_names属性方法返回的所有文件,没有就会走process
      return ['yoochoose_click_binary_1M_sess.dataset']

      def download(self):
      pass

      def process(self):
      data_list = []

      # process by session_id
      grouped = df.groupby('session_id')
      for session_id, group in tqdm(grouped):
      sess_item_id = LabelEncoder().fit_transform(group.item_id)
      group = group.reset_index(drop=True)
      group['sess_item_id'] = sess_item_id
      node_features = group.loc[group.session_id == session_id, ['sess_item_id', 'item_id']].sort_values(
      'sess_item_id').item_id.drop_duplicates().values

      node_features = torch.LongTensor(node_features).unsqueeze(1)
      target_nodes = group.sess_item_id.values[1:]
      source_nodes = group.sess_item_id.values[:-1]

      edge_index = torch.tensor([source_nodes, target_nodes], dtype=torch.long)
      x = node_features

      y = torch.FloatTensor([group.label.values[0]])

      data = Data(x=x, edge_index=edge_index, y=y)
      # 组合所有的图放到data_list
      data_list.append(data)
      # 将多个 Data 对象整理成一个大的数据对象 data 和相关的切片信息 slices--用于快速检索
      data, slices = self.collate(data_list)
      # 保存处理后的数据
      torch.save((data, slices), self.processed_paths[0])
      # 初始化 YooChooseBinaryDataset 的数据集对象,并指定数据集的根目录为 'data/'
      dataset = YooChooseBinaryDataset(root='data/')
    构建网络模型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    # 构建网络模型
    # 节点特征的嵌入维度,调整嵌入维度通常是超参数优化的一部分
    embed_dim = 128
    # 导入图卷积层SAGEConv和图池化(pooling)层
    # SAGEConv通过聚合节点的邻居信息来更新每个节点的表示
    # 池化层用于动态地聚合图中的节点,通常通过一些指标(如节点的度)来选择池化的节点
    from torch_geometric.nn import TopKPooling, SAGEConv
    # 导入全局池化函数--这些函数用于将整个图的信息聚合到一个固定长度的向量中
    # global_mean_pool计算所有节点特征的均值,而global_max_pool则计算所有节点特征的最大值
    from torch_geometric.nn import global_mean_pool as gap, global_max_pool as gmp
    # 激活函数
    import torch.nn.functional as F


    # 针对图进行分类任务
    class Net(torch.nn.Module):
    def __init__(self):
    super(Net, self).__init__()

    # 第一层SAGE卷积,输入维度为embed_dim,输出维度为128
    self.conv1 = SAGEConv(embed_dim, 128)
    # # 第一层池化,输入维度为128,保留比例为0.8
    self.pool1 = TopKPooling(128, ratio=0.8)
    # 第二层SAGE卷积,输入和输出维度均为128
    self.conv2 = SAGEConv(128, 128)
    self.pool2 = TopKPooling(128, ratio=0.8)
    self.conv3 = SAGEConv(128, 128)
    self.pool3 = TopKPooling(128, ratio=0.8)
    # 嵌入层的作用是将离散的物品ID(整数)映射为连续的向量表示
    # 嵌入层,用于将每个物品ID映射成128维的向量
    # num_embeddings 参数设定为 df.item_id.max() + 10,这表示嵌入层可以容纳的item_id的最大数量,这里加上10是为了有一定的余量
    # embedding_dim 参数设定为 embed_dim,即输入将被映射成的向量维度,这里是128维
    self.item_embedding = torch.nn.Embedding(num_embeddings=df.item_id.max() + 10, embedding_dim=embed_dim)
    # 线性层
    self.lin1 = torch.nn.Linear(128, 128)
    self.lin2 = torch.nn.Linear(128, 64)
    self.lin3 = torch.nn.Linear(64, 1)
    # 批归一化层
    self.bn1 = torch.nn.BatchNorm1d(128)
    self.bn2 = torch.nn.BatchNorm1d(64)
    # 激活函数层
    self.act1 = torch.nn.ReLU()
    self.act2 = torch.nn.ReLU()

    def forward(self, data):
    # 提取特征向量,边,以及batch
    x, edge_index, batch = data.x, data.edge_index, data.batch
    # print(x)
    # 特征向量进行编码映射(嵌入)为向量
    # x的形状变为(n, 1, 128),n为节点个数,128是嵌入维度,每一个特征向量将变成128维向量
    x = self.item_embedding(x)
    # print('item_embedding',x.shape)
    # # 去掉维度为1的那一维,现在x的形状为(n, 128),n行代表每一行都是一个节点,128代表128列,每一节点有128个特征向量
    x = x.squeeze(1)
    # print('squeeze',x.shape)
    # 第一层SAGE卷积和ReLU激活函数
    x = F.relu(self.conv1(x, edge_index))
    # print('conv1',x.shape)
    # 第一层TopKPooling,对节点进行池化操作
    x, edge_index, _, batch, _, _ = self.pool1(x, edge_index, None, batch) # pool之后得到 n*0.8个点
    # print('self.pool1',x.shape)
    # print('self.pool1',edge_index)
    # print('self.pool1',batch)
    # x1 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
    # 全局池化操作 gap: 是一个全局平均池化函数,计算每个图批次中所有节点特征值求和后取平均值。
    # 目的是提取图的特征向量,因为是对图进行预测
    x1 = gap(x, batch)
    # print('gmp',gmp(x, batch).shape) # batch*128
    # print('cat',x1.shape) # batch*256
    x = F.relu(self.conv2(x, edge_index))
    # print('conv2',x.shape)
    x, edge_index, _, batch, _, _ = self.pool2(x, edge_index, None, batch)
    # print('pool2',x.shape)
    # print('pool2',edge_index)
    # print('pool2',batch)
    # x2 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
    x2 = gap(x, batch)
    # print('x2',x2.shape)
    x = F.relu(self.conv3(x, edge_index))
    # print('conv3',x.shape)
    x, edge_index, _, batch, _, _ = self.pool3(x, edge_index, None, batch)
    # print('pool3',x.shape)
    # x3 = torch.cat([gmp(x, batch), gap(x, batch)], dim=1)
    x3 = gap(x, batch)
    # print('x3',x3.shape)# batch * 256
    # 整合不同尺度的全局特征
    x = x1 + x2 + x3 # 获取不同尺度的全局特征
    # 线性层
    x = self.lin1(x)
    # print('lin1',x.shape)
    # 激活函数
    x = self.act1(x)
    x = self.lin2(x)
    # print('lin2',x.shape)
    x = self.act2(x)
    # Dropout层,用于防止过拟合
    # 训练时丢弃(指的是置0)50%
    x = F.dropout(x, p=0.5, training=self.training)
    # 使用sigmoid函数输出每个样本的预测结果
    x = torch.sigmoid(self.lin3(x)).squeeze(1) # batch个结果
    # print('sigmoid',x.shape)
    return x
    训练模型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    # 定义训练函数,训练模型(Net)
    # 导入了 DataLoader 类,它用于批量加载数据
    from torch_geometric.loader import DataLoader

    # 定义训练函数 train()
    def train():
    # 设置模型为训练模式,这通常会激活一些特定于训练的行为,例如启用 dropout 等
    model.train()
    # 损失率
    loss_all = 0
    # 遍历训练数据加载器 train_loader 中的每个批次
    for data in train_loader:
    data = data
    # print('data',data)
    # 清零梯度,以准备计算新的梯度
    optimizer.zero_grad()
    # 使用模型进行前向传播,计算输出
    output = model(data)
    # 获取数据的标签
    label = data.y
    # 计算模型预测与真实标签之间的损失
    loss = crit(output, label)
    # 反向传播,计算梯度
    loss.backward()
    # 累加损失,乘以批次中图的数量,以权衡不同批次大小的影响。
    loss_all += data.num_graphs * loss.item()
    # 更新模型参数
    optimizer.step()
    # 最后返回每个图的平均损失
    return loss_all / len(dataset)

    # 开始训练模型
    # 实例化模型
    model = Net()
    # 定义优化器 使用 Adam 优化器来优化模型参数,学习率为 0.001
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    # 定义评估函数 二分类交叉熵损失函数,用于计算模型预测与真实标签之间的损失
    crit = torch.nn.BCELoss()
    # 设置数据加载器, DataLoader 类加载数据集 dataset,每个批次大小为 64
    train_loader = DataLoader(dataset, batch_size=64)
    # 训练10轮
    for epoch in range(10):
    print('epoch:', epoch)
    # 每个 epoch,调用 train() 函数进行训练,并输出每个 epoch 的平均损失
    loss = train()
    print(loss)
    评估模型性能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    # 评估模型性能
    # 导入了 roc_auc_score 函数用以计算roc_auc_score
    # 其值越接近于1,表示模型性能越好
    from sklearn.metrics import roc_auc_score
    # 定义评估函数 evaluate,传参:加载器的数据和模型
    def evalute(loader,model):
    # 将模型设置为评估模式
    model.eval()

    # 存储模型预测的结果
    prediction = []
    # 存储真实标签
    labels = []

    # 禁用梯度计算
    with torch.no_grad():
    # 遍历数据加载器中的每个批次
    for data in loader:
    data = data#.to(device)
    # 使用模型进行预测
    pred = model(data)#.detach().cpu().numpy()

    # 获取数据的真实标签
    label = data.y#.detach().cpu().numpy()
    # 将预测结果添加到列表中
    prediction.append(pred)
    # 将真实标签添加到列表中
    labels.append(label)
    # 将列表中的预测结果和真实标签展平为一维数组
    prediction = np.hstack(prediction)
    labels = np.hstack(labels)
    # 使用 roc_auc_score 计算roc_auc_score(正确率)
    return roc_auc_score(labels,prediction)

    # 开始评估
    # 循环只执行一次
    for epoch in range(1):
    # 调用评估函数
    roc_auc_score = evalute(dataset,model)
    # 返回预测准确率
    print('roc_auc_score',roc_auc_score)
    云端训练
    1
    2
    3
    4
    !unzip /content/GNN.zip -d /content
    %cd /content/GNN
    pip install torch_geometric
    !python 电商购买预测.py --rect
图注意力机制 GAT
  1. Graph Attention Network
    与普通GNN的区别在于:
    1.GNN边上的数据原来是1,即邻接矩阵上的非0数字只有1;
    GAT边上的数据不在是1,而是根据不同的权重数字,比如节点1上所有边上数字和为1.即邻接矩阵上的数字发生改变。
    2.节点的特征向量将发生改变,它将根据边上的权重转换为新的特征向量

  2. 普通的GNN计算方法:

下面以节点1举例子

  1. GAT的操作
    1.节点i和节点j之间的权重参数计算:
    i节点和j节点之间分别乘上学习权重矩阵W后,拼接成新的向量进行Attention操作得到eij

2.具体操作如下:

接下来对节点的特征向量优化

以节点1举例:

序列图神经网络TGNN
  1. 全名 temporal graph neural network—随着时间得的变化节点的数量在发生变化(图的结构发生了变化)
图的相似度计算-SimGNN-GED

Graph Edit Distance

  1. 节点编码成向量-embedding (使用GCN)
  2. 把图编码成向量-embedding
  3. 节点特征向量与图特征向量做内积,内积越小(参考几何),代表与图关联越小,该节点显得不重要;内积越大,节点越重要
  4. 该内积作为节点的权重(注意力机制)
  5. 图的全局特征向量等于图中各节点特征向量乘节点权重的和
    下图为详细步骤:
代码实战

源码:https://github.com/benedekrozemberczki/SimGNN

1
2
3
4
5
6
!unzip /content/SimGNN -d /content
%cd /content/SimGNN-master/
%cd /content/SimGNN-master/src/
pip install torch_geometric
pip install texttable
!python main.py

程序解读
数据集:每一个json文件都包含两个图(一对输入),50个json文件就是50对图,两对输入一类称为图1,一类称为图二

  1. 测试集
    举两条例子
    “graph_1”这表示图1节点之间的连接关系,可以理解为邻接矩阵
    “ged”表示图1与与图2的(Graph Edit Distance)即描述两幅图的差异程度
    “graph_2”这表示图2节点之间的连接关系,可以理解为邻接矩阵
    “labels_2”表示表示图2每个节点的标签(节点属于第几类)(节点分类类别)
    “labels_1”表示表示图1每个节点的标签(节点属于第几类)(节点分类类别)
    1
    {"graph_1": [[0, 8], [0, 9], [0, 2], [0, 3], [0, 11], [1, 2], [1, 3], [1, 5], [1, 6], [1, 7], [2, 3], [2, 5], [2, 6], [2, 7], [2, 8], [2, 10], [2, 11], [3, 5], [3, 7], [3, 8], [3, 10], [3, 11], [4, 9], [4, 10], [4, 5], [4, 6], [4, 7], [5, 7], [5, 8], [5, 11], [6, 7], [6, 8], [6, 11], [7, 8], [7, 10], [7, 11], [8, 9], [10, 11]], "ged": 32, "graph_2": [[0, 1], [0, 2], [0, 4], [1, 8], [1, 10], [1, 2], [1, 7], [2, 4], [2, 7], [2, 9], [2, 11], [3, 10], [3, 11], [3, 5], [3, 6], [3, 7], [4, 9], [4, 11], [5, 8], [5, 9], [5, 6], [6, 9], [7, 9], [7, 10], [7, 11], [8, 9], [8, 10], [9, 10], [9, 11], [10, 11]], "labels_2": ["3", "5", "6", "5", "4", "4", "3", "6", "4", "8", "6", "6"], "labels_1": ["5", "5", "9", "8", "5", "7", "6", "9", "7", "3", "5", "7"]}
    1
    {"graph_1": [[0, 6], [0, 2], [0, 3], [0, 5], [0, 14], [1, 9], [1, 2], [1, 7], [1, 5], [1, 15], [2, 3], [2, 6], [2, 13], [2, 14], [2, 15], [3, 4], [3, 5], [3, 6], [3, 7], [3, 9], [3, 11], [3, 12], [3, 13], [3, 15], [4, 16], [4, 5], [4, 6], [4, 8], [4, 9], [4, 11], [4, 12], [4, 14], [5, 9], [5, 10], [5, 11], [5, 12], [5, 13], [5, 15], [6, 9], [6, 11], [6, 14], [6, 15], [7, 8], [7, 9], [7, 12], [7, 13], [7, 14], [8, 9], [8, 10], [8, 13], [8, 14], [9, 10], [9, 12], [9, 14], [9, 15], [9, 16], [10, 12], [10, 14], [11, 16], [11, 12], [11, 13], [11, 15], [13, 14], [14, 15], [15, 16]], "ged": 6, "graph_2": [[0, 1], [0, 4], [0, 6], [0, 7], [0, 10], [0, 11], [0, 14], [1, 3], [1, 4], [1, 5], [1, 12], [1, 14], [2, 4], [2, 3], [2, 12], [2, 7], [3, 6], [3, 10], [3, 12], [3, 14], [3, 15], [4, 7], [4, 8], [4, 9], [4, 11], [4, 12], [4, 13], [4, 15], [5, 8], [5, 10], [5, 11], [5, 12], [6, 16], [6, 8], [6, 10], [6, 13], [6, 15], [7, 12], [7, 15], [8, 16], [8, 10], [8, 12], [8, 13], [8, 14], [9, 10], [9, 11], [9, 13], [9, 14], [10, 11], [10, 12], [10, 14], [10, 15], [11, 13], [11, 14], [12, 13], [12, 14], [12, 15], [12, 16], [13, 14], [13, 15], [14, 15]], "labels_2": ["7", "6", "4", "7", "10", "5", "7", "5", "8", "5", "10", "7", "12", "8", "10", "8", "3"], "labels_1": ["5", "5", "7", "11", "9", "10", "8", "7", "6", "12", "5", "8", "7", "7", "10", "9", "4"]}
  2. 训练集
    举两条例子
    1
    {"labels_1": ["11", "11", "9", "11", "10", "7", "13", "11", "10", "9", "11", "8", "8", "10", "13"], "labels_2": ["8", "11", "5", "11", "9", "7", "9", "7", "12", "11", "11", "11", "10", "10", "14"], "graph_2": [[0, 1], [0, 4], [0, 5], [0, 8], [0, 11], [0, 12], [0, 13], [0, 14], [1, 2], [1, 3], [1, 6], [1, 7], [1, 8], [1, 10], [1, 11], [1, 12], [1, 13], [1, 14], [2, 9], [2, 12], [2, 5], [2, 14], [3, 4], [3, 6], [3, 7], [3, 8], [3, 9], [3, 10], [3, 11], [3, 12], [3, 13], [3, 14], [4, 6], [4, 8], [4, 9], [4, 10], [4, 11], [4, 13], [4, 14], [5, 7], [5, 9], [5, 10], [5, 13], [5, 14], [6, 8], [6, 9], [6, 10], [6, 11], [6, 12], [6, 14], [7, 8], [7, 9], [7, 10], [7, 14], [8, 9], [8, 10], [8, 11], [8, 12], [8, 13], [8, 14], [9, 10], [9, 11], [9, 13], [9, 14], [10, 11], [10, 12], [10, 14], [11, 12], [11, 13], [11, 14], [12, 13], [12, 14], [13, 14]], "ged": 11, "graph_1": [[0, 1], [0, 2], [0, 3], [0, 4], [0, 6], [0, 7], [0, 8], [0, 10], [0, 12], [0, 13], [0, 14], [1, 3], [1, 4], [1, 6], [1, 7], [1, 8], [1, 9], [1, 10], [1, 11], [1, 13], [1, 14], [2, 5], [2, 6], [2, 7], [2, 9], [2, 10], [2, 11], [2, 12], [2, 14], [3, 4], [3, 5], [3, 6], [3, 7], [3, 8], [3, 9], [3, 11], [3, 13], [3, 14], [4, 5], [4, 6], [4, 8], [4, 9], [4, 10], [4, 13], [4, 14], [5, 6], [5, 7], [5, 12], [5, 13], [6, 7], [6, 8], [6, 10], [6, 11], [6, 12], [6, 13], [6, 14], [7, 8], [7, 9], [7, 10], [7, 12], [7, 14], [8, 9], [8, 10], [8, 12], [8, 14], [9, 10], [9, 11], [9, 14], [10, 11], [10, 13], [10, 14], [11, 13], [11, 14], [12, 13], [12, 14], [13, 14]]}
    1
    {"labels_1": ["9", "10", "10", "8", "8", "11", "8", "8", "7", "10", "13", "10", "10", "9", "9", "10"], "labels_2": ["9", "9", "8", "11", "7", "10", "10", "8", "9", "8", "7", "9", "8", "6", "10", "11"], "graph_2": [[0, 2], [0, 3], [0, 5], [0, 6], [0, 7], [0, 9], [0, 12], [0, 14], [0, 15], [1, 2], [1, 3], [1, 6], [1, 8], [1, 9], [1, 11], [1, 13], [1, 14], [1, 15], [2, 6], [2, 7], [2, 8], [2, 10], [2, 11], [2, 15], [3, 4], [3, 5], [3, 7], [3, 8], [3, 9], [3, 10], [3, 12], [3, 14], [3, 15], [4, 5], [4, 6], [4, 7], [4, 11], [4, 13], [4, 15], [5, 6], [5, 9], [5, 10], [5, 12], [5, 13], [5, 14], [5, 15], [6, 8], [6, 9], [6, 10], [6, 11], [6, 15], [7, 11], [7, 12], [7, 14], [7, 15], [8, 9], [8, 11], [8, 12], [8, 13], [8, 14], [9, 10], [9, 14], [10, 11], [10, 15], [11, 14], [11, 15], [12, 13], [12, 14], [12, 15], [13, 14]], "ged": 9, "graph_1": [[0, 3], [0, 4], [0, 5], [0, 6], [0, 10], [0, 11], [0, 12], [0, 14], [0, 15], [1, 2], [1, 3], [1, 4], [1, 5], [1, 7], [1, 9], [1, 10], [1, 11], [1, 12], [1, 13], [2, 3], [2, 5], [2, 6], [2, 7], [2, 9], [2, 10], [2, 11], [2, 13], [2, 14], [3, 9], [3, 10], [3, 12], [3, 13], [3, 15], [4, 5], [4, 7], [4, 8], [4, 9], [4, 10], [4, 15], [5, 6], [5, 7], [5, 9], [5, 10], [5, 11], [5, 14], [5, 15], [6, 7], [6, 8], [6, 9], [6, 10], [6, 15], [7, 11], [7, 12], [7, 14], [8, 11], [8, 12], [8, 13], [8, 14], [8, 15], [9, 10], [9, 11], [9, 13], [9, 15], [10, 11], [10, 12], [10, 13], [10, 14], [10, 15], [11, 12], [11, 14], [12, 13], [12, 14], [12, 15], [13, 14], [13, 15]]}
  3. 训练 SimGNN 模型由“src/main.py”脚本处理,该脚本提供以下命令行参数
    数据集参数
    1
    2
    3
       参数:数据集     参数类型    参数描述                       默认值(数据集位置)
    --training-graphs STR Training graphs folder. Default is `dataset/train/`.
    --testing-graphs STR Testing graphs folder. Default is `dataset/test/`.
    模型参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
       参数:数据集         参数类型            参数描述                            默认值
    --filters-1 INT Number of filter in 1st GCN layer. Default is 128.
    --filters-2 INT Number of filter in 2nd GCN layer. Default is 64.
    --filters-3 INT Number of filter in 3rd GCN layer. Default is 32.
    --tensor-neurons INT Neurons in tensor network layer. Default is 16.
    --bottle-neck-neurons INT Bottle neck layer neurons. Default is 16.
    --bins INT Number of histogram bins. Default is 16.
    --batch-size INT Number of pairs processed per batch. Default is 128.
    --epochs INT Number of SimGNN training epochs. Default is 5.
    --dropout FLOAT Dropout rate. Default is 0.5.
    --learning-rate FLOAT Learning rate. Default is 0.001.
    --weight-decay FLOAT Weight decay. Default is 10^-5.
    --histogram BOOL Include histogram features. Default is False.

    —filters-1:第1个GCN层中的滤波器数量,默认为128。
    —filters-2:第2个GCN层中的滤波器数量,默认为64。
    —filters-3:第3个GCN层中的滤波器数量,默认为32。
    —tensor-neurons:张量网络层中的神经元数量,默认为16。
    —bottle-neck-neurons:瓶颈层中的神经元数量,默认为16。
    —bins:直方图的分组数量,默认为16。
    —batch-size:每个批次处理的样本对数量,默认为128。
    —epochs:SimGNN训练的轮数,默认为5。
    —dropout:丢弃率,默认为0.5。
    —learning-rate:学习率,默认为0.001。
    —weight-decay:权重衰减(L2正则化项),默认为10^-5。
    —histogram:是否包括直方图特征,布尔值,默认为False。
    瓶颈层是指在神经网络中维度减小的层,通常用于压缩数据表示,从而提高模型的效率和泛化能力
    张量网络层的输出通常是一个更复杂或更高维度的特征表示,能捕捉向量之间更复杂的关系
  4. 使用方法
  • 在默认数据集上训练 SimGNN 模型。
    1
    python src/main.py
  • 训练 100 轮数据的 SimGNN 模型,每个训练批次包含512个样本
    较大的Batch Size可以提高训练过程的效率,但可能会导致内存不足的问题;较小的Batch Size通常能够更好地捕捉数据集的统计特性,但训练过程会更慢。
    1
    python src/main.py --epochs 100 --batch-size 512
  • 使用直方图特征训练 SimGNN。
    1
    python src/main.py --histogram
  • 训练具有直方图特征并设置组数
    1
    python src/main.py --histogram --bins 32
  • 提高学习率和辍学率。
    1
    python src/main.py --learning-rate 0.01 --dropout 0.9
  • 通过添加参数来保存经过训练的模型。—save-path
    1
    python src/main.py --save-path ../path/to/model-name
  • 使用已有的模型
    1
    python src/main.py --load-path ../path/to/model-name
    源代码解读
  1. simgnn.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    """SimGNN class and runner."""

    import glob # 导入glob模块,用于文件路径的模式匹配
    import torch # 导入PyTorch库,用于构建和训练神经网络
    import random # 导入random模块,用于生成随机数
    import numpy as np # 导入NumPy库,用于数值计算
    from tqdm import tqdm, trange # 导入tqdm模块,用于显示进度条
    from torch_geometric.nn import GCNConv # 从PyTorch Geometric中导入GCNConv模块,用于图卷积层
    from layers import AttentionModule, TenorNetworkModule # 从自定义的layers模块中导入AttentionModule和TenorNetworkModule
    from utils import process_pair, calculate_loss, calculate_normalized_ged # 从自定义的utils模块中导入process_pair、calculate_loss和calculate_normalized_ged函数

    #定义SimGNN模型
    class SimGNN(torch.nn.Module):
    """
    SimGNN: A Neural Network Approach to Fast Graph Similarity Computation
    https://arxiv.org/abs/1808.05689
    """
    # 使用时需要传入一个参数对象以及节点标签的数量
    def __init__(self, args, number_of_labels):
    """
    :param args: Arguments object. 参数对象,用于传递训练模型时的参数设置
    :param number_of_labels: Number of node labels. 节点标签的数量
    """
    super(SimGNN, self).__init__() # 调用父类构造函数,初始化模型
    self.args = args # 将参数对象保存到模型中
    self.number_labels = number_of_labels # 保存节点标签的数量到模型中
    self.setup_layers() #初始化模型的各种层,下面有定义
    # 计算瓶颈层的特性个数
    def calculate_bottleneck_features(self):
    """
    Deciding the shape of the bottleneck layer.
    决定瓶颈层的形状
    """
    # 如果参数中设置了histogram为True 特征个数为tensor_neurons数量和组数相加否则个数为tensor_neurons的数量
    if self.args.histogram == True: # 如果参数中设置了histogram为True
    self.feature_count = self.args.tensor_neurons + self.args.bins # 计算特征数量
    else:
    self.feature_count = self.args.tensor_neurons # 否则特征数量为tensor_neurons的数量
    # 定义模型的各层
    def setup_layers(self):
    """
    Creating the layers.
    创建模型的各层
    """
    self.calculate_bottleneck_features() # 调用calculate_bottleneck_features方法,计算特征数量
    self.convolution_1 = GCNConv(self.number_labels, self.args.filters_1) # 第一层图卷积,输入节点标签数量,输出特征数量为filters_1
    self.convolution_2 = GCNConv(self.args.filters_1, self.args.filters_2) # 第二层图卷积,输入特征数量为filters_1,输出特征数量为filters_2
    self.convolution_3 = GCNConv(self.args.filters_2, self.args.filters_3) # 第三层图卷积,输入特征数量为filters_2,输出特征数量为filters_3
    # 注意力机制模块
    self.attention = AttentionModule(self.args) # 注意力模块,传入参数对象args
    # TNT模块
    self.tensor_network = TenorNetworkModule(self.args) # Tensor网络模块,传入参数对象args
    # 全连接层
    self.fully_connected_first = torch.nn.Linear(self.feature_count,
    self.args.bottle_neck_neurons) # 全连接层,输入特征数量为feature_count,输出特征数量为bottle_neck_neurons
    # 最后一层线性层(输出得分)
    self.scoring_layer = torch.nn.Linear(self.args.bottle_neck_neurons, 1) # 评分层,输入特征数量为bottle_neck_neurons,输出特征数量为1

    # 定义计算直方图,输入两个抽象层分别为两个图的特征矩阵会返回相似度分数的直方图
    def calculate_histogram(self, abstract_features_1, abstract_features_2):
    """
    Calculate histogram from similarity matrix.
    从相似度矩阵计算直方图
    :param abstract_features_1: Feature matrix for graph 1. 图1的特征矩阵
    :param abstract_features_2: Feature matrix for graph 2. 图2的特征矩阵
    :return hist: Histsogram of similarity scores. 相似度分数的直方图
    """
    scores = torch.mm(abstract_features_1, abstract_features_2).detach() # 计算特征矩阵的点积,并且将结果从计算图中分离出来
    scores = scores.view(-1, 1) # 将scores变换成1列的张量
    hist = torch.histc(scores, bins=self.args.bins) # 计算scores的直方图
    hist = hist / torch.sum(hist) # 将直方图归一化
    hist = hist.view(1, -1) # 将直方图变形成1行的张量
    return hist # 返回直方图
    # 定义卷积操作函数
    def convolutional_pass(self, edge_index, features):
    """
    Making convolutional pass.
    进行卷积操作
    :param edge_index: Edge indices. 边的索引
    :param features: Feature matrix. 特征矩阵
    :return features: Absstract feature matrix. 抽象特征矩阵
    """
    features = self.convolution_1(features, edge_index) # 第一层图卷积操作
    features = torch.nn.functional.relu(features) # 使用ReLU激活函数
    features = torch.nn.functional.dropout(features,
    p=self.args.dropout,
    training=self.training) # 使用dropout进行正则化

    features = self.convolution_2(features, edge_index) # 第二层图卷积操作
    features = torch.nn.functional.relu(features) # 使用ReLU激活函数
    features = torch.nn.functional.dropout(features,
    p=self.args.dropout,
    training=self.training) # 使用dropout进行正则化

    features = self.convolution_3(features, edge_index) # 第三层图卷积操作
    return features # 返回抽象特征矩阵
    # 定义前向传播函数
    def forward(self, data):
    """
    前向传播函数,用于计算图形数据的相似性分数。
    :param data: 数据字典,包含图的相关信息。
    :return score: 相似性分数。
    """

    # 从数据字典中获取第一个图的边索引和特征
    edge_index_1 = data["edge_index_1"]
    features_1 = data["features_1"]

    # 从数据字典中获取第二个图的边索引和特征
    edge_index_2 = data["edge_index_2"]
    features_2 = data["features_2"]

    # 对第一个图进行卷积传播
    abstract_features_1 = self.convolutional_pass(edge_index_1, features_1)
    # 对第二个图进行卷积传播
    abstract_features_2 = self.convolutional_pass(edge_index_2, features_2)

    # 如果设置了直方图选项
    if self.args.histogram == True:
    # 计算特征之间的直方图
    hist = self.calculate_histogram(abstract_features_1,
    torch.t(abstract_features_2))

    # 对第一个图的抽象特征进行注意力池化
    pooled_features_1 = self.attention(abstract_features_1)
    # 对第二个图的抽象特征进行注意力池化
    pooled_features_2 = self.attention(abstract_features_2)

    # 将池化后的特征输入张量网络
    scores = self.tensor_network(pooled_features_1, pooled_features_2)
    # 对分数进行转置操作
    scores = torch.t(scores)

    # 如果设置了直方图选项
    if self.args.histogram == True:
    # 将直方图拼接到分数张量中,并调整形状为(1,-1)
    scores = torch.cat((scores, hist), dim=1).view(1, -1)

    # 对分数张量进行ReLU激活函数处理
    scores = torch.nn.functional.relu(self.fully_connected_first(scores))
    # 对分数张量进行Sigmoid激活函数处理
    score = torch.sigmoid(self.scoring_layer(scores))

    return score


    class SimGNNTrainer(object):
    """
    SimGNN模型的训练器。
    """

    def __init__(self, args):
    """
    :param args: 参数对象。
    """
    self.args = args

    # 初始化标签枚举
    self.initial_label_enumeration()
    # 设置模型
    self.setup_model()

    def setup_model(self):
    """
    创建SimGNN模型。
    """
    self.model = SimGNN(self.args, self.number_of_labels)

    def initial_label_enumeration(self):
    """
    收集唯一节点标识符。
    """
    print("\n正在枚举唯一标签。\n") # 打印提示信息,表示正在进行唯一标签的枚举

    import glob # 导入glob模块,用于文件路径的匹配
    from tqdm import tqdm # 导入tqdm模块,用于显示进度条

    # 获取训练和测试图形文件的路径列表
    self.training_graphs = glob.glob(self.args.training_graphs + "*.json")
    self.testing_graphs = glob.glob(self.args.testing_graphs + "*.json")

    # 将训练和测试图形文件路径列表合并为一个列表
    graph_pairs = self.training_graphs + self.testing_graphs

    # 初始化一个空的集合,用于存储所有全局标签
    self.global_labels = set()

    # 遍历所有图形对的路径,使用进度条显示遍历进度
    for graph_pair in tqdm(graph_pairs):
    data = process_pair(graph_pair) # 假设有一个名为process_pair的函数处理数据

    # 将当前图形对的标签1和标签2添加到全局标签集合中
    self.global_labels = self.global_labels.union(set(data["labels_1"]))
    self.global_labels = self.global_labels.union(set(data["labels_2"]))

    # 对全局标签集合进行排序(去重)
    self.global_labels = sorted(self.global_labels)

    print("**********global_labels排序后***************")
    print(self.global_labels)
    # 创建一个字典,将全局标签映射到索引
    self.global_labels = {val: index for index, val in enumerate(self.global_labels)}
    print("**********global_labels字典***************")
    print(self.global_labels)
    # 计算全局标签的数量
    self.number_of_labels = len(self.global_labels)

    def create_batches(self):
    """
    从训练图列表创建批次。
    :return batches: 包含批次列表的列表。
    """
    import random # 导入 random 模块,用于洗牌操作
    random.shuffle(self.training_graphs) # 随机打乱训练图列表中的元素顺序

    batches = [] # 初始化空列表 batches,用于存储创建的批次

    # 使用 for 循环遍历训练图列表,步长为每批次大小 self.args.batch_size
    for graph in range(0, len(self.training_graphs), self.args.batch_size):
    # 将从 graph 到 graph + self.args.batch_size 的子列表添加到 batches 中
    batches.append(self.training_graphs[graph:graph + self.args.batch_size])

    return batches # 返回包含批次列表的列表 batches

    def transfer_to_torch(self, data):
    """
    将数据转换为torch,并创建哈希表。
    包括索引、特征和目标。
    :param data: 数据字典。
    :return new_data: Torch张量的字典。
    """
    import torch # 导入 PyTorch 库
    import numpy as np # 导入 NumPy 库
    new_data = dict() # 创建一个空字典 new_data,用于存储转换后的数据

    # 创建图的边列表并进行转换
    # 将有向边转换为无向边
    edges_1 = data["graph_1"] + [[y, x] for x, y in data["graph_1"]]
    edges_2 = data["graph_2"] + [[y, x] for x, y in data["graph_2"]]

    # edges_1 转换为 PyTorch 张量
    edges_1 = torch.from_numpy(np.array(edges_1, dtype=np.int64).T).type(torch.long)
    edges_2 = torch.from_numpy(np.array(edges_2, dtype=np.int64).T).type(torch.long)

    features_1, features_2 = [], [] # 初始化两个空列表,用于存储特征向量

    # 构建特征向量 features_1 代表了节点标签值是否在全局标签中
    # n代表图1中所有节点的标签值,i代表全局标签的所有值,如果两者相等则features_1中追加1,否则追加0
    for n in data["labels_1"]:
    features_1.append([1.0 if self.global_labels[n] == i else 0.0 for i in self.global_labels.values()])

    # 构建特征向量 features_2
    for n in data["labels_2"]:
    features_2.append([1.0 if self.global_labels[n] == i else 0.0 for i in self.global_labels.values()])

    # 将特征向量转换为 Torch 的 FloatTensor 格式
    features_1 = torch.FloatTensor(np.array(features_1))
    features_2 = torch.FloatTensor(np.array(features_2))

    # 将处理后的数据存入 new_data 字典中
    new_data["edge_index_1"] = edges_1
    new_data["edge_index_2"] = edges_2
    new_data["features_1"] = features_1
    new_data["features_2"] = features_2

    # 计算目标值
    norm_ged = data["ged"] / (0.5 * (len(data["labels_1"]) + len(data["labels_2"])))
    new_data["target"] = torch.from_numpy(np.exp(-norm_ged).reshape(1, 1)).view(-1).float()

    return new_data # 返回包含 Torch 张量的字典 new_data

    def process_batch(self, batch):
    """
    处理数据批次的前向传播过程。
    :param batch: 数据批次,包含图对位置信息。
    :return loss: 批次上的损失值。
    """
    # 梯度清零
    self.optimizer.zero_grad()
    losses = 0
    # 遍历每个图对
    for graph_pair in batch:
    # 处理图对数据
    data = process_pair(graph_pair)
    # 转换数据为PyTorch张量
    data = self.transfer_to_torch(data)
    target = data["target"]
    # 模型预测
    prediction = self.model(data)
    # 计算损失
    losses = losses + torch.nn.functional.mse_loss(target, prediction)
    # 反向传播
    losses.backward(retain_graph=True)
    self.optimizer.step()
    loss = losses.item()
    return loss

    def fit(self):
    """
    拟合模型,训练模型。
    """
    print("\n模型训练中.\n")

    # 定义优化器
    self.optimizer = torch.optim.Adam(self.model.parameters(),
    lr=self.args.learning_rate,
    weight_decay=self.args.weight_decay)

    self.model.train()
    epochs = trange(self.args.epochs, leave=True, desc="Epoch")
    # 遍历每个epoch
    for epoch in epochs:
    batches = self.create_batches()
    self.loss_sum = 0
    main_index = 0
    # 遍历每个批次
    for index, batch in tqdm(enumerate(batches), total=len(batches), desc="Batches"):
    # 处理批次数据并返回损失
    loss_score = self.process_batch(batch)
    main_index = main_index + len(batch)
    self.loss_sum = self.loss_sum + loss_score * len(batch)
    loss = self.loss_sum / main_index
    epochs.set_description("Epoch (Loss=%g)" % round(loss, 5))

    def score(self):
    """
    在测试集上评分。
    """
    print("\n\n模型评估中.\n")
    self.model.eval()
    self.scores = []
    self.ground_truth = []
    # 遍历每个测试图对
    for graph_pair in tqdm(self.testing_graphs):
    data = process_pair(graph_pair)
    self.ground_truth.append(calculate_normalized_ged(data))
    data = self.transfer_to_torch(data)
    target = data["target"]
    # 模型预测
    prediction = self.model(data)
    self.scores.append(calculate_loss(prediction, target))
    self.print_evaluation()

    def print_evaluation(self):
    """
    打印误差率。
    """
    # 计算基线误差和模型误差
    norm_ged_mean = np.mean(self.ground_truth)
    base_error = np.mean([(n - norm_ged_mean) ** 2 for n in self.ground_truth])
    model_error = np.mean(self.scores)
    print("\n基线误差: " + str(round(base_error, 5)) + ".")
    print("\n模型测试误差: " + str(round(model_error, 5)) + ".")

    def save(self):
    # 保存模型参数
    torch.save(self.model.state_dict(), self.args.save_path)

    def load(self):
    # 加载模型参数
    self.model.load_state_dict(torch.load(self.args.load_path))




函数作用输入输出
SimGNN(torch.nn.Module)
__init__(self, args, number_of_labels)
建立SimGNN模型,初始化模型的各种层
args,number_of_labels
SimGNN(torch.nn.Module)init(self, args, number_of_labels)建立SimGNN模型,初始化模型的各种层args,number_of_labels
calculate_bottleneck_features(self)计算瓶颈层的特性个数
setup_layers(self)定义模型的各层
calculate_histogram(self, abstract_features_1, abstract_features_2)定义计算直方图abstract_features_1, abstract_features_2hist
convolutional_pass(self, edge_index, features)定义卷积操作函数edge_index, featuresfeatures
forward(self, data)前向传播datascore
class SimGNNTrainer(object)init(self, args)SimGNN模型的训练函数初始化args
setup_model(self)创建SimGNN模型
initial_label_enumeration(self)收集唯一节点标识符
create_batches(self)从训练图列表创建批次batches
transfer_to_torch(self, data)将数据转换为torch,并创建哈希表datanew_data
process_batch(self, batch)处理数据批次的前向传播过程batchloss
fit(self)拟合模型
score(self)在测试集上评分
print_evaluation(self)打印误差率
save(self)保存模型参数
load(self)加载模型参数
  1. layers.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    """Classes for SimGNN modules."""

    import torch

    class AttentionModule(torch.nn.Module):
    """
    SimGNN Attention Module to make a pass on graph.
    """
    def __init__(self, args):
    """
    :param args: Arguments object.
    """
    super(AttentionModule, self).__init__()
    self.args = args
    self.setup_weights()
    self.init_parameters()

    def setup_weights(self):
    """
    Defining weights.
    """
    self.weight_matrix = torch.nn.Parameter(torch.Tensor(self.args.filters_3,
    self.args.filters_3))
    # 定义权重矩阵为可训练参数

    def init_parameters(self):
    """
    Initializing weights.
    """
    torch.nn.init.xavier_uniform_(self.weight_matrix)
    # 使用Xavier初始化方法初始化权重矩阵

    def forward(self, embedding):
    """
    Making a forward propagation pass to create a graph level representation.
    :param embedding: Result of the GCN.
    :return representation: A graph level representation vector.
    """
    global_context = torch.mean(torch.matmul(embedding, self.weight_matrix), dim=0)
    # 计算全局上下文向量,即嵌入矩阵乘以权重矩阵后取平均值

    transformed_global = torch.tanh(global_context)
    # 对全局上下文向量应用双曲正切函数

    sigmoid_scores = torch.sigmoid(torch.mm(embedding, transformed_global.view(-1, 1)))
    # 计算嵌入矩阵与变换后的全局上下文向量之间的相似度得分,通过sigmoid函数映射到0-1之间

    representation = torch.mm(torch.t(embedding), sigmoid_scores)
    # 计算加权后的嵌入矩阵,得到图级表示向量

    return representation

    class TenorNetworkModule(torch.nn.Module):
    """
    SimGNN Tensor Network module to calculate similarity vector.
    """
    def __init__(self, args):
    """
    :param args: Arguments object.
    """
    super(TenorNetworkModule, self).__init__()
    self.args = args
    self.setup_weights()
    self.init_parameters()

    def setup_weights(self):
    """
    Defining weights.
    """
    self.weight_matrix = torch.nn.Parameter(torch.Tensor(self.args.filters_3,
    self.args.filters_3,
    self.args.tensor_neurons))
    # 定义权重矩阵为可训练参数,维度为(filters_3, filters_3, tensor_neurons)

    self.weight_matrix_block = torch.nn.Parameter(torch.Tensor(self.args.tensor_neurons,
    2*self.args.filters_3))
    # 定义块权重矩阵为可训练参数,维度为(tensor_neurons, 2*filters_3)

    self.bias = torch.nn.Parameter(torch.Tensor(self.args.tensor_neurons, 1))
    # 定义偏置项为可训练参数,维度为(tensor_neurons, 1)

    def init_parameters(self):
    """
    Initializing weights.
    """
    torch.nn.init.xavier_uniform_(self.weight_matrix)
    torch.nn.init.xavier_uniform_(self.weight_matrix_block)
    torch.nn.init.xavier_uniform_(self.bias)
    # 使用Xavier初始化方法初始化权重矩阵和偏置项

    def forward(self, embedding_1, embedding_2):
    """
    Making a forward propagation pass to create a similarity vector.
    :param embedding_1: Result of the 1st embedding after attention.
    :param embedding_2: Result of the 2nd embedding after attention.
    :return scores: A similarity score vector.
    """
    scoring = torch.mm(torch.t(embedding_1), self.weight_matrix.view(self.args.filters_3, -1))
    # 计算第一个嵌入矩阵与权重矩阵的转置乘积,重塑维度以适应后续操作

    scoring = scoring.view(self.args.filters_3, self.args.tensor_neurons)
    # 重塑成(filters_3, tensor_neurons)

    scoring = torch.mm(torch.t(scoring), embedding_2)
    # 计算得分,第二个嵌入矩阵与上述结果的乘积

    combined_representation = torch.cat((embedding_1, embedding_2))
    # 将两个嵌入矩阵连接起来

    block_scoring = torch.mm(self.weight_matrix_block, combined_representation)
    # 计算块得分,块权重矩阵与连接后的嵌入矩阵的乘积

    scores = torch.nn.functional.relu(scoring + block_scoring + self.bias)
    # 计算最终得分,包括前面计算的得分、块得分和偏置项,并通过ReLU激活函数处理

    return scores


函数作用输入输出
AttentionModule(torch.nn.Module)
_init__(self, args)
注意力模型的初始化
setup_weights(self)定义权重矩阵
init_parameters(self)初始化权重矩阵
forward(self, embedding)前向传播embeddingrepresentation
TenorNetworkModule(torch.nn.Module)init(self, args)计算相似性向量模型的初始化
setup_weights(self)定义权重矩阵
init_parameters(self)初始化权重矩阵
forward(self, embedding_1, embedding_2)进行前向传播返回相似度得分向量embedding_1, embedding_2scores
  1. param_parser.py
    从命令行获取参数信息(需要的参数可以在这个文件里改值)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
"""从命令行获取参数。"""

import argparse

def parameter_parser():
"""
解析命令行参数的方法。
默认超参数可以提供高性能模型,无需网格搜索。
"""
parser = argparse.ArgumentParser(description="Run SimGNN.") # 创建一个参数解析器对象,设置描述信息为"Run SimGNN."

parser.add_argument("--training-graphs", # 添加一个名为--training-graphs的命令行参数
nargs="?", # 告诉参数解析器这个参数可有可无
default="../dataset/train/", # 默认值为"../dataset/train/"
help="Folder with training graph pair jsons.") # 帮助信息,说明这个参数是训练图对json文件所在的文件夹

parser.add_argument("--testing-graphs", # 添加一个名为--testing-graphs的命令行参数
nargs="?", # 告诉参数解析器这个参数可有可无
default="../dataset/test/", # 默认值为"../dataset/test/"
help="Folder with testing graph pair jsons.") # 帮助信息,说明这个参数是测试图对json文件所在的文件夹

parser.add_argument("--epochs", # 添加一个名为--epochs的命令行参数
type=int, # 类型为整数
default=5, # 默认值为5
help="Number of training epochs. Default is 5.") # 帮助信息,说明这个参数是训练轮数,默认为5

parser.add_argument("--filters-1", # 添加一个名为--filters-1的命令行参数
type=int, # 类型为整数
default=128, # 默认值为128
help="Filters (neurons) in 1st convolution. Default is 128.") # 帮助信息,说明这个参数是第一卷积层中的过滤器(神经元)数量,默认为128

parser.add_argument("--filters-2", # 添加一个名为--filters-2的命令行参数
type=int, # 类型为整数
default=64, # 默认值为64
help="Filters (neurons) in 2nd convolution. Default is 64.") # 帮助信息,说明这个参数是第二卷积层中的过滤器(神经元)数量,默认为64

parser.add_argument("--filters-3", # 添加一个名为--filters-3的命令行参数
type=int, # 类型为整数
default=32, # 默认值为32
help="Filters (neurons) in 3rd convolution. Default is 32.") # 帮助信息,说明这个参数是第三卷积层中的过滤器(神经元)数量,默认为32

parser.add_argument("--tensor-neurons", # 添加一个名为--tensor-neurons的命令行参数
type=int, # 类型为整数
default=16, # 默认值为16
help="Neurons in tensor network layer. Default is 16.") # 帮助信息,说明这个参数是张量网络层中的神经元数量,默认为16

parser.add_argument("--bottle-neck-neurons", # 添加一个名为--bottle-neck-neurons的命令行参数
type=int, # 类型为整数
default=16, # 默认值为16
help="Bottle neck layer neurons. Default is 16.") # 帮助信息,说明这个参数是瓶颈层的神经元数量,默认为16

parser.add_argument("--batch-size", # 添加一个名为--batch-size的命令行参数
type=int, # 类型为整数
default=128, # 默认值为128
help="Number of graph pairs per batch. Default is 128.") # 帮助信息,说明这个参数是每批次图对的数量,默认为128

parser.add_argument("--bins", # 添加一个名为--bins的命令行参数
type=int, # 类型为整数
default=16, # 默认值为16
help="Similarity score bins. Default is 16.") # 帮助信息,说明这个参数是相似性分数的区间数量,默认为16

parser.add_argument("--dropout", # 添加一个名为--dropout的命令行参数
type=float, # 类型为浮点数
default=0.5, # 默认值为0.5
help="Dropout probability. Default is 0.5.") # 帮助信息,说明这个参数是dropout概率,默认为0.5

parser.add_argument("--learning-rate", # 添加一个名为--learning-rate的命令行参数
type=float, # 类型为浮点数
default=0.001, # 默认值为0.001
help="Learning rate. Default is 0.001.") # 帮助信息,说明这个参数是学习率,默认为0.001

parser.add_argument("--weight-decay", # 添加一个名为--weight-decay的命令行参数
type=float, # 类型为浮点数
default=5*10**-4, # 默认值为5*10^-4
help="Adam weight decay. Default is 5*10^-4.") # 帮助信息,说明这个参数是Adam优化器的权重衰减,默认为5*10^-4

parser.add_argument("--histogram", # 添加一个名为--histogram的命令行参数
dest="histogram", # 指定这个参数的目标属性名为histogram
action="store_true") # 当命令行中出现--histogram时,将histogram属性设置为True

parser.set_defaults(histogram=False) # 设置默认情况下,histogram属性为False

parser.add_argument("--save-path", # 添加一个名为--save-path的命令行参数
type=str, # 类型为字符串
default=None, # 默认值为None
help="Where to save the trained model") # 帮助信息,说明这个参数是用于保存训练模型的路径

parser.add_argument("--load-path", # 添加一个名为--load-path的命令行参数
type=str, # 类型为字符串
default=None, # 默认值为None
help="Load a pretrained model") # 帮助信息,说明这个参数是用于加载预训练模型的路径

return parser.parse_args() # 解析命令行参数并返回解析结果

  1. utils.py
    数据处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    """Data processing utilities."""

    import json # 导入json模块,用于处理JSON格式的数据
    import math # 导入math模块,用于数学运算
    from texttable import Texttable # 从texttable模块中导入Texttable类,用于创建文本表格

    def tab_printer(args):
    """
    将参数以表格形式打印输出。
    :param args: 用于模型的参数。
    """
    args = vars(args) # 将参数对象转换为字典
    keys = sorted(args.keys()) # 获取参数字典的键并按字母排序
    t = Texttable() # 创建Texttable对象
    t.add_rows([["Parameter", "Value"]]) # 添加表头
    t.add_rows([[k.replace("_", " ").capitalize(), args[k]] for k in keys]) # 添加参数及其值到表格中
    print(t.draw()) # 打印表格

    def process_pair(path):
    """
    读取包含一对图形的JSON文件。
    :param path: JSON文件的路径。
    :return data: 包含数据的字典。
    """
    data = json.load(open(path)) # 打开并加载JSON文件中的数据
    return data # 返回数据字典

    def calculate_loss(prediction, target):
    """
    计算标准化的平方损失函数。
    :param prediction: 预测的GED的对数值。
    :param target: 实际的标准化GED的对数值。
    :return score: 平方误差。
    """
    prediction = -math.log(prediction) # 对预测值取对数并取负号
    target = -math.log(target) # 对实际值取对数并取负号
    score = (prediction - target) ** 2 # 计算平方误差
    return score # 返回平方误差

    def calculate_normalized_ged(data):
    """
    计算一对图形的标准化GED。
    :param data: 数据表。
    :return norm_ged: 标准化GED分数。
    """
    norm_ged = data["ged"] / (0.5 * (len(data["labels_1"]) + len(data["labels_2"]))) # 计算标准化GED
    return norm_ged # 返回标准化GED分数


函数作用输入输出
tab_printer(args)
将参数以表格形式打印输出
process_pair(path)读取包含一对图形的JSON文件pathdata
calculate_loss(prediction, target)计算归一化的平方损失函数prediction, targetscore
calculate_normalized_ged(data)计算一对图形的归一化GEDdatanorm_ged
  1. main.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    """SimGNN runner."""  # SimGNN运行器

    import warnings
    # 导入警告模块

    # 设置忽略警告,这样在后续运行中不会显示警告信息
    warnings.filterwarnings("ignore")

    from utils import tab_printer # 导入tab_printer函数,用于打印格式化的表格
    from simgnn import SimGNNTrainer # 导入SimGNNTrainer类,用于训练SimGNN模型
    from param_parser import parameter_parser # 导入parameter_parser函数,用于解析命令行参数

    def main():
    """
    解析命令行参数,读取数据。
    拟合和评分SimGNN模型。
    """
    args = parameter_parser() # 解析命令行参数
    tab_printer(args) # 使用tab_printer函数打印参数表格化信息
    trainer = SimGNNTrainer(args) # 创建SimGNNTrainer对象,传入参数args

    if args.load_path:
    trainer.load() # 如果指定了load_path,则加载模型

    else:
    trainer.fit() # 否则,拟合模型

    trainer.score() # 对模型进行评分

    if args.save_path:
    trainer.save() # 如果指定了save_path,则保存模型



    if __name__ == "__main__":
    main() # 执行主函数



图卷积特征提取--数据分析
  1. 假设有700对图,每个图有不多于10个点,每个点属于29个类别之一,即点的特征向量是29维,标签是两幅图的相似度(已知)
  2. batch,假设为128,代表每次读取128对图
  3. features_1:torch.Size([1158,29])代表这128对图中,图1一共有1158个点,每个点有29维向量,可以理解为1158行29列
  4. abstract_features_1:torch.Size([1158,16]),在经过GCN后,图1一共有1158个点(不变),每个点有16维向量
  5. abstract_features_1:torch.Size([1158,16]),在经过GCN后,图2一共有1158个点(不变),每个点有16维向量
轨迹预测--向量化(云端处理)
Argoverse数据集API的准备--linux或者mac
  1. numpy报错
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Collecting numpy==1.19 (from argoverse==1.1.0)
    Using cached numpy-1.19.0.zip (7.3 MB)
    Installing build dependencies ... done
    Getting requirements to build wheel ... done
    error: subprocess-exited-with-error

    × Preparing metadata (pyproject.toml) did not run successfully.
    │ exit code: 1
    ╰─> See above for output.
    解决:
    1
    2
    3
    4
    import numpy as np
    print(np.version.version)
    # 没有就安装numpy
    pip install numpy
    找到自己的numpy版本后修改setup.py文件里面的install_requires(61行左右)中的”numpy==1.19”注释掉
  2. pip报错(版本太新)
    1
    Please use pip<24.1 if you need to use this version.
    解决:
    1
    pip install pip==24.0.0
  3. sklearn出错(该模块已经弃用)
    1
    2
    Downloading sklearn-0.0.post12.tar.gz (2.6 kB)
    error: subprocess-exited-with-error
    解决:
    1
    2
    pip install -U scikit-learn
    # 修改setup.py文件里面的install_requires(78行左右)中的 "sklearn"注释掉
  • 继续安装
    pip install -e /content/argoverse-api/
  • 最后结果
    Successfully installed argoverse-1.1.0
  • 现在打包它方便以后直接使用(可选)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    %cd /content
    # 参数为 打包到哪,打包的文件所在地址
    !tar -czvf /content/argoverse-api.tar.gz argoverse-api/
    # 由于文件太大,需要分割下载
    # 查看文件大小(171M)
    !ls -lh argoverse-api.tar.gz
    # argoverse-api.tar.gz每个文件分割成57M,分卷命名为argoverse-api.tar.gz.part-aa, -ab ,-ac等
    !split -b 57M argoverse-api.tar.gz argoverse-api.tar.gz.part-
    # 下载分割后的文件之后在本地电脑重新组合成一个文件,新文件为 argoverse-api.tar.gz(需要终端进入分割文件所在的目录)
    cat argoverse-api.tar.gz.part-* > argoverse-api.tar.gz
项目下载
  1. https://github.com/xk-huang/yet-another-vectornet
  2. 如果pycharm导入错误,把src文件夹设置成原根
训练集测试集和验证集下载
  1. https://www.argoverse.org/av1.html#download-link中找到Argoverse Motion Forecasting v1.1
  2. 数据集太大了建议删除一些数据
  3. 把数据整理成以下格式,data在项目目录下
    1
    2
    3
    4
    5
    6
    7
    8
    data - train - *.csv
    \ \ ...
    \
    \- val - *.csv
    \ \ ...
    \
    \- test - *.csv
    \ ...
项目运行
  1. 确保安装好了api
    1
    2
    3
    4
    %cd /content/drive/MyDrive/Colab Notebooks/yet-another-vectornet-master/
    !tar -xzvf argoverse-api.tar.gz
    pip install pip==24.0.0
    pip install -e argoverse-api/
  2. 修改 compute_feature_module.py文件
    1
    2
    3
    25行左右注释掉如下内容
    if not re.search(r'val', folder):
    continue
  3. 运行特征构建的py文件(如果已经存在interm_data文件夹建议删除,这里存放特征文件)
    1
    !python compute_feature_module.py
  4. 检查是否安装了torch_geometric
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 没有安装就进行安装
    import torch
    print(torch.__version__)
    print(torch.version.cuda)
    !python --version
    # 打开https://data.pyg.org/whl/
    # 如果torch为2.3.1,请安装低版本
    pip install torch==2.3.0
    # 找到对应版本下载轮子五个文件 cuda12.1版本是121,python版本是3.1的选cp31,还要判断是win还是linux
    # 已经存在的使用强制重新安装
    # pip install --force-reinstall torch_scatter-2.1.2+pt23cpu-cp39-cp39-win_amd64.whl
    # 在线安装轮子 --选择版本后一堆whl文件页面的网址
    pip install torch_scatter -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_scatter-2.1.2%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install torch_sparse -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_sparse-0.6.18%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install torch_cluster -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_cluster-1.6.3%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install torch_spline_conv -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_spline_conv-1.2.2%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install pyg_lib -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/pyg_lib-0.4.0%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    # 最后安装pyg
    pip install torch-geometric

  5. 开始训练
    1
    !python train.py
    错误1 geometric版本错误
    1
    2
    3
    File "/usr/local/lib/python3.10/dist-packages/torch_geometric/data/collate.py", line 322, in <listcomp>
    data.inc(key, value, store)
    TypeError: GraphData.inc() takes 3 positional arguments but 4 were given
    解决:
    1
    !pip install torch-geometric==1.5.0
    错误2 torch版本过高,torch._six被弃用(低于2.0)
    1
    2
    3
     File "/usr/local/lib/python3.10/dist-packages/torch_geometric/data/dataloader.py", line 5, in <module>
    from torch._six import container_abcs, string_classes, int_classes
    ModuleNotFoundError: No module named 'torch._six'
    解决:
    1
    2
    https://blog.csdn.net/BetrayFree/article/details/137692591
    找到提示的torch_geometric包中文件的位置进行修改
    解决:
    1
    !pip install torch==1.11.0
原理解读
  1. 传统卷积全局信息处理不好,会有很多干扰
  2. 把轨迹近似成很多的点,交通灯作为点,车道线近似成很多点,路边的牌子看成点
  3. 假如已知5s内前3s的轨迹点,预测后2s的轨迹点
  4. 轨迹上的点的特征举例:x,y(位置),1(靠左车道线),1(前100米存在两个红绿灯),1(红绿灯绿色),1(红灯),1(左边有车),1(右边有车)
代码下载
colab环境配置

项目readme.md中要求torch==1.4.0, argoverse-api, numpy==1.18.1, pandas==1.0.0, matplotlib==3.1.1, torch-geometric==1.5.0
注意torch和torch-geometric

  1. 创建虚拟环境
    1
    2
    3
    4
    %cd /content/drive/MyDrive/Colab Notebooks/yet-another-vectornet-master/
    !pip install virtualenv
    !virtualenv torch_env # 创建名为 torch_env 的虚拟环境
    !source torch_env/bin/activate # 激活虚拟环境
  2. 安装指定版本的torch
    1
    2
    !pip uninstall torch
    !pip install torch==2.3.0
  3. 安装指定版本的torch-geometric
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import torch
    print(torch.__version__)
    print(torch.version.cuda)
    !python --version
    # 打开https://data.pyg.org/whl/
    # 找到对应版本下载轮子五个文件 cuda12.1版本是121,python版本是3.1的选cp31,还要判断是win还是linux
    # 已经存在的使用强制重新安装
    # pip install --force-reinstall torch_scatter-2.1.2+pt23cpu-cp39-cp39-win_amd64.whl
    # 在线安装轮子 --选择版本后一堆whl文件页面的网址
    pip install torch_scatter -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_scatter-2.1.2%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install torch_sparse -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_sparse-0.6.18%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install torch_cluster -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_cluster-1.6.3%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install torch_spline_conv -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/torch_spline_conv-1.2.2%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    pip install pyg_lib -f https://data.pyg.org/whl/torch-2.3.0%2Bcu121/pyg_lib-0.4.0%2Bpt23cu121-cp310-cp310-linux_x86_64.whl
    # 最后安装pyg
    !pip install torch-geometric==1.5.0
  4. 安装requirements.txt依赖
    1
    !pip install -r requirements.txt