资讯动态

自监督多尺度邻居预测:图预训练解决小样本节点分类

发布时间:2026/10/24 5:03:34 来源:尧图企业网站定制
最近在做一个节点分类项目标注数据少得可怜少到只有一个类别有几十个标签其他类别几乎全盲。一开始我试图用半监督GNN硬训结果验证集F1惨不忍睹。后来我把思路切换到图预训练想着从无标注的图结构里先抠出一批可迁移的表示再拿去做下游任务。绕了一圈对比学习和自编码之后最终把方案收敛到了一个自己起名叫GAINT的做法——基于自监督多尺度邻居预测的节点特征抽取。这篇文章把我完整的思路、框架设计、训练细节以及踩过的几个典型坑都记录下来希望能给正在做图预训练、或者被小样本节点分类折磨的同学一个可以直接参考的落地方案。先交代一下背景图神经网络这个东西本身是个特征提取器它的优势在于把拓扑关系编码进向量。但GNN的通病是一旦标签稀疏、节点特征又弱模型很难学到区分性表示。图预训练的思路本质上是在没有监督信号的情况下利用图自身的结构训练一个Encoder让它在面对下游小样本任务时已经具备理解图结构的基础能力。GAINT最大的特点是不依赖随机游走生成序列不依赖复杂的图增强策略也不用大量负样本对比学习而是直接通过预测不同尺度下的邻居来逼着模型理解局部和全局结构。我在这里把这套方案称作GAINTGraph-based Adaptive multi-scale Neighbor Transformation它是一个针对节点级特征抽取的预训练框架。以下内容都是基于我个人在一个中等规模开源图数据上的复现和实践参数和结论供参考实际使用时需要结合自己的数据调整。1. 为什么图模型需要预训练不是赶时髦是被标注逼出来的1.1 图学习里的标注荒和结构资产做图学习的人应该都有这个体感能拿到的图往往很大但标记过的节点少得可怜。比如引文网络里几万甚至几十万个节点带类别标签的可能只有几百个。半监督GNN在这种场景下不是不能用但很容易过拟合到那几条标注路径上泛化性很差。这里有个容易被忽略的点图数据虽然标签少但结构本身是一种海量资产。节点之间的连接关系、节点的局部邻域形态、节点在整个图上的社区归属这些都是免费的监督信号。图预训练的核心逻辑就是先把这些结构资产转化为模型能力让Encoder在没有下游标签之前已经学会什么样的节点结构对应什么样的语义空间。我早期走过一段弯路以为预训练就必须像对比学习那样把每个节点和它的增强视图拉近。后来发现图上的对比学习其实非常依赖增强策略的设计DropEdge的比例、特征掩码的比例、视图数量每一项都像在炼丹。而且一旦图的平均度很低增强后的视图很容易变成一张残废图学出来的表示往往是塌缩的。1.2 从游走、重构到对比先走通的三条路子在我开始设计GAINT之前市面上已有的图自监督路线大致可以分成三类我分别都试过各有各的坑。第一类是随机游走派代表思路是DeepWalk、Node2Vec这类的延续。核心是通过在图上做随机游走把节点序列喂给语言模型让邻近节点拥有相似表示。这类方法效果稳定但训练成本高而且它本质上是节点级序列建模对GNN这种消息传递架构不是很友好很难直接复用到一个标准的图Encoder上。第二类是重构派比如图自编码器GAE这类。核心是输入图结构输出希望重建邻接矩阵或者特征矩阵。重建邻接矩阵的缺点是很容易退化成只学度的信息节点连边多表示空间就被度数主导了重建特征矩阵在特征稀疏的场景下效果也一般因为你的特征本来就没多少信息量重建完还是一堆零。第三类是对比学习派像GRACE、GraphCL这类。核心是构造多个视图做节点级或者图级对比。这类方法效果不错但计算量大而且对超参数非常敏感。我在实验中反复碰到的问题是温度系数稍微调大所有表示就开始向一个方向靠拢调小了模型又学不到全局信息。这三条路走通之后我开始想一个问题有没有办法不构造视图、不依赖游走路径直接让GNN从预测邻居这个任务里学到结构表示1.3 GAINT想解决什么特征越稀疏越要考拓扑GAINT的设计动机是针对两个现实场景第一个场景是节点特征稀疏。现实中很多图的节点特征就是one-hot编码或者极短的属性向量甚至干脆没有特征只有结构。这种时候你不能指望自编码器重建出什么花来唯一的信息来源就是邻居结构。第二个场景是标签极少但结构丰富。我遇到的那个节点分类项目就是典型每个类别只有个位数的标注样本。这种时候如果预训练Encoder已经能从结构里提取出节点的拓扑指纹下游只需要很少的标签就能把分类器训好。GAINT选择的道路是把自监督信号定义为多尺度邻居预测。它既不像重构派那样重建特征也不像对比学习那样拉近视图而是要求模型在给定的节点表示基础上去预测该节点在多个不同尺度上的邻居节点。这个任务逼着Encoder同时理解紧邻关系一跳邻居、传递关系多跳可达、以及社群归属全局上下文。特征越稀疏这个拓扑信号的价值就越突出。2. GAINT的总体框架把邻居预测拆成三个尺度2.1 为什么是邻居预测而不是重建特征首先说清楚一个基础判断GNN在做消息传递时每一层的本质就是把邻居的信息聚合到自己身上。所以我的邻居是谁这件事对GNN来说是最自然的监督信号。你要模型学会一个节点表示最直接的自监督问题就是给你一个节点的表示你能不能在候选集里找出它的邻居这个任务和对比学习最大的区别是对比学习关注的是两个节点应该是相似的拉近距离而邻居预测关注的是两个节点是否有边关系判别。前者容易让模型倾向于学习平滑表示后者则逼着模型学习判别性边界因为你要在大量负样本中区分出正样本表示必须具备足够的区分度。另一个选择此方案的原因是工程上好落地。你不需要生成复杂的增强视图只需要在训练时从每条边上采样正样本、从全图采样负样本然后计算得分。这天然就适合大规模图数据上的batch训练。2.2 三个尺度具体指什么一跳、K跳、社区GAINT里的多尺度我最终落地为三个预测头分别对应三种邻居定义。尺度一一跳邻居Local。对中心节点u直接预测它的直接邻居v。这个任务训练的是模型对紧邻关系的刻画能力类似于让模型学会谁和谁在一个句子里经常连在一起。在这个尺度上模型能学到非常细粒度的局部结构——三角关系、同配性、局部聚类系数都在这里面发挥作用。尺度二K跳混合邻居Mesoscale。对中心节点u采样它的2到3跳邻居作为正样本。这里不是严格只取二阶邻居而是按照随机游走步长做一个混合采样——2跳邻居占比大约七成3跳邻居占三成。这个尺度的作用是让模型学到传递性相似两个节点不直接相连但共享很多共同邻居它们在语义上往往是可比的。比如在引文网络里同一子领域的论文不一定直接互相引用但它们的参考文献高度重叠。尺度二能捕捉这种关系。尺度三全局社区上下文Global。这一步是对全图做一个粗粒度的社区划分或者用带重启的随机游走生成每个节点的全局上下文向量。然后把与中心节点同属一个社区或者游走访问概率高的一组节点作为正样本。这个尺度的目的是防止模型把目光完全局限在局部邻域里逼着它感知社区归属和大尺度结构模块。三个尺度是平行的三套监督信号而不是串行的。模型在每一个batch里同时在这三个任务上做预测梯度回传时加权相加。这样设计的好处是不同尺度的信息在同一个表示空间里被拉齐下游做特征抽取时无论你关心的是局部相似还是全局归属都能拿到对应的语义分量。2.3 编码器与预测头的组织方式GAINT的编码器我采用了标准的消息传递架构——我实测下来用GraphSAGE的聚合函数比GCN更稳主要是GCN在处理极稀疏特征时容易出现表示过度平滑GraphSAGE的拼接式更新会保留更多节点自身信息。编码器有三层每层隐层维度设为256激活函数用ReLU层间加入dropout。在编码器之上接三个并行的预测头。每个预测头都是一个两层MLP把编码器输出的中心节点表示h_u投影到一个和节点表示等维的空间然后和候选节点表示做点积再除以温度参数得到预测得分。这里有一个我在实现初期忽略掉的细节预测头输出的向量和节点表示必须做L2归一化否则点积的数值范围不稳定温度参数变得非常难调。归一化之后得分天然落在[-1, 1]区间内乘上温度缩放后这给训练收敛提供了很大的稳定空间。2.4 损失函数怎么拼GAINT每个尺度的损失都采用InfoNCE的变体形式。以尺度一为例给定中心节点u和它的一个一跳邻居v正样本以及从全图中采样的一组负样本集合N损失定义为L_1 -log( exp(score(u, v) / τ) / ( exp(score(u, v) / τ) sum_{n in N} exp(score(u, n) / τ) ) )三个尺度分别计算L_1、L_2、L_3最终损失为L_total λ_1 * L_1 λ_2 * L_2 λ_3 * L_3三个权重λ我实践中取的值是0.4、0.3、0.3。一开始我试图三个尺度平均权重各取0.33后来发现尺度三全局社区的监督信号噪声更大如果把它的权重提得太高模型会把过多的注意力放在宏观结构上反而丢了局部判别能力。降到0.3之后整体效果更均衡。温度参数τ的取值通常从0.5到1.0之间我最终定在0.7。这个参数既不像对比学习那样敏感但又确实会影响表示分布的锐度。τ太小预测头会过分追求把正样本分得更开训练不稳定τ太大正负样本梯度趋同表示学不出来。0.7是我在几个数据集上都能稳定工作的值。3. 训练稳定性的核心旋钮防塌缩、负采样与子图构建3.1 塌缩是最常见的失败模式没有之一我在调试GAINT的过程中遇到的最常见的问题是表示塌缩。具体表现是loss在下降但你把学到的节点表示丢到TSNE里看所有点挤成一个实心球完全没有结构。为什么会出现塌缩因为邻居预测本质上给了模型一个逃课的机会——只要所有节点表示都学成同一个常向量那么任意两个节点的得分都是一样的正样本和负样本的区分度为零但softmax损失依然会收敛到一个均衡值。如果你的预测头没有被正则化约束模型完全可以通过这种方式欺骗优化器。我的解决手段有两个。第一是强制L2归一化这个前面已经提过归一化后表示都落在单位球面上至少不会出现所有点集中在原点这种极端塌缩。第二是给预测头加一个额外的均匀性正则项直接惩罚表示分布过于集中在球面上的某个区域。具体实现就是计算一个小batch内所有节点表示的均值向量把它拉向零向量同时把协方差矩阵的对角线往1方向拉。这其实就是对比学习里常用的维熵正则思想实践下来对邻居预测同样有效。如果你在跑类似方案我建议每几个epoch就做一次体检随机抽1000个节点计算它们的表示均值模长、以及两两相似度的分布。正常情况下表示均值模长应该接近0相似度分布应该落在[-0.2, 0.5]区间内如果相似度普遍超过0.8那就是塌缩前兆需要赶紧降低温度或者增加正则系数。3.2 负样本怎么选从均匀采样到游走加权负采样的设计对邻居预测任务的影响远比我预想的大得多。最朴素的方案是全图均匀随机采样。优点是简单但问题也很明显如果一个节点和中心节点之间的最短路径距离只有4它虽然不是直接邻居但结构上高度相关均匀采样会把这一类难负样本疯狂往外推模型被迫学习短路径节点也完全不一样的错误信号表示空间的局部结构会被破坏。我后来改进的方案是随机游走加权负采样。具体操作对每个中心节点u先做一次长度为5的随机游走记录游走过程中访问到的每个节点的频率用这个频率作为它被选为负样本的权重——访问频率越高的节点越有可能被选走。这相当于把与u在结构上接近但不在正样本集合里的节点作为难负样本暴露给模型。实测效果比均匀采样好很多特别是在社区结构明显的数据集上模型学到的表示边界要清晰得多。负样本的数量也很有讲究。我试验过从16到256的取值发现在中等规模图几万个节点上64个负样本是一个性价比很高的点。小于32正负样本区分不充分模型学不到精细结构大于128训练速度急剧下降但精度提升微乎其微。3.3 子图采样与batch组织大图训练的关键GAINT每次前向计算需要用到中心节点及其邻居信息不可能在全图上做完整消息传递。所以在训练时我对图做了邻居采样式的mini-batch构建。每次迭代随机采样N个中心节点我通常用256。对每个中心节点用邻居采样器逐层采样第一层采样12个邻居第二层采样8个第三层采样4个。这样构建出一个以中心节点为根的二跳邻域子图喂给GNN做消息传递。这个采样方式基本沿用了GraphSAGE的做法好处是内存占用可控坏处是采样开销会成为训练瓶颈。一个很关键的实操细节正样本邻居必须在采样前就固定下来。你不能一边采样邻居一边选正样本否则每次迭代看到的结构都不一样模型会陷入比较不同批次结构的混乱。我的做法是在生成每个batch时先确定中心节点再从它们的多跳邻居集合中预选出固定数量的正样本然后带着这批正样本去构建子图。3.4 温度、归一化和学习率的经验取值把训练配置直接给出来方便参考。我用的Adam优化器预训练阶段基础学习率设为0.001并用cosine退火调整到0.0001。batch size为256训练轮数大约40轮每个epoch包含500个batch所以在中等规模数据上整套预训练大概不到两个小时。L2归一化放在编码器输出和预测头输出两层这个前面说过了。Dropout在编码器层间设为0.2在预测头内部设为0.3。我试过把dropout提高到0.5预训练loss会变得非常不稳定反而容易提前碰到塌缩问题。还有一个容易忽略的点梯度裁剪。邻居预测任务在训练早期单条样本的梯度范数可能差异巨大——中心节点如果是一个hub节点度很高它的正邻居数量多得分高梯度就很大。我给全局梯度设了一个最大范数1.0的裁剪训练立刻稳定下来。这个细节在写网络代码时就要带上不然后期调loss经常甩锅给学习率太高。4. 从预训练到下游节点特征抽取的正确使用姿势4.1 三种不同投资度的接法预训练完了下游任务要怎么用这套模型我根据投入成本由低到高总结三种用法。最简单的是直接抽取把预训练好的Encoder在整张图上跑一次前向拿到每个节点的表示向量z_v然后把它当作节点的预训练特征直接喂给下游的浅层分类器比如逻辑回归或者两层的MLP。这种方法适合下游只有一个分类任务且标注量极少的场景。因为浅层分类器参数少几百个标注节点就足够了。第二种是特征拼接把预训练表示z_v和原始特征x_v拼接起来一起作为下游分类器输入。这么做的好处是预训练表示补充了结构信息原始特征补充了属性信息两者互补。实测中直接替换只给模型z_v通常会损失部分属性信息尤其是当原始特征本身质量不错的时候。第三种是微调把预训练权重作为下游GNN的初始化然后在少量标注数据上用监督损失端到端训练。这里需要把预训练学习率调低一个数量级通常从0.0005调到0.00005左右不然容易灾难性遗忘——模型在预训练阶段学到的是结构感知能力微调阶段如果学习率太大这些能力会被新任务的梯度快速覆盖掉。我自己的经验法则是如果标注样本超过每个类别50条直接微调效果最好如果少于每类20条建议用直接抽取或特征拼接如果介于两者之间可以试试微调特征拼接混合——分类器同时接收微调模型的表示和原始特征。4.2 特征拼接为什么经常比替换更稳不少同学做迁移时习惯性地用预训练表示替代原始特征觉得预训练的特征肯定比原始特征好。这个想法在图上不一定成立。原始特征虽然稀疏但它往往携带的是语义信息——比如一篇文章的主题词、一个商品的类别编码。而预训练表示携带的是结构信息——邻居关系、社区归属。这两类信息是不同维度的替换掉任何一个都是浪费。我在实验中做过一个对比只使用原始特征的逻辑回归F1大约0.61只使用GAINT预训练表示F1大约0.72两者拼接后F1能到0.79。拼接带来的提升不是线性的而是结构信息和语义信息互补形成的。所以如果你不确定怎么用预训练表示优先考虑拼接这几乎不会让你亏。4.3 少样本微调的小技巧如果走微调路线有几个小技巧值得分享。第一冻结第一层。GNN的第一层编码的往往是最底层的局部聚合模式这种模式在预训练阶段已经被练得很扎实。微调时如果从头更新第一层少量标注很容易把这一层带偏。我一般冻结第一层参数只微调后面两层。等到标注数据比较充裕时再解冻。第二下游温度可以降低。预训练阶段我们用了0.7的温度来刻画邻居分布但下游分类任务需要的是锐利的分类边界所以微调时把注意力头或者输出层的温度降到0.1分类器往往收敛更快。第三给分类头单独配学习率。分类头是随机初始化的需要相对高的学习率去快速找到决策边界Encoder是预训练过的需要低学习率去保持结构能力。两套学习率分开设一般是分类头0.001Encoder 0.0001效果比统一学习率明显更好。5. 对照实验与典型翻车记录5.1 基线设置和结果概览为了验证GAINT到底值不值得做我做了一组对照实验。数据集用的是开源社区常见的引文网络基准Cora、Citeseer和一个规模更大的商品共现图。下游任务统一设为节点分类每类取20个标注节点训练其余用于测试。指标用F1 score。方案F1 Score引文网络AF1 Score引文网络BF1 Score商品共现图GCN无预训练直接半监督0.610.570.63随机游走预训练 GCN微调0.650.620.66对比学习预训练GraphCL思路0.680.650.69GAINT 特征拼接0.740.700.75GAINT 微调0.760.710.78GAINT在两个尺度上都有收益一是相比无预训练GNNF1提升10-15个点左右二是相比对比学习F1提升4-6个点。这印证了最初的设计判断直接预测邻居结构比构造视图拉近表示更适合图数据的预训练。5.2 翻车现场一预测头初始化不当表示全挤在原点附近我第一次跑通GAINT时loss降得很快但下游F1只有0.2几乎等效于随机。排查了半天发现是预测头MLP的初始化出了问题。MLP输出层的权重非常大导致预测头直接把中心向量映射到一个非常大的范数域L2归一化之后所有的输入向量被压到球面上同一个点附近。修复方案是把预测头输出层的初始化改成零均值、小方差的正态分布标准差设为0.01同时把归一化放在MLP的输出层之后而不是之前。改了之后立刻生效表示分布开始呈现明显的聚类结构。这个坑说明在自监督预训练里loss曲线只是表象真正的疗效要看你表示的分布质量。改完初始化之后我特意又检查了一遍表示均值模长和相似度分布确认没有偏差才继续往下走。5.3 翻车现场二负样本数照搬大模型方案显存和效果双双崩掉我在设计负采样数量时一开始参考了某个大规模对比学习项目的经验直接设成了1024。结果是显卡内存直接被打爆把batch size从256降到64才跑起来但效果居然比64个负样本的版本还差。后来想明白了邻居预测任务和对比学习任务对负样本的需求不一样。对比学习的正样本是同一节点的不同视图本身就不多所以需要更多负样本提供结构压力而GAINT的正样本是真实存在的邻居邻居关系是多对多的一个中心节点可能有几十个正样本因此模型的监督压力比对比学习充足得多。这种情况下负样本超过一定数量后边际收益会变成负值——过多的负样本把表示空间的决策边界推得过于激进反而损伤了局部邻域的精细关系。如果要做大图我建议把负样本数设在64到128之间同时使用难负样本加权采样来弥补数量上的不足。5.4 翻车现场三邻居采样顺序影响结果但影响是可控的还有一次让我困惑了很久的现象同样的代码、同样的参数只是调整了batch内中心节点的排列顺序下游F1居然从0.73波动到0.70。一开始以为采样种子问题反复测了三轮才发现真正的影响因素是邻居采样器的工作顺序——因为采样器是逐层进行的如果中心节点在一个batch内排布太集中比如全部来自某个稠密社区那么采样到的邻居子图重叠度很高梯度更新的多样性降低。解决方式是加一个batch内shuffle每次迭代对中心节点索引做一次随机置换同时把采样器的随机种子也一并更换。这样即使多个中心节点在原始图上本来就邻近它们在batch内部也会被随机打散子图重叠度维持在合理水平。加了这一步之后实验结果的方差显著下降。顺带一提如果你发现几次实验差距很大先不要怀疑模型结构先检查采样和batch的随机性是否到位。图上的自监督训练随机性来源多必须把种子管理当成正式代码逻辑来写否则实验结论根本不敢信。最后想说的整个GAINT做下来我最大的感受是图预训练不需要那么多花哨的模块关键在于把预测什么这个问题想清楚。对比学习在图上之所以难调是因为它对增强策略太敏感而邻居预测直接把监督信号挂在结构本身上简单、直接、不容易欺骗模型。当然GAINT也不是万能的——如果你的图平均度极低或者节点特征非常鲁棒、标签又足够多那直接训练GNN就是最优解。预训练的收益本质上集中在结构信息量大但标注信号稀薄的场景里。这套方案我现在已经沉淀成自己处理图项目时的默认前置步骤先预训练拿表示再根据标注量决定接法整个流程稳定了许多。如果你正在小样本图任务里挣扎不妨试试这个思路特别是多尺度邻居预测这个信号设计我觉得迁移到很多图数据上都值得一试。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