Loading

基于图神经网络的节点表征学习

引言

将节点的属性(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}\)

  1. \(\mathbf{\hat{A}} = \mathbf{A} + \mathbf{I}\)表示插入自环的邻接矩阵,

  2. \(\hat{D}_{ii} = \sum_{j=0} \hat{A}_{ij}\)表示其对角线度矩阵。

  3. 邻接矩阵可以包括不为\(1\)的值,当邻接矩阵不为{0,1}值时,表示邻接矩阵存储的是边的权重。

  4. \(\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\),其中,

  1. \(\hat{d}_i = 1 + \sum_{j \in \mathcal{N}(i)} e_{j,i}\)
  2. \(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的比较

  1. MLP只考虑了节点自身属性,忽略了节点之间的连接关系,而GCN和GAT同时考虑了节点自身信息与邻接节点的信息

  2. GCN与GAT的共同点:

(1)遵循消息传递范式

(2)邻接节点变换时,对邻接节点做归一化和线性变换

(3)邻接节点聚合时,对变换后的邻接节点做sum聚合

(4)中心节点变换时,返回邻接节点聚合阶段的聚合结果

  1. 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
posted @ 2021-06-22 22:35  橘猫非猫gkd  阅读(496)  评论(0)    收藏  举报