现有图神经网络皆基于邻居聚合的框架,即为每个目标节点通过聚合其邻居刻画结构信息,进而学习目标节点的表示
- 谱域方法:利用图上卷积定理从谱域定义图卷积。
- 空间域方法:从节点域出发,通过在节点层面定义聚合函数来聚合每个中心节点和其邻近节点。
谱域图卷积神经网络
谱图理论和图卷积
卷积的傅里叶变换
卷积定理:信号卷积的傅立叶变换等价于信号傅立叶变换的乘积
其中的 f,g 表示两个原始信号,F(f) 表示 f 的傅立叶变换, ⋅ 表示乘积算子, ∗ 表示卷积算子
对上面的公式做傅立叶逆变换,可以得到
利用卷积定理,我们可以对谱空间的信号做乘法,再利用傅里叶逆变换将信号转换到原空间来实现图卷积,从而避免了图数据不满足平移不变性而造成的卷积定义困难问题
图傅里叶变换
图傅立叶变换依赖于图上的拉普拉斯矩阵 L。对 L 做谱分解,我们可以得到
其中 Λ 是特征值矩阵, U 是对应的特征向量矩阵。如下图所示
图上傅立叶变换的定义依赖于拉普拉斯矩阵的特征向量。以特征向量作为谱空间下的一组基底,图上信号 x 的傅立叶变换为:
其中 x 指信号在节点域的原始表示。x^ 指信号 x 变换到谱域后的表示
图卷积
先将图进行傅里叶变化,在谱域完成卷积操作,然后再将频域信号转换回原域。我们将卷积核定义为 gθ(Λ),那么卷积的频域表示可以写为
先对输入 x (其输出为 y) 进行傅里叶变换得到 UTx (以及 UTy),然后对其应用卷积核得到
最后,再利用傅里叶逆变换得到
以上就是最重要的图卷积操作。它一般可以被简化为
谱卷积神经网络
该方法完全按照上述得到的卷积操作,堆叠多层得到。我们可以将上面公式的 x 和 y 替换为图上的节点特征 H(l) 和 H(l+1),那么第 l 层的结构如下
σ 表示非线性激活函数。
切比雪夫网络
谱卷积神经网络其缺点为:
(1)难以从卷积形式中保证节点的信息更新由其邻居节点贡献,因此无法保证局部性
(2)谱卷积神经网络的计算复杂度比较大,难以扩展到大型图网络结构中
切比雪夫网络对卷积核 gθ 进行参数化
其中 θk 是需要学的系数,并定义切比雪夫多项式是可以通过递归求解,递归表达式为
其中初始值 T0(x)=1,T1(x)=x
切比雪夫网络第 l 层的结构定义如下:
当且仅当两个节点满足 K 跳可达时,其拉普拉斯矩阵中这一项不为 0,这一性质使得当 K 较小时,切比雪夫网络具有局部性
图卷积神经网络
图卷积神经网络(GCN)对切比雪夫网络进行了简化,只取 0 阶和 1 阶,形式如下
令 θ=θ0=−θ1,然后应用一个重新标准化的trick,在应用于图神经网络的第 l 层时
图卷积神经网络代码
# 获得图的一些统计特征
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}')
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()}')
Number of nodes
: 图中节点的数量。Number of edges
: 图中边的数量。Average node degree
: 平均节点度,即每个节点连接的平均边数。Has isolated nodes
: 是否存在孤立节点,即没有与其他节点连接的节点。Has self-loops
: 是否存在自环,即节点与自身相连的边。Is undirected
: 图是否是无向图,即边没有方向性。
from torch_geometric.loader import DataLoadertrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
批处理对于图数据比较复杂和麻烦。PyTorch Geometric 选择了一种和常见图像数据集不同的方法来实现多个示例的并行化。 在这里,邻接矩阵以对角方式堆叠(创建一个包含多个孤立子图的巨型图),并且节点和目标特征在节点维度中简单地连接——[num_nodes_total, num_node_features + num_target_features]
与其他批处理程序相比,该程序具有一些关键优势:(1)依赖于消息传递方案的 GNN 算子不需要修改,因为属于不同图的两个节点之间不会交换消息;(2)由于邻接矩阵以稀疏方式保存,仅保存非零条目(即边),因此不存在计算或内存开销
训练 GNN 进行图分类通常遵循一个简单的方法:
- 通过执行多轮消息传递来嵌入每个节点。
- 将节点嵌入聚合为统一的图嵌入(读出层)。
- 在图嵌入上训练最终分类器。
最常见的一种读出层是简单地取节点嵌入的平均值:
PyTorch Geometric 通过 torch_geometric.nn.global_mean_pool
提供此功能,它接受小批量中所有节点的节点嵌入和分配向量批量,以计算批量中每个图的大小为 [batch_size, hide_channels]
的图嵌入
from torch.nn import Linear
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.nn import global_mean_poolclass GCN(torch.nn.Module):def __init__(self, hidden_channels):super(GCN, self).__init__()torch.manual_seed(12345)# 使用 GCNConv # 为了让模型更稳定我们也可以使用带有跳跃链接的 GraphConv# from torch_geometric.nn import GraphConvself.conv1 = GCNConv(dataset.num_node_features, hidden_channels)self.conv2 = GCNConv(hidden_channels, hidden_channels)self.conv3 = GCNConv(hidden_channels, hidden_channels)self.lin = Linear(hidden_channels, dataset.num_classes)def forward(self, x, edge_index, batch):# 1. 获得节点的嵌入x = self.conv1(x, edge_index)x = x.relu()x = self.conv2(x, edge_index)x = x.relu()x = self.conv3(x, edge_index)# 2. 读出层x = global_mean_pool(x, batch) # [batch_size, hidden_channels]# 3. 应用最后的分类器x = F.dropout(x, p=0.5, training=self.training)x = self.lin(x)return xmodel = GCN(hidden_channels=64)
print(model)
空间域图卷积神经网络
pass