图注意力网络(GAT)

一句话概述

图注意力网络(Graph Attention Network, GAT)将注意力机制引入图神经网络,让模型自动学习不同邻居对目标节点的重要性权重。与GCN中邻居权重固定(由图的度决定)不同,GAT的注意力权重是动态计算的——根据目标节点和邻居节点的特征,通过可学习的注意力函数计算相关性分数。GAT的核心计算过程为:首先对每个邻居对(i,j)计算注意力系数e_{ij}=LeakyReLU(a^T[Wh_i || Wh_j]),然后通过softmax归一化得到注意力权重α_{ij},最后对邻居特征加权求和h_i'=σ(Σ_j α_{ij}Wh_j)。多头注意力机制进一步增强了GAT的稳定性和表达能力。GAT不需要预先知道图结构,且可以处理有向图和归纳学习场景。

💡 核心要点:①GAT通过注意力机制动态计算邻居权重,而非GCN的固定归一化权重 ②注意力系数通过可学习的a向量和特征拼接计算 ③多头注意力(Multi-head)增强稳定性和表达能力 ④GAT可以处理有向图和归纳学习,不需要全局图结构

教学与演示

一、GAT的注意力机制:动态邻居权重

是什么(定义):GAT(Graph Attention Network)由Petar Veličković等人于2018年提出。其核心创新是将注意力机制引入邻居聚合:每个邻居j对目标节点i的重要性不是固定的(如GCN中的1/√(d_i·d_j)),而是通过一个可学习的注意力函数动态计算。GAT的注意力计算包含三个步骤:①线性变换Wh(共享权重矩阵),②拼接Wh_i和Wh_j后计算注意力分数e_{ij}=a^T[Wh_i||Wh_j],③LeakyReLU激活+softmax归一化得到最终权重α_{ij}。

大白话 GAT就像是"有选择地听朋友意见"。GCN是"所有朋友的意见平均对待"——不管你朋友说什么,都按相同的权重采纳。GAT是"重点听靠谱的朋友说"——对于每个朋友,你会根据"你们俩的相似度"(注意力分数)来决定听多少。比如你要决定买什么手机,科技达人的意见权重很高,而从不研究手机的朋友的意见权重很低。这个"谁更重要"的判断不是固定的,而是根据具体内容动态变化的。

为什么(原理):GAT的注意力机制解决了GCN的三个核心局限:①GCN的邻居权重是固定的(由度决定),无法适应不同任务;②GCN假设图是无向的,注意力可以自然处理有向图(e_{ij}≠e_{ji});③GCN需要全局图结构,GAT的注意力只依赖邻居特征,可以用于归纳学习。GAT的注意力本质上是"self-attention on graphs"——在邻居集合上计算注意力,而非在整个序列上。

import numpy as np

# GAT注意力机制的核心实现
# 演示如何动态计算邻居权重

