目录
task02 消息传递图神经网络
作业一:MessagePassing类
请总结MessagePassing类的运行流程以及继承MessagePassing类的规范。
1. 构建领域信息,源节点向目标中心节点传递特征
2. 目标节点对接收到的特征通过聚合函数进行聚合
3. 将聚合之后的信息进行变换,更新目标节点
import torch
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(GCNConv, self).__init__(aggr='add', flow='source_to_target')
# Step 5:将相邻节点特征相加("求和"聚合)
# flow='source_to_target' 表示消息从源节点传播到目标节点
self.lin = torch.nn.Linear(in_channels, out_channels)
def forward(self, x, edge_index):
# x has shape [N, in_channels]
# edge_index has shape [2, E]
"""
x = torch.tensor(
[[-1, 1, 2], [1, 1, 1], [3, 1, 2], [2, 1, 1], [-1, 1, 0]]
)
edge_index = torch.tensor(
[[2, 3, 4, 4], [0, 1, 0, 1]]
)
"""
# Step 1: 向邻接矩阵添加自环边
# 目的: 让feature在聚合的过程中加入当前节点自己的feature,没有self-loop聚合的就只有邻居节点的信息
"""
self-loops = tensor([[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]])
edge_index = tensor([[2, 3, 4, 4, 0, 1, 2, 3, 4],
[0, 1, 0, 1, 0, 1, 2, 3, 4]])
"""
edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))
# Step 2: 线性转换节点特征矩阵
x = self.lin(x)
# Step 3: 计算归一化系数
"""
D^(-0.5) A D^(-0.5)
out = torch.zeros((N, ), dtype=dtype, device=index.device)
# [0, 0, 0, 0, 0]
one = torch.ones((index.size(0), ), dtype=out.dtype, device=out.device)
# [1, 1, 1, 1, 1, 1, 1, 1, 1]
deg = out.scatter_add_(0, index, one)
# [3., 3., 1., 1., 1.]
like (adjacency matrix -> degree matrix):
1 0 1 0 1 3 0 0 0 0
0 1 0 1 1 0 3 0 0 0
0 0 1 0 0 -> 0 0 1 0 0
0 0 0 1 0 0 0 0 1 0
0 0 0 0 1 0 0 0 0 1
"""
row, col = edge_index
"""
row = [2, 3, 4, 4, 0, 1, 2, 3, 4]
col = [0, 1, 0, 1, 0, 1, 2, 3, 4]
"""
deg = degree(col, x.size(0), dtype=x.dtype) # 转换成每个边的节点度
"""
deg = tensor([3., 3., 1., 1., 1.])
"""
deg_inv_sqrt = deg.pow(-0.5)
"""
deg_inv_sqrt: tensor([0.5774, 0.5774, 1.0000, 1.0000, 1.0000])
"""
a = deg_inv_sqrt[col]
norm = deg_inv_sqrt[row] * deg_inv_sqrt[col] # 结果被保存在形状[num_edges,]的张量norm中
"""
deg_inv_sqrt[row]: tensor([1.0000, 1.0000, 1.0000, 1.0000, 0.5774, 0.5774, 1.0000, 1.0000, 1.0000])
deg_inv_sqrt[col]: tensor([0.5774, 0.5774, 0.5774, 0.5774, 0.5774, 0.5774, 1.0000, 1.0000, 1.0000])
norm: tensor([0.5774, 0.5774, 0.5774, 0.5774, 0.3333, 0.3333, 1.0000, 1.0000, 1.0000])
"""
# Step 4-5: Start propagating messages.
return self.propagate(edge_index, x=x, norm=norm)
def message(self, x_j, norm):
# x_j has shape [E, out_channels]
# Step 4: Normalize node features.
return norm.view(-1, 1) * x_j
# num_node_features==in_channels?
conv = GCNConv(3, 5)
# x: [num_nodes,num_node_features]
x = torch.tensor([[-1,1,2],[1,1,1],[3,1,2],[2,1,1],[-1,1,0]],dtype=torch.float)
edge_index = torch.tensor([[2,3,4,4],
[0,1,0,1]])
x = conv(x, edge_index)
作业二:自定义图神经网络类
请继承MessagePassing类来自定义以下的图神经网络类,并进行测 试:
-
第一个类,覆写message函数,要求该函数接收消息传递源节点属性x、目标节点度d。
-
第二个类,在第一个类的基础上,再覆写aggregate函数,要求不能调用 super 类的 aggregate 函数,并且不能直接复制 super 类的 aggregate 函数内容。
-
第三个类,在第二个类的基础上,再覆写 update 函数,要求对节点信息做一层线性变换。
-
第四个类,在第三个类的基础上,再覆写message_and_aggregate 函数,要求在这一个函数中实现前面 message 函数和 aggregate 函数的功能。
def message(self, x_i, norm, d):
# x_i has shape [E, out_channels]
# Step 4: Normalize node features.
return norm.view(-1, 1) * d * x_i
def aggregate(self, inputs, index, dim_size):
return scatter_add(inputs, index, dim=self.node_dim, dim_size=dim_size)
def update(self, inputs: Tensor) -> Tensor:
lin = torch.nn.Linear(inputs.size(1), inputs.size(1))
return lin(inputs)
def message_and_aggregate(self, adj_t, x, norm):
inputs = norm.view(-1, 1) * d * x
return scatter_add(inputs, adj_t, dim=self.node_dim, dim_size=dim_size)