图注意力网络#
学习目标与记号#
推导 GAT 的边打分、邻域 Softmax 和加权聚合公式;
正确处理邻接掩码、注意力归一化轴及多头拼接或平均;
实现数值稳定的邻域 Softmax,并审慎解释注意力权重;
本节沿用统一记号:普通小写字母表示标量,粗体小写字母表示向量,粗体大写字母表示矩阵或高阶张量;层编号写作上标 \([l]\),节点、样本或边编号写作下标;转置写作 \(\trans\)。除非另有说明,节点特征按行存放,邻接约定为 \(A_{ij}>0\) 表示节点 \(j\) 向节点 \(i\) 发送消息。正文与练习中的程序都应同时检查数值结果和数组维度。本节练习的参考答案见 图注意力网络(GAT)答案。
GAT 将 消息传递 的聚合权重改为数据依赖形式。它与 Transformer 多头注意力 共享部分思想,但只在图邻域内归一化;固定归一化权重的对照模型见 GCN。
1Veličković 等(2018),Graph Attention Networks。提出背景与意义
在 GAT 出现以前,GCN 等模型通常根据图的连接关系和节点度数,为各个邻居分配预先确定的权重。这种做法简单有效,但无法根据当前节点和邻居的特征判断“哪个邻居提供的信息更加重要”。
Veličković 等在 2017 年提出 GAT,并将其发表于 ICLR 2018 1Veličković 等(2018),Graph Attention Networks。。GAT 把注意力机制引入图的局部邻域,让模型自动学习不同邻居的重要程度,再按照这些权重汇总信息。它使图神经网络能够更灵活地利用邻居信息,也便于把同一组参数用于新节点或新图。不过,较大的注意力权重只表示模型在当前计算中更加重视相应邻居,不能直接理解为因果关系。
GCN 的边权由图结构与节点度固定决定。图注意力网络(graph attention network,GAT)让模型根据节点表示为不同邻居学习不同权重。经典 GAT 不是 Transformer 的完整查询—键—值公式,而是对边两端线性投影后的特征拼接打分,再在每个接收节点的邻域内归一化。
单头图注意力#
图 38 GAT 的边权由节点特征学习,并只在目标节点的真实邻域内做 Softmax#
先做共享线性投影,得到投影后的节点特征向量 \(\bz_i\):
其中 \(\bW\) 对所有节点共享。对每条允许的边 \(j\to i\) 计算未归一化得分
其中 \(\ba\in\mathbb{R}^{2d_{\mathrm{out}}\times 1}\),\(\Vert\) 表示沿特征轴拼接。只在 \(j\in\mathcal{N}(i)\cup\{i\}\) 上做 Softmax:
最后加权聚合
对固定 \(i\),权重非负且和为 1。注意力不是对全图所有节点做归一化,而是对该接收节点允许看到的邻居集合做归一化。若先对全图 Softmax 再把非边位置清零,剩余权重和会小于 1,模型也可能在归一化阶段利用不存在的边。
下面的动画展示目标节点如何分别计算各邻居的注意力得分,再在该节点的邻域内完成 Softmax 归一化,并用不同权重汇总邻居信息。
多头注意力#
第 \(r\) 个头有独立的 \(\bW_r,\ba_r\)。隐藏层常把 \(K\) 个头拼接:
输出宽度为 \(Kd_{\mathrm{head}}\)。分类输出层常对各头取平均,从而保持类别维度不随头数变化。实现时必须明确拼接轴、每头宽度与最终输出宽度,不能只把头数当作一个无需重新核对维度的超参数。
稀疏实现、复杂度与解释边界#
GAT 只需为实际边和自环计算得分,边打分与聚合的结构成本随 \(m\) 增长;若构造 \(n\times n\) 得分矩阵,则会失去稀疏优势。多个头还会按头数增加边得分、显存和聚合成本。
较大的 \(\alpha_{ij}\) 表示在当前层、当前头和当前输入下,节点 \(i\) 的更新对 \(j\) 的线性变换赋予较高混合系数。它不是自动可靠的因果解释:权重会受其他邻居、尺度、头、层和后续非线性影响,不同参数化也可能产生相似输出。
稳定的邻域 Softmax#
下面用 NumPy 实现只在真实邻域内计算的 Softmax。输入 scores 和 mask 是大小相同的二维数组:scores[i, j] 表示节点 \(j\) 向节点 \(i\) 发送信息时尚未归一化的注意力得分,mask[i, j] 表示这条边是否允许参与计算。函数返回大小相同的注意力权重矩阵,每一行只在相应接收节点的有效邻居之间进行归一化。
import numpy as np
def masked_softmax(scores, mask):
scores = np.asarray(scores, dtype=float)
mask = np.asarray(mask, dtype=bool)
if scores.shape != mask.shape or scores.ndim != 2:
raise ValueError("scores 与 mask 必须是同形二维数组")
if np.any(mask.sum(axis=1) == 0):
raise ValueError("每个接收节点至少要有一个允许的邻居")
masked = np.where(mask, scores, -np.inf)
shifted = masked - np.max(masked, axis=1, keepdims=True)
weights = np.where(mask, np.exp(shifted), 0.0)
return weights / weights.sum(axis=1, keepdims=True)
代码先检查 scores 和 mask 是否为大小相同的二维数组,并要求每个接收节点至少有一个有效邻居。随后,它把不存在的边对应的得分设为负无穷大,再从每行的有效得分中减去该行最大值,以降低指数运算产生过大数值的风险。非边位置的权重被设为 0,最后对每一行归一化。因此,返回结果中不存在的边权重为 0,每个接收节点的有效邻居权重之和为 1。
这个实现用每一行表示一个接收节点、每一列表示一个候选发送节点,因而需要建立完整的二维得分矩阵,只适合小图检查。处理大图时,应只保存实际存在的边,并将指向同一接收节点的边分为一组,在各组内部完成 Softmax,而不应构造完整的 \(n\times n\) 得分矩阵。
什么是分段 Softmax
上述“先把边分组,再分别计算 Softmax”的方法称为分段 Softmax(segment softmax)。这里的“分段”不是把一个函数写成几个分段表达式,而是把已经保存的边按照接收节点分成若干组:指向同一个接收节点的边属于同一组,不同组之间互不参与归一化。
设一共保存了 \(M\) 条边,第 \(r\) 条边的注意力得分为 \(s_r\),接收节点编号为 \(g_r\)。先在每一组内找到最大得分,再只使用同组边计算权重:
其中,\(u_i\) 是所有指向节点 \(i\) 的边得分中的最大值。减去它可以降低指数运算产生过大数值的风险;对于每个接收节点,指向它的全部边权重之和等于 1。例如,若前三条边指向节点 \(i\),后两条边指向节点 \(j\),就分别进行一次包含三项的 Softmax 和一次包含两项的 Softmax,不能把五条边放在一起归一化。
分段 Softmax 与上面的二维掩码 Softmax 完成相同的数学运算,但它只保存并处理实际存在的边,因而适合稀疏图,也避免了建立 \(n\times n\) 得分矩阵。
Shiny 交互演示:GAT 邻域注意力
在交互页面中选择 GAT 后,可以改变 Leaky ReLU 的负半轴斜率,并逐步查看边得分、只在真实邻域与自环内计算的 Softmax 权重、逐邻居贡献和节点更新结果。注意力权重描述当前计算中的分配比例,不等同于因果影响。
核心推导与实现核验#
核心关系
推导路径。 每个节点先共享线性投影;每条允许边根据接收和发送表示产生标量得分;在同一接收节点的邻域内做稳定 Softmax,再以这些权重加权发送节点表示。
关键条件
对固定接收节点,邻居置换只重排得分向量。Softmax 与同一置换可交换,加权和也不依赖枚举顺序,因此一层 GAT 对节点编号置换等变。
数据规模
若图中有 \(n\) 个节点,每个节点有 \(d_{\mathrm{in}}\) 个输入特征,单头输出宽度为 \(d_{\mathrm{out}}\),则输入矩阵写为 \(\bH=[\bh_1\trans;\ldots;\bh_n\trans]\in\mathbb{R}^{n\times d_{\mathrm{in}}}\),权重 \(\bW\) 和投影结果 \(\bZ\) 的大小依次为 \(d_{\mathrm{in}}\times d_{\mathrm{out}}\) 和 \(n\times d_{\mathrm{out}}\)。若原图有 \(m\) 条有向边并加入 \(n\) 个自环,就需要计算 \(m+n\) 个注意力权重;\(K\) 个头拼接后每个节点有 \(Kd_{\mathrm{head}}\) 个输出特征。
常见误区
Softmax 必须沿发送邻居轴且在掩码后计算;全被屏蔽的行会产生 NaN;把无向图只保存单向边会让两个方向的邻域不同。
动手检查
检查每个接收节点的权重和为 1、非边权重为 0;将全部得分加同一常数后权重不变;邻居表示相同时,加权和应等于该共同表示。
数值稳定性与规模
邻域 Softmax 先减该邻域最大得分;每个节点至少保留自环或一个有效邻居。稀疏图使用 分段 Softmax,避免构造 \(n\times n\) 得分矩阵。
本节小结#
GAT 通过特征相关的边得分学习邻居权重。
注意力必须在每个接收节点的真实邻域内稳定归一化。
多头提高表示灵活性,也增加维度、计算与解释上的复杂度。
综合练习#
程序题应写出维度断言并报告运行环境。全部参考答案见 图注意力网络(GAT)答案。
邻域注意力计算。 某接收节点只有两个允许的发送节点,边得分分别为 \(0\) 和 \(\log3\),对应投影后值向量为 \((2,0)\) 与 \((0,4)\)。计算两个注意力权重和聚合结果;再说明若先对包含一个非邻居的全图得分做 Softmax、之后把非邻居权重清零,为什么结果一般不同。
归一化与置换证明。 证明固定接收节点的注意力权重非负且和为 1;再证明改变该节点邻居的枚举顺序只会同步重排得分与权重,不改变加权和。由此说明共享参数的 GAT 层为什么满足节点置换等变性。
稳定 Softmax 计算。 对得分 \((1000,999,-5)\) 和掩码 \((1,1,0)\),使用“先掩码、再减有效最大值”的方法计算权重。证明给同一邻域全部有效得分加任意常数不改变权重,并解释直接计算指数可能出现的问题。
多头维度与计算量。 某有向图有 \(n=100\) 个节点、\(m=500\) 条已存边,输入维度为 16;GAT 使用 4 个头,每头输出维度为 8,并为每个节点加入一个自环。写出每头投影矩阵、注意力向量、投影结果和拼接输出的维度;计算忽略偏置时的投影与注意力参数量,以及需要计算的边得分数。
分段 Softmax 实现。 根据 分段 Softmax 的定义,基于一维边得分和每条边的接收端点实现稳定
segment_softmax。不得构造 \(n\times n\) 矩阵;应按接收节点减最大值、归一化,并拒绝没有自环或入边的节点。用第 1、3 题和含重复边的小图测试。单头与多头 GAT 实现。 基于
edge_index、第 5 题的 分段 Softmax 和共享线性投影实现单头 GAT,再扩展到多头拼接与平均。返回边得分和权重;与逐节点循环版本比较前向结果以及输入、投影矩阵和注意力向量的梯度。性质与边界测试。 为 GAT 编写节点置换等变性、邻居顺序不变性、得分平移不变性、权重分段和为 1、非边权重为 0、相同值向量聚合不变等测试。加入全空邻域、极大绝对值得分、无向边只存一个方向和错误 Softmax 分组的失败用例。
GCN 与 GAT。 在同一节点分类数据上比较两层 GCN 与 GAT。两种模型使用相同的训练集、验证集和测试集划分、随机种子、输入特征、近似参数量和训练更新次数,并使用同一个验证集和相同标准选择模型;报告准确率或 macro-F1、训练时间、固定图批量预测时间、峰值内存、参数量、边得分数和图层运行次数。
注意力头数。 在保持总输出宽度近似不变的前提下比较 \(K\in\{1,2,4,8\}\) 个头,并另列“每头宽度固定”设置以揭示计算扩张。固定数据划分、随机种子集合、层数、训练更新次数和评价代码;报告预测质量、训练时间、固定批量预测时间、峰值内存、参数量、边得分数和注意力层调用次数。
学习权重与均匀权重。 保持其他设置不变,把 GAT 学到的邻域注意力权重换成均匀权重,再与原 GAT 和 GCN 比较。各组使用相同的数据划分、随机种子、隐藏宽度、训练更新次数、优化器和评价程序;报告预测质量、训练时间、固定批量预测时间、峰值内存、边得分数与图层运行次数,并比较稀疏实现和仅用于小图检查的稠密实现。根据结果分析学习邻居权重可能带来的影响。