geometric源码阅读和分析:MessagePassin类详解和使用

下面所有博客是个人对EEG脑电的探索,项目代码是早期版本不完整,需要完整项目代码和资料请私聊。


数据集
1、脑电项目探索和实现(EEG) (上):研究数据集选取和介绍SEED
相关论文阅读分析:
1、EEG-SEED数据集作者的—基线论文阅读和分析
2、图神经网络EEG论文阅读和分析:《EEG-Based Emotion Recognition Using Regularized Graph Neural Networks》
3、EEG-GNN论文阅读和分析:《EEG Emotion Recognition Using Dynamical Graph Convolutional Neural Networks》
4、论文阅读和分析:Masked Label Prediction: Unified Message Passing Model for Semi-Supervised Classification
5、论文阅读和分析:《DeepGCNs: Can GCNs Go as Deep as CNNs?》
6、论文阅读和分析: “How Attentive are Graph Attention Networks?”
7、论文阅读和分析:Simplifying Graph Convolutional Networks

8、论文阅读和分析:LightGCN: Simplifying and Powering Graph Convolution Network for Recommendation

相关实验和代码实现:
1、用于图神经网络的脑电数据处理实现_图神经网络 脑电
2、使用GCN训练和测试EEG的公开SEED数据集
3、使用GAT训练和测试EEG公开的SEED数据集
4、使用SGC训练和测试SEED数据集
5、使用Transformer训练和测试EEG的公开SEED数据集_eeg transformer
6、使用RGNN训练和测试EEG公开的SEED数据集
辅助学习资料:
1、官网三个简单Graph示例说明三种层次的应用_graph 简单示例
2、PPI数据集示例项目学习图神经网络
3、geometric库的数据处理详解
4、NetworkX的dicts of dicts以及解决Seven Bridges of Königsberg问题
5、geometric源码阅读和分析:MessagePassin类详解和使用
6、cora数据集示例项目学习图神经网络
7、Graph 聚合
8、QM9数据集示例项目学习图神经网络
9、处理图的开源库

MessagePassing设计整体思路的理解

MessagePassing类使用模板方法设计模式进行设计,因为对于图的操作可以分为两步:聚合邻居的特征、标签信息和消息传递到下一层。模板方法非常适合,定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。通俗的说,成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。优点是:具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。

python实现虚函数的方法是通过继承,定义子类必须重写的虚函数可以通过raise NotImplementedError来控制,如下:

def message_and_aggregate(
        self,
        adj_t: Union[SparseTensor, Tensor],
    ) -> Tensor:
        raise NotImplementedError

统一代码框架:

图的卷积操作一般可以分成两步:邻居聚合和消息传递 neighborhood aggregation or message passing scheme.

With x i ( k − 1 ) ∈ R F \mathbf{x}^{(k-1)}_i \in \mathbb{R}^F xi(k1)RFdenoting node features of node i i i in layer ( k − 1 ) (k-1) (k1)and e j , i ∈ R D \mathbf{e}_{j,i} \in \mathbb{R}^D ej,iRDdenoting (optional) edge features from node j j jto node i i i, message passing graph neural networks can be described as:

x i ( k ) = γ ( k ) ( x i ( k − 1 ) , □ j ∈ N ( i )   ϕ ( k ) ( x i ( k − 1 ) , x j ( k − 1 ) , e j , i ) ) , \mathbf{x}_i^{(k)} = \gamma^{(k)} \left( \mathbf{x}_i^{(k-1)}, \square_{j \in \mathcal{N}(i)} \, \phi^{(k)}\left(\mathbf{x}_i^{(k-1)}, \mathbf{x}_j^{(k-1)},\mathbf{e}_{j,i}\right) \right), xi(k)=γ(k)(xi(k1),jN(i)ϕ(k)(xi(k1),xj(k1),ej,i)),
where ◻ ◻ denotes a differentiable, permutation invariant function, e.g., sum, mean or max, and γ \gamma γ and ϕ \phi ϕ denote differentiable functions such as MLPs (Multi Layer Perceptrons).

使用

MessagePassing(aggr=“add”,flow=“source_to_target”,node_dim=-2):

定义要使用的聚合方案(“add”、“mean”或“max”)和消息传递的流向(“source_to-target”或“target_to_source”)。此外,node_dim属性指示沿哪个轴传播。

MessagePassing.propagate(edge_index,size=None,**kwargs):

开始传播消息的初始调用。获取边索引和构造消息和更新节点嵌入所需的所有附加数据。注意,propagate()不仅限于在形状[N,N]的正方形邻接矩阵中交换消息,还可以通过传递size=(N,M)作为附加参数,在形状[N,M]的一般稀疏分配矩阵(例如二分图)中交换消息。如果设置为“无”,则假定赋值矩阵为方阵。对于具有两个独立的节点和索引集合的二分图,并且每个集合都持有自己的信息,可以通过将信息作为元组传递来标记这种分裂,例如x=(x_N,x_M)。

MessagePassing.message(…)

