简介:这份PPT面向希望系统入门图神经网络的算法学习者与研究人员,用100页篇幅梳理从基础概念到主流变体的完整知识脉络。内容从「为什么需要GNN」切入,对比欧式与非欧式数据的差异,讲解图的基本构成、邻接矩阵与邻接列表的表示方式,以及在神经网络中使用图数据面临的挑战;随后展开图卷积网络的空间域与频域两条路线,并逐一介绍DCNN、NN4G、MPNN、GAT、GAE与VGAE等变体的核心思想,最后落到自然语言处理、计算机视觉、推荐系统与预测问题四类应用场景。资源包仅含1个pptx文件,约12.69MB,结构按四大PART组织,便于按章节讲解或自学。目前已有1585人学习,适合作为课程讲义或组会分享材料,帮助读者快速建立GNN知识框架并理解各变体间的联系与差异。
1. 图神经网络:为什么 CNN 的卷积核搬不到社交网络上
把一张图片旋转 90 度,CNN 照样能认出里面的猫;但把社交网络里节点的编号顺序打乱,同一套卷积核就彻底失效了。这个反直觉的差异,正是图神经网络(GNN)存在的根本理由。图像、文本、声音属于欧式数据,节点排列在规则网格上,每个像素的邻居数量固定,所以一个全局共享的卷积核就能横扫整张图。而知识图谱、社交关系、分子结构属于非欧式数据,节点没有固定顺序,邻居数量因点而异,平移不变性这个前提直接崩塌。GNN 要解决的,就是在这种「连接关系本身就是信息」的数据上,重新定义一套可学习、可堆叠、且对节点顺序不敏感的信息传播机制。搞懂它,你才能理解为什么推荐系统、药物发现、语义分割这些场景里,传统深度模型总是差一口气。
2. 从欧式到非欧式:卷积失效的底层原因
2.1 平移不变性到底依赖什么
CNN 能用一个卷积核扫遍全图,靠的不是卷积运算本身,而是数据背后的网格假设。图像里任意一个像素,它的上下左右邻居在空间上是确定的,卷积核在滑动时看到的局部模式结构一致,所以权重可以共享。这个性质叫平移不变性:目标出现在图像左上角还是右下角,网络都能识别。文本也类似,词序列虽然是一维的,但位置关系固定,一维卷积同样成立。
问题在于,这个假设在图数据上不成立。一个社交网络里,某个用户可能连接 3 个人,另一个用户可能连接 3000 个人。你没法定义一个固定大小的卷积核去覆盖「所有邻居」,因为邻居集合的大小和排列顺序都是动态的。更麻烦的是,节点编号本身没有语义——把邻接矩阵的行列同时置换,图还是同一张图,但卷积核看到的输入完全变了。常见的实现思路是:放弃「固定窗口」的执念,转而让每个节点主动去聚合邻居的信息,聚合函数的输入是邻居特征的多重集,输出与顺序无关。这就是 GNN 信息传递范式的起点。
2.2 邻接矩阵的存储陷阱
最直觉的图表示是邻接矩阵:一个 N×N 的方阵,第 i 行第 j 列为 1 表示节点 i 和 j 之间有边。数学上很干净,工程上却是个坑。社交网络动辄上亿节点,N×N 的稠密矩阵根本存不下;即便存下,绝大多数位置是 0,稀疏矩阵的高效计算本身就是难题。更隐蔽的问题是顺序敏感性:邻接矩阵的行列顺序一变,矩阵内容全变,但图的结构没变。如果神经网络直接吃这个矩阵,输出就会随节点编号漂移,这在训练中是灾难性的。
所以实际系统里更常见的是邻接列表:维护一个长度为边数的列表,第 i 项记录第 i 条边连接的两个节点编号,节点和边的特征各自用向量存储。这种结构只保存真实存在的边,存储高效;而且边的顺序可以任意打乱,只要同步调整列表项,图的语义不变。下面这段伪代码展示了邻接列表的基本组织方式,以及如何从它构造一次邻居聚合的输入:
# 邻接列表表示:edges[i] = (src, dst),feats 为节点特征字典 edges = [(0, 1), (1, 2), (2, 0), (2, 3)] feats = {0: [1.0, 0.0], 1: [0.5, 0.5], 2: [0.0, 1.0], 3: [0.8, 0.2]} # 为每个节点收集邻居特征,顺序无关(这里用集合去重后求和) from collections import defaultdict neighbor_feats = defaultdict(list) for src, dst in edges: neighbor_feats[dst].append(feats[src]) # 有向边:src -> dst neighbor_feats[src].append(feats[dst]) # 若为无向图,反向也加 # 聚合:对邻居特征求和,结果与邻居遍历顺序无关 aggregated = {node: [sum(v) for v in zip(*nbrs)] if nbrs else [0.0, 0.0] for node, nbrs in neighbor_feats.items()}这段代码的关键不在求和本身,而在zip(*nbrs)这个操作对邻居列表顺序不敏感——无论邻居以什么顺序被收集,逐维求和的结果一致。这就是 GNN 聚合函数必须满足的性质:置换不变性。参数上,edges的每一项是有向边,无向图需要双向写入;feats的维度就是节点特征长度,聚合后的维度保持不变,方便后续与自身特征拼接或相加。
2.3 图上的三类学习任务
在把图喂给神经网络之前,得先明确要学什么。图上的问题大致分三层:图层面,比如给一堆分子图判断哪个含有某种环结构,输出是整张图的标签;顶点层面,比如在师生社交图里判断每个学生属于哪个阵营,输出是逐节点的分类;边层面,比如语义分割后判断人物之间的关系,输出是边的属性。这三类任务对应不同的读出方式:图层面需要对所有节点特征做全局池化,顶点层面直接取节点输出,边层面则要构造边特征或对两端节点做组合。GNN 的层设计基本不变,变的是最后的输出头和损失函数。理解这个分层,后面看变体时就不会混淆——GCN 改的是聚合方式,GAT 改的是聚合权重,GAE 改的是训练目标。
3. GNN 的核心机制:聚合、更新、迭代
3.1 隐藏状态如何被邻居改写
GNN 最早的设计思想很朴素:每个节点维护一个隐藏状态,每一轮用邻居的状态来更新自己。假设节点 5 的邻居是 2、3、4,那么它的新状态可以写成对自身旧状态和邻居状态的函数。原始论文里的更新函数形式各异,但骨架一致:先收集邻居信息,再与自身信息融合,最后过一个非线性变换。这个过程重复若干轮,节点就能「看到」多跳之外的邻居。
用公式化的语言说,第 k 轮节点 v 的隐藏状态 h_v^(k) 由 h_v^(k-1) 和它所有邻居的 h_u^(k-1) 共同决定。关键在于,这个函数必须对邻居集合的排列不敏感,否则同一张图换个编号就得到不同结果。常见的实现思路是把邻居信息求和或求平均,再与自身特征拼接后过一层全连接。求和和平均都是置换不变的,拼接也不引入顺序依赖,这就满足了基本要求。
3.2 聚合操作的数学约束
聚合函数不是随便写的,它要满足两个硬约束:一是置换不变性,邻居顺序打乱结果不变;二是能处理变长邻居集合,邻居数量从 0 到几千都要能算。求和、平均、最大值都满足这两条。求和保留了邻居数量信息(邻居多的节点聚合结果模长更大),平均则做了归一化,最大值突出最显著的邻居特征。选择哪种,取决于任务对「邻居数量」是否敏感。
以节点 A 聚合邻居 B、C、D 为例,若 B、C、D 的特征分别是 (2,2,2,2,2)、(3,3,3,3,3)、(4,4,4,4,4),加权系数为 a、b、c,则聚合结果 N = a·(2,2,2,2,2) + b·(3,3,3,3,3) + c·(4,4,4,4,4)。这里的 a、b、c 可以是固定的(如均值里的 1/3),也可以是学出来的(如注意力权重)。固定权重实现简单,但把所有邻居一视同仁;可学习权重让模型自己决定谁更重要,这就是 GAT 的出发点。参数上,特征维度在聚合前后保持一致,加权系数对所有维度共享,这样计算量可控。
3.3 迭代轮数与感受野
聚合一次,节点只看到直接邻居;聚合两次,能看到邻居的邻居,感受野扩大一跳。这和 CNN 堆叠卷积层扩大感受野是同一个道理。但图上的迭代不能无限堆,原因有两个:一是过平滑,多轮平均后所有节点特征趋于一致,区分度消失;二是计算爆炸,每轮都要遍历所有边,轮数多了开销线性增长。实践中通常堆 2 到 4 层,具体看图的直径和任务需要。判断层数是否合适,可以观察验证集上节点特征的方差——如果层数增加后方差急剧下降,说明过平滑已经发生,该收手了。
4. 变体拆解:GCN、GAT、GAE 各自改了什么
4.1 图卷积的两条路线:空间域与频域
GCN 是 GNN 最出名的变体,但它内部其实分两派。空间域的思路很直观:既然不能定义固定卷积核,那就直接在邻居集合上做加权聚合,邻接矩阵在这里充当「谁和谁相连」的掩码。频域的思路则绕了一道:借助图拉普拉斯矩阵的特征分解,把图信号变换到频域,在频域做滤波再变换回来。频域方法理论优雅,但特征分解是 O(N³) 的,大图上不现实,所以实际落地多是空间域或其近似。
DCNN(Diffusion-Convolution Neural Network)是空间域的一个代表,它把信息传播建模成扩散过程:每个节点把自己的特征按转移概率扩散到邻居,多跳扩散后取各跳结果的组合。NN4G(Neural Networks for Graph)则更直接,每一层对邻居特征求和后与自身特征拼接,堆叠多层实现多跳聚合。这两个变体的共同点是都放弃了「卷积核」的字面意义,转而用聚合操作替代卷积,这正是 GNN 设计的核心取舍——用置换不变的聚合换取对任意图结构的适应能力。
4.2 GAT:把聚合权重交给注意力
固定权重的聚合有个明显缺陷:所有邻居贡献相同,但现实中有些邻居就是更相关。GAT(Graph Attention Network)引入注意力机制,为每对相邻节点计算一个注意力系数,再按系数加权求和。计算方式是:对节点 i 和邻居 j,用一个小神经网络对两者的特征拼接打分,过 softmax 归一化后得到权重。这样,模型在聚合时能自动聚焦到重要邻居上,而且注意力系数是逐边计算的,不依赖全局图结构,泛化到未见过的图也没问题。
GAT 的代价是计算量增加:每轮聚合要为每条边算一次注意力,边数多时开销明显。工程上常用多头注意力来稳定训练,即并行算多组注意力再拼接,类似 Transformer 里的多头机制。参数上,注意力网络的隐藏维度通常设得较小(如 8 或 16),头数 4 到 8,既能捕捉不同视角的邻居重要性,又不至于让参数量失控。
4.3 GAE 与 VGAE:用重构损失学图表示
前面几类变体解决的是「怎么聚合」,GAE(Graph Autoencoder)解决的是「怎么无监督地学表示」。它的结构是编码器-解码器:编码器用 GCN 把节点映射到低维向量,解码器用这些向量重构邻接矩阵(通常用内积,两节点向量内积越大表示越可能有边)。训练目标是让重构出的邻接矩阵逼近真实邻接矩阵,损失用交叉熵。这样学到的节点向量就编码了图的结构信息,可以直接拿去做链路预测或聚类。
VGAE(Variational Graph Autoencoder)在 GAE 基础上引入变分推断,编码器输出的是均值和对数方差,从中采样得到隐向量再解码。变分框架让隐空间更连续、更平滑,生成新图或补全缺失边时表现更稳。两者的关键区别在损失函数:GAE 只有重构损失,VGAE 多了 KL 散度正则项,约束隐向量分布接近标准正态。参数上,隐维度一般取 16 到 64,太小欠拟合,太大则重构损失主导、正则失效。
4.4 MPNN:把消息传递抽象成统一框架
MPNN(Message Passing Neural Network)不算新模型,而是一个统一视角:把上述各种变体都看成「消息生成—消息聚合—状态更新」三步。每条边生成一条消息,节点聚合收到的所有消息,再更新自己的状态。GCN 是消息为邻居特征、聚合为求和的特例;GAT 是消息带注意力权重的特例;GAE 则是把消息传递用在编码器里。这个抽象的价值在于,设计新模型时不用从零想,只需回答三个问题:消息怎么算、怎么聚合、怎么更新。下面这段伪代码展示了 MPNN 的通用骨架:
def message_passing(node_feats, edges, num_rounds): h = node_feats.copy() for _ in range(num_rounds): messages = {} for src, dst in edges: # 消息函数:这里用源节点特征,实际可加入边特征 messages.setdefault(dst, []).append(h[src]) new_h = {} for node in h: # 聚合:对收到的消息求和,置换不变 agg = sum(messages.get(node, [[0.0] * len(h[node])])) # 更新:拼接自身与聚合结果,过一层可学习的变换 new_h[node] = update_fn(h[node], agg) h = new_h return hmessage_passing的轮数num_rounds就是感受野跳数;update_fn通常是线性层加激活函数,参数在所有节点间共享。这段骨架把 GCN、GAT 的差异压缩到了消息函数和聚合函数两个可替换的槽位里,读源码时按这两个槽位去对照,很快就能定位到具体变体的改动点。
5. 避坑与排查:图神经网络落地时的常见翻车点
5.1 节点特征没归一化,训练直接发散
现象:损失在前几个 epoch 剧烈震荡,甚至出现 NaN。原因:图数据的节点特征量纲差异大(比如年龄是几十,收入是几万),聚合时求和会让大数值特征主导,梯度爆炸。解决:在输入层对每个特征维度做标准化(减均值除标准差),或者用平均聚合替代求和,把聚合结果的尺度压回可控范围。我一般会在数据预处理阶段强制检查特征的最大绝对值,超过阈值就先归一化再进模型。
5.2 邻接矩阵没做对称化,无向图被当成有向图
现象:模型在无向图上表现异常,同一对节点的相互影响不对称。原因:构造邻接列表时只写了一个方向,聚合时节点 A 能看到 B,B 却看不到 A。解决:无向图必须双向写入边,或者在聚合前对邻接矩阵做A = A + A.T的对称化。排查方法是随机抽几条边,检查两个方向的邻居列表里是否都包含对方。
5.3 层数堆太多,过平滑让所有节点趋同
现象:加深网络后训练集准确率不升反降,节点表示几乎一样。原因:每轮聚合都在做邻域平均,多轮后高频信息被抹平,所有节点收敛到同一向量。解决:控制层数在 2 到 4 层,加入残差连接保留自身特征,或者用 JK-Net 式的跨层跳跃连接。验证方法是计算不同层输出下节点特征的方差,方差骤降的那一层就是过平滑的起点。
5.4 稀疏图上的邻居采样偏差
现象:在度分布极不均匀的图上,高度节点主导训练,低度节点学不到东西。原因:全图聚合时,高度节点的邻居集合巨大,聚合结果被大量重复信息淹没;低度节点信息不足。解决:对高度节点做邻居采样(每轮随机抽固定数量的邻居),或者用加权聚合平衡度的影响。采样数量一般取 10 到 25,太少丢信息,太多失去采样意义。
5.5 训练/推理阶段的图结构不一致
现象:离线训练效果好,上线推理时指标掉一大截。原因:训练时用了完整的图结构(包括未来才出现的边),推理时图是动态变化的,模型没见过这种分布。解决:训练时模拟推理时的图快照,按时间切分边,只用历史边做聚合。排查时对比训练图和推理图的度分布,差异大的特征维度要重点检查。
6. 设计取舍:为什么 GNN 没有唯一的正确答案
GNN 的每一个设计选择背后都是一组权衡,没有放之四海皆准的配置。聚合函数选求和还是平均,取决于你是否想让邻居数量成为信号:社交网络里粉丝多的人影响力大,求和更合适;分子图里原子数量不代表重要性,平均更稳。层数选 2 还是 4,取决于图的直径和过平滑的容忍度:小图 2 层够用,大图需要更多跳但得配合残差。GAT 的注意力比 GCN 的固定权重表达力强,但参数量和计算开销也高,边数上百万时可能得不偿失。GAE 的无监督学习省标注,但重构目标未必对齐下游任务,有标签时还是监督训练更直接。
这些取舍没法靠读文档拍板,只能靠实验验证。我的习惯是:先用一个最朴素的 GCN(平均聚合、2 层、特征标准化)跑通基线,记录验证集指标和节点特征方差;然后逐项替换——换 GAT 看注意力是否带来提升,加层看是否过平滑,换 GAE 看无监督预训练是否有帮助。每次只改一个变量,对比指标变化,才能定位到真正起作用的机制。图数据的不规则性决定了 GNN 调参比 CNN 更依赖对数据本身的理解,光看 loss 曲线不够,得回头看聚合后的邻居特征分布是否符合直觉。
那份 100 页的讲解材料把这条线从欧式/非欧式的动机、图的表示与三类任务、聚合更新迭代的核心机制,一直铺到 GCN、GAT、GAE、MPNN 的变体拆解和四类应用场景,正好覆盖了上面这些取舍的来龙去脉。想顺着源码级细节继续深挖的,可以从这份材料里的变体对比和聚合公式入手,对照本文的骨架去读,会快很多。从那以后我每次上手一个新的图任务,都强制先画一遍消息传递的三步骨架,再决定改哪个槽位——希望帮到你。
本文还有配套的精品资源,点击获取