《GRAPH ATTENTION NETWORKS》
解决GNN聚合邻居节点的时候没有考虑到不同的邻居节点重要性不同的问题,GAT借鉴了Transformer的idea,引入masked self-attention机制, 在计算图中的每个节点的表示的时候,会根据邻居节点特征的不同来为其分配不同的权值。
1.论文公式:
注意力系数:代表节点j的特征对i的重要性。
经过归一化:
得到最终注意力系数:
a是 注意力机制权重 , h是 节点特征 ,W是 节点特征的权重
如图:
将多头注意力得到的系数整合:
一共k个注意力头,求i节点聚合后的特征。
如果是到了最后一层:
使用avg去整合
如图:
2.论文导图:
3.核心代码:
从输入h节点特征 到 输出h`的过程:
- 数据处理方式和GCN一样,对边、点、labels编码,并得到h和adj矩阵。
- GraphAttention层的具体实现:
定义权重W,作用是将input的维度转换,且是可学习参数。
self.W = nn.Parameter(torch.empty(size=(in_features, out_features))) #一开始定义的是空的
nn.init.xavier_uniform_(self.W.data, gain=1.414) #随机初始化,然后在学习的过程中不断更新学习参数。
定义 注意力机制权重a,用来求注意力分数,也是可学习参数。
self.a = nn.Parameter(torch.empty(size=(2*out_features, 1)))
nn.init.xavier_uniform_(self.a.data, gain=1.414) #随机初始化
求得注意力分数
Wh1 = torch.matmul(Wh, self.a[:self.out_features, :])
Wh2 = torch.matmul(Wh, self.a[self.out_features:, :])
e = Wh1 + Wh2.T
然后进行softmax、dropout。
zero_vec = -9e15*torch.ones_like(e)
##如果如果两个节点有边链接则使其注意力得分为e,如果没有则使用-9e15的mask作为其得分
attention = torch.where(adj > 0, e, zero_vec)
##使用 softmax 函数对注意力得分进行归一化
attention = F.softmax(attention, dim=1)
##dropout,为模型的训练增加了一些正则化
attention = F.dropout(attention, self.dropout, training=self.training)
- 整体GAT实现:
根据头数n,定义n个GraphAttention 层(每个头都具有相同的参 数,并且 共享输入特征 ),和一个 GraphAttention 输出层。
n个层各输出一个注意力分数,最后拼接成一个。 features( features.shape[1] ) --> hidden(自定义) --*n(自定义) --> n*hidden
self.attentions = [GraphAttentionLayer(nfeat, nhid, dropout=dropout, alpha=alpha, concat=True) for _ in range(nheads)]
for i, attention in enumerate(self.attentions):
self.add_module('attention_{}'.format(i), attention) #将层命名然后加到模块里
输出层将 n*hidden --> class( int(labels.max()) + 1)
self.out_att = GraphAttentionLayer(nhid * nheads, nclass, dropout=dropout, alpha=alpha, concat=False)
forward(x, adj)函数:
x = F.dropout(x, self.dropout, training=self.training)
x = torch.cat([att(x, adj) for att in self.attentions], dim=1) #遍历每个层对象,传参去运行。重复n次后得到 n * hidden 维的拼接后的注意力分数
x = F.dropout(x, self.dropout, training=self.training)
x = F.elu(self.out_att(x, adj))
return F.log_softmax(x, dim=1) #输出层最后得到class维的分类概率矩阵。
整体是 通过 x 在层之间传递参数的,x一直在变,不断的dropout。
4.采取的正则化:
正则化通过对算法的修改来减少泛化误差,目前在深度学习中使用较多的策略有参数范数惩罚,提前终止,DropOut等。
- dropout: 通过使其它隐藏层神经网络单元不可靠从而 阻止了共适应的发生 。因此,一个隐藏层神经元不能依赖其它特定神经元去纠正其错误。因为dropout程序导致两个神经元不一定每次都在一个dropout网络中出现。这样权值的更新不再依赖于有固定关系的隐含节点的共同作用,阻止了某些特征仅仅在其它特定特征下才有效果的情况 。迫使网络去学习更加鲁棒的特征 ,这些特征在其它的神经元的随机子集中也存在。
- 提前停止:是将一部分训练集作为验证集。 当 验证集的性能越来越差时或者性能不再提升,则立即停止对该模型的训练。 这被称为提前停止。
在当前best的loss对应的epoch之后后,又跑了patience轮,但是新的loss没有比之前best loss小的,也就是loss越来越大了,出现过拟合了。这时就应该停止训练。
if loss_values[-1] < best:
best = loss_values[-1]
best_epoch = epoch
bad_counter = 0
else:
bad_counter += 1
if bad_counter == args.patience:
break
5.和GCN对比adj的用法:
GCN通过变换(归一、正则)的邻接矩阵和节点特征矩阵相乘,得到了考虑邻接节点后的节点特征矩阵。通过两层卷积,得到output。
GAT利用Wh和注意力权重a计算出注意力分数,再用adj矩阵把不相邻的节点的分数mask掉。即,可以给不同节点分配不同权重。