一、前言
GeniePath,一种可扩展的能够学习自适应感受路径的图神经网络框架。它定义在具有排列不变性的图数据上(permutation invariant graph data)。它的自适应路径层(adaptive path layer)包括两个互补的功能单元,分别用来进行广度与深度的探索,前者用来学习一阶邻居节点的权重,后者用来提取和过滤高阶邻居内汇聚的信息。
1、感受野的定义
2基于谱域的图卷积网络通过拉普拉斯矩阵为每个节点定义邻居的重要性,因此邻居就是感受野。
基于空间域的图卷积网络的感受野或多或少都通过手动定义,比如GraphSage中用节点的固定size的邻居的均值或最大值定义感受野,或用一个需要预先选择邻居节点顺序的LSTM聚合器定义感受野。
上述两种定义方式都限制了在图中找到一种有意义的自适应的感受野。例如简单堆叠多GCN(Kipf
and Welling 2016)使得感受野变大或路径变深,但是性能会急剧下降。(一些论文中使用残差连接使得性能稍有提升)
图中的每个顶点的感受野都不同。
这篇文中定义的感受野(receptive field):图中一个节点的感受野为包含从这个节点出发到目标节点的路径上的所有节点构成的子图。
2、此文解决了下面两个没有解决的问题:
- 在图卷积中是否存在一条对表示最有贡献的路径?
- 在图卷积网络中是否存在一种自适应、能自动选择感受野或路径的方式?
3、贡献
(1)构造了排列不变性的图数据的函数空间,其中任何符合条件的聚合函数都应该满足这个空间
(2)提出了由两部分构成的能自适应的路径层:
- 能适应广度的函数:能够自动选择重要性最大的one-hop邻居
- 能适应深度的函数:可以提取和过滤有用的、有噪声的的信号
二 、GeniePath模型
1、为什么感受路径是重要的?
(1)graphs could be noisy
红色表示恶意账户,恶意帐户倾向于聚合在一起,实际上,普通账号和恶意帐户可以连接到同一个IP代理。因此,不能简单地从图中看出标记为“绿色”的帐户是否是恶意的
根据每个节点的特征,可以验证此绿色账号是否与其他恶意账号具有类似的行为模式。因此,节点的特征可以作为额外的信息来细化邻居和路径的重要性。
(2) 不同节点的感受路径不同
图中阴影区域是文中认为的有意义的感受路径,而不是简单的对邻居信号进行聚合,其中有意义的路径可以看作是和目标节点相关的一个子图。
现在,需要做的是广度/深度的探索和过滤有用的/有噪声的信号。广度的探索决定了哪些邻居是重要的,即引导探索的方向,而深度的探索决定了距离目标节点多少hop的邻居仍然有用。子图上的这种信号过滤本质上就是学习感受路径。
GeniePath算法的核心:当沿着已经学过的路径传播信号时学习感受路径,而不是根据预先定义的路径。这个问题就等价于对每一个节点通过广度(哪个one-hop邻居是重要的)和深度(邻居的重要性被忽略了)扩展检测一个子图。
2、Adaptive Path Layer 自适应的路径层
Adaptive Path Layer 由Adaptive Breadth和Adaptive Depth 两个模块构成:
(1)Adaptive Breadth基于GAT学习一跳邻居(隐)特征的重要性,决定继续探索的方向
(2)Adaptive Depth 则是引入LSTM的长短记忆,保存和更新每一跳邻居信息:基于新观察到的邻居聚合信息,更新门将新的有用信息加到到记忆中,遗忘门参考过滤掉无用的老记忆,故在探索更远的邻居时起到了选择抽取与过滤的作用。 并基于输出门和最新的记忆,输出节点i的第t + 1 t+1层
三、GeniePath模型:
class Breadth(torch.nn.Module):
def __init__(self, in_dim, out_dim):
super(Breadth, self).__init__()
self.gatconv = GATConv(in_dim, out_dim, heads=heads)
def forward(self, x, edge_index):
x = torch.tanh(self.gatconv(x, edge_index)) #3480*256
return x
class Depth(torch.nn.Module):
def __init__(self, in_dim, hidden):
super(Depth, self).__init__()
self.lstm = torch.nn.LSTM(in_dim, hidden, 1, bias=False)
def forward(self, x, h, c):
x, (h, c) = self.lstm(x, (h, c)) #1*3480*256 1*3480*256
return x, (h, c)
class GeniePathLayer(torch.nn.Module): #就是将Breadth和Depth串联起来,成为一个循环模块
def __init__(self, in_dim):
super(GeniePathLayer, self).__init__()
self.breadth_func = Breadth(in_dim, dim)
self.depth_func = Depth(dim, lstm_hidden)
def forward(self, x, edge_index, h, c):
x = self.breadth_func(x, edge_index)
x = x[None, :]
x, (h, c) = self.depth_func(x, h, c)
x = x[0]
return x, (h, c)
class GeniePath(torch.nn.Module):
def __init__(self, in_dim, out_dim, device): #50 121 cuda
super(GeniePath, self).__init__()
self.device = device
self.lin1 = torch.nn.Linear(in_dim, dim)#50→256
self.gplayers = torch.nn.ModuleList([GeniePathLayer(dim) for i in range(layer_num)])
self.lin2 = torch.nn.Linear(dim, out_dim)
def forward(self, x, edge_index): #2401*50 2*64218
x = self.lin1(x) #2401*256
h = torch.zeros(1, x.shape[0], lstm_hidden).to(self.device)
c = torch.zeros(1, x.shape[0], lstm_hidden).to(self.device)
for i, l in enumerate(self.gplayers):
x, (h, c) = self.gplayers[i](x, edge_index, h, c)
x = self.lin2(x) #2401*121
return x
GeniePathLazy模型:
首先声明变量:
class Breadth(torch.nn.Module):
def __init__(self, in_dim, out_dim):
super(Breadth, self).__init__()
self.gatconv = GATConv(in_dim, out_dim, heads=heads)
def forward(self, x, edge_index):
x = torch.tanh(self.gatconv(x, edge_index)) #3480*256
return x
class Depth(torch.nn.Module):
def __init__(self, in_dim, hidden):
super(Depth, self).__init__()
self.lstm = torch.nn.LSTM(in_dim, hidden, 1, bias=False)
def forward(self, x, h, c):
x, (h, c) = self.lstm(x, (h, c)) #1*3480*256 1*3480*256
return x, (h, c)
class GeniePathLazy(torch.nn.Module):
def __init__(self, in_dim, out_dim, device): #50 121 cuda
super(GeniePathLazy, self).__init__()
self.device = device
self.lin1 = torch.nn.Linear(in_dim, dim) #50→256
self.breaths = torch.nn.ModuleList([Breadth(dim, dim) for i in range(layer_num)])
self.depths = torch.nn.ModuleList([Depth(dim * 2, lstm_hidden) for i in range(layer_num)])
self.lin2 = torch.nn.Linear(dim, out_dim) #256→121
def forward(self, x, edge_index): #3480*50 2*106574
x = self.lin1(x) #50→256
h = torch.zeros(1, x.shape[0], lstm_hidden).to(self.device) #1*3480*256
c = torch.zeros(1, x.shape[0], lstm_hidden).to(self.device) #1*3480*256
h_tmps = []
for i, l in enumerate(self.breaths):
h_tmps.append(self.breaths[i](x, edge_index)) #list 4 3480*256
x = x[None, :]#1*3480*256 加一维
for i, l in enumerate(self.depths):
in_cat = torch.cat((h_tmps[i][None, :], x), -1)#1*3480*512
x, (h, c) = self.depths[i](in_cat, h, c)
x = self.lin2(x[0])#x[0].shape=2480*256 3480*121
return x
其中
四、总结
研究了图卷积网络寻找有意义的感受路径的问题
提出了能指导寻找感受路径,由自适应广度函数和自适应深度函数构成的自适应路径层
实验表明, 在大型数据上,GeniePath比当前state-of-the-art的方法效果都好,并且对叠加层的深度不敏感
GeniePath的成功表明对不同节点选择合适的感受路径是很重要的
在研究邻居节点顺序很重要的temporal graphs上具有很大的挑战