葡萄书--图卷积网络

现有图神经网络皆基于邻居聚合的框架,即为每个目标节点通过聚合其邻居刻画结构信息,进而学习目标节点的表示

  • 谱域方法:利用图上卷积定理从谱域定义图卷积。
  • 空间域方法:从节点域出发,通过在节点层面定义聚合函数来聚合每个中心节点和其邻近节点。

谱域图卷积神经网络

谱图理论和图卷积

卷积的傅里叶变换

卷积定理:信号卷积的傅立叶变换等价于信号傅立叶变换的乘积

其中的 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 DataLoader

train_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_pool


class GCN(torch.nn.Module):
    def __init__(self, hidden_channels):
        super(GCN, self).__init__()
        torch.manual_seed(12345)
        # 使用 GCNConv 
        # 为了让模型更稳定我们也可以使用带有跳跃链接的 GraphConv
        # from torch_geometric.nn import GraphConv
        self.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 x

model = GCN(hidden_channels=64)
print(model)

空间域图卷积神经网络

图卷积神经网络的空域理解

从邻居节点信息聚合的角度出发,应该做的如下两件事情

  • 对节点的信息进行转换(Message Transformation)
  • 对节点信息进行聚合 (Message Aggregation)

其中第一项表示邻居节点信息的转换和聚合,第二项表示自身节点信息的变换

上述的公式的矩阵表达可以写为

空域图卷积的统一范式和GraphSAGE

图卷积的统一范式

其中 TRANSu(l)​ 表示对邻居节点信息的转换,TRANSv(l)​ 表示对自身节点信息的转换,AGG1l​ 表示对邻居节点信息的聚合,AGG2l​ 表示对自身节点信息的聚合

可以看出来 GCN 在进行聚合的时候是没有考虑边的权重的而当作 1 进行简单的加和

GraphSAGE

  1. 在训练时,采样方式将 GCN 的全图采样优化到部分以节点为中心的邻居抽样,这使得大规模图数据的分布式训练成为可能,并且使得网络可以学习没有见过的节点,这也使得 GraphSAGE 可以做 Inductive Learning。
  2. GraphSAGE 研究了若干种邻居聚合的方式,及其 AGG 聚合函数可以使用
    • 平均
    • Max Pooling
    • LSTM

在 GraphSAGE 之前的 GCN 模型中,都是采用的全图的训练方式,也就是说每一轮的迭代都要对全图的节点进行更新

GraphSAGE 提出了一个mini-batch解决方案:

  1. 对邻居进行随机采样,每一跳抽样的邻居数不多于 Sk​ 个;
  2. 生成目标节点的 embedding:先聚合二跳邻居的特征,生成一跳邻居的embedding,再聚合一跳的 embedding,生成目标节点的 embedding;
  3. 将目标节点的 embedding 输入全连接网络得到目标节点的预测值。

GraphSAGE代码

# 分离负样本
adj = sp.coo_matrix((np.ones(len(u)), (u.numpy(), v.numpy())))
adj_neg = 1 - adj.todense() - np.eye(g.number_of_nodes())
neg_u, neg_v = np.where(adj_neg != 0)

uv表示一条边,创建一个adj稀疏矩阵,通过1-得到不存在边的地方

from dgl.nn import SAGEConv

# 构建一个两层的 GraphSAGE 模型
class GraphSAGE(nn.Module):
    def __init__(self, in_feats, h_feats):
        super(GraphSAGE, self).__init__()
        self.conv1 = SAGEConv(in_feats, h_feats, 'mean')
        self.conv2 = SAGEConv(h_feats, h_feats, 'mean')

    def forward(self, g, in_feat):
        h = self.conv1(g, in_feat)
        h = F.relu(h)
        h = self.conv2(g, h)
        return h
# 构建正样本和负样本的图
train_pos_g = dgl.graph((train_pos_u, train_pos_v), num_nodes=g.number_of_nodes())
train_neg_g = dgl.graph((train_neg_u, train_neg_v), num_nodes=g.number_of_nodes())

test_pos_g = dgl.graph((test_pos_u, test_pos_v), num_nodes=g.number_of_nodes())
test_neg_g = dgl.graph((test_neg_u, test_neg_v), num_nodes=g.number_of_nodes())

正负样本是通过节点对表示的,在这里把它们变成图,使得正负样本可以直接作为图的结构输入到模型中

import dgl.function as fn

class DotPredictor(nn.Module):
    def forward(self, g, h):
        with g.local_scope():
            g.ndata['h'] = h
            # 通过点积计算一个新的边的分数
            g.apply_edges(fn.u_dot_v('h', 'h', 'score'))
            # u_dot_v 返回了一个 1-element 的向量,所以需要压平它
            return g.edata['score'][:, 0]

class MLPPredictor(nn.Module):
    def __init__(self, h_feats):
        super().__init__()
        self.W1 = nn.Linear(h_feats * 2, h_feats)
        self.W2 = nn.Linear(h_feats, 1)

    def apply_edges(self, edges):
        """
        Computes a scalar score for each edge of the given graph.

        Parameters
        ----------
        edges :
            Has three members ``src``, ``dst`` and ``data``, each of
            which is a dictionary representing the features of the
            source nodes, the destination nodes, and the edges
            themselves.

        Returns
        -------
        dict
            A dictionary of new edge features.
        """
        h = torch.cat([edges.src['h'], edges.dst['h']], 1)
        return {'score': self.W2(F.relu(self.W1(h))).squeeze(1)}

    def forward(self, g, h):
        with g.local_scope():
            g.ndata['h'] = h
            g.apply_edges(self.apply_edges)
            return g.edata['score']

两个图神经网络中常用的预测器类:DotPredictorMLPPredictor

  1. DotPredictor 类:图上两点的特征做点积
  2. MLPPredictor 类:源节点和目标节点的特征拼接后过MLP

optimizer = torch.optim.Adam(itertools.chain(model.parameters(), pred.parameters()), lr=0.01)

# 训练
all_logits = []
for e in range(100):
    # 前向传播
    h = model(train_g, train_g.ndata['feat'])
    pos_score = pred(train_pos_g, h)
    neg_score = pred(train_neg_g, h)
    loss = compute_loss(pos_score, neg_score)

    # 更新参数
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if e % 5 == 0:
        print('In epoch {}, loss: {}'.format(e, loss))

# 计算AUC
with torch.no_grad():
    pos_score = pred(test_pos_g, h)
    neg_score = pred(test_neg_g, h)
    print('AUC', compute_auc(pos_score, neg_score))

SAGE提供特征,预测器打分,优化器同时更新model和pred

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值