图神经网络入门:从邻接矩阵到GCN/GAT与消息传递
简介:这份100页PPT面向希望系统入门图神经网络的算法学习者与工程实践者,从欧式/非欧式数据差异切入,解释传统CNN处理图结构时的局限,并梳理GNN的聚合、更新与迭代思路。内容按四部分展开:为何需要GNN、图与GNN基础、主要变体及落地应用;变体部分覆盖DCNN、NN4G、MPNN、GAT、GAE与VGAE,应用部分涉及NLP、计算机视觉、推荐系统和预测问题,便于建立从概念到模型谱系的知识框架。压缩包共1个pptx文件,约12.69MB,结构清晰,适合课堂汇报、组会分享或个人查漏补缺。已有1588人学习,可作为图神经网络专题入门与复习的轻量资料。
1. 从一张不讲道理的图片说起:为什么图像那套办法到这里失灵了
如果你跟着教程跑过图像分类,脑子里大概已经形成一种直觉:数据是一张方方正正的格子,像素排成行和列,谁挨着谁一目了然,把某个小窗口的权重从头滑到尾就行。可一旦换成社交关系、分子结构、用户和商品的购买记录,这套直觉立刻失灵:这些东西没有上下左右,节点谁挨着谁、挨几个,全凭关系说话,换个顺序它还长一个样。图神经网络(GNN)就是专门为这种“没有固定阵型”的数据准备的一整套方法。这份 100 页的 PPT 讲的正是它:为什么需要它、它到底在算什么、GCN 和 GAT 这些变体各有什么脾气,以及它在自然语言处理、计算机视觉、推荐、预测这四类场景里怎么用。你要是刚接触这块,把它当成第一张地图来看,比直接硬啃论文正文要舒服得多。
2. 先搞懂三个词:节点、边、邻接矩阵到底在说什么
2.1 图就是一张关系清单,而不是一张画
很多人第一次看到“图”这个词,脑子里浮现的是一张折线图或者一张漂亮的可视化网络图,然后就卡在这儿了。其实在 GNN 的语境里,图(graph)就是一串实体加上它们之间的连接。实体叫节点(node),用 V 表示;关系叫边(edge),用 E 表示;整张图整体还有个全局信息,用 U 表示。三个字母记住了,后面看任何资料都不会再懵。
举个最生活的例子:班上五个同学,谁和谁互相认识。五个同学是 5 个节点,“认识”这件事是边。你完全不需要画出来也能把它写下来,写成两行代码就行:
# 用最朴素的方式描述一张图:先记人,再记关系 nodes = {"小A": 1, "小B": 2, "小C": 3, "小D": 4, "小E": 5} # 每条边写成一对 (起点, 终点) edges = [ ("小A", "小B"), ("小A", "小C"), ("小B", "小C"), ("小B", "小D"), ("小C", "小D"), ("小D", "小E"), ] # 把边整理成「每个节点认识谁」的字典 neighbors = {} for u, v in edges: neighbors.setdefault(u, []).append(v) neighbors.setdefault(v, []).append(u) # 无向图要补反方向 print(neighbors["小D"]) # 看看小D都认识谁这段代码跑出来会打印['小B', '小C', '小E']。nodes里放的是节点和它们的编号,编号后面要拿来当下标用,所以我一开始就用了数字;edges里每一对括号是一条边;循环里那句setdefault(...).append(...)是在给每个节点往口袋里塞邻居,最后补的那一行append(u)是关键,因为“认识”是双向的,不补的话小B的口袋里就只有别人、没有小A。
养成这个习惯很重要:图在程序里从来不是一张画,而是一张清单。你在任何资料里看到“图”,都可以先在心里替换成“一份节点名单加一份关系名单”,理解成本立刻降一大截。我第一次画图出来看,纯粹是为了检查自己有没有把边连错,真正的数据永远是这些清单。
2.2 把图片、文本、社交网络都翻译成图
第二节是最容易被跳过、但实际最值钱的一节:既然 GNN 处理图,那我手上的图片、句子、用户行为记录,怎么变成图?这份 PPT 在这里给了三个非常干净的对照,我按自己的话复述一遍。
图片变图:把每个像素当成一个节点,相邻的像素之间连一条边。3×3 的图就是 9 个节点,每个节点最多跟上下左右 4 个邻居相连。文本变图:把每个词当成一个节点,词和它后面那个词之间连一条有向边,句子“Graphs are all around us”就串成一条链。社交网络变图:某两个人只要在同一场合同框出现过,就给他俩连一条边。三种东西看起来毫无关系,落到图这一层全成了同一件事。
用代码把图片那一种实现出来最直观,也最能验证你有没有真的听懂:
import numpy as np # 一张 3x3 的灰度图,数字本身当作像素值(特征) img = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) n = img.size # 9 个像素 = 9 个节点 A = np.zeros((n, n), dtype=int) # 先建一个空的关系表 for r in range(3): for c in range(3): i = r * 3 + c # 按行优先编号:左上角是 0,右下角是 8 if c + 1 < 3: # 右边还有像素就连 j = r * 3 + (c + 1) A[i, j] = A[j, i] = 1 if r + 1 < 3: # 下面还有像素就连 j = (r + 1) * 3 + c A[i, j] = A[j, i] = 1 print(A)这里有两个地方值得说明。一是编号规则i = r * 3 + c,行优先是最常见的约定,你换成列优先也完全可以,只要后面用的时候前后一致就行。二是我只看了“右”和“下”两个方向,因为无向图里“左”和“上”会跟已经连过的边重复,多连一次不会报错,但会让某些节点的连接被算两遍,这在后面做归一化的时候会变成玄学误差。
跑完你会看到一个 9×9 的矩阵,只有对角线的邻居位置是 1,其他基本都是 0。9 个节点就有 81 个格子,可真正有连接的才 12 条边。这个“空得多、实得少”的观感,就是下一节要解决的问题的源头。
2.3 邻接矩阵和邻接列表:同一件事的两种记法
上图的那个 9×9 矩阵叫邻接矩阵(adjacency matrix):横轴纵轴都是节点编号,两个节点有连接就填 1。它直观、好算,但这份 PPT 很实在地点出了它的两个毛病。
第一个毛病是大。矩阵是“节点数 × 节点数”的方阵。你想想一个真实的社交网络,几百万用户就是几百万乘几百万,这个矩阵光是想一下内存就让人头疼。而且里面绝大多数格子是 0,属于纯粹的浪费。这种“大部分是 0”的矩阵有个专门的名字叫稀疏矩阵,高效地算稀疏矩阵本身就是一个不小的工程问题。
第二个毛病更微妙,也更值得新手记住:邻接矩阵的行和列顺序可以随便交换,图还是同一张图。你换成另一种编号,矩阵长得会完全不一样,但它俩表达的是同一件事。这就意味着,如果你写了个模型,输出结果依赖于你按什么顺序给节点编号,那这个模型就是错的,因为同一张图不该有两个答案。
PPT 里给出的办法是换成邻接列表:节点特征单独存一份,边特征单独存一份,全局信息单独存一份,另外维护一个长度等于边数的列表,第 i 项记录第 i 条边连着哪两个节点。它的好处只有两条,但都很致命:存储上只存真正存在的边,浪费不了;顺序上你随便打乱边的排列,只要把邻接列表对应位置跟着改,结果不变。
# 邻接列表:只记真正存在的连接 edges = [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (3, 4)] src = [e[0] for e in edges] # 每条边的起点 dst = [e[1] for e in edges] # 每条边的终点 print(len(src), len(dst)) # 都等于边数 6,与节点数无关 # 想找节点 1 的邻居?扫一遍就行,数据量小的时候完全够用 nei = [dst[k] for k in range(len(src)) if src[k] == 1] nei += [src[k] for k in range(len(dst)) if dst[k] == 1] print(sorted(nei)) # [0, 2, 3]src和dst两个列表长度都等于边数,这是邻接列表和邻接矩阵最本质的区别:空间跟边数走,不跟节点数的平方走。代码里找邻居的办法是把所有边扫一遍,节点几百万的时候当然不该这么写,实际实现会用哈希或者排序后二分,但入门阶段先理解“它在存什么”,比先背实现技巧重要得多。
3. GNN 到底在算什么:聚合、更新、迭代三步走
3.1 一句大白话:每个节点都在打听邻居的消息
前面铺垫了那么多“图怎么表示”,其实都在回答一个问题:怎么让图这种结构被神经网络吃得下去。这份 PPT 给了一个很轻巧的回答——图神经网络最早的一批研究者做的事情,本质上是把神经网络的方法往图数据上搬。搬过来的核心动作只有三个词:聚合、更新、迭代。
用大白话讲:一开始每个节点只知道自己的特征,比如用户的年龄、商品的类目、原子的元素类型。第一轮,每个节点去问自己的邻居要一份特征摘要,把摘要跟自己原来的特征揉在一起,变成一个新的自己。第二轮,因为邻居上一轮也揉过了别人的信息,所以这次拿到的摘要里已经间接包含了两跳以外的信息。就这样一轮一轮滚下去,节点身上就带上了它周围一整片区域的上下文。
这个视角的好处是,你不需要一上来就面对那些复杂的公式。你先接受“节点之间互相打听消息”这件事,公式只是把“打听”这个动作写得更精确而已。我建议你第一次读公式的时候,先用这个大白话在心里翻译一遍,再回过头来看符号,效率会高很多。
3.2 手算一遍:节点 5 的一次聚合长什么样
光听不练容易飘。这份 PPT 里给了一个非常具体的例子:假设要更新节点 5,它有三个邻居,节点 2、3、4,每个邻居的特征都是 5 维向量,分别是全 2、全 3、全 4。每个邻居给一个权重 a、b、c,那么聚合的结果就是三份特征按权重加起来。这个例子好就好在它小到你可以在纸上算完。
import numpy as np # 三个邻居的特征,各 5 维 h2 = np.array([2., 2., 2., 2., 2.]) h3 = np.array([3., 3., 3., 3., 3.]) h4 = np.array([4., 4., 4., 4., 4.]) # 权重:越重要的邻居给越大的数,这里先随便定 a, b, c = 0.2, 0.5, 0.3 N = a * h2 + b * h3 + c * h4 # 聚合:邻居信息的加权和 print("邻居摘要:", N) h5 = np.array([1., 1., 1., 1., 1.]) # 节点 5 自己的特征 new_h5 = np.tanh(h5 + N) # 更新:自己的 + 邻居的,再过激活 print("更新后:", new_h5)跑出来N是每个位置都等于0.2*2 + 0.5*3 + 0.3*4 = 3.1的五维向量,new_h5是tanh(4.1) ≈ 0.9995的五维向量。数字本身没什么意义,重点是这三行代码对应了三个动作:a * h2 + b * h3 + c * h4是聚合,h5 + N是把邻居摘要并进自己,np.tanh(...)是加一个非线性让表达能力不退化。权重a、b、c在真正的模型里不是手填的,而是可学习的参数,模型训练就是在调它们。
提示:
tanh只是众多激活函数里的一种,换成 ReLU 或者别的都行,入门阶段不必纠结。真正要记住的是“聚合必须是对称的”——邻居顺序换了,求和结果不变,这才满足前面说的顺序无关。
把这一层重复两遍、三遍,就是多层 GNN。下面这个例子把刚才的东西放大到一个 5 个节点的完整小图上,一次跑通两层传播,你可以直接复制去跑:
import numpy as np np.random.seed(0) # 1) 一张 5 个节点的无向图,用边列表描述 edges = [(0,1),(0,2),(1,2),(1,3),(2,3),(3,4)] N = 5 A = np.zeros((N, N)) for i, j in edges: A[i, j] = A[j, i] = 1 # 无向图对称填两次 # 2) 每个节点先给一个 4 维特征(真实场景里就是年龄、活跃度之类) X = np.random.randn(N, 4) # 3) 加自环:聚合时把自己的信息也带上,不然自己的特征会被冲淡 A_self = A + np.eye(N) # 4) 归一化:度大的节点邻居多,不除一下会把特征加爆 deg = A_self.sum(axis=1) D_inv_sqrt = np.diag(1.0 / np.sqrt(deg)) A_norm = D_inv_sqrt @ A_self @ D_inv_sqrt # 5) 第一层:聚合 + 激活 H1 = np.tanh(A_norm @ X) # 6) 第二层:在第一层的结果上再来一遍,信息扩散到两跳 H2 = np.tanh(A_norm @ H1) print("输入形状:", X.shape) # (5, 4) print("一层后:", H1.shape) # (5, 4) print("两层后:", H2.shape) # (5, 4)形状一直是(5, 4),这个结果会让不少人愣一下:跑了半天维度没变?是的,这一版没有加可学习的权重矩阵,所以它只是在做信息的混合,不是在学习映射。要让它变成真正的网络层,得在中间插一个W,比如A_norm @ X @ W,其中W的形状是(4, 8),输出就变成(5, 8),维度也变了。np.diag那一步是把向量摊成对角矩阵,@是矩阵乘法,1.0 / np.sqrt(deg)是常见的对称归一化写法,目的是让每个节点收到的邻居信号量级差不多。
这十几行代码值得你改着玩三次:把edges改一条,看输出变不变;把层数从二加到五,看数值是不是越来越像;把np.tanh换成lambda x: x,看会不会数值爆炸。改坏了不要紧,这正是理解 GNN 最快的路。
3.3 三类任务:图层面、顶点层面、边层面
模型跑通了,接下来要问一个问题:我费这么大劲算出来的东西,到底拿来干什么?这份 PPT 把图的用途分成三层,我觉得这个划分非常好用,建议直接背下来。
图层面:给整张图打一个标签。比如给一堆分子结构图,判断哪一张里面有两个环。这里的输出是“一个图对应一个结果”,做法通常是把所有节点的表示汇总成一个向量再分类。顶点层面:给每个节点打标签。比如根据学生和两位老师的关系图,判断每个学生站哪一队。边层面:给每条边打标签。比如先把图片里的人和背景分割出来(节点已经有了),再去判断两个人之间是什么关系。
这个三层划分之所以好用,是因为你拿到任何一个新任务,第一件事就可以先问自己:我要预测的到底是整张图、某个节点,还是某条边?答案一确定,输出层的形状和损失函数基本就定了,剩下的都是细节。
4. 沿着这份材料往下走:四个变体各自的脾气
4.1 卷积怎么搬到图上:DCNN 和两条技术路线
前面那个A_norm @ X @ W其实已经摸到了图卷积的门。所谓图卷积神经网络(GCN),干的事就是把“卷积”这个动作从图片搬到图上:图片里是用一个小窗口在像素上滑动,图里没有滑动这回事,那就换成“每个节点收集邻居特征,再乘一个共享的权重矩阵”。这个“共享”很关键,所有节点用同一个W,参数量才压得住,也才具备前面说的顺序无关性。
按照这份 PPT 的分法,GCN 往下还能分成两支。一支走空间域:直接在节点和邻居之间做加权求和,思路就是上面那段代码,直观好懂。一支走频域:借助图的拉普拉斯变换,把信号换到另一个“频率”视角上去做滤波,再换回来。频域这条路数学门槛高一些,但它是很多后续工作的理论根基,看懂它需要补一点线性代数里特征值、特征向量的东西。
这一支里 PPT 点到了两个具体变体。一个是 DCNN(Diffusion-Convolution Neural Network),名字里的 diffusion 是扩散的意思,它把一个节点的信息按不同跳数扩散出去,分别收集一跳邻居、两跳邻居、三跳邻居的信息,各自过一层变换,再拼起来。好处是不同距离的关系被显式区分开了,坏处是跳数一多计算量涨得快。另一个是 NN4G(Neural Networks for Graph),思路更接近“逐层累加”的朴素做法,可以理解成早期把神经网络往图上套的一次尝试。入门阶段不用把这两个的公式全背下来,能说出“它们都是 GCN 家族里对聚合方式做了不同取舍的成员”就够了。
4.2 注意力上场:GAT 给每个邻居分配不同权重
刚才那个手算例子里,a、b、c三个权重是我随口定的。你可能会想,那真实场景里怎么定?最直接的想法是所有邻居一视同仁,取平均。这确实是一种做法,但很容易翻车:一个用户有五百个好友,其中一个是至亲、四百九十九个是点赞之交,你把他们的信息等权平均,重要信号会被彻底稀释。
GAT(Graph Attention Network)就是针对这一点来的。它不再给所有邻居同一个权重,而是让模型自己学出一组权重:对每一对“我”和“我的某个邻居”,算一个注意力分数,再把这些分数归一化,然后按分数加权求和。这跟你在别处听过的注意力机制是同一个东西,只不过用在图上的邻居集合上。它的好处很实在——权重是数据驱动学出来的,而且对邻居数量变化不敏感,一个节点有三个邻居还是三百个邻居,都能处理。
值得注意的是,注意力分数通常要算多次,也就是“多头”。一个头可能关注“关系最近的邻居”,另一个头可能关注“类型相同的邻居”,最后把多个头的结果拼起来。这跟人看问题的角度有点像,单一视角容易偏,多几个视角稳一些。
4.3 自编码器路线:GAE 和 VGAE 在还原什么
第三个变体方向跟前面两个都不太一样。GCN、GAT 解决的是“怎么聚合邻居信息”,而 GAE(图自编码器)和 VGAE(变分图自编码器)解决的是另一个问题:如果我手上连节点标签都没有,只有图结构本身,怎么学出有用的表示?
自编码器的套路大家应该不陌生:先用一个编码器把输入压成一个低维向量,再用一个解码器把它还原回去,训练目标是“还原得越像越好”。放到图上,编码器就是一层层的图卷积(把每个节点压成一个向量),解码器通常是拿两个节点的向量做内积,算出“这两个节点之间应该有多大概率存在一条边”。训练完之后,编码器产出的向量就是节点的表示,可以拿去做下游任务。
GAE 和 VGAE 的区别在于编码器输出的是一个确定的向量还是一个分布。VGAE 多了一层“变分”的处理,让编码器输出均值和方法,再从对应的分布里采样。这一手在数据稀疏的时候往往更稳,因为它相当于给表示加了一点约束,不容易过拟合。判断该用哪个,一个粗略的经验是:图结构比较稠密、样本比较多,GAE 够用;图很稀疏、边很少,VGAE 通常更抗造。
4.4 MPNN 和 NN4G:把它们收进同一个框里
这几个变体名字看得人头大,其实它们之间是有共同骨架的。MPNN(Message Passing Neural Network)做的事就是把它们统一起来:每个节点接收邻居发来的消息(message),把消息聚合(aggregate),再更新(update)自己的状态,最后读出一个结果(readout)。你回头看第 3 章那三步,会发现完全对得上。
这个统一视角的价值在于,你以后每看到一个没听过的新变体,都可以拿这四个空去套:它的消息函数怎么写?聚合用求和还是求平均还是注意力?更新用 GRU 还是简单加一层全连接?读出用的是全局池化还是只取某个节点?套完一遍,这个模型大概长什么样你就心里有数了,不用从头读公式。
把几个变体放在一起对比,能看得更清楚:
| 变体 | 聚合方式的核心特点 | 适合的场景 | 入门难度 |
|---|---|---|---|
| GCN(空间域) | 邻居特征加权求和,权重来自度 | 节点分类、中等规模图 | 低 |
| GCN(频域) | 借助图拉普拉斯做滤波 | 需要理论分析的场景 | 高 |
| DCNN | 按不同跳数分别收集再拼接 | 需要区分远近关系的任务 | 中 |
| GAT | 注意力分数动态加权 | 邻居数量差异大的图 | 中 |
| GAE / VGAE | 编码后靠内积还原结构 | 无标签、做表示学习 | 中 |
| MPNN | 统一的“消息-聚合-更新”框架 | 理解与对比各类变体 | 中 |
表格里“入门难度”这一列是我自己的体感,不是标准答案,你完全可以不同意。但有一件事基本是共识:从空间域 GCN 起步,先把聚合、归一化这两个动作摸熟,再去碰 GAT 和 GAE,顺序别乱,乱了容易半途而废。
5. 新手最容易翻车的五个地方:一份排查清单
5.1 数据准备阶段的三个坑
坑一:邻接矩阵建出来大面积是 0,只有对角线有一排 1。现象是模型训练好几轮,loss 几乎不动,预测结果全是同一个类别。原因八成是节点编号没对齐——你手里有节点名字列表和边列表,但建矩阵的时候一边用了字符串键、一边用了数字下标,导致所有边都落到了不存在的格子上。解决办法很土但很有效:先显式建一张“名字到编号”的映射表,所有边统一过一遍这张表,建完之后打印矩阵的非零元素个数,跟你手上的边数比一比,对不上就是这里出错。我见过太多人跳过这一步,在模型层查了两天,最后发现问题出在第一行。
坑二:没有归一化,训练到一半 loss 开始剧烈震荡甚至变成 NaN。现象是前几十步还算正常,之后突然爆掉,梯度爆炸。原因是不同节点的度差异太大,有的节点只有一个邻居,有的有几千个,聚合的时候度高的一次就把特征加到了几百倍量级。解决办法就是第 3 章代码里那一步:加自环之后按度做对称归一化,1/sqrt(deg)是最常见的形式,也可以用1/deg,前者更平稳。另外,特征本身也建议做个标准化,把数值压到相近的量级。
坑三:层数堆到五六层,效果反而比两层差。现象是验证集指标不升反降,而且所有节点的表示越来越像,趋同到分不开。这个现象叫过平滑,原因是每一层都在混合邻居信息,层数一多,每个节点都“看过”了整张图,大家的表示自然就收敛到一块去了。解决办法不是硬堆层数:大部分任务两到三层就够,需要更远的感受野时,加残差连接、换用更聪明的聚合方式,通常比单纯加深更划算。这一点新手最容易踩,因为直觉上会觉得“深一点总不会错”。
5.2 模型与训练阶段的两个坑
坑四:训练集上表现很好,验证集一塌糊涂。现象是训练 loss 一路降到很小,验证指标原地踏步甚至倒退。原因往往不在模型,而在划分方式:你按节点随机切了训练集和验证集,但图里相邻的节点高度相似,训练集里的节点和验证集里的节点紧挨着,模型其实是在“背答案”。正确的做法之一是按连通子图或者按时间切分,让验证集里的节点跟训练集尽量隔开。这一步不做,你看到的指标是不可信的,属于自己骗自己。
坑五:换个随机种子,结果差出一大截,甚至邻居顺序一换输出就变了。现象是同一份代码跑三次三个结果,你开始怀疑代码有 bug。原因有两个层面:一是模型初始化、采样本身带随机性,这很正常,跑多次取平均即可;二是如果你的聚合函数写成了“按固定顺序拼接邻居”,那邻居输入顺序一变结果就变,这就不正常了,因为它违反了图最基本的性质。解决办法是检查聚合那一步用的到底是不是求和、求平均、注意力这类对称操作,如果是拼接,要么换成对称函数,要么在拼接前给邻居排个固定的序。顺便说一句,把随机种子固定下来,是排查这类问题最省事的习惯,先排除随机性的干扰,再谈别的。
这五条如果你只能记住一条,我希望是坑四。前面四个坑会让你看到难看的数字,坑四会让你看到好看的、但是假的数字,后者更危险。
6. 下一步学什么:从看懂到跑通的一条路线
6.1 一周内的三步走
如果你是零基础,我建议的顺序是这样的。第一步,把这份 100 页的 PPT 从第一页翻到最后一页,不求全部看懂,只求知道每一部分在讲什么,尤其是前面那套“欧式数据和非欧式数据”的对比,把“为什么需要 GNN”这个问号彻底消掉。第二步,把第 2、3 章我给的几段代码亲手敲一遍,一行都别复制粘贴,敲的过程中你会被迫思考每个参数在干什么。第三步,找一份公开的小图数据集(几百到几千个节点那种),把第 3 章那个两层传播的例子改成读真实数据,只需要改两处:把edges换成数据里的边,把X换成数据里的节点特征。
第三步有个小检查,可以用代码判断你的图有没有读对:
import numpy as np # 假设 edges 和 num_nodes 已经从数据里读进来了 num_nodes = 5 edges = [(0,1),(0,2),(1,2),(1,3),(2,3),(3,4)] deg = np.zeros(num_nodes, dtype=int) for i, j in edges: deg[i] += 1 deg[j] += 1 print("最大度:", deg.max()) # 有没有异常大的节点 print("孤立节点:", np.sum(deg == 0)) # 有没有完全没连边的节点 print("边数:", len(edges))deg.max()特别大说明图里存在超级节点(比如某个特别活跃的用户),归一化的必要性就有了实感;孤立节点数量不为 0 说明你读数据时漏了边,或者这份数据本身就有没连边的点,得单独处理。这三行检查花不了半分钟,但能提前挡掉后面一大半的玄学问题。
6.2 该补什么、先别碰什么
| 阶段 | 该做的事 | 暂时可以先放一放 |
|---|---|---|
| 第 1 周 | 看懂欧式与非欧式的差别,跑通两层传播 | 频域 GCN 的推导 |
| 第 2 周 | 在真实小数据集上跑通节点分类 | 大规模分布式训练 |
| 第 3 周 | 对比 GCN 和 GAT 在同一份数据上的差别 | 自己设计新变体 |
| 第 4 周 | 用 GAE 试一次无标签的表示学习 | 复杂下游任务的工程化 |
表格右边那一列不是“没用”,是“现在学性价比低”。我见过有人第一周就扎进频域卷积的数学推导,两周之后放弃了,其实他只要先跑通一个节点分类,成就感就能支撑他走很远。先把能跑的跑起来,再去补理论,这个顺序对新手特别重要。
最后说一个我自己的血泪经验。我刚开始学的时候,上来就想复现一份复杂变体,结果卡在数据读取上三天,连一张图都没建对,差点以为这东西不适合自己。后来我改成每次只做一件事:先确认图建对了,再确认单层能跑,再确认两层没崩,每步都打印形状和非零元素个数。从那以后我每次碰一个新的图数据集,都强制先把“节点数、边数、度分布”这三个数打印一遍再往下走,中途没再翻过大车。你手上这份 100 页的材料,本身就是按“为什么需要、是什么、有哪些变体、用在哪”这条路铺好的,顺着它走,别跳步,第一张图很快就能跑出来。希望帮到你。
本文还有配套的精品资源,点击获取