构造消息到节点 i i i相当于对每条边 ( j , i ) ∈ ε (j,i)\in\varepsilon (j,i)ε如果flow=“source_to_target”,或者条边 ( i , j ) ∈ ε (i,j)\in\varepsilon (i,j)ε如果flow="target_to_source"进行 ϕ \phi ϕ运算。可以使用propagate传递的所有参数。并且tensors通过propagate()可以通过添加后缀 _ i , _ j \_i,\_j _i,_j被映射到各自的节点 i i i和节点 j j j。例如 x i , x j x_i,x_j xi,xj,通常 i i i是中心节点,而 j j j是邻居节点。

MessagePassing.update(aggr_out, …):

更新节点嵌入相当于对每个节点 i ∈ V i\in V iV进行 γ \gamma γ运算.将聚合的输出作为第一个参数和最初传递给propagate()的任何参数.

在propagate中顺序调用message、aggregate()和update函数。

示例:The GCN layer is mathematically defined as

x i ( k ) = ∑ j ∈ N ( i ) ∪ { i } 1 deg ⁡ ( i ) ⋅ deg ⁡ ( j ) ⋅ ( W ⊤ ⋅ x j ( k − 1 ) ) + b , \mathbf{x}_i^{(k)} = \sum_{j \in \mathcal{N}(i) \cup \{ i \}} \frac{1}{\sqrt{\deg(i)} \cdot \sqrt{\deg(j)}} \cdot \left( \mathbf{W}^{\top} \cdot \mathbf{x}_j^{(k-1)} \right) + \mathbf{b}, xi(k)=jN(i){i}deg(i) deg(j) 1(Wxj(k1))+b,

抽象的框架如下:

在这里插入图片描述

对应于基本的框架的话:
ϕ ( k ) ( x i ( k − 1 ) , x j ( k − 1 ) , e j , i ) = 1 deg ⁡ ( i ) ⋅ deg ⁡ ( j ) ⋅ ( W ⊤ ⋅ x j ( k − 1 ) ) + b \phi^{(k)}\left(\mathbf{x}_i^{(k-1)}, \mathbf{x}_j^{(k-1)},\mathbf{e}_{j,i}\right)=\frac{1}{\sqrt{\deg(i)} \cdot \sqrt{\deg(j)}} \cdot \left( \mathbf{W}^{\top} \cdot \mathbf{x}_j^{(k-1)} \right) + \mathbf{b} ϕ(k)(xi(k1),xj(k1),ej,i)=deg(i) deg(j) 1(Wxj(k1))+b

□ j ∈ N ( i ) = ∑ j ∈ N ( i ) ∪ { i } \square_{j \in \mathcal{N}(i)}=\sum_{j \in \mathcal{N}(i) \cup \{ i \}} jN(i)=jN(i){i}

γ ( k ) = D i r e c t   m a p p i n g \gamma^{(k)}=Direct \ mapping γ(k)=Direct mapping

代码实现:
注意,和公式描述有区别,使用矩阵乘法代替求和的聚合操作,减少计算量;

"""
1、Add self-loops to the adjacency matrix.
2、Linearly transform node feature matrix.
3、Compute normalization coefficients.
4、Normalize node features
5、Sum up neighboring node features ("add" aggregation).Apply a final bias vector.

Steps 1-3 are typically computed before message passing takes place. Steps 4-5 can be easily processed using the MessagePassing base class. The full layer implementation is shown below:
"""
import torch
from torch.nn import Linear, Parameter
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import add_self_loops, degree

class GCNConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super().__init__(aggr='add')  # "Add" aggregation (Step 5).
        
        # 设置可训练参数
        self.lin = Linear(in_channels, out_channels, bias=False)#shape=(in_channels, out_channels)
        self.bias = Parameter(torch.Tensor(out_channels))

        #重置和初始化可学习的参数
        self.reset_parameters()

    def reset_parameters(self):
        self.lin.reset_parameters()
        self.bias.data.zero_()

    def forward(self, x, edge_index):
        # x has shape [N, in_channels]
        # edge_index has shape [2, E]

        # Step 1: Add self-loops to the adjacency matrix.
        edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))

        # Step 2: Linearly transform node feature matrix.
        # 使用矩阵乘法代替求和操作,提高运算速度;
        x = self.lin(x)

        # Step 3: Compute normalization.
        row, col = edge_index
        # 计算度和开方
        deg = degree(col, x.size(0), dtype=x.dtype)
        deg_inv_sqrt = deg.pow(-0.5)
        deg_inv_sqrt[deg_inv_sqrt == float('inf')] = 0        
        norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]# 数组,节点和所有邻居的度的开方乘积;

        # Step 4-5: Start propagating messages.
        # 使用基类的聚合操作
        out = self.propagate(edge_index, x=x, norm=norm)

        # Step 6: Apply a final bias vector.
        out += self.bias

        return out

    def message(self, x_j, norm):
        # x_j has shape [E, out_channels]
        # x_j i节点每条边的source node的特征,就是节点i的邻居节点的特征;
        # Step 4: Normalize node features.使用矩阵乘法代替累加,提高运算速度
        return norm.view(-1, 1) * x_j

