ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

对比学习驱动的深度图聚类:方法、调参、代码与排错

对比学习驱动的深度图聚类:方法、调参、代码与排错 1. 先把问题说透深度图聚类到底卡在哪一步图聚类这件事说到底就是把一张图里的节点分成若干组使得组内连接密集、组间连接稀疏。听起来像个老问题谱聚类、模块度优化、标签传播这些方法已经跑了几十年。但只要真拿它们去处理现实中的图问题立刻冒出来节点数上百万、特征维度几千、图结构还带噪声传统方法要么算不动拉普拉斯矩阵的特征分解要么得手工设计相似度核稍微换个数据集就得重新调参。深度图聚类Deep Graph ClusteringDGC就是在这种背景下被推上前台的——用图神经网络做编码器把结构和特征一起压进低维表示再在表示空间里做划分。我最早接触这个方向的时候思路还停留在GAE 重构邻接矩阵 KMeans的组合上跑完发现指标比谱聚类好不了多少。后来才明白问题不在于编码器不够深而在于优化目标和聚类目标根本不是一回事。重构损失关心的是能不能把边还原出来聚类关心的是哪些点该在一起两者之间存在天然的错位。对比学习进入这个领域恰恰是为了补上这个错位——它不依赖标签通过构造正负样本对直接约束表示空间的几何结构。这篇文章我会把对比学习在图聚类里的几种典型落法、关键参数怎么定、代码怎么落地、以及实际跑下来会遇到哪些坑一次性讲清楚。适合已经了解 GNN 基础、想往无监督方向深入的同学也适合手上有一批无标注图数据、急需一个可落地方案的工程同学。1.1 从谱聚类到图自编码器深度模型为什么非上不可先说清楚深度模型进来的理由不然很容易陷入为了深而深的误区。传统谱聚类本质上是对图的拉普拉斯矩阵做特征分解取前 k 个特征向量再做 KMeans。它的理论很漂亮但工程上有两个硬伤一是特征分解的复杂度在节点数上是立方级的几万个节点就开始吃力二是它只用了图的拓扑结构节点自带的特征文本、画像、属性完全用不上。现实场景里节点的属性信息往往比边更有区分度比如社交网络里用户的兴趣标签、电商图里商品的类目文本。图自编码器GAE/VGAE把这个问题解决了一半编码器用 GCN 把特征和结构一起编码解码器重构邻接矩阵训练完成后取中间层表示做聚类。这条路子在当时确实打开了局面但它把学表示和做聚类拆成了两个阶段中间没有任何反馈。编码器学到的表示是最有利于还原边的而还原边这件事对稠密社区内部的区分度并不敏感——一个节点和它邻居的边很容易还原但它在社区边界上的归属重构损失根本不管。更麻烦的是重构目标会让表示偏向于保留局部结构长程的社区结构反而被稀释掉。后来出现的一批方法开始尝试联合优化比如在编码器后面接一个聚类头用 KL 散度把软分配往目标分布上推形成自训练闭环。这类思路确实把两个阶段捏到了一起但也引入了新的麻烦自训练依赖伪标签伪标签来自当前聚类结果一旦早期划分偏了后面就会一路错下去越训越自信。这就是我下面要展开的核心痛点。1.2 三个真痛点表示坍塌、伪标签噪声、目标错位表示坍塌是最致命也最隐蔽的一个。所谓坍塌是指编码器输出的表示在空间里挤成一团所有节点的向量几乎平行余弦相似度接近 1。这时候你做 KMeans它会把所有点分到一两个簇里剩下的簇全是空的或者只有零星几个点。指标看上去 ACC 可能还有 30% 多因为大簇恰好覆盖了某个真实类别但模型实际上什么都没学到。坍塌在纯重构目标下不容易发生但在对比学习或者聚类自训练目标下非常容易出现因为这两类目标都存在把所有东西映射到同一个点就能让损失最小的退化解。伪标签噪声是自训练路线的通病。软分配矩阵里高置信度的节点通常是度数高、特征典型的节点低置信度的往往是边界节点。自训练会不断强化高置信度那部分边界节点的归属被永久固化。我在一个引文网络上做过统计训练到 200 轮之后置信度低于 0.4 的节点大概占 15%这些节点的划分结果和初始化时的 KMeans 结果重合度超过 90%——也就是说后半程的训练对它们几乎没有任何修正作用。目标错位则是一个结构性矛盾。重构损失、聚类损失、对比损失三者的梯度方向未必一致。重构想让邻居表示接近聚类想让同簇节点接近对比想让正对接近负对远离。当图的社区结构和局部邻接不一致时比如存在大量跨社区的桥边这几个目标就会互相拉扯表现出来就是损失在降但指标在震荡。提示判断是否发生坍塌有个特别快的土办法——随机抽 200 个节点算两两余弦相似度的均值。健康情况下这个值在 0.1~0.5 之间如果超过 0.9基本可以断定塌了。1.3 对比学习补的到底是哪块板对比学习的价值用一句话概括它提供了一个不依赖标签、又能直接约束表示几何结构的自监督信号。InfoNCE 这类损失可以拆成两项看一项是对齐性alignment拉近正样本对另一项是均匀性uniformity把表示推向超球面的均匀分布。均匀性这一项天生就是反坍塌的——所有点挤在一起均匀性损失会爆炸。更关键的是它不需要重构邻接矩阵。传统的重构目标隐含了一个假设邻居的表示应该相似这个假设在异配图heterophily上直接失效。对比学习可以通过视图构造绕开这个假设比如对同一张图做两次不同的扰动要求两次编码结果一致这个约束只关心扰动不该改变语义而不关心谁和谁相连。这个视角的转换是深度图聚类这几年最大的思路变化。我在实际项目里的体会是对比项最好当骨架用聚类项当导向用。骨架负责把表示空间撑开、防止塌缩导向负责把表示往簇结构上拉。两者缺一不可但配比很讲究后面第 3 节会详细说。2. 四种落法对比学习和图聚类怎么拼对比学习和图聚类的结合方式目前主流能归成四类。这四类不是互斥的很多方法会取两三种混着用。我把它们的构造逻辑、优缺点和适用场景挨个拆开讲你在选型时可以直接对号入座。2.1 视图级对比给同一张图照两张影子视图级对比是最直观的一类思路来自图像领域的 SimCLR同一张图做两次随机增强得到两个视图同一个节点在两个视图里的表示构成正对不同节点的表示构成负对。图上的增强手段主要有三种——边扰动随机删掉一部分边、特征掩码把部分特征维度置零、子图采样随机采样一个子图。这套东西的好处是实现简单、通用性强几乎不用改编码器结构。缺点也很明显增强的力度极难拿捏。增强太弱两个视图几乎一样正对太容易区分学不到东西增强太强图的语义被破坏正对之间的一致性假设不成立了模型反而在学噪声。我在引文网络上试过边扰动率 0.6 的配置训练损失降得很漂亮但聚类 NMI 比不加对比还低——因为图已经被打散成随机图了。另一个隐藏成本是负样本。标准 InfoNCE 要求每个正对配一批负样本节点数是 N 的话相似度矩阵是 N×N几万个节点直接爆显存。工程上一般用两种绕法一是每个 batch 内做对比batch 内的其他样本当负样本二是对每个节点随机采样固定数量的负样本比如 256 个。前者实现简单但受 batch size 限制后者需要额外写采样逻辑。2.2 节点与原型对比把簇心直接放进表示空间原型对比prototype contrast是我个人最偏好的做法因为它把聚类结构直接编进了对比目标里。具体做法是维护 k 个可学习的原型向量可以理解成簇心每个节点和自己所属的原型构成正对和其他原型构成负对。原型本身也可以通过聚类中心的滑动平均来更新不一定非要当可学习参数。这么设计的好处是对比目标和聚类目标在语义上完全对齐了——节点往自己簇心靠、远离别的簇心这本身就是聚类想要的效果。它不需要构造两个视图也就不存在增强力度难调的问题。同时负样本数量天然等于 k-1计算量只和簇数有关跟节点数无关扩展性很好。坑在于原型的初始化。如果 k 个原型初始化得太近早期所有节点都会被拉到同一个原型上形成事实上的坍塌。我的做法是用一次 KMeans 的结果初始化原型或者干脆从表示空间里随机挑 k 个相距较远的节点当初始原型。另外原型数量 k 如果设错比如真实类别是 7 类但你设了 10多出来的原型会抢走一部分节点指标会掉得很难看。这一点目前没有特别优雅的无监督解法实践中一般靠肘部法、轮廓系数或者谱分析的特征值间隙来估。2.3 簇级对比与难负样本让同类靠拢、异类推开簇级对比是把对比的粒度从节点提升到簇。做法是先对当前表示做一次软聚类得到每个簇的质心表示然后让簇和簇之间互相排斥——同一簇内的节点表示要紧凑不同簇的质心要拉开距离。这类方法和原型对比有点像区别在于簇质心是动态算出来的不是可学习参数。难负样本挖掘是另一个常被忽视的点。随机采的负样本里绝大多数和锚点节点的相似度都很低梯度贡献接近零等于白算。真正有用的是那些看起来像但实际不同类的难负样本。常见的挖法是先算所有候选的相似度取相似度最高的一批但排除正样本作为负样本。这里的风险是假负样本——两个节点视觉上很像实际上是同一类但被误判成负对强行推开会破坏表示。缓解办法是用聚类置信度做过滤只在高置信度的不同簇之间构造负对。2.4 停梯度与无负样本绕开大 batch 的取舍BYOL 那条路线证明了不用负样本也能避免坍塌关键在于停梯度stop-gradient和非对称结构一个分支带预测头另一个分支不带且不回传梯度给另一分支。图领域有对应的工作把这套搬了过来用动量编码器更新目标分支主分支去预测目标分支的输出。这种做法的最大好处是不依赖大 batch也不依赖负样本采样在十万级节点的图上跑起来很舒服。代价是训练更敏感动量系数、预测头结构、学习率三者耦合很紧稍微不对就容易塌。而且它缺少均匀性约束表示的分布容易偏向某些方向。我一般会在它上面再加一个轻量的均匀性正则项把表示协方差矩阵往单位阵推实测能明显改善簇的均衡度。方法类型需要负样本计算开销调参难度适合的图规模视图级对比是中到高高增强力度敏感中小规模节点-原型对比是k-1 个低中任意规模簇级对比 难负样本是中中高中小规模停梯度无负样本否低高大规模3. 参数怎么定增强力度、温度与损失配比这一节是我认为整篇文章最该反复看的部分。对比学习类的方法结构和公式都是公开的真正决定结果的是那几个标量超参。下面每个参数我都给出取值逻辑和实测区间。3.1 图增强边扰动和特征掩码的取值逻辑边扰动率 p_e 的本质是控制视图之间的信息差。太低正对太简单太高语义被破坏。取值和一个图的平均度强相关平均度高的稠密图可以承受更高的扰动率因为删掉一部分边之后局部结构还在稀疏图平均度 2~3就很脆弱p_e 超过 0.3 就容易把节点变成孤岛。我的经验区间图类型平均度边扰动率 p_e特征掩码率 p_f稀疏引文网络2~50.1~0.20.1~0.3中等密度社交图10~300.2~0.40.2~0.4稠密共现图500.3~0.50.3~0.5特征掩码率 p_f 的取值还要看特征维度。特征维度高、且信息冗余比如 TF-IDF 向量时掩掉 30% 也不太伤特征维度低比如只有 10 维画像特征掩 20% 就可能把关键信号抹掉了。有个技巧是按特征的重要度加权掩码用特征方差或者互信息做权重重要特征少掩、冗余特征多掩实测比均匀掩码好 1~2 个点。注意边扰动一定要做无向化处理。删边的时候如果只删一个方向剩下的单向边会让 GCN 的聚合结果出现方向性偏差这个坑我踩过一次找了两天才定位到。3.2 温度系数 τ分母里的那把尺子温度系数 τ 出现在 InfoNCE 的分母里控制 softmax 的尖锐程度。公式写出来是这样的$$L_{i} -\log \frac{\exp(\text{sim}(z_i, z_i^{})/\tau)}{\sum_{j}\exp(\text{sim}(z_i, z_j)/\tau)}$$τ 越小分布越尖锐模型越关注那些相似度高的难负样本梯度也越大τ 越大分布越平滑所有负样本的权重被拉平对比信号变弱。常见取值 0.2~0.5图像领域 SimCLR 用的是 0.5图领域因为负样本质量参差很多人用 0.2~0.3。有个容易被忽视的耦合关系τ 的合理取值和 batch size 有关。batch 小的时候负样本少需要更小的 τ 来放大有限负样本之间的差异batch 大了负样本多τ 可以适当放大。我做过一组对照节点数 2708 的引文网络batch 取全图时 τ0.5 表现最好如果改成 batch256 的小批量τ0.2 反而更优。如果你不确定就从这个网格里搜τ ∈ {0.1, 0.2, 0.3, 0.5, 0.8}。别嫌麻烦这个参数对结果的影响经常比网络层数还大。3.3 对比项与聚类项的配比 λ联合损失一般是这个形式$$L L_{cluster} \lambda L_{contrast}$$λ 控制两者的相对权重。λ 太大模型只顾着撑开表示空间簇结构反而被冲淡表现为簇的划分和真实类别对不上λ 太小对比项起不到防坍塌的作用聚类项会把表示往退化方向拽。我的建议是分段式而不是固定权重。前 100~200 轮只训对比项相当于预训练让表示空间先被撑开之后把 λ 设成 0.1~1.0 的固定值联合训练。这个策略比一上来就联合训练效果好得多因为早期聚类结果本身很不可靠用不可靠的伪标签去引导表示等于给模型喂噪声。如果你非要用固定权重那就从 λ1.0 起步观察聚类项的 KL 损失和对比损失的量级让两者大致在同一个数量级上。数量级差太多的话小的那一项基本等于不存在。3.4 三道防坍塌保险坍塌是对比学习图聚类里最容易翻车的地方我一般会同时上三道保险第一道是投影头加停梯度。编码器输出 z再加一个两层 MLP 投影头得到 h对比损失算在 h 上聚类损失算在 z 上。这个设计的道理是投影头吸收掉了对比学习为了区分样本而引入的过度信息让 z 保留更多和聚类相关的语义。梯度只回传到编码器的一部分也能抑制塌缩。第二道是均匀性正则。直接对表示矩阵算一个均匀性损失把归一化后的表示往均匀分布上推$$L_{uni} \log \mathbb{E}_{i,j}\left[\exp(-2| \tilde{z}_i - \tilde{z}_j |^2)\right]$$这一项权重给很小0.01~0.1就够了主要是兜底。第三道是聚类均衡约束。每次更新后检查各簇的样本数如果最大的簇占比超过 40%就把它的原型往外推一点或者对分配矩阵做一次 Sinkhorn 归一化强制让各簇的边缘分布接近均匀。这一道对指标提升最直接尤其是在类别不平衡没那么极端的图上。4. 从零跑通一版代码与训练流程光说原理容易飘我把一套能直接跑的最小实现贴出来基于 PyTorch 和 PyG。这套代码我在好几个中等规模的图上验证过节点数几千到几万都能跑。4.1 数据与预处理先做两件最基础的事特征行归一化、边去重和无向化。特征归一化不是可选项GCN 第一层的聚合对特征尺度非常敏感不做归一化的话方差大的那几维会主导整个表示。边去重是因为很多公开数据集的边表里有重复边重复边在 GCN 里等价于给这条边加权会扭曲聚合结果。import torch import torch.nn.functional as F from torch_geometric.utils import to_undirected, remove_self_loops def preprocess(data): x data.x.float() x F.normalize(x, p2, dim1) edge_index, _ remove_self_loops(data.edge_index) edge_index to_undirected(edge_index, num_nodesdata.num_nodes) return x, edge_index4.2 编码器、投影头与聚类头编码器用两层 GCN 就够了。这里我要强调一下层数图聚类任务上两层几乎总是最优的三层以上的表示会出现过平滑——节点表示趋同簇的边界被抹平。我做过对照同一套对比损失两层 GCN 的 NMI 比三层高 5~8 个点比四层高十几个点。如果你的图确实需要更大的感受野用 APPNP 或者带残差的跳连别硬堆层数。from torch_geometric.nn import GCNConv class Encoder(torch.nn.Module): def __init__(self, in_dim, hid_dim, out_dim, dropout0.5): super().__init__() self.conv1 GCNConv(in_dim, hid_dim) self.conv2 GCNConv(hid_dim, out_dim) self.dropout dropout def forward(self, x, edge_index): x self.conv1(x, edge_index) x F.relu(x) x F.dropout(x, pself.dropout, trainingself.training) x self.conv2(x, edge_index) return x class Projector(torch.nn.Module): def __init__(self, dim, hid_dim256): super().__init__() self.net torch.nn.Sequential( torch.nn.Linear(dim, hid_dim), torch.nn.ReLU(), torch.nn.Linear(hid_dim, dim) ) def forward(self, z): return F.normalize(self.net(z), dim1)聚类头直接用软分配不需要额外的参数矩阵簇心单独维护def soft_assign(z, centers, alpha1.0): # Student-t 核alpha1 时即 DEC 的标准形式 dist torch.cdist(z, centers, p2) ** 2 q (1.0 dist / alpha).pow(-(alpha 1) / 2) q q / q.sum(dim1, keepdimTrue) return q def target_dist(q): weight q ** 2 / q.sum(dim0, keepdimTrue) return (weight.t() / weight.sum(dim1)).t()4.3 增强、对比损失与联合目标增强函数和对比损失from torch_geometric.utils import dropout_edge, mask_feature def augment(x, edge_index, p_e0.2, p_f0.2): ei, _ dropout_edge(edge_index, pp_e, force_undirectedTrue) x_aug, _ mask_feature(x, pp_f, modemultinomial) return x_aug, ei def info_nce(h1, h2, tau0.3): logits h1 h2.t() / tau labels torch.arange(h1.size(0), deviceh1.device) loss_a F.cross_entropy(logits, labels) loss_b F.cross_entropy(logits.t(), labels) return (loss_a loss_b) / 2注意这里做了双向计算再取平均。原因是相似度矩阵本身不对称虽然 h1h2.T 在归一化后理论上对称但因为负样本集合的视角不同两个方向的梯度贡献不一样双向计算能让每个节点既当一次锚点也当一次负样本实测比单向高 1~3 个点。节点数超过两万的时候全量相似度矩阵会吃掉大量显存。这时候改成随机负采样版本def info_nce_sampled(h1, h2, tau0.3, n_neg256): h1 F.normalize(h1, dim1) h2 F.normalize(h2, dim1) n h1.size(0) idx torch.randint(0, n, (n, n_neg), deviceh1.device) neg h2[idx] # [n, n_neg, d] pos (h1 * h2).sum(dim1, keepdimTrue) / tau neg_sim torch.bmm(neg, h1.unsqueeze(2)).squeeze(2) / tau logits torch.cat([pos, neg_sim], dim1) labels torch.zeros(n, dtypetorch.long, deviceh1.device) return F.cross_entropy(logits, labels)4.4 训练循环与监控指标整体训练分两段前段只跑对比后段联合。每轮都要记录三个数对比损失、聚类 KL 损失、以及簇大小的最大占比。第三个数是坍塌的早期预警一旦超过 0.5 就该停下来检查。def train(model, projector, x, edge_index, k, epochs400, warmup150, lam0.5, tau0.3): opt torch.optim.Adam( list(model.parameters()) list(projector.parameters()), lr1e-3, weight_decay5e-4 ) # 用一次前向 KMeans 初始化簇心 with torch.no_grad(): z model(x, edge_index) from sklearn.cluster import KMeans km KMeans(n_clustersk, n_init20).fit(z.cpu().numpy()) centers torch.tensor(km.cluster_centers_, dtypetorch.float, devicez.device) for ep in range(epochs): model.train(); projector.train() opt.zero_grad() x1, ei1 augment(x, edge_index) x2, ei2 augment(x, edge_index) z1 model(x1, ei1) z2 model(x2, ei2) loss_c info_nce(projector(z1), projector(z2), tau) if ep warmup: q soft_assign(z1, centers) p target_dist(q) loss_k F.kl_div(q.log(), p, reductionbatchmean) loss loss_k lam * loss_c else: loss_k torch.tensor(0.0) loss loss_c loss.backward() opt.step() if ep warmup: with torch.no_grad(): q soft_assign(z1, centers) centers (q.t() z1) / q.sum(dim0, keepdimTrue).t().clamp(min1e-6) if ep % 20 0: with torch.no_grad(): q soft_assign(model(x, edge_index), centers) share q.max(dim1).values.mean().item() size_max (q.argmax(1).bincount(minlengthk).max().item() / x.size(0)) print(fep {ep} | c {loss_c:.4f} | k {loss_k:.4f} | conf {share:.3f} | maxshare {size_max:.3f}) return model, centers4.5 评测ACC、NMI、ARI 的三个对齐坑第一个坑是聚类标签对齐。KMeans 输出的簇编号和真实类别的编号没有对应关系算 ACC 之前必须做一次最优匹配。用匈牙利算法scipy.optimize.linear_sum_assignment在混淆矩阵上做最小代价匹配别自己写贪心匹配贪心在类别数多的时候会明显低估。import numpy as np from scipy.optimize import linear_sum_assignment from sklearn.metrics import confusion_matrix def cluster_acc(y_true, y_pred): cm confusion_matrix(y_true, y_pred) row, col linear_sum_assignment(-cm) return cm[row, col].sum() / cm.sum()第二个坑是 KMeans 的随机性。同一套表示跑五次 KMeans 的 ACC 波动可能有 2~3 个点。所以评测时必须跑多次取平均我一般跑 10 次并且报告标准差。只报最好那一次的结果说服力很弱。第三个坑是用标签调超参。这是这个领域里公开的秘密——不少工作嘴上说无监督实际上拿真实标签反复调 τ、λ、学习率。你自己做实验的时候一定要留出一部分标签完全不看只用它做最终评测。否则线上换一批数据指标会掉得你怀疑人生。5. 排查手册跑不出结果的七种典型情况这一节全是我自己踩过的坑按症状归类你可以直接当速查表用。5.1 损失在降聚类指标不动这是最常见的情况十有八九是对比项和聚类项各干各的。具体表现是对比损失从 5 降到 1但 NMI 一直在 0.3 附近晃。原因通常是两者作用在不同的表示上或者 λ 设得太小让聚类项的梯度被淹没。排查顺序先看聚类 KL 损失有没有在降。如果 KL 也不降说明簇心的更新有问题检查中心更新的分母是否用了 clamp 防止除零。如果 KL 在降但 NMI 不涨说明模型在优化一个错误的划分——通常是初始化太差KMeans 一开始就分错了后面只是把这个错误固化了。解法是重跑几次不同的 KMeans 初始化取 KL 最终值最低的那次或者在 warmup 阶段把对比损失权重拉满让表示充分撑开再聚类。5.2 所有点挤进一两个簇典型坍塌。按概率从高到低排查症状可能原因处理方式表示余弦相似度均值 0.9缺少反坍塌机制加投影头 停梯度或加均匀性正则对比损失极低0.5温度 τ 太大 / 增强太弱τ 降到 0.2增强力度调高簇心之间距离接近 0中心更新发散检查中心更新的分母加 L2 归一化最大簇占比 0.5类别不均衡 无均衡约束加 Sinkhorn 归一化或簇大小正则我遇到过一次特别隐蔽的情况所有簇心在初始化时被设成了同一个值代码里torch.zeros忘了改模型训了 300 轮全部塌到一个簇损失还特别好看。这个问题靠肉眼看损失根本发现不了必须监控簇心之间的距离。5.3 显存爆掉与训练慢节点数超过三万全量相似度矩阵基本就撑不住了。三个解法按推荐顺序一是改随机负采样把负样本数固定到 128~256二是改小批量训练但要注意小批量会破坏图的结构完整性需要用邻居采样比如 GraphSAINT 那类思路来保证每个 batch 里的子图结构合理三是用半精度训练torch.cuda.amp两行代码就能开显存能省 40% 左右注意在计算 KL 散度时把 q 转回 fp32否则会出现 log(0) 的数值问题。5.4 相邻层表示几乎一样过平滑。两层以上的 GCN 在图聚类任务上收益递减三层开始掉点。判断方法很简单算每一层输出表示的两两平均距离如果第三层的距离不到第一层的一半说明已经在趋同了。缓解手段有残差连接、PairNorm、或者干脆停在两层。我个人更推荐后者简单有效别为了架构好看硬堆深度。5.5 换数据集就崩超参过拟合。在引文网络上调到 NMI 0.6换到共现网络上直接掉到 0.35。根本原因是不同图的平均度、特征稀疏度、类别均衡度差异太大一套超参吃不遍。我的处理办法是让超参跟着图统计量走比如把边扰动率设成0.5 / log(平均度 1)这种形式让它在稀疏图上自动变小、在稠密图上自动变大。这个经验公式没什么理论基础但在十几个数据集上试下来比固定值靠谱得多。6. 影响范围这套思路还能搬去哪对比学习加图聚类表面上是学术问题但它解决的问题——无标签图数据的结构化分组——在实际场景里到处都是。我把见过的落地场景和对应的改造要点列一下。6.1 场景清单与改造要点场景数据形态关键改造点用户分群行为二部图投影成用户图边权重要映射成采样概率别用二值化文本主题聚类文档-词共现图 TF-IDF特征掩码率调到 0.4 以上文本特征冗余度高单细胞类型划分细胞相似度图特征高维稀疏必须做特征降维否则前 50 维主导表示异常群体识别设备-行为图对比项用局部子图采样异常点天然稀疏商品类目发现共现/共购图类别数不确定需要用特征值间隙估 k用户分群这块我做得最多。有一个特别实际的体会真实业务图里边的权重几乎都是有意义的购买次数、互动频率直接二值化会丢掉大量信息。把边权归一化成采样概率做边扰动比统一丢 20% 的边效果好很多。6.2 后续还能往哪扩三个方向我觉得值得投入。异质图是目前最大的空白真实场景里节点类型往往不止一种现有的对比方法大多假设同质图处理异质图时要么先做元路径投影会丢信息要么得重新设计视图构造方式。动态图是另一个方向社区结构随时间演化对比学习的正对可以从同一时刻的两个视图扩展到相邻时刻的同一节点这个思路已经有工作在做了但工程落地的经验还很少。大规模分布式训练则是纯工程问题十万级节点以上单机怎么调都吃力得考虑图分区加参数服务器那套。6.3 一点个人体会最后说个我觉得最值钱的经验。这套方法里80% 的效果来自三件事表示别塌、簇心初始化别太差、评测别作弊。网络结构、损失函数的具体形式、增强策略的精细设计加起来大概只占 20%。我见过太多人一上来就魔改损失函数加各种花哨的对比项结果基础的三件事没做好指标还不如一个调好参数的 DEC。还有一个反直觉的点别过早引入聚类损失。warmup 阶段给足让对比学习把表示空间充分撑开簇结构会自然浮现出来。急着聚类等于在噪声上做决策。我在一个四万节点的图上把 warmup 从 100 轮加到 300 轮其他一概不动NMI 从 0.41 涨到 0.53。这个改动成本几乎为零但很多人懒得等。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表