class GATLayer:
    def __init__(self, d_in=4, d_out=3):
        np.random.seed(42)
        # 共享的线性变换权重矩阵 W
        self.W = np.random.randn(d_in, d_out) * 0.3
        # 注意力向量 a(用于计算注意力分数)
        # 注意:a需要2*d_out维,因为要拼接两个向量
        self.a = np.random.randn(2 * d_out, 1) * 0.1

    def attention_score(self, h_i, h_j):
        """计算节点i对邻居j的注意力分数"""
        # 步骤1:拼接两个节点的特征
        concat = np.concatenate([h_i, h_j])  # 形状: (2*d_out,)
        # 步骤2:计算原始注意力分数 e_{ij} = a^T [Wh_i || Wh_j]
        e_ij = concat @ self.a  # 标量
        return e_ij[0]

    def forward_one_node(self, X, A, node_idx):
        """对单个节点执行GAT消息传递"""
        # 变换当前节点特征
        h_i = X[node_idx] @ self.W  # Wh_i

        # 找到邻居(包括自身)
        neighbors = np.where(A[node_idx] == 1)[0]
        neighbors = np.append(neighbors, node_idx)  # 添加自环

        # 计算所有邻居的注意力分数
        attention_scores = []
        neighbor_features = []
        for nbr in neighbors:
            h_j = X[nbr] @ self.W  # Wh_j
            e_ij = self.attention_score(h_i, h_j)
            attention_scores.append(e_ij)
            neighbor_features.append(h_j)

        attention_scores = np.array(attention_scores)
        neighbor_features = np.array(neighbor_features)

        # LeakyReLU激活
        attention_scores = np.where(attention_scores > 0, attention_scores, 0.2 * attention_scores)

        # Softmax归一化得到注意力权重
        scores = attention_scores - np.max(attention_scores)
        alpha = np.exp(scores) / np.sum(np.exp(scores))

        # 加权求和
        h_i_new = np.sum(alpha.reshape(-1, 1) * neighbor_features, axis=0)

        return h_i_new, alpha, neighbors

    def forward_all(self, X, A):
        """对所有节点计算GAT"""
        n = X.shape[0]
        d_out = self.W.shape[1]
        new_X = np.zeros((n, d_out))
        all_alpha = []

        for i in range(n):
            new_X[i], alpha, _ = self.forward_one_node(X, A, i)
            all_alpha.append(alpha)

        return new_X, all_alpha


# 创建示例图
A = np.array([
    [0, 1, 0, 1, 0],
    [1, 0, 1, 0, 1],
    [0, 1, 0, 0, 0],
    [1, 0, 0, 0, 1],
    [0, 1, 0, 1, 0],
])

X = np.array([
    [0.5, 0.2, 0.1, 0.8],
    [0.1, 0.9, 0.3, 0.2],
    [0.3, 0.1, 0.7, 0.1],
    [0.8, 0.1, 0.2, 0.5],
    [0.2, 0.6, 0.1, 0.3],
])

gat = GATLayer(d_in=4, d_out=3)

# 分析节点0的注意力
print("=== GAT注意力机制演示 ===\n")
print("节点0的邻居:1和3(加上自身0)\n")

h_new, alpha, neighbors = gat.forward_one_node(X, A, 0)

print(f"节点0的注意力权重:")
for i, (nbr, a) in enumerate(zip(neighbors, alpha)):
    print(f"  邻居{nbr}: α = {a:.4f}")

print(f"\n→ 节点0对邻居3的关注度({alpha[2]:.4f})高于邻居1({alpha[1]:.4f})")
print("→ 这是因为节点0和3的特征更相似(通过注意力函数计算)")
print("→ 而GCN中,两者的权重都是固定的1/√(d_0·d_nbr)")

# 全图GAT
new_X, all_alpha = gat.forward_all(X, A)
print(f"\n\n=== 全图GAT输出 ===")
print(f"输入形状: {X.shape} → 输出形状: {new_X.shape}")
for i in range(5):
    print(f"  节点{i}: {np.round(new_X[i], 3)}")
GAT的注意力分数计算\(e_{ij} = \text{LeakyReLU}\left(\mathbf{a}^T [W\mathbf{h}_i \| W\mathbf{h}_j]\right), \quad \alpha_{ij} = \frac{\exp(e_{ij})}{\sum_{k \in \mathcal{N}(i) \cup \{i\}} \exp(e_{ik})}\)
大白话 GAT的注意力计算就是"打分+归一化"。先给每个邻居打分——分数取决于"你是什么样的"和"邻居是什么样的"(拼接Wh_i和Wh_j),然后通过一个可学习的评分器(a向量)给出分数。接着用softmax把分数变成百分比——所有邻居的百分比加起来等于100%。最后用这个百分比来决定每个邻居的意见采纳多少。LeakyReLU确保即使分数为负,也有一个小梯度,防止"零梯度"问题。

什么用(应用):GAT在多个图基准上超越了GCN。在Cora节点分类上,GAT(多注意力头)达到了约83%的准确率(vs GCN的81%)。GAT的注意力权重具有可解释性——可以可视化哪些邻居对分类决策最重要。GAT特别适合异质图(节点特征差异大)和需要归纳学习的场景(如动态社交网络中的新用户)。