示例2:Implementing the Edge Convolution

The edge convolutional layer processes graphs or point clouds and is mathematically defined as
x i ( k ) = max ⁡ j ∈ N ( i ) h Θ ( x i ( k − 1 ) , x j ( k − 1 ) − x i ( k − 1 ) ) , \mathbf{x}_i^{(k)} = \max_{j \in \mathcal{N}(i)} h_{\mathbf{\Theta}} \left( \mathbf{x}_i^{(k-1)}, \mathbf{x}_j^{(k-1)} - \mathbf{x}_i^{(k-1)} \right), xi(k)=jN(i)maxhΘ(xi(k1),xj(k1)xi(k1)),
where h Θ h_{\mathbf{\Theta}} hΘ denotes an MLP.

对应于基本框架的话:
ϕ ( k ) ( x i ( k − 1 ) , x j ( k − 1 ) , e j , i ) = h Θ ( x i ( k − 1 ) , x j ( k − 1 ) − x i ( k − 1 ) ) , \phi^{(k)}\left(\mathbf{x}_i^{(k-1)}, \mathbf{x}_j^{(k-1)},\mathbf{e}_{j,i}\right)=h_{\mathbf{\Theta}} \left( \mathbf{x}_i^{(k-1)}, \mathbf{x}_j^{(k-1)} - \mathbf{x}_i^{(k-1)} \right), ϕ(k)(xi(k1),xj(k1),ej,i)=hΘ(xi(k1),xj(k1)xi(k1)),

□ j ∈ N ( i ) = max ⁡ j ∈ N ( i ) \square_{j \in \mathcal{N}(i)}= \max_{j \in \mathcal{N}(i)} jN(i)=jN(i)max

γ ( k ) = D i r e c t   m a p p i n g \gamma^{(k)}=Direct \ mapping γ(k)=Direct mapping

import torch
from torch.nn import Sequential as Seq, Linear, ReLU
from torch_geometric.nn import MessagePassing

class EdgeConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super().__init__(aggr='max') #  "Max" aggregation.
        # 定义MLP层
        self.mlp = Seq(Linear(2 * in_channels, out_channels),
                       ReLU(),
                       Linear(out_channels, out_channels))

    def forward(self, x, edge_index):
        # x has shape [N, in_channels]
        # edge_index has shape [2, E]

        return self.propagate(edge_index, x=x)

    def message(self, x_i, x_j):
        # x_i has shape [E, in_channels]
        # x_j has shape [E, in_channels]

        tmp = torch.cat([x_i, x_j - x_i], dim=1)  # tmp has shape [E, 2 * in_channels]
        return self.mlp(tmp)

参考:

Creating Message Passing Networks — pytorch_geometric documentation (pytorch-geometric.readthedocs.io)

23 种设计模式详解(全23种)_鬼灭之刃的博客-CSDN博客_设计模式

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Pytorch Geometric提供了一些内置的归一化和池化方法,可以方便地用于GCN模型中。下面分别介绍这些方法的用法。 1. 归一化 Pytorch Geometric提供了两种常见的归一化方法:对称归一化和随机游走归一化。 对称归一化: ```python import torch_geometric.transforms as T data = T.NormalizeSymm()(data) ``` 随机游走归一化: ```python import torch_geometric.transforms as T data = T.RandomWalk()(data) ``` 其中,`data`是一个包含图数据的对象,比如`torch_geometric.data.Data`。 2. 池化 池化操作可以将一张大图缩小到一张小图,从而减少模型参数和计算量。Pytorch Geometric提供了几种常见的池化方法,比如TopK池化、SAG Pooling和Diff Pooling。 TopK池化: ```python import torch_geometric.nn.pool as pool x, edge_index, batch = pool.topk(x, ratio=0.5, batch=batch) ``` 其中,`x`是节点特征矩阵,`edge_index`是边的索引矩阵,`batch`是节点所属的图的标识符。`ratio`是池化后每个图保留的节点数占原图节点数的比例。 SAG Pooling: ```python import torch_geometric.nn.pool as pool x, edge_index, _, batch, _, _ = pool.sag_pool(x, edge_index, batch) ``` 其中,`x`、`edge_index`和`batch`的含义同TopK池化。SAG Pooling使用节点嵌入向量计算每个节点的注意力权重,根据权重进行池化。 Diff Pooling: ```python import torch_geometric.nn as nn diffpool = nn.DiffPool(in_channels, hidden_channels, num_classes) x, edge_index, edge_attr, batch, perm, score = diffpool(x, edge_index) ``` 其中,`in_channels`是输入节点特征的维度,`hidden_channels`是池化后节点特征的维度,`num_classes`是分类的类别数。`x`、`edge_index`和`batch`的含义同TopK池化。Diff Pooling使用GraphSAGE卷积层计算每个节点的嵌入向量,根据嵌入向量进行池化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KPer_Yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值