目录
  1. 1. 一、图信号处理基础
    1. 1.1. 1.1 图上的信号
    2. 1.2. 1.2 拉普拉斯矩阵的谱分解
    3. 1.3. 1.3 图傅里叶变换
  2. 2. 二、谱域图卷积
    1. 2.1. 2.1 卷积定理的图推广
    2. 2.2. 2.2 Bruna et al. (2014):可学习的对角滤波器
    3. 2.3. 2.3 Defferrard et al. (2016, ChebNet):切比雪夫多项式近似
  3. 3. 三、GCN(Kipf & Welling, 2017)
    1. 3.1. 3.1 一阶近似
    2. 3.2. 3.2 重归一化技巧
    3. 3.3. 3.3 GCN 的逐节点视角
    4. 3.4. 3.4 完整的 PyTorch 实现
  4. 4. 四、空间域 GNN(Message Passing)
    1. 4.1. 4.1 消息传递范式
    2. 4.2. 4.2 GraphSAGE
    3. 4.3. 4.3 GAT(Graph Attention Networks)
    4. 4.4. 4.4 GIN(Graph Isomorphism Network)
  5. 5. 五、谱域 vs 空间域对比
  6. 6. 六、过平滑问题
    1. 6.1. 6.1 过平滑的数学解释
    2. 6.2. 6.2 定量分析
    3. 6.3. 6.3 缓解过平滑的策略
  7. 7. 七、GNN 常见问题与最佳实践
    1. 7.1. 7.1 模型选择决策树
    2. 7.2. 7.2 超参数调优指南
  8. 8. 八、GCN 变体一览
  9. 9. 面试/自查问题
  10. 10. 九、大规模图训练技术
    1. 10.1. 9.1 邻居采样(Neighbor Sampling)
    2. 10.2. 9.2 ClusterGCN
    3. 10.3. 9.3 GraphSAINT
  11. 11. 十、GNN 评估与基准测试
    1. 11.1. 10.1 标准评估协议
    2. 11.2. 10.2 消融实验设计
    3. 11.3. 10.3 常见失败模式
  12. 12. 十一、GNN 的可解释性
    1. 12.1. 11.1 GNNExplainer
    2. 12.2. 11.2 基于梯度的归因
  13. 13. 十二、实际应用场景
    1. 13.1. 12.1 工业级推荐系统
    2. 13.2. 12.2 药物发现中的 GNN
    3. 13.3. 12.3 社交网络分析
    4. 13.4. 12.4 交通预测
  14. 14. 十三、GNN 的局限性与未来方向
    1. 14.1. 13.1 当前局限性
    2. 14.2. 13.2 前沿方向
  15. 15. 十四、从零实现 GCN(不用框架)
  16. 16. 十五、完整训练脚本(Cora 节点分类)
  17. 17. 十七、图傅里叶变换的可视化理解
  18. 18. 十八、SGC(Simplifying Graph Convolutional Networks)
  19. 19. 十九、附录:关键数学公式速查表
  20. 20. 二十、总结
  21. 21. 二十一、补充:图的同质性与异质性
    1. 21.1. 21.1 同质性(Homophily)
    2. 21.2. 21.2 异质性(Heterophily)
    3. 21.3. 21.3 图同质性实验
  22. 22. 二十二、阅读论文推荐顺序
  23. 23. 二十三、GNN 实践 Checklist
【GNN原理解析】图信号处理与图卷积神经网络

图卷积神经网络(GCN)是图深度学习中最具影响力的模型。本文从图信号处理的谱域视角出发,完整推导 GCN 的数学原理:从图傅里叶变换、谱域卷积,到切比雪夫多项式近似、一阶近似和重归一化技巧。同时涵盖空间域 GNN(GraphSAGE、GAT、GIN)以及过平滑问题的系统分析。

一、图信号处理基础

1.1 图上的信号

图信号 x ∈ R^N 是一个定义在图节点上的函数,x_i 表示节点 v_i 的信号值。在 GNN 的语境中,x 就是某一维度的节点特征。

在欧几里得空间(图像)中,信号定义在规则网格上,傅里叶变换将信号分解为不同频率的正弦波。在图空间中,我们需要一个类似的工具来分析图信号的”频率”成分。

1.2 拉普拉斯矩阵的谱分解

图拉普拉斯矩阵 L 是一个实对称半正定矩阵,可以进行特征分解:

L = U Λ U^T

其中:

  • Λ = diag(λ_1, λ_2, …, λ_N),λ_1 ≤ λ_2 ≤ … ≤ λ_N 是特征值
  • U = [u_1, u_2, …, u_N] 是正交的特征向量矩阵,满足 U^T U = I

拉普拉斯特征向量的物理意义:

  • 小特征值 λ_k 对应的特征向量 u_k 在图上变化平缓(低频),反映了图的宏观结构
  • 大特征值 λ_k 对应的特征向量 u_k 在图上变化剧烈(高频),反映了局部差异
  • 特征向量可以视为图上的”傅里叶基函数”
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt

# 构造路径图并计算拉普拉斯特征向量
N = 20
G = nx.path_graph(N)
A = nx.adjacency_matrix(G).toarray()
D = np.diag(A.sum(axis=1))
L = D - A

eigenvalues, eigenvectors = np.linalg.eigh(L)

# 可视化前 5 个特征向量(低频到高频)
fig, axes = plt.subplots(5, 1, figsize=(12, 10))
for i in range(5):
axes[i].stem(range(N), eigenvectors[:, i])
axes[i].set_title(f'特征向量 {i+1} (λ_{i+1}={eigenvalues[i]:.3f})')
axes[i].set_ylabel('振幅')
plt.tight_layout()
plt.show()

1.3 图傅里叶变换

类比经典傅里叶变换,定义图傅里叶变换(Graph Fourier Transform)

x̂(λ_k) = <x, u_k> = Σ_{i=1}^N x_i · u_k(i)      即 x̂ = U^T x

逆图傅里叶变换

x_i = Σ_{k=1}^N x̂(λ_k) · u_k(i)                     即 x = U x̂

物理含义:图信号 x 被分解为 N 个”图频率”分量,每个分量对应一个特征向量 u_k 及其系数 x̂(λ_k)。小 λ_k 对应低频分量(图结构上平滑),大 λ_k 对应高频分量(图结构上震荡)。

二、谱域图卷积

2.1 卷积定理的图推广

经典信号处理中,卷积定理:时域卷积 = 频域逐元素乘积。

(f * g)(t) ⟷ f̂(ω) · ĝ(ω)         (傅里叶变换域中的乘法)