哪些坑(缺点):GAT的计算复杂度高于GCN——每个节点需要与每个邻居独立计算注意力分数,复杂度为O(|V|·F·F' + |E|·F'),其中F和F'是输入输出维度。对于稠密图(边数接近n²),计算开销很大。此外,GAT的注意力只考虑节点特征,没有利用边特征(如边的类型、权重),GATv2通过修改注意力计算顺序解决了原版GAT的"静态注意力"问题。

二、多头注意力:稳定训练与增强表达

是什么(定义):GAT引入多头注意力(Multi-head Attention)来增强模型稳定性。具体做法是:并行计算K个独立的注意力头(每个头有独立的W^k和a^k),然后将K个头的输出进行拼接(concat)或平均(average)。对于中间层(除最后一层外),使用拼接:h_i' = ||{k=1}^K σ(Σ_j α{ij}^k W^k h_j)。对于最后一层(预测层),使用平均:h_i' = σ(1/K Σ_k Σ_j α_{ij}^k W^k h_j)。

大白话 多头注意力就是"多个专家同时打分"。一个专家(单头)打分可能不稳定——这次给邻居A打分高,下次给邻居B打分高。多个专家(多头)各自独立打分,最后把意见汇总(拼接或平均),结果更稳定、更全面。就像面试时多个面试官各自打分,最后综合评定——比一个面试官说了算更公平、更可靠。

为什么(原理):多头注意力的有效性来自两个方面。第一,稳定性——多个头的平均减少了单个头的方差,类似于集成学习(ensemble)。第二,多样性——不同的头可能学习到不同的注意力模式(有的头关注局部结构,有的关注全局特征),拼接后融合了多视角信息。原GAT论文使用K=8个头,实验表明多头显著提升了性能。

import numpy as np

# GAT多头注意力实现
# 演示多头如何增强稳定性和表达能力

class MultiHeadGATLayer:
    def __init__(self, d_in=4, d_out=3, num_heads=3):
        np.random.seed(42)
        self.num_heads = num_heads
        self.d_out = d_out

        # 为每个头创建独立的权重矩阵和注意力向量
        self.W = [np.random.randn(d_in, d_out) * 0.3 for _ in range(num_heads)]
        self.a = [np.random.randn(2 * d_out, 1) * 0.1 for _ in range(num_heads)]

    def single_head_attention(self, X, A, node_idx, head_idx):
        """单个注意力头的计算"""
        W = self.W[head_idx]
        a = self.a[head_idx]

        h_i = X[node_idx] @ W
        neighbors = np.where(A[node_idx] == 1)[0]
        neighbors = np.append(neighbors, node_idx)

        scores = []
        features = []
        for nbr in neighbors:
            h_j = X[nbr] @ W
            concat = np.concatenate([h_i, h_j])
            e_ij = concat @ a
            e_ij = np.where(e_ij > 0, e_ij, 0.2 * e_ij)[0]
            scores.append(e_ij)
            features.append(h_j)

        scores = np.array(scores)
        features = np.array(features)

        # Softmax
        scores = scores - np.max(scores)
        alpha = np.exp(scores) / np.sum(np.exp(scores))

        # 加权求和
        output = np.sum(alpha.reshape(-1, 1) * features, axis=0)
        return output, alpha

    def forward(self, X, A, concat=True):
        """多头GAT前向传播"""
        n = X.shape[0]
        all_head_outputs = []
        all_attentions = []

        for h in range(self.num_heads):
            head_outputs = np.zeros((n, self.d_out))
            head_attn = []
            for i in range(n):
                head_outputs[i], alpha = self.single_head_attention(X, A, i, h)
                head_attn.append(alpha)
            all_head_outputs.append(head_outputs)
            all_attentions.append(head_attn)

        if concat:
            # 拼接多头输出
            output = np.hstack(all_head_outputs)  # (n, num_heads*d_out)
        else:
            # 平均多头输出(最后一层)
            output = np.mean(all_head_outputs, axis=0)  # (n, d_out)

        return output, all_attentions


