这次我一开始真的看懵了,不知道我要干什么,消息传递图神经网络读起来也怪怪的。
直到快要交作业的时候,我貌似有些明白标题的意思了(好多次都是这样,到节点才开始……)
我的理解是这样的,所谓消息传递(MessagePassing)的目的是为了将每个节点生成node embedding,这就很像transformer里的注意力机制了。
这个embedding的过程大约是:
我是我,
我不是我,
我还是我。
即一开始,图中的节点从描述了实际的情况,但是这么直白的描述所包含的信息太孤立了,这种情况下做图训练和做CV估计都差不多了,我们希望每个节点所包含的信息是不仅有自身的属性,还有联系的属性,毕竟哲学告诉我们世界是联系的。
然后,就使用消息传递范式用节点周围的节点和边(可选)与该节点来个“糅合”,打太极的那种,混的越玄乎说不定效果越好。
之后,当每个节点都被”糅合“了以后,大家就包含了互相的信息,这是我还是我,但是我不仅有我,我心里还有大家。
这个”糅合“ 的过程是convolution operator,也就是卷积运算,所以”卷“这个东西虽然折磨人吧,但是用它去折磨数据还是挺有益的。
torch-geometry官方一开始只是用符号描述了convoluntion operator的概念(大佬都是搭框架的,并且搭出来还很好用),然后具体介绍了两种方法,GCN layer from Kipf and Welling和EdgeConv layer from Wang et al.
让我们看一下总概念公式和两个具体的公式,这样就清晰多了。
这两个公式如何实现呢?
torch-geometry已经写好了MessagePassing的类,继承过来相应改下就Ok了。
GCN layer
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') # "Add" aggregation (Step 5).
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]
# 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.
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
关于这一步“# Step 1: Add self-loops to the adjacency matrix.”,群里有人说是为了把自己的信息保留住,防止自身信息丢失。
edge convolution layer
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(EdgeConv, self).__init__(aggr='max') # "Max" aggregation.
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)
如下将edge convolution 升级为动态的convolution
from torch_geometric.nn import knn_graph
class DynamicEdgeConv(EdgeConv):
def __init__(self, in_channels, out_channels, k=6):
super(DynamicEdgeConv, self).__init__(in_channels, out_channels)
self.k = k
def forward(self, x, batch=None):
edge_index = knn_graph(x, self.k, batch, loop=False, flow=self.flow)
return super(DynamicEdgeConv, self).forward(x, edge_index)
本次组队学习的教程还讲了如何覆写propagate,message,update以及官网介绍貌似没有说的aggregate与message_and_aggragate函数,应该都是在公式上进行个性化制定。 本次作业也是MessagePassing这几个函数的覆写,我刚刚看懂,比较吃力,还是留着以后再研究吧