推广到图上,定义图卷积:

x * G y = U ( (U^T x) ⊙ (U^T y) )       ⊙ 表示逐元素乘法

更一般地,定义带有可学习谱域滤波器 g_θ 的图卷积:

x * G g_θ = U g_θ(Λ) U^T x

其中 g_θ(Λ) = diag(θ_1, θ_2, …, θ_N) 是谱域中的滤波器参数。

2.2 Bruna et al. (2014):可学习的对角滤波器

Bruna 等人直接学习 g_θ(Λ) 的对角元素:

H^{(l+1)} = σ( U diag(θ^{(l)}) U^T H^{(l)} )

优点:数学上最直接、最精确的图卷积。
缺点

  1. 需要完整的特征分解(O(N^3) 复杂度,对大规模图不可行)
  2. 非局部化:参数 θ_k 影响全局(通过 u_k),不是局部化的
  3. 非共享:滤波器参数 θ 和特定的图 G 绑定,不能跨图使用
  4. 参数量:O(N) 个参数(每个特征值一个),与节点数成正比

2.3 Defferrard et al. (2016, ChebNet):切比雪夫多项式近似

Defferrard 等人的核心创新:用切比雪夫多项式近似 g_θ(Λ),避免昂贵的特征分解。

切比雪夫多项式的定义:

T_0(x) = 1
T_1(x) = x
T_k(x) = 2x T_{k-1}(x) - T_{k-2}(x) (k ≥ 2)

切比雪夫多项式构成了 [-1, 1] 上的正交基。

对 g_θ(Λ) 做 K 阶切比雪夫展开:

g_θ(Λ) ≈ Σ_{k=0}^{K-1} θ_k T_k(Λ̃)

其中 Λ̃ = 2Λ/λ_max - I 是将特征值缩放到 [-1, 1] 的归一化操作。

关键性质:利用 T_k(L̃) = U T_k(Λ̃) U^T(切比雪夫多项式与特征分解可交换),得到:

x * G g_θ = Σ_{k=0}^{K-1} θ_k T_k(L̃) x

其中 L̃ = 2L/λ_max - I。

重要:这个公式完全不需要计算特征分解。T_k(L̃) 通过递归关系 T_k(L̃)x = 2L̃ T_{k-1}(L̃)x - T_{k-2}(L̃)x 计算,每次迭代只需要稀疏矩阵-向量乘法(O(|E|)),总复杂度 O(K|E|)。

此外,切比雪夫多项式在 [-1, 1] 上是有界的(|T_k(x)| ≤ 1),保证了计算的数值稳定性。

局部化性质:K 阶切比雪夫多项式 T_K(L̃) 的求解只涉及每个节点不超过 K-hop 的邻居。因此 ChebNet 是严格局部化的 K 阶图卷积

三、GCN(Kipf & Welling, 2017)

3.1 一阶近似

Kipf & Welling 对 ChebNet 做了两个极简的近似:

近似 1:K=1(一阶近似,每层只看 1-hop 邻居)

x * G g_θ ≈ θ_0 x + θ_1 L̃ x
= θ_0 x + θ_1 (2L/λ_max - I) x

近似 2:λ_max ≈ 2

x * G g_θ ≈ θ_0 x + θ_1 (L - I) x
= θ_0 x + θ_1 (D - A - I) x
= θ_0 x + θ_1 (D - (A + I)) x

进一步令 θ = θ_0 = -θ_1 减少参数:

x * G g_θ ≈ θ (I + D^{-1/2} A D^{-1/2}) x

3.2 重归一化技巧

I + D^{-1/2} A D^{-1/2} 的特征值范围为 [0, 2]。在深层网络中反复使用这个算子会导致梯度消失或爆炸。

重归一化技巧(Renormalization Trick)

定义 Ã = A + I(带自环的邻接矩阵),D̃_ii = Σ_j Ã_ij(带自环的度矩阵)。使用:

D̃^{-1/2} Ã D̃^{-1/2}

替换 I + D^{-1/2} A D^{-1/2}。这个新算子的特征值范围在 (-1, 1] 之间,数值上更稳定。

最终 GCN 层的公式:

H^{(l+1)} = σ( D̃^{-1/2} Ã D̃^{-1/2} H^{(l)} W^{(l)} )

其中:

  • H^{(l)} ∈ R^{N×d_l}:第 l 层的节点嵌入矩阵
  • W^{(l)} ∈ R^{d_l×d_{l+1}}:可学习的权重矩阵
  • σ:激活函数(通常为 ReLU)
  • D̃^{-1/2} Ã D̃^{-1/2}:重归一化后的邻接矩阵(固定,在训练前计算一次)

3.3 GCN 的逐节点视角

将矩阵乘积展开到单个节点 v,可以更直观地理解 GCN 在做什么:

h_v^{(l+1)} = σ( W^{(l)}^T · Σ_{u∈N(v)∪{v}} ( 1 / √(d_u+1)(d_v+1) ) · h_u^{(l)} )

节点 v 的新嵌入 = 自身嵌入 + 所有邻居嵌入的加权平均,然后通过 W^{(l)} 做线性变换,再通过 σ 做非线性激活。

权重 1/√((d_u+1)(d_v+1)) 同时对发送节点 (d_u+1) 和接收节点 (d_v+1) 进行归一化,防止高度数的节点主导聚合结果。

3.4 完整的 PyTorch 实现

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv

class GCN(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels, num_layers=2, dropout=0.5):
super().__init__()
self.dropout = dropout
self.convs = nn.ModuleList()
self.convs.append(GCNConv(in_channels, hidden_channels))
for _ in range(num_layers - 2):
self.convs.append(GCNConv(hidden_channels, hidden_channels))
self.convs.append(GCNConv(hidden_channels, out_channels))

def forward(self, x, edge_index):
for i, conv in enumerate(self.convs[:-1]):
x = conv(x, edge_index)
x = F.relu(x)
x = F.dropout(x, p=self.dropout, training=self.training)
x = self.convs[-1](x, edge_index)
return x

# 训练 GCN
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root='/tmp/Cora', name='Cora')
data = dataset[0]

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GCN(dataset.num_features, hidden_channels=64,
out_channels=dataset.num_classes).to(device)
data = data.to(device)

optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

def train():
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.cross_entropy(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss.item()

def test():
model.eval()
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)
accs = []
for mask in [data.train_mask, data.val_mask, data.test_mask]:
acc = (pred[mask] == data.y[mask]).float().mean().item()
accs.append(acc)
return accs