# 演示多头GAT
X = np.array([
    [0.5, 0.2, 0.1, 0.8],
    [0.1, 0.9, 0.3, 0.2],
    [0.3, 0.1, 0.7, 0.1],
    [0.8, 0.1, 0.2, 0.5],
    [0.2, 0.6, 0.1, 0.3],
])
A = np.array([
    [0, 1, 0, 1, 0],
    [1, 0, 1, 0, 1],
    [0, 1, 0, 0, 0],
    [1, 0, 0, 0, 1],
    [0, 1, 0, 1, 0],
])

gat_mh = MultiHeadGATLayer(d_in=4, d_out=3, num_heads=3)

# 拼接模式(中间层)
output_concat, attns = gat_mh.forward(X, A, concat=True)
print("=== GAT多头注意力 ===\n")
print(f"头数: {gat_mh.num_heads}")
print(f"拼接输出形状: {output_concat.shape} ({X.shape[0]}个节点, {3*3}维)")
print("→ 每个头输出3维,3个头拼接后9维")

# 展示不同头的注意力差异
print("\n不同注意力头对节点0的邻居权重:")
for h in range(3):
    print(f"  头{h+1}: {np.round(attns[h][0], 3)}")

print("\n观察:不同头给邻居分配了不同的权重")
print("→ 头1可能更关注邻居1,头2更关注邻居3")
print("→ 多头融合了多种注意力模式,比单头更稳定、更全面")
多头GAT的拼接与平均\(\text{中间层}: h_i' = \big\|_{k=1}^{K} \sigma\left(\sum_{j \in \mathcal{N}(i)} \alpha_{ij}^k W^k h_j\right), \quad \text{最后一层}: h_i' = \sigma\left(\frac{1}{K}\sum_{k=1}^{K}\sum_{j \in \mathcal{N}(i)} \alpha_{ij}^k W^k h_j\right)\)
大白话 多头GAT的中间层用"拼接"(把所有专家的意见放在一起),最后一层用"平均"(综合所有专家的意见给出最终答案)。中间层拼接是因为需要保留更多信息给下一层处理;最后一层平均是因为输出维度需要和类别数一致,平均是最自然的融合方式。

什么用(应用):多头GAT在PPI(蛋白质相互作用)数据集上达到了当时最优的归纳学习结果(micro-F1=0.973)。GAT的注意力机制启发了后续许多工作——GATv2修复了静态注意力问题,HAN(异构图注意力网络)扩展到异构图,MAGNN处理元路径。GAT的注意力可解释性也被用于药物发现中识别关键分子子结构。

哪些坑(缺点):原版GAT存在"静态注意力"问题——注意力分数的排序对所有节点是相同的(即如果节点j对节点i最重要,那么j对所有节点的注意力排名都是第一)。GATv2通过交换W和a的顺序(先计算Wh_i和Wh_j,再分别乘a的前半和后半)解决了这个问题。此外,多头注意力的计算开销是单头的K倍,对于大图需要权衡头数和效率。

三、GAT vs GCN:归纳学习与动态权重

是什么(定义):GAT相比GCN的核心优势在于:①动态权重——注意力权重依赖节点特征,而非固定图结构;②归纳学习(Inductive Learning)——可以处理训练时未见过的节点和图,因为注意力计算只依赖局部邻居特征;③自然处理有向图——注意力分数e_{ij}≠e_{ji},天然支持有向边。GCN则需要整个图的邻接矩阵进行归一化,无法直接泛化到新图。

大白话 GAT和GCN的区别就像"关系找人"和"自己判断"。GCN是"关系找人"——你信任谁取决于你们的社交关系(共同朋友数、度),这是固定的。GAT是"自己判断"——你信任谁取决于你们说了什么(特征),这是动态的。一个新朋友来了,GCN需要重新计算整个图的归一化,GAT可以直接根据特征计算注意力,立即可用。

