新学期开始,班主任小林接到一个任务:快速判断新接手的每个班级是“优秀班”还是“普通班”。她拿到的不是成绩单,而是一张班级人际关系图——每个学生是一个圆点,学生之间谁和谁是好朋友就连一条线。小林逐个找学生谈话,再听他们聊起朋友的情况,慢慢掌握每个人的状态;接着把全班几十个人的信息汇总成一份班级总结,最后对照学校的评定标准给出结论。整个过程,就是人工智能里“图分类”的缩影:输入一整张图,输出一个类别标签。图分类被广泛用在分子毒性预测、蛋白质功能分类、程序图分析等真实工作中,本篇我们就沿着小林的评估流程,把图神经网络、图池化、分类器和数据增强这些概念一一讲透。
图分类是图神经网络最核心的应用之一。掌握从节点特征到图级特征的完整流程,你就能直接上手分子属性预测、蛋白质分类等真实业务,这是成为图学习方向工程师的必修课。
可视化演示
展示SSTF和FCFS两种磁盘调度算法的磁头移动过程,逐步演示磁头如何在各柱面间移动并计算总寻道距离。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+caps.sstf+'
图分类初识
小林站在教室门口,手里拿着一张画满圆点和连线的纸。这份“班级人际关系图”里,每个圆点是一个学生,每条线是一对好朋友。她要在不看成绩单的前提下,判断这个班级整体是“优秀班”还是“普通班”。这就是图分类的直观画面:输入一整张图,输出一个类别标签,而不是给某个学生单独下结论。传统机器学习处理的是“一行数据一个样本”,而图分类的样本是“一整张关系网”。
在数学上,一张图写作 $\mathcal{G}=(\mathcal{V},\mathcal{E})$,其中 $\mathcal{V}$ 是节点的集合(学生),$\mathcal{E}$ 是边的集合(朋友关系)。图分类的任务,就是学习一个映射函数,把整张图映射到有限的类别集合上,比如分子是否有毒(有毒/无毒)、蛋白质承担什么功能(催化/运输/免疫等)。它与我们熟悉的节点分类有本质区别:节点分类是评定“某个学生”的好坏,图分类是评定“整个班级”的风气;前者只看局部,后者必须把握全局。
对分子而言,节点是原子,边是化学键;对班级而言,节点是学生,边是朋友关系。小林看懂了这张图,但她不可能一个个记住所有学生——她需要一套系统的方法,先从每个学生入手,再整合成全班的判断。这套方法的第一环,就是图编码器 GNN。
- 图结构图由节点集和边集组成,$\mathcal{V}$ 存放所有原子(学生),$\mathcal{E}$ 记录它们之间的连接关系(化学键、好友关系)。
- 映射记号箭头表示“输入图,输出标签”,$\rightarrow$ 左边是输入,右边是输出空间。
- 类别空间大括号里有 $C$ 个取值,$1,2,\dots,C$ 代表不同的类别编号,例如 $0$=无毒、$1$=有毒。
图编码器GNN
小林开始逐个找学生谈话。她发现一个聪明的办法:不只问学生本人的情况,还要问“你和谁最要好?他们是什么样的人?”朋友勤奋,他多半也受感染;朋友散漫,他多少会被影响。于是每谈完一个学生,她就在笔记本上更新一次对这位学生的认识——这轮谈话中获得的“朋友的情报”,会融入下一位学生的档案里。在 GNN(图神经网络)中,这个动作叫消息传递。
消息传递是 GNN 的灵魂:每一层网络让每个节点“收集”邻居节点的特征,再和自己的特征融合,生成新的节点表征。就像小林组织全班讨论:第一轮,每个学生只了解同桌;第二轮,通过同桌的了解延伸到朋友的朋友;若干轮之后,每个学生的档案里其实已经包含了整个班级的间接信息。这些携带全局信息的节点特征,就是后续图分类的基础。
经过 $K$ 轮消息传递,每个节点都拥有了包含全局视野的表征。但对小林来说,她要评定的是整个班级,而不是某几个学生。几十份学生档案摊在桌上,怎么浓缩成一句话?这正是下一节“图池化”要做的事。
- 节点表征$\mathbf{h}_v^{(k)}$ 是节点 $v$ 在第 $k$ 轮更新后的特征向量,可以理解为小林笔记本上关于学生 $v$ 的“最新档案记录”。
- 邻居集合$\mathcal{N}(v)$ 是节点 $v$ 的所有邻居,求和符号 $\sum$ 表示把这些邻居的档案逐项加起来,相当于小林综合了所有朋友的情报。
- 线性变换$\mathbf{W}^{(k)}$ 是第 $k$ 层可学习的权重矩阵,它决定“哪些情报更重要”,就像小林判断“乐天派的话可信、暴躁鬼的话要打折扣”。
- 激活函数$\sigma$ 是激活函数(如 ReLU),把加权结果压缩到合理范围,防止数值无限膨胀,相当于在档案上盖一个“规范化”的章。
图池化机制
谈话结束了,小林面对厚厚一摞学生档案,却犯了难:评“优秀班”需要一个整体结论,不能把几十份档案直接堆给校长。她灵机一动,把每份档案里的关键信息摘出来,按“学习态度、互助氛围、纪律表现”几个维度做平均,最后汇成一张一页纸的《班级总结报告》。这个“从几十个学生信息浓缩成一个图级向量”的操作,就是图池化(也叫 Readout)。
最常用的池化是平均池化:把所有节点的表征逐维度求平均,得到一个固定长度的向量。此外还有求和池化(适合保留“总量”,比如分子总原子数影响毒性)和最大池化(适合保留“最突出的信号”,比如某个异常原子)。进阶方法还有注意力池化(让模型自己决定哪些节点更重要)和层次池化(像合并同类项一样逐步把班级分成小组再汇总)。它们的共同目标是:把变长数量的节点特征,压缩成固定长度的图级特征。
班级总结报告出炉,小林接下来要做的就是对照学校的评定标准给出结论。这套“评定标准”从哪来?靠数据学出来——这就是分类器的职责。
- 图级向量$\mathbf{h}_{\mathcal{G}}$ 是代表整张图的向量,相当于那份《班级总结报告》,它不关心班级有 30 人还是 50 人,输出维度始终固定。
- 求和取平均$\frac{1}{|\mathcal{V}|}\sum_{v\in\mathcal{V}}$ 先对所有节点的表征求和,再除以节点总数,得到“平均水平”,就像把全班成绩加总再除以人数得到平均分。
- 最终表征$\mathbf{h}_v^{(L)}$ 是第 $L$ 层(最后一层)输出的节点表征,代表消息传递全部结束后每位学生的最新档案。
分类器设计
小林手里有了《班级总结报告》,但“优秀班”和“普通班”的界限模糊得很。她决定用一个打分模型:把报告中的各项指标加权组合,经过层层提炼,最终算出“优秀班”的得分和“普通班”的得分,哪个高就选哪个。这个打分模型在深度学习里就是MLP(多层感知机),它由若干全连接层和激活函数堆叠而成,可以拟合非常复杂的评定规则。
为了让打分结果变成“概率”,模型最后接一个 Softmax 层:把两个原始得分归一化成和为 1 的概率,比如“优秀班 82%、普通班 18%”。训练时,我们用交叉熵损失衡量模型预测与真实标签的差距:如果真实标签是“优秀班”(one-hot 向量为 [1,0]),而模型预测 [0.82,0.18],损失就很低;预测偏了损失就高。通过梯度下降不断更新 MLP 的权重,模型慢慢学会像资深班主任一样精准定级。
有了分类器和损失函数,小林开始拿历史数据训练自己的评定能力。但训练样本总归有限,怎样让班主任在班型千奇百怪的情况下依然稳准狠?答案是给训练数据“添点花样”——数据增强。
- 真实标签$y_c$ 是真实类别的 one-hot 编码,若真实类别是 $c$ 则 $y_c=1$,否则为 $0$,相当于“标准答案”。
- 预测概率$\mathrm{softmax}(\mathrm{MLP}(\mathbf{h}_{\mathcal{G}}))_c$ 是模型预测该图属于类别 $c$ 的概率,$\log$ 对概率取对数便于计算。
- 求和惩罚$\sum_{c=1}^{C}$ 遍历所有类别,$y_c$ 为 $1$ 的那一项被保留,其余项为 $0$,所以损失只看“真实类别对应的预测概率”。
数据增强
小林发现,自己见过的班级类型太少了:全是规规矩矩的环形座位分布。万一遇到一个“两派分裂”的班级,她可能误判。有经验的同事给她支招:把几个学生的座位调换一下(改变边),把档案上的措辞换种说法(扰动特征),甚至截取班级里的小圈子单独评审(子图采样)——这样就能变出大量“分身班级”,让小林在相似但略有不同的样本上反复练习,评定的稳定性大幅提高。这就是图数据增强的直觉。
图数据增强要格外小心:图的结构信息非常敏感,乱改边可能制造出毫无意义的样本。常用的安全策略有三类。特征扰动:给节点特征加少量高斯噪声,相当于评语微调,不改变图结构。边丢弃:以一定概率随机删掉边,模拟“某两个学生这学期不熟了”,让模型不那么依赖某条具体的边。子图采样:随机抽取图的一部分作为新样本,迫使模型学习局部与整体的关系。增强不是越多越好,力度过猛会把“优秀班”扭成“普通班”,反而伤害训练。
有了丰富的“分身班级”练手,小林的评定经验越来越足。但她怎么知道自己的评定水平到底如何?还需要一套客观的检验机制——评估与调试。
- 扰动后特征$\tilde{\mathbf{h}}_v$ 是加噪后的节点特征向量,它在原特征 $\mathbf{h}_v$ 的基础上产生微小偏移,相当于评语换了种说法但意思不变。
- 高斯噪声$\boldsymbol{\varepsilon}_v\sim\mathcal{N}(\mathbf{0},\sigma^2\mathbf{I})$ 表示噪声向量从均值为 $0$、方差为 $\sigma^2$ 的高斯分布中随机抽取,$\sigma$ 控制扰动强度,噪声太大就会掩盖真实信息。
评估与调试
期末到了,教导处拿出历年的真实班级档案(模型没见过的测试集),让小林重新评定一遍。她评对了多少个班,准确率一目了然。准确率是图分类最直观的指标:预测正确的样本数除以总样本数。但这里暗藏一个陷阱——如果 100 个班里只有 5 个优秀班,小林闭着眼睛全评“普通班”也能有 95% 准确率,这种虚高的分数在类别不平衡时毫无意义。这时要看每个类别的精确率和召回率,或者用 F1 值综合衡量。
训练过程中还要警惕过拟合:训练准确率一路飙升到 99%,测试准确率却卡在 70%,说明小林把历年档案背得滚瓜烂熟,却没有真正学会“评估班级的方法”。对策包括增加数据增强力度、加 Dropout、减小模型规模或提前停止训练。此外,可视化调试非常实用:用工具把图池化后的向量投影到二维平面,如果同类别样本聚成一团,说明模型学到了结构特征;如果杂乱无章,就要回头检查 GNN 层数或聚合方式。
教学评估结束,小林把整个流程总结成一张闭环图:读图(输入)→ 逐一了解学生(GNN 编码)→ 汇成报告(池化)→ 对照标准定级(分类器)→ 收集反馈优化(评估与调试)。这正是图分类模型从训练到部署的完整生命周期。
- 样本总数$N$ 是测试集里图样本的总数,分母 $N$ 让准确率成为 $0$ 到 $1$ 的比例。
- 指示函数$\mathbb{1}(\hat{y}_i=y_i)$ 中,若第 $i$ 个图的预测标签 $\hat{y}_i$ 与真实标签 $y_i$ 相等则取 $1$,否则取 $0$,相当于“评对了记一分”。
AI 应用场景
分子毒性预测
阶段:模型推理与决策
解决的问题:药物研发早期需要筛选海量化合物,传统做法是逐一做湿实验,耗时数年且成本高昂。用图分类模型把每个分子建模成原子-化学键的图,直接预测“有毒/无毒”,把候选化合物从几十万快速筛到几千。
具体案例:某制药公司收集了 3 万个小分子的化学结构图及实验毒性标签,训练一个 GCN+平均池化+MLP 的图分类模型,在外部测试集上达到 85% 的准确率,将初筛阶段的实验量减少了 70%。
蛋白质功能分类
阶段:特征提取与建模
解决的问题:蛋白质由氨基酸链折叠成三维结构,其功能类别与空间结构密切相关。把蛋白质残基看作节点、空间相邻关系看作边,构建蛋白质结构图,用图分类自动识别“酶/抗体/运输蛋白”等功能类别,替代昂贵且主观的人工比对。
具体案例:生物信息团队利用 AlphaFold 预测的蛋白质结构构建残基接触图,训练图分类模型对未知蛋白按 Gene Ontology 功能类别打标,辅助基因功能注释流水线,将注释效率提升约 40%。
程序图恶意代码检测
阶段:数据增强与鲁棒性优化
解决的问题:恶意软件常通过加壳、混淆逃避基于签名的检测。把程序的控制流图和数据依赖图作为输入,用图分类判断“恶意/正常”,并通过边丢弃、子图采样等增强策略提升对混淆变体的鲁棒性。
具体案例:安全团队对 1.2 万个程序样本构建控制流图,训练图分类模型识别勒索软件家族。配合边 Dropout 增强后,模型对未见过的混淆样本检出率从 68% 提升到 83%。
Python 演示
import numpy as np def make_circle(n): """构造一类图:环形结构,每个节点度数为2""" adj = np.zeros((n, n)) for i in range(n): adj[i, (i + 1) % n] = 1 adj[(i + 1) % n, i] = 1 return adj def make_two_clusters(n): """构造另一类图:两个小团用一条边连接""" adj = np.zeros((n, n)) half = n // 2 for i in range(n): for j in range(n): if i != j and ((i < half and j < half) or (i >= half and j >= half)): adj[i, j] = 1 adj[half - 1, half] = 1 adj[half, half - 1] = 1 return adj def build_dataset(n_samples, n_nodes=10): """生成两类图样本:环形图标0,双团图标1""" graphs, labels = [], [] for i in range(n_samples): y = i % 2 adj = make_circle(n_nodes) if y == 0 else make_two_clusters(n_nodes) adj = adj + np.eye(n_nodes) # 加自环 x = np.ones((n_nodes, 1)) # 所有节点初始特征为1 graphs.append((x, adj)) labels.append(y) return graphs, np.array(labels) def relu(x): return np.maximum(x, 0) def softmax(z): e = np.exp(z - np.max(z)) return e / np.sum(e) class SimpleGNN: """单层GCN + 平均图池化 + 线性分类器""" def __init__(self, in_dim=1, hidden=16, out=2, lr=0.1): self.W1 = np.random.randn(in_dim, hidden) * 0.2 self.W2 = np.random.randn(hidden, out) * 0.2 self.lr = lr def forward(self, x, adj): xp = adj @ x # 消息传递:聚合邻居特征 h = relu(xp @ self.W1) # 全连接+激活 hg = h.mean(axis=0) # 平均图池化 logits = hg @ self.W2 # 分类打分 return logits, h, hg def train_step(self, x, adj, y): xp = adj @ x h = relu(xp @ self.W1) hg = h.mean(axis=0) logits = hg @ self.W2 probs = softmax(logits) dlogits = probs.copy() dlogits[y] -= 1 # 交叉熵梯度 dW2 = np.outer(hg, dlogits) dhg = dlogits @ self.W2.T dh = np.broadcast_to(dhg / h.shape[0], h.shape) dh = dh * (h > 0) # ReLU 梯度 dW1 = xp.T @ dh self.W2 -= self.lr * dW2 self.W1 -= self.lr * dW1 def predict(self, x, adj): logits, _, _ = self.forward(x, adj) return int(np.argmax(logits)) def main(): graphs, labels = build_dataset(200, n_nodes=10) split = 150 model = SimpleGNN() for epoch in range(300): for g, y in zip(graphs[:split], labels[:split]): model.train_step(g[0], g[1], int(y)) if epoch % 100 == 0: train_acc = np.mean( [model.predict(g[0], g[1]) == y for g, y in zip(graphs[:split], labels[:split])] ) print("epoch {} train_acc {:.2f}".format(epoch, train_acc)) test_acc = np.mean( [model.predict(g[0], g[1]) == y for g, y in zip(graphs[split:], labels[split:])] ) print("test_acc {:.2f}".format(test_acc)) if __name__ == "__main__": main()
adj = adj + np.eye(n_nodes)给邻接矩阵加自环,让节点在聚合时把自己的特征也算进去,这是 GCN 的常见技巧。xp = adj @ x矩阵乘法一次性完成所有节点的邻居特征聚合,是消息传递的向量化实现。hg = h.mean(axis=0)对所有节点特征取平均,实现全局平均池化,把图压缩成一个固定长度的向量。dlogits[y] -= 1交叉熵损失对 logits 的梯度推导结果,是反向传播的关键一步。np.broadcast_to(dhg / h.shape[0], h.shape)平均池化的梯度回传:把图级梯度均摊到每个节点上。dh = dh * (h > 0)ReLU 的梯度 mask,负区间梯度为 0,保证反向传播正确。
常见误区
误区图分类不就是把图中所有节点的预测结果做个投票吗?比如先预测每个节点属于哪类,再统计多数决定整张图的类别。
正解这是视角的根本错误。图分类是对整体结构的建模,就像班主任评定班级风气不能靠“把每个学生打晕后投票”——整体大于部分之和。分子是否有毒,并不取决于某个原子单独的性质,而是原子们连接成的拓扑模式。所以必须用池化把节点特征融合成全局表征,再交给分类器,而不是简单投票。
为何常见:初学者刚学完节点分类,自然想复用“逐点预测”的思路,忽略了图级任务需要全局信息整合的本质。
误区图池化就是求个平均或者求个和,所有节点一视同仁,反正最后都是个向量。
正解平均池化只是最简单的选择,且不一定好用。求和池化能保留图的大小信息(比如分子越大毒性可能越强),最大池化能捕捉最异常的节点信号,注意力池化则让模型学习“哪些节点对判定更关键”。就像写班级总结,有些调皮鬼的信息要弱化,班长的话要重点参考——选什么池化方式,本质是选择“如何压缩信息”,直接影响分类效果。
为何常见:看到 READOUT 函数长得很像“求和/平均”,就以为它只是简单统计,忽视了它承载的结构语义。
误区图像可以随便旋转、裁剪、加噪声,图数据也一样,随机乱删边、乱加边总能增加样本量。
正解图的结构是有语义的!分子图里删掉一条化学键,可能就从“无毒”变成“剧毒”。图增强必须保证语义基本不变:特征加噪要控制幅度,边丢弃要适度,子图采样要合理。这就像调换班级座位可以增加样本多样性,但如果把整个教室拆了重建,班级就不是原来的班级了。增强力度过猛,等于在训练数据里掺假。
为何常见:图像增强的成功经验容易让人惯性迁移,忽略了图和图像的本质差异——图的结构本身就是语义的一部分。
误区测试集准确率 95%,模型肯定很牛,可以放心上线了。
正解如果类别不平衡(比如 100 个分子里只有 5 个有毒),模型全预测“无毒”也有 95% 准确率,但一个毒药都筛不出来,毫无实用价值。必须同时看各类别的精确率/召回率,或使用 F1 值;还要对比训练集与测试集的差距判断是否过拟合。就像班主任评定 100 个班,如果他不分青红皂白全评“普通班”,准确率也能有 95%,但这显然不是好班主任。
为何常见:准确率是最直观的指标,人们容易只盯这一个数,忽略了数据分布对指标的扭曲。
面试题
Q1. 图分类任务与节点分类任务的核心区别是什么?
- A.图分类输出的是节点类别,节点分类输出整图类别
- B.图分类对整图输出标签,节点分类对单个节点输出标签← 正确答案
- C.两者完全相同,只是叫法不同
- D.图分类不需要图结构信息
答案:B。图分类把整张图当作一个样本,输出图的类别标签;节点分类关注图中某个节点的标签。B 正确描述了二者的区别。A 把两者说反了,C 忽略了粒度差异,D 错误——图分类恰恰依赖图结构。
Q2. 消息传递中聚合的对象是哪些节点?
- A.图中所有节点
- B.当前节点的所有邻居节点← 正确答案
- C.当前节点自身
- D.随机选择的节点
答案:B。消息传递的核心是聚合 $\mathcal{N}(v)$,即当前节点 $v$ 的邻居集合。就像小林找学生谈话时只参考朋友的情报,而非全班的每一个人。A 范围太大,C 忽略了邻居,D 不具备结构语义。
Q3. 下列哪些属于图分类的真实应用场景?
- A.预测分子是否有毒性← 正确答案
- B.对蛋白质功能进行分类← 正确答案
- C.判断程序控制流图是否恶意← 正确答案
- D.识别图片中猫和狗
答案:ABC。分子毒性预测、蛋白质功能分类、程序图恶意代码检测都能建模成“整图→标签”的问题。D 是图像分类而非图分类,图片是规则网格而非任意拓扑结构。
Q4. 平均图池化输出的特征向量有什么特点?
- A.长度随图中节点数量变化
- B.长度固定,且包含全图平均信息← 正确答案
- C.只保留最大特征值
- D.与节点顺序强相关
答案:B。平均池化把所有节点特征逐维求平均,输出向量的维度只取决于特征维度而非节点数量,因此长度固定,且对节点顺序不敏感。A 错误(长度不受节点数影响),C 是最大池化的行为,D 与池化的置换不变性矛盾。
Q5. Softmax 层在图分类模型中的作用是什么?
- A.把特征映射成固定长度的图向量
- B.把分类得分归一化成概率分布← 正确答案
- C.在图结构中传播消息
- D.增加训练样本数量
答案:B。Softmax 把 MLP 输出的原始得分转换成所有类别上求和为 1 的概率,便于用交叉熵损失训练。A 是池化的作用,C 是 GNN 的职责,D 是数据增强的功能。
Q6. 下列哪些是合理的图数据增强策略?
- A.给节点特征添加小幅度高斯噪声← 正确答案
- B.随机删除少量边构造扰动样本← 正确答案
- C.随机抽取子图作为新训练样本← 正确答案
- D.把图中所有边反向连接
答案:ABC。特征扰动、边丢弃、子图采样都是课堂介绍的安全增强策略,能在保持语义基本不变的前提下增加样本多样性。D 把所有边反向没有意义,因为图常视为无向,且可能破坏结构语义。
Q7. 交叉熵损失中,当模型对真实类别的预测概率越高,损失会怎样变化?
- A.损失越大
- B.损失越小← 正确答案
- C.损失不变
- D.损失随机波动
答案:B。交叉熵损失 $-\log(\hat{p}_c)$ 是减函数,真实类别的预测概率越高,负对数越小,损失就越低。这鼓励模型对正确类别给出更高概率,与“预测越准惩罚越小”的直觉一致。
Q8. 关于求和池化与平均池化的区别,下列说法正确的是?
- A.求和池化对图的大小敏感,平均池化消除了大小影响← 正确答案
- B.平均池化保留总能量,求和池化保留平均强度
- C.求和池化的输出维度是平均池化的两倍
- D.两者在数学上完全等价
答案:A。求和池化会把“节点数量”的信息编码进结果,图越大向量值越大;平均池化除以 $|\mathcal{V}|$ 后,反映的是平均强度而非规模。因此求和池化对图大小敏感,适合分子大小影响性质的场景。B 把两者说反,C 维度相同,D 显然错误。
Q9. 训练图分类模型时,要如何判断是否过拟合?
- A.训练准确率远高于验证集准确率← 正确答案
- B.验证损失先降后升,训练损失持续下降← 正确答案
- C.训练集和验证集准确率都接近满分
- D.增加数据增强后测试准确率明显提升
答案:AB。过拟合的典型信号是训练集与验证集表现差距拉大:训练准确率居高不下而验证准确率停滞或下降,验证损失曲线呈 U 形而训练损失一直降。C 说明模型拟合得很好不一定过拟合,D 只是说明增强有效。
Q10. 多层 GNN 堆叠后出现过平滑问题时,最直接有效的应对策略是?
- A.无脑增加 GNN 层数
- B.减少层数或用残差连接与跳跃机制← 正确答案
- C.把池化换成最大池化
- D.删除所有邻居信息
答案:B。过平滑指层数过多后所有节点的表征趋于一致,丧失区分能力。减少层数、加残差连接或 JK-Net 等跳跃机制能让梯度与信息跨越较多层传播。A 会加剧过平滑,C 是池化策略与过平滑无关,D 破坏了消息传递的根基。