for epoch in range(200):
loss = train()
if epoch % 20 == 0:
train_acc, val_acc, test_acc = test()
print(f'Epoch {epoch:03d}: Loss {loss:.4f}, '
f'Train {train_acc:.4f}, Val {val_acc:.4f}, Test {test_acc:.4f}')

四、空间域 GNN(Message Passing)

4.1 消息传递范式

谱域 GCN 是数学上优美的,但空间域的”消息传递”视角更直观、更通用:

h_v^{(l+1)} = UPDATE( h_v^{(l)}, AGGREGATE( { h_u^{(l)} : u ∈ N(v) } ) )

每个节点首先从邻居收集”消息”,然后将消息聚合,最后用聚合结果更新自身表示。

4.2 GraphSAGE

GraphSAGE(Hamilton et al., NeurIPS 2017)是一个归纳式(inductive)的空间 GNN。核心公式:

h_v^{(l+1)} = σ( W^{(l)} · CONCAT( h_v^{(l)}, AGGREGATE({h_u^{(l)}: u∈N(v)}) ) )

GraphSAGE 提出了三种聚合器:

Mean Aggregator

AGGREGATE = 1/|N(v)| Σ_{u∈N(v)} h_u

LSTM Aggregator:将邻居视为序列,用 LSTM 聚合。优点是表达能力强,缺点是输入顺序敏感(不是置换不变的)。

Pooling Aggregator

AGGREGATE = max( { W_pool·h_u + b : u∈N(v) } )

GraphSAGE 的关键创新是邻居采样:不对所有邻居操作,而是随机采样固定数量的邻居(如 25 个)。这使得训练可以 mini-batch 化,支持大规模图。

from torch_geometric.nn import SAGEConv

class GraphSAGE(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels):
super().__init__()
self.conv1 = SAGEConv(in_channels, hidden_channels)
self.conv2 = SAGEConv(hidden_channels, out_channels)

def forward(self, x, edge_index):
x = F.relu(self.conv1(x, edge_index))
x = F.dropout(x, p=0.5, training=self.training)
x = self.conv2(x, edge_index)
return x

4.3 GAT(Graph Attention Networks)

GAT(Velickovic et al., ICLR 2018)引入注意力机制来动态计算不同邻居的重要性:

注意力系数计算

α_ij = softmax_j( LeakyReLU( a^T [Wh_i || Wh_j] ) )

多头注意力聚合

h'_i = σ( 1/K Σ_{k=1}^K Σ_{j∈N(i)} α_ij^k W^k h_j )
from torch_geometric.nn import GATConv

class GAT(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels, heads=8):
super().__init__()
self.conv1 = GATConv(in_channels, hidden_channels, heads=heads, dropout=0.6)
self.conv2 = GATConv(hidden_channels * heads, out_channels, heads=1,
concat=False, dropout=0.6)

def forward(self, x, edge_index):
x = F.dropout(x, p=0.6, training=self.training)
x = F.elu(self.conv1(x, edge_index))
x = F.dropout(x, p=0.6, training=self.training)
x = self.conv2(x, edge_index)
return x

GAT vs GCN 关键区别

  • GCN 的邻居权重是固定的(1/√(d_u d_v)),由图的拓扑结构决定
  • GAT 的邻居权重是动态计算的,由节点特征和注意力机制决定
  • GAT 通过多头注意力增强了模型的鲁棒性

4.4 GIN(Graph Isomorphism Network)

GIN(Xu et al., ICLR 2019)从图同构的角度出发,证明了 SUM 聚合器 + MLP 可以达到 WL 测试的表达能力上限:

h_v^{(l+1)} = MLP( (1 + ε^{(l)}) h_v^{(l)} + Σ_{u∈N(v)} h_u^{(l)} )

其中 ε 是可学习参数或固定为 0。MLP 是至少两层的多层感知器。

为什么 SUM 比 MEAN/MAX 更强大?

  • MAX 聚合器:只保留邻居中每维的最大值,丢失了邻居的分布信息。多个有相同最大值的邻居结构无法区分
  • MEAN 聚合器:能捕捉分布的平均水平,但丢失了邻居数量的信息。有 2 个(1,2)邻居的节点 vs 有 4 个(2,1)邻居的节点,MEAN 输出相同
  • SUM 聚合器:同时保留了分布水平和邻居数量(多重集的大小)。因此 SUM 的表达能力等于 WL 测试
import torch
from torch_geometric.nn import GINConv
from torch_geometric.nn.glob import global_add_pool

class GIN(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels, num_layers=4):
super().__init__()
self.convs = nn.ModuleList()
for i in range(num_layers):
mlp = nn.Sequential(
nn.Linear(in_channels if i == 0 else hidden_channels, hidden_channels),
nn.BatchNorm1d(hidden_channels),
nn.ReLU(),
nn.Linear(hidden_channels, hidden_channels),
nn.ReLU()
)
self.convs.append(GINConv(mlp, train_eps=True))

self.lin = nn.Linear(hidden_channels, out_channels)

def forward(self, x, edge_index, batch):
for conv in self.convs:
x = conv(x, edge_index)
# 图分类:用 SUM 池化聚和所有节点的表示
x = global_add_pool(x, batch)
x = self.lin(x)
return x

五、谱域 vs 空间域对比

