文章说明:
1)参考资料:PYG官方文档。超链。
2)博主水平不高,如有错误还望批评指正。
3)我在百度网盘上传了这篇文章的jupyter notebook和有关文献。超链。提取码8848。
文章目录
- Pumed数据集
- 文献阅读
- 继续实验
Pumed数据集
导库
from torch_geometric.transforms import NormalizeFeatures
from torch_geometric.datasets import Planetoid
下载数据处理数据导入数据
dataset=Planetoid(root='/DATA/Planetoid',name='PubMed',transform=NormalizeFeatures())
其他说明1:这段代码会在C盘生成一个DATA的文件并将数据集放在DATA中,有强迫症注意一下。
其他说明2:如果下载发生错误直接去官网上下载。下载好了复制C:\DATA\Planetoid\PubMed\raw中。官网链接。不会有人没梯子吧。
数据描述
data=dataset[0]
print(data.num_nodes,end=" ");print(data.num_edges)
print(data.train_mask.sum().item(),end=" ");print(data.val_mask.sum().item(),end=" ");print(data.test_mask.sum().item())
print(data.has_isolated_nodes(),end=" ");print(data.has_self_loops(),end=" ");print(data.is_undirected(),end=" ")
#输出如下
#19717 88648
#60 500 1000
#False False True
其他说明:Pumbed数据集开源的生物医学文献数据库。不用细究。
文献阅读
参考文献: Cluster-GCN: An Efficient Algorithm for Training Deep and Large Graph Convolutional Networks。原文链接。
功能概述: 介绍一种方法在时间与空间完爆其他方法。其他方法是指:1)Full-batch gradient:在空间上:O(NLF)(N代表序列的长度,F代表特征的数量,L代表网络层数量)。在时间上,梯度下降收敛较慢。2)Mini-batch SGD:在时间空间上引入大量计算开销造成原因邻域扩展。计算某个节点损失需要第L-1层的嵌入然后需要第L-2层的嵌入递归下去。3)VR-GCN:克服上述领域扩展有关问题但是需要第L-1层的嵌入所以空间要求太高NG。然后Cluster-GCN就来啦。1)Cluster-GCN空间小特别是在大型图上2)Cluster-GCN在浅层GCN中速度等同于VR-GCN;深层GCN中Cluster-GCN快得多,Cluster-GCN是线性,VR-GCN是指数。3)尽管有些工作表明深层图神经网络的效果不佳,但是实验表明Cluster-GCN深层的效果不错。下图:各种方法的时空复杂度。
理论分析1::将节点划分为n组如
V
1
,
V
2
,
…
V
n
]
\mathcal{V}_{1},\mathcal{V}_{2},\dots \mathcal{V}_{n}]
V1,V2,…Vn]。同组节点保存邻接不同组间直接断开。所以图被划分为
G
‾
=
[
G
1
,
G
2
,
…
,
G
n
]
=
[
{
V
1
,
E
1
}
,
{
V
2
,
E
2
}
,
…
,
{
V
n
,
E
n
}
]
\overline{G}=[G_{1},G_{2},\dots,G_{n}]=[\{\mathcal{V}_{1},\mathcal{E}_{1}\},\{\mathcal{V}_{2},\mathcal{E}_{2}\},\dots,\{\mathcal{V}_{n},\mathcal{E}_{n}\}]
G=[G1,G2,…,Gn]=[{V1,E1},{V2,E2},…,{Vn,En}]。这个相当于对图作近似。
Δ
\Delta
Δ保留了删除信息。特征向量以及标签按照节点划分划分。多层图神经网络便变为:
Z
L
=
A
‾
′
σ
(
A
‾
′
σ
(
…
σ
(
A
‾
′
X
W
0
)
W
1
)
…
)
W
L
−
1
Z^{L}=\overline{A}^{\prime}\sigma(\overline{A}^{\prime}\sigma(\dots\sigma(\overline{A}^{\prime}XW^{0})W^{1})\dots)W^{L-1}
ZL=A′σ(A′σ(…σ(A′XW0)W1)…)WL−1。
A
‾
′
\overline{A}^{\prime}
A′是分块对角阵
A
‾
\overline{A}
A的标准化。损失函数变为:
L
A
‾
′
=
∑
t
∣
V
t
∣
N
L
A
‾
t
t
′
\mathcal{L}_{\overline{A}^{\prime}}=\sum_{t}\frac{|\mathcal{V}_{t}|}{N}\mathcal{L}_{\overline{A}^{\prime}_{tt}}
LA′=∑tN∣Vt∣LAtt′ and
L
A
‾
t
t
′
=
1
∣
V
t
∣
∑
i
∈
V
t
l
o
s
s
(
y
i
,
z
i
L
)
\mathcal{L}_{\overline{A}^{\prime}_{tt}}=\frac{1}{|\mathcal{V}_{t}|}\sum_{i \in \mathcal{V}_{t}}loss(y_{i},z_{i}^{L})
LAtt′=∣Vt∣1∑i∈Vtloss(yi,ziL) 。这个便就就是核心思想。大概就是按照下图右边那样进行分割。
理论分析2: 划分引入一种误差,这种误差是与
Δ
\Delta
Δ成正比,所以我们应该减小这种误差。于是引入Metis以及Graclus方法 。重点分析了Metics划分,比起随机划分效果更好如下:这些指标都是Accuracy_Score吧。
理论分析3:
还有问题。1)毕竟还是删除了一些边,模型效果可能还是受到影响。2)由于集群分配算法导致相似节点被分为了一堆,所以可能会与原始数据不同(PS:原文这样写的,我不能够理解),使用随机梯度算法可能会带来偏差吧。所以为了解决问题或者减小问题影响,提出了一个 stochastic multiple clustering方法。简单来说是这样的:随机梯度算法更新权重需要进行Batch的划分;之前邻接矩阵已经被处理成分块对角矩阵。选择m个对角矩阵进入Batch,前面删除的边重新加上。如下这样。(好吧这个图我也没看懂但是大致想法是清晰的)
结果如下:
算法的伪代码:
PS1:后面好像是实验的内容部分,没时间看就这样吧。PS:这是我的理解所以不一定对。
继续实验
导库
from torch_geometric.loader import ClusterData,ClusterLoader
聚类划分,构建批量
cluster_data=ClusterData(data,num_parts=128)
train_loader=ClusterLoader(cluster_data,batch_size=32,shuffle=True)
打印信息
total_num_nodes=0
for step,sub_data in enumerate(train_loader):
print(f'Step {step + 1}:')
print('=======')
print(f'Number of nodes in the current batch: {sub_data.num_nodes}')
print(sub_data)
print()
total_num_nodes+=sub_data.num_nodes
print(f'Iterated over {total_num_nodes} of {data.num_nodes} nodes!')
#输出如下
#Step 1:
#=======
#Number of nodes in the current batch: 4924
#Data(x=[4924, 500], y=[4924], train_mask=[4924], val_mask=[4924], test_mask=[4924], edge_index=[2, 15404])
#
#Step 2:
#=======
#Number of nodes in the current batch: 4939
#Data(x=[4939, 500], y=[4939], train_mask=[4939], val_mask=[4939], test_mask=[4939], edge_index=[2, 17834])
#
#Step 3:
#=======
#Number of nodes in the current batch: 4928
#Data(x=[4928, 500], y=[4928], train_mask=[4928], val_mask=[4928], test_mask=[4928], edge_index=[2, 17524])
#
#Step 4:
#=======
#Number of nodes in the current batch: 4926
#Data(x=[4926, 500], y=[4926], train_mask=[4926], val_mask=[4926], test_mask=[4926], edge_index=[2, 16042])
#
#Iterated over 19717 of 19717 nodes!
导库
from torch_geometric.nn import GCNConv
import torch.nn.functional as F
import torch
随便搭建
class GCN(torch.nn.Module):
def __init__(self,hidden_channels):
super(GCN,self).__init__()
self.conv1=GCNConv(dataset.num_node_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(model)
#输出如下
#GCN(
# (conv1): GCNConv(500, 16)
# (conv2): GCNConv(16, 3)
#)
开始训练
model=GCN(hidden_channels=16);optimizer=torch.optim.Adam(model.parameters(),lr=0.01,weight_decay=5e-4);criterion=torch.nn.CrossEntropyLoss()
def train():
model.train()
for sub_data in train_loader:
out=model(sub_data.x,sub_data.edge_index)
loss=criterion(out[sub_data.train_mask],sub_data.y[sub_data.train_mask])
loss.backward()
optimizer.step()
optimizer.zero_grad()
def test():
model.eval()
out=model(data.x,data.edge_index)
pred=out.argmax(dim=1)
accs=[]
for mask in [data.train_mask,data.val_mask,data.test_mask]:
correct=pred[mask]==data.y[mask]
accs.append(int(correct.sum())/int(mask.sum()))
return accs
for epoch in range(1,51):
loss=train()
train_acc,val_acc,test_acc=test()
print(f'Epoch: {epoch:03d}, Train: {train_acc:.4f}, Val Acc: {val_acc:.4f}, Test Acc: {test_acc:.4f}')
#最后一次输出如下
#Epoch: 050, Train: 0.9833, Val Acc: 0.8060, Test Acc: 0.7880