基于图神经网络的节点表征学习
引言
将节点的属性(x)、边的端点信息(edge_index)、边的属性(edge_attr,如果有的话)输入到多层图神经网络,经过图神经网络每一层的一次节点间信息传递,图神经网络为节点生成节点表征。我们的任务是根据节点的属性(可以是类别型、也可以是数值型)、边的信息、边的属性(如果有的话)、已知的节点预测标签,对未知标签的节点做预测。
原理
卷积图神经网络GCN:\(\mathbf{X}^{\prime} = \mathbf{\hat{D}}^{-1/2} \mathbf{\hat{A}}\mathbf{\hat{D}}^{-1/2} \mathbf{X} \mathbf{\Theta}\),
-
\(\mathbf{\hat{A}} = \mathbf{A} + \mathbf{I}\)表示插入自环的邻接矩阵,
-
\(\hat{D}_{ii} = \sum_{j=0} \hat{A}_{ij}\)表示其对角线度矩阵。
-
邻接矩阵可以包括不为\(1\)的值,当邻接矩阵不为
{0,1}值时,表示邻接矩阵存储的是边的权重。 -
\(\mathbf{\hat{D}}^{-1/2} \mathbf{\hat{A}}\mathbf{\hat{D}}^{-1/2}\)对称归一化矩阵。
它的节点式表述为:\(\mathbf{x}^{\prime}_i = \mathbf{\Theta} \sum_{j \in \mathcal{N}(v) \cup\{ i \}} \frac{e_{j,i}}{\sqrt{\hat{d}_j \hat{d}_i}} \mathbf{x}_j\),其中,
- \(\hat{d}_i = 1 + \sum_{j \in \mathcal{N}(i)} e_{j,i}\);
- \(e_{j,i}\)表示从源节点\(j\)到目标节点\(i\)的边的对称归一化系数(默认值为1.0)。
图注意力神经网络GAT:\(\mathbf{x}^{\prime}_i = \alpha_{i,i}\mathbf{\Theta}\mathbf{x}_{i} +\sum_{j \in \mathcal{N}(i)} \alpha_{i,j}\mathbf{\Theta}\mathbf{x}_{j}\)
- 其中注意力系数\(\alpha_{i,j}\)的计算方法为:\(\alpha_{i,j} =\frac{\exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top}[\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_j]\right)\right)}{\sum_{k \in \mathcal{N}(i) \cup \{ i \}}\exp\left(\mathrm{LeakyReLU}\left(\mathbf{a}^{\top}[\mathbf{\Theta}\mathbf{x}_i \, \Vert \, \mathbf{\Theta}\mathbf{x}_k]\right)\right)}.\)
MLP、GCN和GAT的比较
-
MLP只考虑了节点自身属性,忽略了节点之间的连接关系,而GCN和GAT同时考虑了节点自身信息与邻接节点的信息
-
GCN与GAT的共同点:
(1)遵循消息传递范式
(2)邻接节点变换时,对邻接节点做归一化和线性变换
(3)邻接节点聚合时,对变换后的邻接节点做sum聚合
(4)中心节点变换时,返回邻接节点聚合阶段的聚合结果
- GCN与GAT的区别(归一化方法):
(1)GCN根据中心节点与邻接节点的度计算归一化系数,GAT根据中心节点与邻接节点的相似度计算归一化系数
(2)GCN归一化依赖图的拓扑结构,GAT归一化依赖中心节点与邻接节点的相似度
代码实现
MLP:
import torch
from torch.nn import Linear
import torch.nn.functional as F
class MLP(torch.nn.Module):
def __init__(self, hidden_channels):
super(MLP, self).__init__()
torch.manual_seed(12345)
self.lin1 = Linear(dataset.num_features, hidden_channels)
self.lin2 = Linear(hidden_channels, dataset.num_classes)
def forward(self, x):
x = self.lin1(x)
x = x.relu()
x = F.dropout(x, p=0.5, training=self.training)
x = self.lin2(x)
return x
model = MLP(hidden_channels=16)
print(model)
model = MLP(hidden_channels=16)
criterion = torch.nn.CrossEntropyLoss() # Define loss criterion.
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) # Define optimizer.
def train():
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
for epoch in range(1, 201):
loss = train()
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
测试
def test():
model.eval()
out = model(data.x)
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
test_acc = test()
print(f'Test Accuracy: {test_acc:.4f}')
GCN
from torch_geometric.nn import GCNConv
class GCN(torch.nn.Module):
def __init__(self, hidden_channels):
super(GCN, self).__init__()
torch.manual_seed(12345)
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(model)
GAT
import torch
from torch.nn import Linear
import torch.nn.functional as F
from torch_geometric.nn import GATConv
class GAT(torch.nn.Module):
def __init__(self, hidden_channels):
super(GAT, self).__init__()
torch.manual_seed(12345)
self.conv1 = GATConv(dataset.num_features, hidden_channels)
self.conv2 = GATConv(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

浙公网安备 33010602011771号