节点分类给每个节点预测标签;图分类则要给整张图输出结果。消息传递之后,还需要把可变数量的节点表示汇成图级表示,这一步通常称为 Readout。

批处理不等于把图混在一起

一个批次可以装多张独立图,但池化时必须保留每个节点所属的图。对所有节点直接求一个平均,会把多张图合成一个样本。PyG 的全局平均池化使用批次归属向量,分别计算各图均值。global_mean_pool 定义

下面用 NumPy 检查图归属、节点置换和池化形状,不涉及任何分子训练数据。

import numpy as np

def mean_pool(features, graph_ids, graph_count):
    sums = np.zeros((graph_count, features.shape[1]))
    counts = np.zeros(graph_count, dtype=int)
    np.add.at(sums, graph_ids, features)
    np.add.at(counts, graph_ids, 1)
    if (counts == 0).any():
        raise ValueError("empty graphs need an explicit policy")
    return sums / counts[:, None]

x = np.array([[1., 2.], [3., 4.], [10., 8.]])
batch = np.array([0, 0, 1])
pooled = mean_pool(x, batch, 2)
np.testing.assert_allclose(pooled, [[2., 3.], [10., 8.]])
order = [2, 0, 1]
np.testing.assert_allclose(mean_pool(x[order], batch[order], 2), pooled)

assert sum([1, 1, 2]) == sum([2, 2])
assert sum([1, 1, 2]) != sum([1, 1, 2, 1])
print("graph membership, permutation and sum-collision checks passed")

Sum保留计数线索,不代表永远不会碰撞

Mean 对图规模变化相对不敏感;Sum 可能保留有价值的数量线索,也可能受规模影响。应结合任务判断,必要时另加图大小等特征,不能靠一个池化名字保证表达力。

上例中 [1,1,2] 和 [2,2] 都求和为 4,所以不能从一个普通标量总和唯一还原邻居多重集。GIN 的表达力结论依赖其理论条件下的映射、聚合和读出设计,不是说任何原始特征直接求和都变成单射,也不是说所有图都可区分。GIN 原论文

实现细节也要核对:PyG 的 GINConv 默认 train_eps=False,所以 epsilon 并非默认可学习参数。GINConv 官方说明

图级评估的边界

数据划分的单位应与实际泛化问题一致。多张图来自同一对象、相似分子骨架或相同来源时,简单随机划分可能高估泛化能力。训练集拟合预处理、验证集选择配置、测试集最终报告这条边界仍然成立。

小数据集单次划分的分数容易波动。应记录划分、随机种子和置信范围;不能把讲义中的示例成绩当成本次运行结果。具体数据集的标签含义也必须回到说明核对,不能把致突变性等特定标签泛化成“任意毒性”。

来源与核验边界

本篇依据疏锦行图神经网络专题《图分类:图级特征提取》讲义,重点重写 Readout 与 GIN 表达力的边界。示例已运行;MUTAG 和 GIN 训练未复现。视频口头内容尚未逐节核验,原课件不公开。