为什么(原理):GAT的归纳学习能力源于其"局部性"——注意力计算只涉及目标节点和邻居节点的特征,不需要全局图信息(如度矩阵)。因此,GAT的参数(W和a)训练好后,可以直接应用于新的图结构。而GCN的归一化依赖全局度矩阵,新图需要重新计算。这使得GAT在动态图、异构图、跨图迁移等场景中更加灵活。

import numpy as np

# GAT vs GCN:归纳学习能力对比
# 演示GAT如何直接应用于新图而GCN需要重新归一化

class GATvsGCN:
    def __init__(self):
        pass

    def gcn_normalize(self, A):
        """GCN归一化:需要全局图信息"""
        A_tilde = A + np.eye(A.shape[0])
        D_tilde = np.diag(np.sum(A_tilde, axis=1))
        D_inv_sqrt = np.diag(1.0 / np.sqrt(np.diag(D_tilde)))
        return D_inv_sqrt @ A_tilde @ D_inv_sqrt

    def demonstrate(self):
        print("=== GAT vs GCN:归纳学习能力 ===\n")

        # 训练时的图
        A_train = np.array([
            [0, 1, 0, 1],
            [1, 0, 1, 0],
            [0, 1, 0, 0],
            [1, 0, 0, 0],
        ])

        print("训练图(4个节点):")
        print(A_train)

        A_norm_train = self.gcn_normalize(A_train)
        print(f"\nGCN:训练时计算的归一化矩阵(4×4):")
        print(np.round(A_norm_train, 3))

        # 测试时的新图(不同大小)
        A_test = np.array([
            [0, 1, 1],
            [1, 0, 0],
            [1, 0, 0],
        ])

        print(f"\n新图(3个节点,训练时未见):")
        print(A_test)

        print(f"\nGCN:训练时的归一化矩阵是4×4的,无法应用于3×3的新图!")
        print("   → 需要重新计算归一化(且需要重新训练)")
        print("   → 这是GCN的转导学习限制")

        print(f"\nGAT:注意力计算只依赖邻居特征,不依赖全局图结构")
        print("   → W和a训练好后,可以直接应用于任意大小的新图")
        print("   → 只需要计算新节点与邻居的注意力分数")
        print("   → 这就是GAT的归纳学习能力!")

        print("\n\n总结:")
        print("┌──────────┬──────────────┬──────────────┐")
        print("│   特性   │    GCN       │    GAT       │")
        print("├──────────┼──────────────┼──────────────┤")
        print("│ 邻居权重 │ 固定(度)   │ 动态(注意力)│")
        print("│ 归纳学习 │ 否           │ 是           │")
        print("│ 有向图   │ 需特殊处理   │ 自然支持     │")
        print("│ 计算效率 │ 高           │ 中           │")
        print("│ 可解释性 │ 低           │ 高(注意力权重)│")
        print("└──────────┴──────────────┴──────────────┘")


GATvsGCN().demonstrate()
GAT的归纳学习能力(形式化)\(\text{GAT}(v_{\text{new}}) = \sigma\left(\sum_{j \in \mathcal{N}(v_{\text{new}})} \alpha_{\text{new},j} W h_j\right), \quad \alpha_{\text{new},j} = \frac{\exp(\text{attn}(h_{\text{new}}, h_j))}{\sum_{k} \exp(\text{attn}(h_{\text{new}}, h_k))}\)
大白话 GAT就像一个"学会了看人"的社交达人——无论加入哪个新圈子,他都能根据每个人的特质(特征)来判断谁值得深交(注意力),不需要重新学习。GCN就像一个"按资排辈"的组织——每个人的地位由他在组织中的位置决定(度),换一个组织就得重新排。这就是GAT的归纳学习能力——学到的是"如何判断",而不是"谁是谁"。

