3. 图的注意力机制原理:自注意力、图注意力、softmax归一化
注意力机制,说白了就是让模型学会「该看哪」。我在做分子性质预测时,最开始用GCN对所有邻居一视同仁,结果发现某个官能团其实对活性贡献更大——GCN根本抓不住。换成GAT之后,模型自动把注意力集中在关键原子上,效果提升很明显。
这一节咱们把注意力机制的三个核心概念拆开讲:自注意力、图注意力、softmax归一化。理解这些,你才算真正懂了GAT。
3.1 自注意力机制:先学会「自己看自己」
自注意力(Self-Attention)是Transformer的基石。它让每个节点自己决定「我该多关注谁」。
回忆一下经典公式:Attention(Q,K,V) = softmax(QK^T/√d)V
其中Q、K、V分别代表查询、键、值。每个节点都算自己的Q、K、V,然后计算它与所有其他节点之间的注意力分数。
我个人习惯把Q想象成「我想问的问题」,K想象成「我能回答什么」,V就是「我实际提供的信息」。分数越高,说明这个邻居的回答越匹配我的问题。
举个具体例子:假设只有一个batch,3个节点,特征维度d=4。
import torch
import torch.nn.functional as F
# 假设3个节点,每个节点4维特征
X = torch.randn(3, 4) # [3, 4]
W_q = torch.randn(4, 4) # 可学习矩阵
W_k = torch.randn(4, 4)
W_v = torch.randn(4, 4)
Q = X @ W_q # [3, 4]
K = X @ W_k # [3, 4]
V = X @ W_v # [3, 4]
# 计算相似度分数 [3, 3]
scores = Q @ K.T / (4 ** 0.5) # 缩放因子√d=2
attn_weights = F.softmax(scores, dim=-1) # 对每一行归一化
output = attn_weights @ V # [3, 4]
print(output.shape)
这个输出就是自注意力后的表示。每个节点的表示现在融合了全部其他节点的信息,权重由注意力分数决定。
所以,图注意力机制应运而生。
3.2 图注意力机制:只看「邻居」的注意力
图注意力(Graph Attention)跟自注意力最大的区别就是:只在邻居节点之间计算注意力。这样既保留了注意力动态分配权重的能力,又遵循了图的拓扑结构。
GAT的论文里用了一个单层前馈网络来计算注意力系数:
e_ij = LeakyReLU( a^T [ W h_i || W h_j ] )
其中||表示向量拼接。这个e_ij就是节点i和邻居j之间的原始注意力分数。然后对每个节点的所有邻居做softmax归一化,得到α_ij。
我曾在一个配体-蛋白对接项目里用GAT,一开始没处理好自环(self-loop)。每个节点应该关注自身的信息,我忘了在邻居集合里包含节点自己,结果模型收敛很慢。嗯,这里要注意:GAT实现时通常会给每个节点加一条自环边。
下面给出带掩码的图注意力计算伪代码:
def graph_attention(H, adj, W, a):
# H: [N, d] 节点特征, adj: [N, N] 邻接矩阵(含自环)
Wh = H @ W # [N, d_out]
# 拼接所有节点对的特征
Wh_i = Wh.unsqueeze(1).expand(-1, N, -1) # [N, N, d_out]
Wh_j = Wh.unsqueeze(0).expand(N, -1, -1) # [N, N, d_out]
concat = torch.cat([Wh_i, Wh_j], dim=-1) # [N, N, 2*d_out]
e = F.leaky_relu(concat @ a.squeeze(-1), negative_slope=0.2) # [N, N, 1]
# 掩码:只保留邻居(包括自身)
e = e.squeeze(-1).masked_fill(adj == 0, -1e9)
alpha = F.softmax(e, dim=-1) # [N, N]
out = alpha @ Wh # [N, d_out]
return out
自注意力和图注意力的核心差异,我整理成下表:
| 维度 | 自注意力 (Transformer) | 图注意力 (GAT) |
|---|---|---|
| 注意范围 | 全部节点(全局) | 邻居节点(局部) |
| 掩码方式 | 序列掩码(因果) | 邻接矩阵掩码 |
| 计算复杂度 | O(N²) | O(E) 边数 |
| 分子图适用性 | 不直接适用,无视化学键 | 天然适合,利用拓扑 |
| 可学参数 | W_q, W_k, W_v | W, a(单层网络) |
3.3 Softmax归一化:把「原始分数」变成「权重分布」
softmax是注意力机制里的最后一步,也是最容易被忽略的一步。它把原始分数转换成一个概率分布,所有邻居的注意力权重之和为1。
公式很简单:
α_ij = exp(e_ij) / Σ_{k∈N(i)} exp(e_ik)
为什么不用普通的归一化(比如除以和)?你想想看,原始分数可能是负的,而且数值范围很大。softmax通过指数函数把任何实数映射到正数,再归一化,既避免负数权重,又放大差异——分数高一点,权重就会明显高出一截。
我处理过一个分子数据集,有些原子度数很高(比如金属中心),邻居数多达10几个。如果不做softmax,模型会把注意力均分给每个邻居,等于没学到重点。加入softmax后,模型学会了把大部分注意力集中在关键配位原子上。
e = e - e.max(dim=-1, keepdim=True)[0] 防止exp溢出。
这里还有一个小细节:GAT论文里在计算e_ij时用了LeakyReLU,而标准的Transformer用的是无激活的线性层。为什么呢?我个人猜测是为了引入非线性,让注意力分数更灵活——毕竟图结构复杂,线性打分可能不够用。
下图总结了从自注意力到图注意力的完整流程,以及softmax的角色:
从SVG图可以清楚看到:左边自注意力全连接所有节点,右边图注意力先用邻接矩阵「剪枝」,只让信息沿着边流动。而softmax归一化是两者最后的共同环节,只不过图注意力的归一化分母只加和邻居。
理解这些后,你就能看懂GAT的每一行代码了。下一期咱们直接动手实现一个分子图上的GAT模型,到时候你会更深刻体会这些机制的妙处。