文章说明:
1)参考资料:PYG官方文档。超链。
2)博主水平不高,如有错误还望批评指正。
3)我在百度网盘上传了这篇文章的jupyter notebook。超链。提取码8888。
参考文献:SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS
中文翻译:用图神经网络进行半监督的分类
我在百度网盘上传这篇文献。超链。提取码8888。
文献首先:介绍了其他前辈的工作。在损失函数中使用拉普拉斯正则化项。公式如下(打这个公式真费劲,还的学Latex): L = L 0 + λ L r e g \mathcal{L}=\mathcal{L}_{0}+\lambda\mathcal{L}_{reg} L=L0+λLreg with L r e g = ∑ i , j A i , j ∣ ∣ f ( X i ) − f ( X j ) ∣ ∣ 2 = f ( X ) T Δ f ( X ) \mathcal{L}_{reg}=\sum_{i,j}{A}_{i,j}||\mathcal{f}({X}_{i})-\mathcal{f}({X}_{j})||^{2}=\mathcal{f}(X)^{T}\Delta\mathcal{f}(X) Lreg=∑i,jAi,j∣∣f(Xi)−f(Xj)∣∣2=f(X)TΔf(X)
符号说明: L \mathcal{L} L表示为损失函数。 L 0 \mathcal{{L}_{0}} L0表示为有标签的损失(还有没标签的毕竟是半监督)。 λ \lambda λ表示为权重系数。 A i , j {A_{i,j}} Ai,j表示为图边。 f ( ⋅ ) \mathcal{f}(\cdot) f(⋅)表示为像神经网络的可微函数。 X X X表示为特征矩阵。 Δ = D − A \Delta=D-A Δ=D−A表示为非规范化的拉普拉斯算子。 D D D表示为度的矩阵, D i , i = ∑ j A i , j D_{i,i}=\sum_{j}A_{i,j} Di,i=∑jAi,j。
文章然后:简单说明使用上述公式需要有个假设:图中连接节点共享相同标签。于是作者这篇文章便就来了,为了解决这个问题,使用神经网络模型 f ( X , A ) f(X,A) f(X,A)编码图结构,避免使用显示基于图正则化。文章有两贡献,1.提出一种简单良好直接作用于图上的神经网络传播规则并且展示它是如何从谱图卷积的一阶逼近得到反馈。2.演示了基于图神经网络是如何分类的。
文章然后:具体开始阐述理论。 H l + 1 = σ ( D ~ − 1 2 A ~ D ~ − 1 2 H l W l ) H^{l+1}=\sigma(\tilde{D}^{-\frac{1}{2}}\tilde{A}\tilde{D}^{-\frac{1}{2}}H^{l}W^{l}) Hl+1=σ(D~−21A~D~−21HlWl)。(知道核心公式就好,其他细节跳过因为我看不懂)
符号说明: D i , i = ∑ j A i , j D_{i,i}=\sum_{j}A_{i,j} Di,i=∑jAi,j表示为度的矩阵。 A ~ = A + I N \tilde{A}=A+I_{N} A~=A+IN表示为邻接矩阵加上一个单位矩阵。 W l W^{l} Wl表示为权重系数。 σ \sigma σ表示为激活函数。 H l H^{l} Hl为第 l l l层的特征矩阵。 H 0 H^{0} H0即为 X X X。
文章然后:进行代码分类实操,他们这里搭建了两层GCN。所以最后的公式为 Z = f ( X , A ) = s o f t m a x ( A ^ R e l u ( A ^ X W 0 ) W 1 ) Z=f(X,A)=softmax(\widehat{A}Relu(\widehat{A}XW^{0})W^{1}) Z=f(X,A)=softmax(A Relu(A XW0)W1)。这里 A ^ = D ~ − 1 2 A ~ D ~ − 1 2 \widehat{A}=\tilde{D}^{-\frac{1}{2}}\tilde{A}\tilde{D}^{-\frac{1}{2}} A =D~−21A~D~−21。损失函数就使用交叉熵 L = − ∑ l ∈ Y l ∑ f = 1 F Y l f ln Z l f L=-\sum_{l \in \mathcal{Y}_{l}}\sum_{f=1}^FY_{lf}\ln{Z_{lf}} L=−∑l∈Yl∑f=1FYlflnZlf吧。
文章然后:介绍图半监督学习领域以及图上运行神经网络领域两个领域相关工作。
文章然后:进行实验展示结果。
文章然后:进行讨论。1.作者模型可以克服Skip-gram方法难以优化多步流程限制同时时间以及效果表现更好。2.未来工作1)解决内存:作者证明对于无法使用GPU大型图,用CPU是可行的。用小批量随机梯度可以缓解这个问题。但是生成小批量时应该考虑GCN的层数,对于非常大且密集连接的图可能需要进一步地近似。2)不支持有向图,但是有解决方法的(具体是什么我没看懂)3)考虑一个权衡参数 λ \lambda λ可能会有益。具体来说就是修改生成自循环图时用的 λ \lambda λ。即 A ~ = A + λ I \tilde{A}=A+\lambda I A~=A+λI。
文章然后:得到结论。
文章最后:引用以及其他工作。1)WL-1算法2)深层的GCN。太深不好。
PS:以上仅是我的理解,我的理解可能不对。然后关于这个GCN以及WL算法,有两篇文章研究了它们,还是挺有趣的。我在百度网盘上传了这连篇文章。超链。提取码8888。
导入对应的库
import matplotlib.pyplot as plt
import networkx as nx
定义可视化的函数
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):
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
h=h.detach().cpu().numpy()
plt.scatter(h[:,0],h[:,1],s=140,c=color,cmap="Set2")
if epoch is not None and loss is not None:
plt.xlabel(f'Epoch: {epoch}, Loss: {loss.item():.4f}',fontsize=16)
plt.show()
导入对应的库:数据集1
from torch_geometric.datasets import KarateClub
dataset=KarateClub()
KarateClub数据集简单说明:34个人的社交网络,如果在俱乐部之外两人认识连一条边。然后由于俱乐部的内部冲突,人们选择站队所以分成两派。
打印数据集的信息
print(len(dataset),dataset.num_features,dataset.num_classes)
#输出:1 34 4
简单说明:num_features:33加上1。33指,这个节点与其他的33个节点是否有边,有边为1,无边为0。1是指度。num_classer:按理应该为2,但是官方做了修改,所以为4。
data=dataset[0]
#具体到确定的图上
print(data.num_nodes,data.num_edges,data,data.train_mask.sum().item())
#输出:34 156 Data(x=[34, 34], edge_index=[2, 156], y=[34], train_mask=[34]) 4
print(data.has_isolated_nodes(),data.has_self_loops(),data.is_undirected())
#输出:False False True
edge_index=data.edge_index
print(edge_index.t())
#输出:不表
导入对应的库
from torch_geometric.utils import to_networkx
可视化图网络
G=to_networkx(data,to_undirected=True)
visualize_graph(G,color=data.y)
from torch_geometric.nn import GCNConv
from torch.nn import Linear
import torch
class GCN(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv1=GCNConv(dataset.num_features,4)
self.conv2=GCNConv(4,4)
self.conv3=GCNConv(4,2)
self.classifier=Linear(2,dataset.num_classes)
def forward(self,x,edge_index):
h=self.conv1(x,edge_index)
h=h.tanh()
h=self.conv2(h,edge_index)
h=h.tanh()
h=self.conv3(h,edge_index)
h=h.tanh()
out=self.classifier(h)
return out,h
model=GCN()
print(model)
#输出
#GCN(
# (conv1): GCNConv(34, 4)
# (conv2): GCNConv(4, 4)
# (conv3): GCNConv(4, 2)
# (classifier): Linear(in_features=2, out_features=4, bias=True)
#)
简单说明: X v ( l + 1 ) = W ( l + 1 ) ∑ w ∈ N ( v ) ∪ { v } 1 c w , v ⋅ X w ( l ) X_{v}^{(l+1)}=W^{(l+1)}\sum_{w \in N(v)\cup{\{v\}}}\frac{1}{c_{w,v}}\cdot X_{w}^{(l)} Xv(l+1)=W(l+1)∑w∈N(v)∪{v}cw,v1⋅Xw(l)
可视化图嵌入(这里只有正向传播)
model=GCN()
_,h=model(data.x,data.edge_index)
visualize_embedding(h,color=data.y)
model=GCN()
criterion=torch.nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(model.parameters(),lr=0.01)
def train(data):
optimizer.zero_grad()
out,h=model(data.x,data.edge_index)
loss=criterion(out[data.train_mask],data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss,h
for epoch in range(401):
loss,h=train(data)
if epoch==400:
visualize_embedding(h,color=data.y,epoch=epoch,loss=loss)