什么用(应用):GAT的归纳学习能力在动态图场景中特别有价值。在社交网络中,新用户不断加入,GAT可以直接为新用户生成高质量嵌入。在药物发现中,训练好的GAT可以直接应用于新分子(新图结构),预测其性质。在知识图谱中,新实体不断出现,GAT可以灵活处理。

哪些坑(缺点):GAT的归纳学习虽然灵活,但完全依赖节点特征——如果新节点的特征分布与训练数据差异很大,注意力权重可能不准确。此外,GAT在小图上可能过拟合(注意力参数多),需要正则化。GAT的计算复杂度高于GCN,在大规模图上需要采样策略(如结合GraphSAGE的采样方法)。

概念关系图谱

概念核心含义与AI的关系关联概念
图注意力(Graph Attention)动态计算邻居重要性的注意力机制GAT的核心创新,替代GCN的固定权重多头注意力、自注意力
注意力系数(α_{ij})节点i对邻居j的归一化注意力权重决定邻居信息在聚合中的贡献Softmax、权重
多头注意力并行使用多个注意力头,拼接或平均输出增强GAT稳定性和表达能力集成学习、多样性
归纳学习可以处理训练时未见过的节点和图GAT相比GCN的核心优势转导学习、泛化
静态注意力问题注意力排序对所有节点相同原版GAT的局限,GATv2修复动态注意力

重点答疑

Q1: GAT的注意力与Transformer的自注意力有什么区别?

两者都使用Q、K、V的框架,但关键区别:①Transformer的自注意力是在整个序列上计算(所有位置两两比较),GAT的注意力只在邻居集合上计算;②Transformer有独立的Q、K、V变换,GAT使用共享的W变换后拼接Wh_i和Wh_j;③GAT的注意力引入了LeakyReLU,而Transformer使用缩放点积。本质上,GAT是"图上的自注意力",只关注邻居而不是所有节点。

Q2: 什么是GAT的静态注意力问题?

原版GAT中,注意力分数的排序对所有节点是相同的——如果节点j对节点i的注意力最高,那么j对所有其他节点的注意力排名也是第一。这意味着注意力不是真正"动态"的,而是由节点特征决定的一个全局排序。GATv2通过交换计算顺序解决了这个问题:先分别计算Wh_i和Wh_j的注意力贡献,再相加,使得注意力排序真正依赖于目标节点i。

Q3: GAT和GraphSAGE的主要区别是什么?

两者都支持归纳学习,但聚合方式不同:GraphSAGE使用固定的聚合函数(平均、LSTM、池化),GAT使用学习的注意力权重。GAT的注意力权重具有可解释性(可以可视化重要邻居),但计算开销更大。GraphSAGE支持邻居采样(处理大图),GAT通常需要额外引入采样。实践中,GAT在小到中型图上效果更好,GraphSAGE在大图上更实用。

章节单词汇总

英文音标术语/释义
Graph Attention Network (GAT)/ɡræf əˈtenʃən ˈnetwɜːrk/图注意力网络,通过注意力动态计算邻居权重
Attention Coefficient/əˈtenʃən ˌkoʊɪˈfɪʃənt/注意力系数,α_{ij},邻居j对节点i的权重
LeakyReLU/ˈliːki rɛluː/带泄漏的ReLU,负区间有小的斜率(0.2)
Multi-head Attention/ˈmʌlti hed əˈtenʃən/多头注意力,并行多个注意力头
Inductive Learning/ɪnˈdʌktɪv ˈlɜːrnɪŋ/归纳学习,可泛化到未见过的节点/图
Static Attention/ˈstætɪk əˈtenʃən/静态注意力,注意力排序不依赖目标节点
Dynamic Attention/daɪˈnæmɪk əˈtenʃən/动态注意力,注意力真正依赖目标节点
Concatenation/kənˌkætɪˈneɪʃən/拼接,将多头输出连接起来

面试练习