维度 谱域 GNN 空间域 GNN
数学基础 图信号处理、拉普拉斯谱分解 消息传递、邻居聚合
代表性模型 GCN, ChebNet GraphSAGE, GAT, GIN
特征分解 需要(ChebNet/K=1 GCN 不需要) 不需要
泛化能力 谱域滤波器与图绑定,难以泛化到新图 空间域聚合函数可泛化到新图
计算复杂度 O(N^3) 如果做特征分解,O(K E
灵活性 较低(限于对称邻接矩阵) 高(可处理有向图、边特征、多种聚合方式)
理论保障 强的理论框架(谱域) 有 WL 测试的表达能力上限

现代 GNN 研究主要沿空间域方向发展,因为空间域方法更灵活、更高效、更易扩展到异构图和动态图。

六、过平滑问题

6.1 过平滑的数学解释

堆叠多层 GCN 后,所有节点的嵌入趋于相同(收敛到图的”平均”状态):

lim_{k→∞} (D̃^{-1/2} Ã D̃^{-1/2})^k H = Π H

其中 Π 是将信号投影到”主特征空间”(零特征值对应的特征向量方向)的投影矩阵。随着层数增加,高频信息被反复平滑掉,所有节点变得无法区分。

6.2 定量分析

Li et al. (2018) 证明:在 K 层 GCN 后,两个不同节点的嵌入之差的 L2 范数满足:

||h_u^{(K)} - h_v^{(K)}|| ≤ (sλ)^K ||h_u^{(0)} - h_v^{(0)}||

其中 s 是权重矩阵的最大奇异值,λ 是 D̃^{-1/2} Ã D̃^{-1/2} 的第二大特征值。当 |sλ| < 1 时,差距指数衰减到 0。

6.3 缓解过平滑的策略

1. 浅层 GCN(2-4 层)
最简单的方法:不要用太多层。实验表明 2 层 GCN 在多数任务上已足够。

2. DropEdge(Rong et al., ICLR 2020)
训练时随机丢弃一部分边:

def drop_edge(edge_index, drop_rate=0.2):
mask = torch.rand(edge_index.size(1)) > drop_rate
return edge_index[:, mask]

DropEdge 减少了图的”连通性”,有效延缓了过平滑,使得可以训练更深(最多 8-10 层)的 GCN。

3. JK-Net(Xu et al., ICML 2018)
将每一层的输出拼接/池化起来作为最终表示:

h_v^{final} = CONCAT/MAX( h_v^{(1)}, h_v^{(2)}, ..., h_v^{(K)} )

这允许模型在”不同层的表示”之间选择,浅层保留局部信息,深层保留全局信息。

4. GCNII(Chen et al., ICML 2020)
在 GCN 中引入 initial residual connection(与输入层的连接)和 identity mapping:

H^{(l+1)} = σ( ((1-α_l) D̃^{-1/2} Ã D̃^{-1/2} + α_l I) H^{(l)} ( (1-β_l) I + β_l W^{(l)} ) )

这使得非常深(64 层)的 GCN 也能有效训练。

5. PairNorm(Zhao & Akoglu, 2019)
在每层后归一化节点嵌入,保持总 pairwise 距离不变:

def pair_norm(x):
# 中心化
mean = x.mean(dim=0, keepdim=True)
x_centered = x - mean
# 缩放到固定的总平方距离
scale = (x_centered.size(0) / x_centered.pow(2).sum()).sqrt()
return scale * x_centered

七、GNN 常见问题与最佳实践

7.1 模型选择决策树

图规模?
├── 小图 (< 10K 节点) → GCN 或 GAT
├── 中图 (10K - 1M 节点) → GraphSAGE(邻居采样)或 ClusterGCN
│ └── 需要注意力?→ GAT
└── 大图 (> 1M 节点) → GraphSAGE + 采样 或简化版 GCN (SGC)

任务类型?
├── 节点分类 → GCN / GAT / GraphSAGE
├── 链接预测 → GCN + 点积解码器 / SEAL
├── 图分类 → GIN / GatedGCN(全局池化)
└── 图生成 → VGAE / GraphVAE

7.2 超参数调优指南

超参数 推荐值 搜索范围
层数 2-3 1-5
隐藏层维度 64-256 16-512
学习率 0.01 (Adam) 0.001-0.05
Dropout 0.5-0.6 0.1-0.8
Weight Decay 5e-4 1e-5 - 1e-3
Normalization LayerNorm BN / LN / 无
Skip Connection 推荐使用 加 / 不加

八、GCN 变体一览

模型 年份 + 会议 核心创新 聚合方式
GCN ICLR 2017 谱域一阶近似 + 重归一化 归一化求和
GraphSAGE NeurIPS 2017 归纳式、邻居采样 Mean/LSTM/MaxPool
GAT ICLR 2018 注意力机制 加权求和(注意力)
GIN ICLR 2019 表达力 = WL 测试 SUM + MLP
SGC ICML 2019 去除 GCN 的非线性 无激活、纯线性
GCNII ICML 2020 初始残差连接、恒等映射 重归一化求和 + 残差
PNA NeurIPS 2020 多种聚合器的组合 mean+min+max+std
GatedGCN 2018 边门控机制 门控边信息聚合

面试/自查问题

Q1:GCN 的 D^{-1/2} A D^{-1/2} 从何而来?

从谱域卷积的推导:

  1. 图卷积 = g_θ(L) x = U g_θ(Λ) U^T x
  2. 令 g_θ(Λ) = θ I(一阶多项式)
  3. 归一化拉普拉斯 L_sym = I - D^{-1/2} A D^{-1/2}
  4. 卷积变为:θ (I - L_sym) x = θ D^{-1/2} A D^{-1/2} x
  5. 引入自环和重归一化:D^{-1/2}(A + I)D^{-1/2}

本质上是图信号处理中谱域滤波器的低阶多项式近似。

Q2:GCN、GraphSAGE、GAT 的核心区别?

  • GCN:邻居权重由图的度矩阵归一化决定(固定的),聚合方式为归一化求和
  • GraphSAGE:聚合函数可以是 mean/lstm/pooling,显式拼接自身和邻居信息,支持邻居采样
  • GAT:通过自注意力机制动态学习每个邻居的重要性权重,支持多头注意力增强鲁棒性

Q3:为什么 GNN 不能像 CNN 那样堆叠很多层?

核心原因是过平滑(Over-smoothing)

  1. 每次 GCN 层都是在对节点特征做图拉普拉斯平滑(加权平均邻居特征)
  2. 经过足够多层后,所有节点的表示趋于相同(收敛到零空间)
  3. CNN 的感受野随层数线性增长,GNN 的感受野指数增长 —— 3 层 GCN 可能已经覆盖了大多数节点的大多数近邻
  4. 解决方案:残差连接(GCNII)、DropEdge、JK-Net、PairNorm

Q4:SUM vs MEAN vs MAX 聚合器的表达能力差异?

从 GIN 的理论分析:

  • MAX(1)最多仅能区分存在哪些不同的邻居特征值(丢失多重集信息)
  • MEAN 能区分邻居特征的分布(丢失了邻居数量的信息)
  • SUM 同时保留了邻居特征分布和邻居数量信息,表达能力最强

这是 GIN 使用 SUM 聚合器并证明其等于 WL 测试表达能力的理论基础。

九、大规模图训练技术

9.1 邻居采样(Neighbor Sampling)

GraphSAGE 的邻居采样是处理大图的核心技术:

def sample_neighbors(adj_list, nodes, sample_sizes=[25, 10]):
"""
多层邻居采样
sample_sizes: 每层采样的邻居数,如 [25, 10] 表示:
- 第 1 层:每个节点采样 25 个一阶邻居
- 第 2 层:每个一阶邻居再采样 10 个二阶邻居
"""
sampled = [set(nodes)]
for size in sample_sizes:
next_nodes = set()
for node in sampled[-1]:
neighbors = adj_list.get(node, [])
if len(neighbors) > size:
sampled_neighbors = random.sample(neighbors, size)
else:
sampled_neighbors = neighbors
next_nodes.update(sampled_neighbors)
sampled.append(next_nodes)
return sampled

# 采样后在这个子图上运行 GNN
# 总复杂度降低到 O(Π sample_sizes) << O(|V|^2)

9.2 ClusterGCN

ClusterGCN(Chiang et al., KDD 2019)通过图聚类将大图划分为多个子图,在每个子图上运行 GCN,从而避免采样引入的偏差:

import metis

def cluster_graph(adj_matrix, num_clusters):
"""使用 METIS 算法划分图"""
# METIS 是一个经典的图划分算法,将图划分为大致均匀的子图
# 同时最小化跨分区的边数(最小化 cut)
edge_cuts, parts = metis.part_graph(adj_matrix, nparts=num_clusters)
clusters = [[] for _ in range(num_clusters)]
for node, part in enumerate(parts):
clusters[part].append(node)
return clusters

# 训练时每次随机选择一个或几个 cluster 作为 mini-batch
# 在 cluster 内部的图上运行 GCN

9.3 GraphSAINT

GraphSAINT(Zeng et al., ICLR 2020)通过多种采样策略(节点采样、边采样、随机游走采样)构建 mini-batch 子图。核心代码:

def node_sampler(graph, num_roots):
"""以节点为中心的采样器"""
roots = random.sample(range(graph.num_nodes), num_roots)
# 对每个 root 节点,执行随机游走,收集子图节点
subgraph_nodes = set()
for root in roots:
walk = random_walk(graph, root, walk_length=3)
subgraph_nodes.update(walk)
return subgraph_nodes

def edge_sampler(graph, num_edges):
"""以边为中心的采样器"""
edges = random.sample(graph.edges, num_edges)
subgraph_nodes = set()
for u, v in edges:
subgraph_nodes.add(u)
subgraph_nodes.add(v)
# 可选:包含边的两端节点的邻居
subgraph_nodes.update(graph.neighbors(u))
subgraph_nodes.update(graph.neighbors(v))
return subgraph_nodes

十、GNN 评估与基准测试

10.1 标准评估协议

节点分类(Node Classification)

from torch_geometric.datasets import Planetoid
from sklearn.metrics import f1_score

def evaluate_node_classification(model, data):
model.eval()
with torch.no_grad():
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)

