4、GAT模型结构详解:GAT层设计、消息传递、聚合函数
GAT层到底是怎么工作的?说实话,我刚接触的时候也有点懵。注意力机制加上图结构,总觉得比普通GCN复杂不少。但后来我带团队做分子性质预测项目,手写了几个GAT层,踩了不少坑才算真正吃透。这里我把核心逻辑拆开给你看。
4.1 GAT层设计:注意力是怎么算出来的?
GAT层的精髓就俩字:加权。它不是简单地把邻居特征加起来,而是给每个邻居配一个动态权重。这个权重就是注意力系数。
具体来说,给定当前节点i和邻居j,我们先算一个关联分数:
e_ij = LeakyReLU( a^T · [W h_i || W h_j] )
这里W是共享的线性变换矩阵,a是注意力向量,||表示拼接。LeakyReLU就是那个斜率为0.2的激活函数,我试过用ReLU,结果梯度直接死了不少——这小坑有教训。
然后对节点i的所有邻居做softmax归一化,得到最终的注意力系数α_ij:
α_ij = exp(e_ij) / ∑_{k∈N(i)} exp(e_ik)
归一化保证了所有邻居权重加起来等于1。我刚开始实现时忘了加mask(只保留真实邻居),结果把不存在的虚拟节点也算了进去,训练直接崩了。嗯,这算一个避坑点。
4.2 消息传递:邻居的信息怎么送过来?
算好了注意力系数,下一步就是消息传递。这个步骤跟GCN很像,但GAT传递的是加权后的特征。
每个邻居j向节点i发送消息:
m_j→i = α_ij · W h_j
你看,分母上的归一化已经做完了,所以这里直接乘系数。如果α很大(比如0.8),说明节点i非常关注这个邻居;如果α接近0,这个邻居就基本被忽略掉了。
我在做分子图时遇到过这种情况:一些原子虽然空间距离近,但化学性质差异很大,注意力系数就会变得很小。这比固定权重的GCN灵活多了。
消息传递的完整图示可以看下面的SVG结构图:
图里很直观:三个邻居节点带着各自的特征,通过不同的注意力系数(α),汇聚到目标节点i上。注意系数之和必须为1,我刚开始写softmax时忘记加epsilon,出现过除零错误。
4.3 聚合函数:怎么把加权消息合成新特征?
消息传进来后,得合到一起。GAT核心就一种聚合方式:加权求和。但实际项目中会出现各种变体,我列了几个常见的:
| 聚合方法 | 公式 | 特点 | 我在分子图上用的感受 |
|---|---|---|---|
| 加权和 | h'_i = ∑ α_ij · W h_j | GAT标准,保留尺度信息 | 适合分子性质回归,注意力系数直接反映贡献度 |
| 加权平均 | h'_i = (∑ α_ij · W h_j) / sum | 归一化后尺度稳定 | 在分子分类任务中防止特征爆炸,效果不错 |
| 拼接+线性 | h'_i = W_out · [h_i || ∑ α_ij · W h_j] | 保留自身特征,增强表达能力 | 我用在原子-键联合预测中,比纯聚合高2%准确率 |
| 多头均值 | h'_i = (1/K)∑_k ∑ α^k_ij · W^k h_j | 多头注意力融合,鲁棒性强 | 建议至少用4头,效果明显优于单头 |
你可能要问:这些聚合函数怎么选?我的经验是:先跑加权和,看收敛情况。如果损失震荡厉害,换成加权平均。如果觉得信息不够,加个残差连接或拼接自身。
4.4 避坑指南:我在GAT实现中踩过的三个坑
- 注意力掩码遗漏:我曾经忘记在softmax前用mask屏蔽非邻居节点,结果每个节点都跟全图节点算了注意力,内存直接爆了。一定要用邻接矩阵或edge_index生成mask。
- LeakyReLU负半轴斜率太小:默认0.2可能让注意力权重过于锐利,我试过0.01之后梯度更稳定。
- 多头注意力拼接顺序:多个头的输出最好先拼接再做线性变换,而不是先平均。我在一个项目中先平均再投影,效果差了5%。