图神经网络(GNN)的论文看起来多且杂:GCN、GraphSAGE、GAT、GIN、PNA……名字五花八门。但它们绝大多数可以装进同一个框架里——消息传递(Message Passing)。理解了这个框架,再看新模型基本就是"换了个聚合函数"或者"换了个权重"。
一、为什么图数据特殊
先说清楚图数据为什么不能直接套 CNN 或 RNN:
| 性质 | 含义 | 带来的麻烦 |
|---|---|---|
| 排列不变性 | 节点没有天然顺序 | 换个编号,结果应该不变 |
| 变长邻居 | 每个节点邻居数不同 | 没法像图像那样用固定尺寸卷积核 |
| 无网格结构 | 没有规则的空间邻接 | 平移不变性不再成立 |
所以 GNN 的核心思路是:让每个节点反复"收集邻居的信息"来更新自己。这个思路和 CNN 的局部感受野其实一脉相承,只不过从规则网格推广到了任意拓扑。
二、消息传递框架
第 k 层对节点 v 的更新可以统一写成:
消息: m_v^k = AGGREGATE^k( { h_u^{k-1} : u ∈ N(v) } )
更新: h_v^k = UPDATE^k( h_v^{k-1}, m_v^k )
其中 N(v) 是 v 的邻居集合。
几个关键观察:
- AGGREGATE 必须排列不变,通常用 sum / mean / max。否则节点编号一变结果就变。
- 层数 = 感受野半径。k 层 GNN 能"看到" k 跳以内的邻居。这是理解过平滑的关键。
- 节点自己的信息通过 UPDATE 保留(或者把 v 自己加进邻居集合)。
三、三个经典模型
GCN:谱域推导出来的均值聚合
Kipf & Welling (2017) 从图傅里叶变换出发做了一阶近似,最终形式异常简洁:
H' = σ( D̃^{-1/2} Ã D̃^{-1/2} H W )
其中 Ã = A + I(邻接矩阵加自环),D̃ 是 Ã 的度矩阵。
那个对称归一化 D^{-1/2} A D^{-1/2} 不是随便加的:它防止高度数节点的特征数值爆炸。如果不归一化,多层堆叠后高度节点的特征值会远超其他节点,训练极不稳定。
import torch
import torch.nn as nn
class GCNLayer(nn.Module):
def __init__(self, in_dim, out_dim):
super().__init__()
self.linear = nn.Linear(in_dim, out_dim)
def forward(self, h, adj):
# adj: 带自环并已对称归一化的邻接矩阵
return torch.relu(self.linear(adj @ h))
GraphSAGE:采样 + 更灵活的聚合
Hamilton et al. (2017) 解决的核心问题是大规模图的扩展性。两个创新:
1. 邻居采样。 不再用完整邻接矩阵,每层只随机采固定数量邻居:
h_v^k = σ( W^k · CONCAT( h_v^{k-1}, AGG({ h_u^{k-1} : u ∈ S(v) }) ) )
其中 S(v) 是采样出的邻居子集(比如每层 25 个)。这让 mini-batch 训练成为可能,也支撑了 inductive 学习——可以泛化到训练时没见过的节点。
2. 可替换的聚合器。 论文给了三种:
Mean:逐元素求均值(类似 GCN)LSTM:把邻居随机排列后过 LSTM,表达力强但破坏了排列不变性(实践中有争议)Pooling:每个邻居过一层 MLP 后取 max/mean
class SAGELayer(nn.Module):
def __init__(self, in_dim, out_dim, aggregator='mean'):
super().__init__()
self.linear_self = nn.Linear(in_dim, out_dim)
self.linear_neigh = nn.Linear(in_dim, out_dim)
self.aggregator = aggregator
def forward(self, h, neighbors):
# neighbors: list of 邻居索引列表(已采样)
neigh_h = torch.stack([
h[n].mean(0) if len(n) > 0 else torch.zeros(h.size(1))
for n in neighbors
])
return torch.relu(self.linear_self(h) + self.linear_neigh(neigh_h))
GAT:让注意力决定邻居权重
Veličković et al. (2018) 认为"不同邻居的重要性本来就不一样",引入注意力:
e_ij = LeakyReLU( a^T [ W h_i ‖ W h_j ] )
α_ij = softmax_j(e_ij) = exp(e_ij) / Σ_{k∈N(i)} exp(e_ik)
h_i' = σ( Σ_{j∈N(i)} α_ij W h_j )
配合多头注意力(类似 Transformer),把 8 个头的输出拼接或平均。
GAT 的好处是可解释性更强——注意力权重能告诉你模型"在看哪些邻居"。代价是计算量和显存都更高,而且注意力头数多了容易过拟合。
四、三个模型怎么选
| 模型 | 聚合方式 | 适合场景 |
|---|---|---|
| GCN | 固定归一化均值 | 小图、全图训练、基线首选 |
| GraphSAGE | 采样 + 可换聚合器 | 大图、mini-batch、需要 inductive |
| GAT | 注意力加权 | 邻居重要性差异大、需要可解释性 |
实践经验:先用 GCN 建立基线。很多任务上 GCN 和花哨模型的差距,没有调超参、加残差、换归一化带来的提升大。
五、过平滑:GNN 的层数陷阱
这是个绕不开的问题。
因为每层聚合都在做局部平均,层数越多,节点表示越趋于一致。极端情况下,所有节点的表示收敛到同一个向量,图结构信息完全丢失,节点变得不可区分——这就是过平滑(Over-smoothing)。
直接后果:2~3 层的 GNN 往往比 8 层的效果更好。这和 CNN 越深越强的直觉完全相反。
常用的缓解手段:
- 残差连接 ——
h^k = h^{k-1} + f(h^{k-1}),最有效也最常用 - 初始残差(APPNP 思路) —— 每层都把初始特征拉回来一点:
h^k = (1-α)·Âh^{k-1} + α·h^0 - 归一化层 —— PairNorm、BatchNorm 等,限制表示坍缩
- 别堆太深 —— 先问自己:这个任务真的需要 8 跳信息吗?
六、一份务实的清单
刚上手 GNN 时值得记住的几条:
- 邻接矩阵一定要加自环,否则节点会"忘记自己"
- 归一化很重要,尤其是度数分布长尾的图
- 层数先从 2 开始,效果不好再考虑加,同时配残差
- 任务类型决定评估方式:节点分类、边预测、图分类的 loss 和切分方式完全不同
- PyTorch Geometric 能省大量时间,但理解底层原理后再用,不然 debug 会很痛苦
小结
消息传递是个很强的统一视角。GCN 是均值聚合,GraphSAGE 是采样 + 可换聚合器,GAT 是注意力加权——本质差异就在 AGGREGATE 和权重怎么定。
抓住"层数 = 感受野"和"聚合 = 局部平滑"这两点,大部分 GNN 论文的核心动机就能猜个八九不离十。
愿你的 loss 单调下降,注意力权重都能看懂。
参考文献
- Gilmer, J., et al. (2017). Neural Message Passing for Quantum Chemistry. arXiv:1704.01212. https://arxiv.org/abs/1704.01212
- Kipf, T. N., & Welling, M. (2017). Semi-Supervised Classification with Graph Convolutional Networks. arXiv:1609.02907. https://arxiv.org/abs/1609.02907
- Hamilton, W., Ying, Z., & Leskovec, J. (2017). Inductive Representation Learning on Large Graphs. arXiv:1706.02216. https://arxiv.org/abs/1706.02216
- Veličković, P., et al. (2018). Graph Attention Networks. arXiv:1710.10903. https://arxiv.org/abs/1710.10903
- Xu, K., et al. (2019). How Powerful are Graph Neural Networks? arXiv:1810.00826. https://arxiv.org/abs/1810.00826
- Gasteiger, J., Bojchevski, A., & Günnemann, S. (2019). Predict then Propagate: Graph Neural Networks meet Personalized PageRank. arXiv:1810.05997. https://arxiv.org/abs/1810.05997