# 标准划分:每类 20 个节点用于训练,500 用于验证,1000 用于测试
test_acc = (pred[data.test_mask] == data.y[data.test_mask]).float().mean()
return test_acc.item()

链接预测(Link Prediction)

def evaluate_link_prediction(model, data):
# 随机遮蔽 10% 的边作为测试集
# 使用模型输出的节点嵌入,计算节点对的内积作为预测得分
# 评估指标:AUC-ROC, Hits@K
from sklearn.metrics import roc_auc_score

edge_index = data.edge_index
num_edges = edge_index.size(1)

# 随机划分
perm = torch.randperm(num_edges)
train_edges = edge_index[:, perm[:int(0.8 * num_edges)]]
test_pos_edges = edge_index[:, perm[int(0.8 * num_edges):]]

# 生成负样本(不存在的边)
test_neg_edges = sample_negative_edges(data.num_nodes, test_pos_edges.size(1))

# 计算得分
embeddings = model(data.x, train_edges)
pos_scores = (embeddings[test_pos_edges[0]] * embeddings[test_pos_edges[1]]).sum(dim=1)
neg_scores = (embeddings[test_neg_edges[0]] * embeddings[test_neg_edges[1]]).sum(dim=1)

scores = torch.cat([pos_scores, neg_scores])
labels = torch.cat([torch.ones(pos_scores.size(0)), torch.zeros(neg_scores.size(0))])
auc = roc_auc_score(labels.numpy(), scores.numpy())
return auc

10.2 消融实验设计

在进行 GNN 实验时,以下消融实验是标准做法:

  1. 层数消融:测试 1/2/3/4/5 层,观察过平滑何时发生
  2. 宽度消融:测试 hidden_dim ∈ {16, 32, 64, 128, 256}
  3. Dropout 率消融:测试 dropout ∈ {0, 0.2, 0.4, 0.5, 0.6, 0.8}
  4. 归一化消融:测试 None / BN / LN / GraphNorm
  5. 聚合函数消融:测试 mean / sum / max / attention
  6. 残差连接消融:测试 有残差/无残差

10.3 常见失败模式

失败模式 症状 可能原因 解决方案
过平滑 增加层数后性能下降 感受野过大,所有节点嵌入趋同 DropEdge, GCNII, JK-Net
过拟合 训练准确率高,测试准确率低 模型太深/太宽,训练集太小 增加 dropout, 减少层数/维度
欠拟合 训练准确率低 模型太浅,学习率不当 增加层数/维度,调整学习率和优化器
训练不收敛 Loss 震荡 学习率过大 降低学习率,使用 warmup
度数偏差 高度数节点预测好,低度数节点差 归一化不足或归一化过度 调整聚合方式,考虑 PNA
特征依赖 只依赖特征、忽略图结构也能达到相似的准确率 图的归纳偏差未被利用 检查图结构是否真的有信息,可能任务不适合 GNN

十一、GNN 的可解释性

11.1 GNNExplainer

GNNExplainer(Ying et al., NeurIPS 2019)学习一个软掩码来识别对预测最重要的子图结构和节点特征:

from torch_geometric.nn import GNNExplainer

explainer = GNNExplainer(model, epochs=200, return_type='log_prob')
node_idx = 10 # 要解释的节点

# 解释
node_feat_mask, edge_mask = explainer.explain_node(node_idx, x=data.x, edge_index=data.edge_index)

# edge_mask 给出了每条边的重要性得分
# 可以据此可视化对预测最重要的子图

11.2 基于梯度的归因

def gradient_attribution(model, data, node_idx):
"""计算每个输入特征和邻居对该节点预测的贡献"""
x = data.x.clone().requires_grad_(True)
out = model(x, data.edge_index)
loss = out[node_idx].max() # 对预测类别得分求梯度

model.zero_grad()
loss.backward()

# 特征梯度揭示重要的输入特征维度
# 邻接矩阵梯度揭示重要的邻居
feature_importance = x.grad[node_idx].abs()
return feature_importance

十二、实际应用场景

12.1 工业级推荐系统

