图读出:池化与图级任务
核心观点:节点任务和图任务不是一回事
这是图神经网络中最容易被忽视的认知误区之一。
当我们训练一个节点分类模型时,模型的输出是每个节点的表示向量
但当我们做图分类时,模型的输出是一个图的整体表示
两者数学上的根本区别在于:
| 节点任务 | 图任务 | |
|---|---|---|
| 输入 | 单个节点 | 整张图 |
| 输出 | 节点标签 | 图标签 |
| 信息聚合 | 邻居 | 节点 |
| 不变性要求 | 无特殊要求 | 必须满足置换不变性 |
节点任务处理的是局部结构,图任务处理的是全局结构。 节点分类只需要看到节点的邻居图就够了,图分类则需要看到整张图的拓扑特征。
1. 图级任务的定义
1.1 Graph Classification(图分类)
给定一张图
典型应用场景:
- 分子性质预测:每个分子可以建模为图,原子为节点,化学键为边。判断分子是否有某种毒性、是否可溶性。百康(Pfizer)的药物筛选就用 GNN 做图分类。
- 蛋白质功能预测:蛋白质结构
图,判断蛋白质属于哪一类功能家族。 - 社交网络类型识别:一个在线社区的社交网络图,判断其属于哪类社区(粉丝群、代购群、兴趣群)。
- 代码语义分类:抽象语法树(AST)
图,判断代码片段的语义类别。
1.2 Graph Regression(图回归)
给定一张图
典型应用场景:
- 分子属性预测:预测分子的溶解度(logP)、药物分子的半数有效浓度(IC50)、原子化能。
- 材料科学:预测某种晶体结构的稳定性、-band gap。
- 药物设计:预测化合物与靶点的结合亲和力(binding affinity)。
1.3 Graph Similarity(图相似度)
衡量两张图之间的相似程度,不用于直接分类/回归,而是作为下游任务的一部分。
典型应用:
- 图匹配:药物分子对接时比较分子图的相似性。
- 图检索:在大型分子库中检索与给定query分子结构相似的分子。
- 度量学习:学习一个图嵌入空间,使得相似图距离近、不相似图距离远。
2. Readout Function:从节点到图的数学推导
2.1 形式化定义
设经过
其中
2.2 置换不变性的数学要求
关键约束:对于任意排列
这就是 Permutation Invariance(置换不变性)。
为什么必须保持这个性质?因为图没有天然的节点顺序。同一个图,用邻接矩阵的第 2 行描述还是第 5 行描述,在数学上是完全等价的。任何依赖于节点顺序的操作都会导致同一个图得到不同的表示,这在图任务是灾难性的。
2.3 Sum / Mean / Max Pooling 的数学差异
这是三种最基础的置换不变性 Readout 函数。
Sum Pooling
数学性质:
- 线性,满足叠加原理
- 对节点数量 敏感——节点数量翻倍,图表示的模长翻倍
- 保留所有节点的总量信息
Mean Pooling(Average Pooling)
数学性质:
- 对节点数量 归一化——不受图大小影响
- 不保留节点的总量信息——一个节点和一百个节点的贡献是一样的
- 反映节点的平均特征
Max Pooling
数学性质:
- 非线性(逐元素取最大值)
- 对节点数量 不敏感
- 保留节点的极值信息——每个维度取最突出的那个节点的贡献
- 选择性强:只保留”最显著”的信号,忽略其他节点
2.4 三种基础 pooling 的直观对比
| pooling | 公式 | 对图大小的敏感性 | 信息类型 | 适用场景 |
|---|---|---|---|---|
| Sum | 敏感(随节点数线性增长) | 总量信息 | 节点数固定的场景,或特征本身就包含归一化信息 | |
| Mean | 不敏感 | 平均信息 | 需要消除图大小影响的场景,如分子性质预测 | |
| Max | 不敏感 | 极值信息 | 需要捕捉”是否有某种特征”的场景 |
2.5 为什么单纯节点平均不一定能表达图结构
Mean Pooling 看似公平——把每个节点的贡献平等对待。但它有一个致命的缺陷:丢失了图的结构信息。
考虑以下两个例子:
例1:完全连通图(Clique)vs 链状图(Chain)
设所有节点特征相同
- 完全连通图(
个节点): , , - 链状图(
个节点): , ,
当节点特征完全相同时,三种 pooling 结果相同,完全无法区分完全连通图和链状图。
例2:两个子结构A和B各占一半节点
- 如果 A 类节点特征是
,B 类节点特征是 - Mean Pooling 得到
,看起来两类均匀混合 - 但如果问题是”图中是否存在子结构 A”,Mean Pooling 的均值化会掩盖这个信号
- 换成 Max Pooling,如果 A 类节点的某个维度突出,Max 会捕捉到这个信号
根本问题:Mean Pooling 是一个”平均主义”的操作,它假设每个节点同等重要。但如果图的类别由少数关键子结构决定,平均化会稀释关键信号。
3. 层次池化(Hierarchical Pooling)与子图抽取
3.1 为什么需要层次池化
单次 Readout(如简单 Mean Pooling)面临两个问题:
- 结构信息丢失:无法区分拓扑结构不同的图(如同上节的 Clique vs Chain)
- 信息压缩损失:将整个图压缩成一个向量,早期层的节点信息可能没有得到充分利用
层次池化的思路是逐步 coarsening(图粗化):先把图压缩,再 Readout。
3.2 层次池化的数学框架
设第
- 节点选择/重要性计算:为每个节点打分
- 节点聚类/分配:将节点分配到
个簇中,得到分配矩阵 - 图粗化:基于
构建粗化图 - 表示更新:计算新节点的表示
数学上:
其中
3.3 常见层次池化方法
DiffPool(2019,NeurIPS)
层次池化的经典方法。核心思想是学习一个软分配矩阵:
每一层将图粗化到原来的约 50%。与 GNN 结合形成双线性更新:
- 池化分支:学习簇分配
- GNN 分支:更新节点表示
优点:端到端可学习,产生层次化结构
缺点:生成
SAGPool(2019,ICLR)
基于自注意力的层次池化。使用 GAT 风格的注意力:
通过 mask 操作选取 top-
ASAP(Adaptive Structure-aware Pooling)
结合了注意力机制和层次池化,将节点分配到不同簇中,用注意力权重作为软分配。
Graclus(谱方法)
基于谱聚类的粗化方法,通过特征值分解找到最优的图粗化方案。数学上求解:
3.4 子图抽取的意义
层次池化的本质是子图抽取:将图划分成若干子图,每个子图粗化成一个新节点。
为什么子图抽取很重要?
- 保留局部结构:每个粗化节点代表一个子结构,子结构内部信息被保留(如分子中的官能团)
- 多尺度视角:不同层的池化对应不同粒度的子结构
- 层次化语义:底层的节点代表小范围子结构,高层的节点代表大范围子结构
在分子图中,每个子结构对应一个化学功能团(如苯环、羧基),这些功能团决定了分子的化学性质。层次池化让模型能够识别这些子结构。
4. 图级任务中全局结构信息的重要性
4.1 局部 vs 全局
节点级任务(如节点分类)主要依赖局部结构——节点的邻居、邻居的邻居。在小 receptive field 内就能获得足够信息。
图级任务则需要全局结构信息:
- 拓扑模式:是否存在某种全局连接模式(如小世界性质、稠密子图)
- 子结构分布:某种子结构在图中出现了多少次(如分子中苯环的个数)
- 图的大小和密度:图有多少节点、多少边,密度如何
- 路径信息:两个关键节点之间是否存在路径(如药物靶点间的信号通路)
这些信息在单次 Mean Pooling 中被严重压缩甚至丢失。
4.2 一个具体的例子
考虑分子性质预测中的溶解度预测:
溶解度(logS)是一个连续值,取决于分子整体的极性表面积、氢键供体/受体数量、分子量等全局性质。
如果只用 Mean Pooling:
- 每个原子平均贡献,看似合理
但实际上:
- 分子的极性主要由含氧/氮官能团贡献,羟基(-OH)和氨基(-NH₂)的表示应该被重点关注
- 这些官能团在分子中可能只有 2-3 个节点,用 Mean Pooling 会被其他100个碳原子稀释
- Max Pooling 在捕捉”是否存在含氧官能团”上比 Mean Pooling 更有效
- 更好的做法是用 Attention Pooling,让模型学习关注哪些节点
5. 工程流程:图级任务实践
5.1 从 Batch 中构造多个图
在节点分类中,一个 batch 通常包含一个或几个图的节点(邻居采样后的子图)。
在图分类中,一个 batch 包含多张独立的图。
典型的 DataLoader 构造方式:
from torch_geometric.loader import DataLoader
# 每个 Data 对象是一张完整的图
dataset = MoleculeDataset(root='data/', name='Molecule')
loader = DataLoader(dataset, batch_size=32, shuffle=True,
num_workers=4)PyG 的 batch 对象有两个关键属性:
batch[i]:返回节点所属的图的索引 batch.unique():返回当前 batch 中的图索引列表
5.2 图大小差异很大时的处理策略
不同图的节点数差异可能非常大(如一个分子只有 10 个原子,另一个有 1000 个原子)。
| 策略 | 方法 | 优缺点 |
|---|---|---|
| 节点采样 | 对大图随机采样固定数量的节点 | 简单,但可能丢失关键节点 |
| 邻居采样 | 在 GNN 层中使用邻居采样 | 控制每层的计算量,对大图友好 |
| 虚拟节点 | 添加一个连接所有节点的虚拟节点 | 简单,但增加 |
| 分层池化降采样 | 通过层次池化逐步压缩大图 | 结构保留较好,计算开销大 |
| 动态批大小 | 根据节点数动态调整 batch size | 内存利用率高,实现复杂 |
5.3 图级标签稀缺时的训练策略
图级任务的一个核心挑战是标签稀缺。不像节点分类可以利用半监督学习(图中有大量无标签节点),图分类的每张图都需要标签。
| 策略 | 说明 |
|---|---|
| 数据增强 | 对图做随机子图采样、节点 dropout、边 dropout |
| 正则化 | Dropout、Label smoothing |
| Transfer Learning | 在大规模无监督预训练的图上微调(如 GraphCL、InfoGraph) |
| 自监督预训练 | 用对比学习预训练节点表示,再做图级分类 |
| 多任务学习 | 联合训练节点级和图级任务,让节点表示同时被两种 loss 监督 |
| 集成学习 | 训练多个模型并集成,降低方差 |
6. 读出层与分类头的连接方式
6.1 基本架构
节点特征 → L层 GNN → Readout → MLP 分类头 → 类别/回归值
一个典型的图分类模型:
class GraphClassifier(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels):
super().__init__()
self.gnn = GCN(in_channels, hidden_channels, num_layers=3)
self.readout = SumPooling() # 或 MeanPooling / AttentionPooling
self.classifier = nn.Sequential(
nn.Linear(hidden_channels, hidden_channels),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(hidden_channels, out_channels)
)
def forward(self, data):
x, edge_index = data.x, data.edge_index
x = self.gnn(x, edge_index)
x = self.readout(x, data.batch) # 按 batch 聚合
return self.classifier(x)6.2 Readout 与 Batch 的配合
PyG 中 DataLoader 会将多个图合并成一个大图,batch 数组标记每个节点属于哪张图:
data.batch[i] = b:节点属于第 张图 readout(x, data.batch):对每张图分别做 readout,得到 batch 中所有图的表示
6.3 读出层与分类头的连接方式
方式一:单纯 Readout
最简单的方式,直接对节点表示做 Readout:
适用于图大小相近、节点重要性均匀的场景。
方式二:层次池化 + Readout
通过多层池化得到图的层次表示,再在最顶层做 Readout:
适用于复杂拓扑结构的图分类任务。
方式三:层级注意力聚合
使用层级注意力(Hierarchical Attention),不同层次赋予不同权重:
方式四:Readout + 图级辅助任务
在 Readout 之外,增加图级自监督任务(如重建图的某些全局属性)作为辅助信号:
# 多任务学习示例
def forward(self, data):
x = self.gnn(data.x, data.edge_index)
graph_rep = self.readout(x, data.batch)
main_out = self.classifier(graph_rep) # 主任务:图分类
aux_out = self.aux_head(graph_rep) # 辅助任务:图重构
return main_out, aux_out7. 常见图池化方法总结
| 方法 | 年份 | 核心思路 | 置换不变性 | 计算代价 |
|---|---|---|---|---|
| Sum Pooling | - | 是 | ||
| Mean Pooling | - | 是 | ||
| Max Pooling | - | 是 | ||
| Attention Pooling | 2017 | 是 | ||
| Set Transformer | 2019 | 使用 Self-Attention 模拟置换不变聚合 | 是 | |
| DiffPool | 2019 | 可学习的层次粗化,软分配矩阵 | 是 | |
| SAGPool | 2019 | 自注意力 + top- | 是 | |
| ASAP | 2020 | 结构感知 + 注意力池化 | 是 | |
| EdgePool | 2020 | 基于边强度的图粗化 | 是 | |
| Graclus | - | 谱聚类粗化 | 是 | 谱分解开销大 |
8. 图级任务建模建议表
| 场景 | 推荐 Readout 方式 | 关键考量 |
|---|---|---|
| 分子性质预测(连续值) | Attention Pooling 或层次池化 + MLP | 需要关注特定官能团,避免均值稀释 |
| 分子图分类(类别少,数据充足) | DiffPool 或 SAGPool | 层次结构能捕捉子结构信息 |
| 社交网络分类 | Mean Pooling + MLP | 图大小归一化,拓扑信息相对均匀 |
| 蛋白质功能分类 | 层次池化 + 层级注意力 | 结构复杂,需要多尺度信息 |
| 图相似度学习 | Graph Embedding + Readout | 学习置换等变的图嵌入 |
| 标签极稀缺 | Transfer Learning / 自监督预训练 + 简单 Readout | 避免过拟合,依赖预训练表示 |
9. 总结:为什么图级任务比节点级任务更复杂
- 信息聚合的不可逆性:节点
图的聚合是单向的,一旦压缩就无法完全恢复。节点任务中的信息流动是可逆的(有 skip connection 等),图 Readout 则是彻底的压缩。 - 置换不变性约束:图没有节点顺序,所有操作必须满足置换不变性,这限制了模型的设计空间。
- 全局 vs 局部:图级任务需要捕捉全局拓扑信息,而不仅仅是局部结构。这意味着需要更好的 Readout 机制。
- 标签稀缺:图级任务的标签获取成本高(需要实验测定分子性质),远不如节点级任务容易获取。
- 图大小差异:节点级任务中节点数量相对固定(图内部),图级任务中不同图之间大小差异巨大,需要特殊处理。
记住:节点任务做得好,不代表图任务做得好。 图级任务需要专门设计的 Readout 和池化策略,而不是简单地把节点表示做平均。