图读出:池化与图级任务

核心观点:节点任务和图任务不是一回事

这是图神经网络中最容易被忽视的认知误区之一。

当我们训练一个节点分类模型时,模型的输出是每个节点的表示向量 。我们关心的是”这个节点属于哪一类”,信息流动发生在节点与邻居之间。

但当我们做图分类时,模型的输出是一个图的整体表示 。我们关心的是”这张图的整体特征是什么”。节点级别的信息必须被压缩、聚合、抽象成图级别的表示,这个过程叫 Readout(读出)或 Pooling(池化)。

两者数学上的根本区别在于:

节点任务图任务
输入单个节点 的特征 整张图
输出节点标签 图标签
信息聚合邻居 节点(Message Passing)节点 图(Readout)
不变性要求无特殊要求必须满足置换不变性

节点任务处理的是局部结构,图任务处理的是全局结构。 节点分类只需要看到节点的邻居图就够了,图分类则需要看到整张图的拓扑特征。


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 形式化定义

设经过 层 GNN 后,每个节点 获得了表示向量 。Readout 函数 的目标是:

其中 是图的表示向量。

2.2 置换不变性的数学要求

关键约束:对于任意排列 (对节点重新编号),Readout 函数必须满足:

这就是 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)面临两个问题:

  1. 结构信息丢失:无法区分拓扑结构不同的图(如同上节的 Clique vs Chain)
  2. 信息压缩损失:将整个图压缩成一个向量,早期层的节点信息可能没有得到充分利用

层次池化的思路是逐步 coarsening(图粗化):先把图压缩,再 Readout。

3.2 层次池化的数学框架

设第 层池化将 个节点压缩为 个节点:

  1. 节点选择/重要性计算:为每个节点打分
  2. 节点聚类/分配:将节点分配到 个簇中,得到分配矩阵
  3. 图粗化:基于 构建粗化图
  4. 表示更新:计算新节点的表示

数学上:

其中 是分配矩阵, 是投影矩阵。

3.3 常见层次池化方法

DiffPool(2019,NeurIPS)

层次池化的经典方法。核心思想是学习一个软分配矩阵

每一层将图粗化到原来的约 50%。与 GNN 结合形成双线性更新:

  • 池化分支:学习簇分配
  • GNN 分支:更新节点表示

优点:端到端可学习,产生层次化结构
缺点:生成 的分配矩阵,内存开销大;需要监督信号(如多任务学习)

SAGPool(2019,ICLR)

基于自注意力的层次池化。使用 GAT 风格的注意力:

通过 mask 操作选取 top- 个重要节点。优点是计算效率高,不需要生成 矩阵。

ASAP(Adaptive Structure-aware Pooling)

结合了注意力机制和层次池化,将节点分配到不同簇中,用注意力权重作为软分配。

Graclus(谱方法)

基于谱聚类的粗化方法,通过特征值分解找到最优的图粗化方案。数学上求解:

3.4 子图抽取的意义

层次池化的本质是子图抽取:将图划分成若干子图,每个子图粗化成一个新节点。

为什么子图抽取很重要?

  1. 保留局部结构:每个粗化节点代表一个子结构,子结构内部信息被保留(如分子中的官能团)
  2. 多尺度视角:不同层的池化对应不同粒度的子结构
  3. 层次化语义:底层的节点代表小范围子结构,高层的节点代表大范围子结构

在分子图中,每个子结构对应一个化学功能团(如苯环、羧基),这些功能团决定了分子的化学性质。层次池化让模型能够识别这些子结构。


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_out

7. 常见图池化方法总结

方法年份核心思路置换不变性计算代价
Sum Pooling-
Mean Pooling-
Max Pooling-
Attention Pooling2017,
Set Transformer2019使用 Self-Attention 模拟置换不变聚合
DiffPool2019可学习的层次粗化,软分配矩阵
SAGPool2019自注意力 + top- 选择
ASAP2020结构感知 + 注意力池化
EdgePool2020基于边强度的图粗化
Graclus-谱聚类粗化谱分解开销大

8. 图级任务建模建议表

场景推荐 Readout 方式关键考量
分子性质预测(连续值)Attention Pooling 或层次池化 + MLP需要关注特定官能团,避免均值稀释
分子图分类(类别少,数据充足)DiffPool 或 SAGPool层次结构能捕捉子结构信息
社交网络分类Mean Pooling + MLP图大小归一化,拓扑信息相对均匀
蛋白质功能分类层次池化 + 层级注意力结构复杂,需要多尺度信息
图相似度学习Graph Embedding + Readout学习置换等变的图嵌入
标签极稀缺Transfer Learning / 自监督预训练 + 简单 Readout避免过拟合,依赖预训练表示

9. 总结:为什么图级任务比节点级任务更复杂

  1. 信息聚合的不可逆性:节点 图的聚合是单向的,一旦压缩就无法完全恢复。节点任务中的信息流动是可逆的(有 skip connection 等),图 Readout 则是彻底的压缩。
  2. 置换不变性约束:图没有节点顺序,所有操作必须满足置换不变性,这限制了模型的设计空间。
  3. 全局 vs 局部:图级任务需要捕捉全局拓扑信息,而不仅仅是局部结构。这意味着需要更好的 Readout 机制。
  4. 标签稀缺:图级任务的标签获取成本高(需要实验测定分子性质),远不如节点级任务容易获取。
  5. 图大小差异:节点级任务中节点数量相对固定(图内部),图级任务中不同图之间大小差异巨大,需要特殊处理。

记住:节点任务做得好,不代表图任务做得好。 图级任务需要专门设计的 Readout 和池化策略,而不是简单地把节点表示做平均。