Pinterest 的 PinSage 使用 GraphSAGE 的变体在大规模 pin-board 异构图(30 亿节点 + 180 亿边)上执行推荐。核心设计:

  • 重要性池化:根据随机游走的访问频率加权采样邻居
  • 多 GPU 分布式训练
  • MapReduce 风格的邻居采样管道
class PinSageConv(nn.Module):
"""PinSage 图卷积层的简化实现"""
def __init__(self, in_dim, out_dim):
super().__init__()
self.lin = nn.Linear(in_dim * 2, out_dim)

def forward(self, x, edge_index, importance):
# importance: 邻居采样时根据随机游走频率计算的权重
out = []
for v in range(x.size(0)):
neighbors = edge_index[1][edge_index[0] == v]
if len(neighbors) == 0:
out.append(self.lin(torch.cat([x[v], torch.zeros_like(x[v])])))
continue
# 加权邻居聚合
neighbor_agg = (x[neighbors] * importance[neighbors].unsqueeze(1)).sum(0)
neighbor_agg /= importance[neighbors].sum()
out.append(F.relu(self.lin(torch.cat([x[v], neighbor_agg]))))
return torch.stack(out)

12.2 药物发现中的 GNN

分子可以自然地表示为图:原子是节点,化学键是边。GNN 广泛应用于:

  • 分子性质预测(溶解度、毒性、logP)
  • 药物-靶标相互作用预测
  • 逆合成路线规划
  • 分子生成与优化

12.3 社交网络分析

  • 好友推荐(链接预测)
  • 社区检测(节点聚类)
  • 虚假新闻检测(节点分类 + 异构图)
  • 影响力最大化(关键节点发现)

12.4 交通预测

在道路网络中,传感器是节点,道路连接是边。使用时空 GNN(如 DCRNN, STGCN)预测交通流量、速度和拥堵状态。

十三、GNN 的局限性与未来方向

13.1 当前局限性

局限 描述
浅层性 由于过平滑,GNN 通常只有 2-4 层,限制了感受野
表达能力上限 消息传递 GNN 的表达能力受 WL 测试限制
位置/距离感知 GNN 难以区分异质节点中的”位置”
可扩展性 在大图上训练仍然困难(邻居爆炸、内存爆炸)
动态图 现有 GNN 假设静态图,动态图上的 GNN 仍在早期
定向边 大部分 GNN 假设无向图,有向图上的研究较少

13.2 前沿方向

  • Graph Transformers:结合 GNN 和 Transformer 的优势(Graphormer, SAN)
  • 等变 GNN(Equivariant GNNs):处理 3D 坐标(分子构象),满足旋转/平移等变性
  • 图的生成模型:通过扩散模型、自回归模型生成分子图
  • GNN + LLM:将大语言模型与 GNN 结合,处理文本属性图
  • 自监督学习 in GNNs:GraphCL, BGRL, MAE for graphs

十四、从零实现 GCN(不用框架)

import numpy as np
import scipy.sparse as sp

def normalize_adjacency(adj):
"""计算 D^{-1/2} A D^{-1/2}"""
# 添加自环
adj = adj + sp.eye(adj.shape[0])
# 计算度矩阵
d = np.array(adj.sum(1)).flatten()
d_inv_sqrt = np.power(d, -0.5)
d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.
d_mat_inv_sqrt = sp.diags(d_inv_sqrt)
# 归一化
return d_mat_inv_sqrt @ adj @ d_mat_inv_sqrt

class SimpleGCN:
def __init__(self, input_dim, hidden_dim, output_dim, lr=0.01):
# 随机初始化权重
self.W1 = np.random.randn(input_dim, hidden_dim) * np.sqrt(2.0 / input_dim)
self.W2 = np.random.randn(hidden_dim, output_dim) * np.sqrt(2.0 / hidden_dim)
self.lr = lr

def forward(self, X, A_norm):
# 第一层
H1 = A_norm @ X @ self.W1
H1 = np.maximum(0, H1) # ReLU
# 第二层
H2 = A_norm @ H1 @ self.W2
# Softmax
exp_H2 = np.exp(H2 - np.max(H2, axis=1, keepdims=True))
self.out = exp_H2 / np.sum(exp_H2, axis=1, keepdims=True)
return self.out

def backward(self, X, A_norm, y, mask):
"""简化版反向传播(仅示意)"""
# Cross-entropy loss
N = mask.sum()
loss = -np.sum(np.log(self.out[mask] + 1e-10) * y[mask]) / N

# 输出层梯度
grad_H2 = self.out.copy()
grad_H2[mask] -= y[mask]
grad_H2 /= N

# W2 梯度
H1 = np.maximum(0, A_norm @ X @ self.W1)
grad_W2 = (A_norm @ H1).T @ grad_H2

# W1 梯度
grad_H1 = grad_H2 @ self.W2.T
grad_H1[H1 <= 0] = 0 # ReLU 反向传播
grad_W1 = X.T @ (A_norm.T @ grad_H1)

# 更新权重
self.W2 -= self.lr * grad_W2
self.W1 -= self.lr * grad_W1

return loss

# 使用示例
# A: scipy.sparse 邻接矩阵
# X: N×d 特征矩阵
# y: N×C one-hot 标签
# mask: 训练节点掩码
# A_norm = normalize_adjacency(A)
# gcn = SimpleGCN(d, 16, C)
# for epoch in range(200):
# gcn.forward(X, A_norm)
# loss = gcn.backward(X, A_norm, y, mask)

十五、完整训练脚本(Cora 节点分类)

import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
import numpy as np

# 固定随机种子确保可复现
torch.manual_seed(42)
np.random.seed(42)

# 数据
dataset = Planetoid(root='/tmp/Cora', name='Cora')
data = dataset[0] # x: (2708, 1433), edge_index: (2, 10556), y: (2708,)

# 模型
class GCN(torch.nn.Module):
def __init__(self, in_feat, hidden_feat, out_feat, dropout=0.5):
super().__init__()
self.conv1 = GCNConv(in_feat, hidden_feat)
self.conv2 = GCNConv(hidden_feat, out_feat)
self.dropout = dropout

def forward(self, x, edge_index):
x = F.dropout(x, p=self.dropout, training=self.training)
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, p=self.dropout, training=self.training)
x = self.conv2(x, edge_index)
return F.log_softmax(x, dim=1)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GCN(dataset.num_features, 64, dataset.num_classes).to(device)
data = data.to(device)

optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

best_val_acc = 0
best_test_acc = 0

for epoch in range(200):
# 训练
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()

# 评估
model.eval()
with torch.no_grad():
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)
val_acc = (pred[data.val_mask] == data.y[data.val_mask]).float().mean()
test_acc = (pred[data.test_mask] == data.y[data.test_mask]).float().mean()