Q1 [单选] GAT中注意力分数e_{ij}是如何计算的?

  • A. e_{ij} = h_i^T h_j
  • B. e_{ij} = LeakyReLU(a^T [Wh_i || Wh_j])
  • C. e_{ij} = softmax(h_i^T h_j)
  • D. e_{ij} = 1/√(d_i·d_j)
解答:GAT的注意力分数为e_{ij} = LeakyReLU(a^T[Wh_i||Wh_j]),其中Wh_i和Wh_j拼接后与可学习的注意力向量a做点积。

Q2 [单选] GAT相比GCN的核心优势是什么?

  • A. 计算速度更快
  • B. 动态注意力权重和归纳学习能力
  • C. 更少的参数量
  • D. 不需要邻居信息
解答:GAT的核心优势是动态注意力权重(根据特征计算,而非固定度归一化)和归纳学习能力(可处理新节点/图)。GCN的权重是固定的,且是转导模型。

Q3 [单选] 原GAT论文中使用了多少个注意力头?

  • A. 2
  • B. 4
  • C. 8
  • D. 16
解答:原GAT论文使用K=8个注意力头。中间层拼接输出(8×d_out维),最后一层(预测层)使用平均。

Q4 [多选] 关于GAT,以下哪些说法是正确的?

  • A. 注意力权重是动态计算的,依赖节点特征
  • B. GAT可以处理有向图
  • C. GAT支持归纳学习
  • D. GAT的注意力计算不需要任何参数
  • E. 多头注意力可以增强GAT的稳定性
解答:GAT的注意力权重动态计算,支持有向图,支持归纳学习,多头增强稳定性。注意力计算需要可学习的W和a参数。

Q5 [单选] GAT的LeakyReLU中,负斜率通常取多少?

  • A. 0.0
  • B. 0.2
  • C. 0.5
  • D. 1.0
解答:GAT使用LeakyReLU,负斜率通常取0.2。这确保了即使注意力分数为负,也有一个小梯度,防止梯度消失。

Q6 [单选] GAT的注意力计算复杂度与什么相关?

  • A. 仅与节点数有关
  • B. 与节点数和边数都有关
  • C. 仅与边数有关
  • D. 与图的大小无关
解答:GAT需要为每条边计算注意力分数,复杂度为O(|V|·F·F' + |E|·F')。对于稠密图(|E|≈|V|²),复杂度接近O(|V|²)。

Q7 [多选] 关于GAT的多头注意力,以下哪些是正确的?

  • A. 中间层使用拼接(concat)合并多头输出
  • B. 最后一层使用平均(average)合并多头输出
  • C. 所有层都使用拼接
  • D. 所有层都使用平均
  • E. 每个头有独立的W和a参数
解答:GAT中间层用拼接(保留更多信息),最后一层用平均(输出维度与类别数匹配)。每个头有独立的参数。

Q8 [单选] GAT的静态注意力问题是指什么?

  • A. 注意力权重永远不变
  • B. 注意力分数的排序对所有目标节点是相同的
  • C. 注意力计算速度太慢
  • D. 注意力权重总是0
解答:静态注意力指注意力分数排序不依赖目标节点——如果j对i最重要,那么j对所有节点的注意力排名都是第一。GATv2通过修改计算顺序解决了这个问题。

Q9 [多选] 以下哪些场景适合使用GAT?

  • A. 动态社交网络中的节点分类
  • B. 需要归纳学习的分子性质预测
  • C. 需要可解释邻居重要性的场景
  • D. 超大规模图(数十亿节点)的实时推理
  • E. 有向图上的节点分类
解答:GAT适合动态图、归纳学习、需要可解释性、有向图场景。超大规模图(数十亿节点)需要结合采样策略,原生GAT效率不够。

Q10 [单选] GAT的注意力权重α_{ij}的取值范围是什么?

  • A. [-1, 1]
  • B. [0, 1],且Σ_j α_{ij} = 1
  • C. [0, ∞)
  • D. 任意实数
解答:经过softmax归一化后,α_{ij}∈[0,1],且对每个节点i,所有邻居的注意力权重之和为1(概率分布)。