1. 摘要
我们介绍了一种在图结构数据上可扩展的半监督学习方法,该方法是基于一种高效的有向图卷积神经网络变体。我们通过谱图卷积局部一阶近似来激励我们的卷积架构的选择。我们的模型在图边的数量上线性缩放,并且学习隐藏层对局部图结构和节点特征进行编码的表示。在大量的基于引用网络与知识图谱数据集上的实验中我们证明我们的方法表现比起相关方法具有很大的优势。
2. 相关符号表示
- KaTeX parse error: Undefined control sequence: \cal at position 4: {{ \̲c̲a̲l̲ ̲G } = ( { \cal …表示一个图, KaTeX parse error: Undefined control sequence: \cal at position 4: {{ \̲c̲a̲l̲ ̲V } , { \cal E …分别表示相应的节点集与边集,KaTeX parse error: Undefined control sequence: \cal at position 16: {u \,, v \in { \̲c̲a̲l̲ ̲V }}表示图中的节点, KaTeX parse error: Undefined control sequence: \cal at position 17: …u \, , v \in { \̲c̲a̲l̲ ̲E }} 表示图中的边。
- A {A} A表示图的邻接矩阵(adjacency matrix)。
- D {D} D 表示图的度矩阵(degree matrix)。
- L {L} L表示图的拉普拉斯矩阵(Laplacian matrix),KaTeX parse error: Undefined control sequence: \cal at position 4: {{ \̲c̲a̲l̲ ̲L }} 表示图的归一化拉普拉斯矩阵。
3. 相关矩阵的定义
邻接矩阵
图的邻接矩阵指用于表示图中节点的连接情况的矩阵。该矩阵可以是二值的,也可以是带权的。对于有N个节点的无向图来说,邻接矩阵是一个 N × N { N \times N } N×N 的实对称矩阵。
度矩阵
节点的度表示与该节点相连的边的数量。图的度矩阵即用于描述图中每个节点的度的矩阵,一般使用 D {D} D 表示。其中, D i , i 1 ≤ i ≤ N {D _ { i ,i } \; 1 \leq i \leq N} Di,i1≤i≤N表示节点 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1OrBIPL-1651321352701)(https://www.zhihu.com/equation?tex=i)] 的度。度矩阵是一个对角矩阵,对于无向图来说,一般只使用入度矩阵或出度矩阵。
拉普拉斯矩阵
定义矩阵
L
{L}
L ,其元素为:
KaTeX parse error: Undefined control sequence: \cal at position 110: … u , v ) \in { \̲c̲a̲l̲ ̲E } } \\ { 0 } …
其中, [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bYlWxFRG-1651321352701)(https://www.zhihu.com/equation?tex=d_{v})] 表示节点 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vjNEO0fe-1651321352702)(https://www.zhihu.com/equation?tex=v)] 的度,则该矩阵称之为图 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7HJb3UTb-1651321352702)(https://www.zhihu.com/equation?tex=\mathcal{G})] 的拉普拉斯矩阵( [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PfokXu2c-1651321352703)(https://www.zhihu.com/equation?tex=L%3DD-A)] )。相应的归一化拉普拉斯矩阵为:
因此,图的归一化拉普拉斯矩阵可以通过下式计算:
图的拉普拉斯矩阵也可以称为导纳矩阵(admittance matrix)或基尔霍夫矩阵(Kirchohoff matrix)。
谱图卷积推理过程比较难理解。。。暂时略过
4. GCN的原理形象解释
A
:
图
结
构
的
邻
接
矩
阵
A
~
:
有
自
连
接
的
邻
接
矩
阵
A
~
=
A
+
I
D
~
:
有
自
连
接
的
邻
接
矩
阵
的
度
矩
阵
D
~
i
i
=
∑
j
A
~
i
j
H
:
图
节
点
的
特
征
\begin{aligned} & A:图结构的邻接矩阵 \\ & \widetilde{A}:有自连接的邻接矩阵 \\ & \widetilde{A} = A + I \\ & \widetilde{D}:有自连接的邻接矩阵的度矩阵 \\ & \widetilde{D}_{ii} = \sum_{j} \widetilde{A}_{ij} \\ & H:图节点的特征 \end{aligned}
A:图结构的邻接矩阵A
:有自连接的邻接矩阵A
=A+ID
:有自连接的邻接矩阵的度矩阵D
ii=j∑A
ijH:图节点的特征
问题:
-
GCN的输入是邻接矩阵A和节点特征H,直接做内积,再乘一个参数矩阵W,然后激活一下,不就相当于一个的神经网络层?为什么要有自连接的邻接矩阵?
提示:无法区分“自身节点”与“无连接节点”。只使用A的话,由于A的对角线上都是0,所以在和特征矩阵H相乘的时候,只会计算一个节点的所有邻居的特征的加权和,该节点本身的特征却被忽略了。
-
为什么需要有自连接的邻接矩阵的度矩阵?
提示:A是没有经过归一化的矩阵,这样与特征矩阵H相乘会改变特征原本的分布,所以对A做一个标准化处理。平衡度很大的节点的重要性。(对称归一化拉普拉斯矩阵)
N o r m A i j = A i j d i d j NormA_{ij} = \frac{A_{ij}}{\sqrt{d_{i}}\sqrt{d_{j}}} NormAij=didjAij
5. Model
本文使用了一个两层的GCN进行节点分类。模型结构图如下图所示:
其具体流程为:
- 首先获取节点的特征表示 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SNF6k4D0-1651321352705)(https://www.zhihu.com/equation?tex=X)] 并计算邻接矩阵 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhttvEi8-1651321352705)(https://www.zhihu.com/equation?tex=\hat{A}%3D\tilde{D}{-\frac{1}{2}}\tilde{A}\tilde{D}{-\frac{1}{2}})] 。
- 将其输入到一个两层的GCN网络中,得到每个标签的预测结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1at6fgFl-1651321352706)(https://s2.loli.net/2022/04/30/7l8oDYGuc3aPv5C.png)]
其中, [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7W8TgVt-1651321352706)(https://www.zhihu.com/equation?tex=W{(0)]}\in\mathbb{R}{C\times+H}) 为第一层的权值矩阵,用于将节点的特征表示映射为相应的隐层状态。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H2hEYdVr-1651321352707)(https://www.zhihu.com/equation?tex=W{(1)]}\in\mathbb{R}{H\times+F}) 为第二层的权值矩阵,用于将节点的隐层表示映射为相应的输出( [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9EzVo1O-1651321352708)(https://www.zhihu.com/equation?tex=F)] 对应节点标签的数量)。最后将每个节点的表示通过一个softmax函数,即可得到每个标签的预测结果。
对于半监督分类问题,使用所有有标签节点上的期望交叉熵作为损失函数:
其中, [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yhpuB7zZ-1651321352712)(https://www.zhihu.com/equation?tex=\mathcal{Y}_{L})] 表示有标签的节点集。
6. 简单代码示例
利用Pytorch与torch_geometric进行代码编写
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
class GCN(torch.nn.Module):
def __init__(self, num_node_features, num_classes):
"""
:param num_node_features: 节点的特征维度
:param num_classes: 节点的类别数
"""
super(GCN, self).__init__()
self.conv1 = GCNConv(num_node_features, 16)
self.conv2 = GCNConv(16, num_classes)
def forward(self, data):
x, edge_index = data.x, data.edge_index
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, training=self.training)
x = self.conv2(x, edge_index)
return F.log_softmax(x, dim=1)
def load_data(src, name):
"""
数据集加载函数
:param src: 数据集根路径
:param name: 数据集名称
:return:
"""
return Planetoid(root=src, name=name)
def train(data_set):
"""
模型训练函数
:param data_set: 数据集
:return:
"""
gcn = GCN(data_set.num_node_features, data_set.num_classes)
data = data_set[0]
optimizer = torch.optim.Adam(gcn.parameters(), lr=0.01, weight_decay=5e-4)
gcn.train()
for epoch in range(200):
out = gcn(data)
loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
optimizer.zero_grad()
loss.backward()
optimizer.step()
return gcn
def eval(model, data_set):
"""
模型评估函数
:param model: 需要评估的模型
:param data_set: 参与模型评估的数据集
:return:
"""
data = data_set[0]
model.eval()
pred = model(data).argmax(dim=1)
correct = (pred[data.test_mask] == data.y[data.test_mask]).sum()
acc = int(correct) / int(data.test_mask.sum())
print(f'Accuracy: {acc:.4f}')
def main():
data_set = load_data('./data/Cora', 'Cora')
model = train(data_set)
eval(model, data_set)
if __name__ == '__main__':
main()
def main():
data_set = load_data(‘./data/Cora’, ‘Cora’)
model = train(data_set)
eval(model, data_set)
if name == ‘main’:
main()