if val_acc > best_val_acc:
best_val_acc = val_acc
best_test_acc = test_acc

if epoch % 20 == 0:
print(f'Epoch {epoch:03d}: Loss {loss:.4f}, Val {val_acc:.4f}, Test {test_acc:.4f}')

print(f'Best Test Accuracy: {best_test_acc:.4f}')

## 十六、GNN 与 CNN 参数效率对比实验

在 Cora 数据集上,对比 GCN、GAT 和 MLP(忽略图结构):

```python
# MLP(不使用图结构)作为 baseline
class MLP(torch.nn.Module):
def __init__(self, in_feat, hidden_feat, out_feat):
super().__init__()
self.lin1 = torch.nn.Linear(in_feat, hidden_feat)
self.lin2 = torch.nn.Linear(hidden_feat, out_feat)

def forward(self, x, edge_index=None):
x = F.relu(self.lin1(x))
x = self.lin2(x)
return F.log_softmax(x, dim=1)

# 典型结果(Cora 数据集,20 nodes/class 训练):
# MLP: ~55% (忽略了图结构,仅靠节点特征)
# GCN (2层): ~81% (利用了图结构和节点特征)
# GAT (2层): ~83% (注意力机制进一步提升了邻居信息的利用效率)
# SGC: ~80% (去除非线性,仅在预处理阶段做图滤波,然后接线性分类器)

MLP 和 GCN 之间的性能差距(26%)完全来自图结构的贡献。这表明在 Cora 引文网络中,论文的引用关系对其分类有巨大的信息增益。

十七、图傅里叶变换的可视化理解

为了直观理解图傅里叶变换,考虑一个简单的路径图:

节点: 0 — 1 — 2 — 3 — 4 — 5 — 6 — 7 — 8 — 9

特征值 λ_1 (最小): 特征向量平滑变化,对应"低频信号"
[0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32, 0.32]

特征值 λ_5 (中频): 特征向量有若干次正负交替
[0.5, 0.4, 0.1, -0.3, -0.5, -0.5, -0.3, 0.1, 0.4, 0.5]

特征值 λ_10 (最大): 特征向量在相邻节点间急速震荡,"高频信号"
[0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5]

GCN 的低通滤波特性:由于使用了归一化的邻接矩阵 D^{-1/2} A D^{-1/2},GCN 本质上是一个低通滤波器。它保留了图的低频信号(社区结构、簇),而抑制了高频信号(孤立节点的噪声)。这也是 GCN 能有效进行节点分类的内在原因:同一社区中的节点倾向于有相同的标签,而低频信号正好捕捉了社区结构。

十八、SGC(Simplifying Graph Convolutional Networks)

Wu et al. (ICML 2019) 提出了一个激进的问题:GCN 的非线性变换真的必要吗?

他们去除了 GCN 中的所有 ReLU 激活和中间权重矩阵,只保留初始的图滤波步骤:

SGC: H = softmax( S^K X W )

其中 S = D^{-1/2} Ã D^{-1/2} 是归一化邻接矩阵,S^K 是在预处理阶段计算好的(不需要在训练中计算梯度)。

class SGC(nn.Module):
def __init__(self, in_feat, out_feat, K=2):
super().__init__()
self.lin = nn.Linear(in_feat, out_feat)
self.K = K

def forward(self, x, edge_index, edge_weight=None):
# 在预处理阶段计算好 S^K X
# 训练时只需要一次线性变换
x = self.precomputed_x
return F.log_softmax(self.lin(x), dim=1)

SGC 在多个基准上的性能与 GCN 相当,但速度快 100 倍以上。这表明:GCN 的大部分收益来自图结构的预处理(邻域聚合),而非深层的非线性变换。

这也引出了对深层 GNN 的重新思考:如果 2 层的 SGC 就足够,那为什么还需要更深?答案是:对于需要更大感受野的任务(如大图上的长程依赖、异质图中需要远距离传播信息的场景),深层 GNN 是必要的。但关键是通过残差连接、DropEdge、PairNorm 等技术来缓解过平滑。

十九、附录:关键数学公式速查表

对象 公式 维度
邻接矩阵 A N × N
度矩阵 D = diag(Σ_j A_{ij}) N × N
组合拉普拉斯 L = D - A N × N
对称归一化拉普拉斯 L_sym = I - D^{-1/2} A D^{-1/2} N × N
图傅里叶变换 x̂ = U^T x N
逆图傅里叶变换 x = U x̂ N
谱域图卷积 x * g = U g_θ(Λ) U^T x N
ChebNet H’ = Σ_{k=0}^{K-1} θ_k T_k(L̃) H N × d
GCN 传播 H’ = D̃^{-1/2} Ã D̃^{-1/2} H W N × d’
GraphSAGE (mean) h’v = σ(W·CONCAT(h_v, MEAN{u∈N(v)} h_u)) d’
GAT 注意力 α_ij = softmax(LeakyReLU(a^T [Wh_i || Wh_j])) 标量
GIN h’v = MLP((1+ε)h_v + Σ{u∈N(v)} h_u) d’

二十、总结

从图信号处理到 GCN,本文完整展示了一条优美的数学推导路径:

  1. 图拉普拉斯的谱分解定义了图上的”频率”概念
  2. 图傅里叶变换将图信号分解为不同频率分量
  3. 谱域图卷积 = 在傅里叶域中对每个频率分量做逐元素乘积
  4. ChebNet 用切比雪夫多项式避免特征分解,实现了局部化卷积
  5. GCN 将 ChebNet 简化到一阶 + 重归一化,使之实用且高效
  6. 空间域 GNN(GraphSAGE, GAT, GIN)从消息传递的角度重新诠释了图卷积

理解这条路径,才能真正理解 GCN 中那些看似神秘的归一化操作(D^{-1/2} A D^{-1/2})从何而来,以及为什么仅仅平均邻居的特征就能取得如此强大的性能。核心洞见:GCN 是一个图低通滤波器,它平滑了节点特征,使得同一社团内的节点表示趋于一致。在图同质性假设下(相连的节点倾向于有相同标签),这种平滑自然导向更好的分类性能。

二十一、补充:图的同质性与异质性

21.1 同质性(Homophily)

图的同质性是指”相似的节点倾向于相互连接”。社交网络中,”朋友往往有相似的兴趣”就是这个道理。同质性的数学度量:

H(G) = (1/|V|) Σ_{v∈V} (  |{u∈N(v) : y_u=y_v}| / |N(v)|  )

大多数真实世界图(引文网络、社交网络)具有高同质性(H > 0.5),GCN 在这些图上效果最好。

21.2 异质性(Heterophily)

某些图的连接模式是”不同类别的节点倾向于连接”(如约会网络、金融交易网络中的欺诈者-受害者关系)。GCN 在异质性图上表现不佳,因为低通滤波会混合不同类别的信息。

针对异质性图的 GNN 设计:

  • H2GCN:区分同质邻居和异质邻居,分别聚合
  • GPRGNN:使用广义 PageRank(允许负权重)替代纯低通滤波
  • FAGCN:学习每条边的正/负权重,实现高通+低通自适应滤波

21.3 图同质性实验

def compute_homophily(edge_index, y):
"""计算图的同质性指标"""
src, dst = edge_index[0], edge_index[1]
# 有同标签邻居的边占总边的比例
same_label = (y[src] == y[dst]).float().mean()
return same_label.item()

# Cora 的同质性约为 0.81(高度同质)
# Actor co-occurrence 网络的同质性约为 0.22(高度异质)
# GCN 在 Cora 上 81%,在 Actor 上仅 ~27%(不如 MLP 的 ~35%)

二十二、阅读论文推荐顺序

对于想要深入理解 GNN 的读者,推荐以下论文阅读路径:

  1. Kipf & Welling, ICLR 2017: Semi-Supervised Classification with Graph Convolutional Networks(GCN 原始论文,必读)
  2. Hamilton et al., NeurIPS 2017: Inductive Representation Learning on Large Graphs(GraphSAGE,邻居采样)
  3. Velickovic et al., ICLR 2018: Graph Attention Networks(GAT,注意力机制)
  4. Xu et al., ICLR 2019: How Powerful are Graph Neural Networks?(GIN,表达能力分析)
  5. Li et al., ICML 2018: Deeper Insights into Graph Convolutional Networks(过平滑分析)
  6. Rong et al., ICLR 2020: DropEdge: Towards Deep Graph Convolutional Networks(缓解过平滑)
  7. Chen et al., ICML 2020: Simple and Deep Graph Convolutional Networks(GCNII,极深 GCN)
  8. Wu et al., ICML 2019: Simplifying Graph Convolutional Networks(SGC,去除非线性)
  9. Klicpera et al., ICLR 2019: Diffusion-Convolutional Neural Networks(图扩散卷积统一框架)
  10. Brossard et al., NeurIPS 2020: Graph Convolutional Networks with Eigenpooling(特征池化)

二十三、GNN 实践 Checklist

在开始一个 GNN 项目之前,确认以下各项:

  • 图是同质性的还是异质性的?→ 选择合适的模型(GCN vs H2GCN)
  • 图规模多大?→ 小型用 GCN/GAT,大型用 GraphSAGE/ClusterGCN
  • 是归纳式还是直推式?→ 直推式用 GCN,归纳式用 GraphSAGE
  • 节点特征是否可用?→ 如否,先做 DeepWalk/Node2Vec 生成初始嵌入
  • 是否实现了 baseline(MLP + Logistic Regression)?→ 需要验证图结构确实在贡献信息
  • 实验是否复现?→ 固定随机种子,报告均值和标准差
  • 是否做了消融实验?→ 层数、宽度、dropout、聚合函数
  • 是否检查了过平滑?→ 如果加层后性能下降,可能是过平滑
  • 是否正确处理了孤立节点?→ 确保 mask 中包含这些节点
  • 训练/验证/测试集划分是否合理?→ 使用标准数据集划分或服从官方 benchmark 协议

## 二十四、总结

图信号处理为 GNN 提供了严密的数学基础。从图的傅里叶变换到谱域图卷积,从切比雪夫多项式近似到 GCN 的一阶简化,再到 GAT 的注意力机制和 GraphSAGE 的归纳式采样——每一步演化都为 GNN 增加了新的能力维度:

| 模型 | 核心贡献 | 适用场景 |
|------|----------|----------|
| Spectral CNN | 谱域图卷积的数学定义 | 理论基准 |
| ChebNet | K 阶多项式近似,局部化 | 中等规模图 |
| GCN | 一阶近似 + 重归一化技巧 | 直推式节点分类 |
| GAT | 注意力权重,异质性图能力 | 异质性图、归纳式 |
| GraphSAGE | 邻居采样 + 聚合函数 | 大规模归纳式图 |
| GIN | Weisfeiler-Lehman 同构测试 | 图分类 |
| SGC | 去除非线性,极简 GCN | 快速baseline |
| APPNP | PageRank 传播解耦 | 缓解过平滑 |
| GCNII | 初始残差 + 恒等映射 | 深层 GCN |
| ClusterGCN | 图聚类 + 子图 SGD | 超大规模图 |

图卷积的根本设计挑战永远不会变:**如何在保留图结构信息的同时,避免过平滑和过压缩?** 这一对张力驱动着 GNN 从 3 层到 64 层,从局部邻域到全局信息,从各向同性聚合到各向异性注意力。理解图信号处理的底层数学,是驾驭这些模型演化的钥匙。对于实践者而言,在着手新项目时,GCN + GAT + GraphSAGE 的组合覆盖了大多数场景,而本文的 Checklist 则提供了从理论到实践的完整桥接。

## 参考

- Shuman et al., "The Emerging Field of Signal Processing on Graphs", IEEE SPM 2013
- Defferrard et al., "Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering", NeurIPS 2016
- Kipf & Welling, "Semi-Supervised Classification with Graph Convolutional Networks", ICLR 2017
- Veličković et al., "Graph Attention Networks", ICLR 2018
- Hamilton et al., "Inductive Representation Learning on Large Graphs", NeurIPS 2017
- Xu et al., "How Powerful are Graph Neural Networks?", ICLR 2019
- Wu et al., "Simplifying Graph Convolutional Networks", ICML 2019
- Klicpera et al., "Predict then Propagate: Graph Neural Networks meet Personalized PageRank", ICLR 2019
- Chen et al., "Simple and Deep Graph Convolutional Networks", ICML 2020
- Chiang et al., "Cluster-GCN: An Efficient Algorithm for Training Deep and Large Graph Convolutional Networks", KDD 2019
文章作者: Leo·Cheung
文章链接: http://tufusi.com/2022/03/20/%E3%80%90GNN%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90%E3%80%91%E5%9B%BE%E4%BF%A1%E5%8F%B7%E5%A4%84%E7%90%86%E4%B8%8E%E5%9B%BE%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ONE·PIECE
打赏
  • 微信
  • 支付宝

评论