首页 / 资讯中心 / 文章详情

Node2Vec图嵌入算法详解:从原理到实战调参

Node2Vec图嵌入算法详解:从原理到实战调参 ★ FEATURED ARTICLE
1. 从图结构到向量空间Node2Vec到底在解决什么问题图这种数据结构做数据的人都不陌生。社交网络里用户之间的关注关系是一张图电商平台里用户和商品的购买关系是一张图分子结构里原子和化学键也是一张图甚至代码仓库里模块之间的依赖关系同样是一张图。图能非常自然地表达实体以及实体之间的关联但问题也随之而来绝大多数机器学习模型无论是传统的逻辑回归、SVM还是现在主流的深度神经网络它们能直接吃进去的数据格式是向量、矩阵这类规整的数值结构而不是“节点A连着节点B”这种拓扑描述。所以核心矛盾就出现了——图结构数据没法直接喂给下游模型。你总不能把一张有几十万个节点的社交网络图原封不动地塞进一个分类器里。于是图嵌入Graph Embedding这个方向就诞生了它的目标很明确把图中的每个节点映射到一个低维稠密的向量空间里同时尽可能保留原图中的结构信息。映射完之后每个节点就是一个固定长度的向量下游想做什么任务都方便了——节点分类、链路预测、社区发现、推荐召回、异常检测全都能接上。Node2Vec就是图嵌入这个方向里一个非常经典、非常实用的算法。它在2016年被提出核心思想借鉴了自然语言处理里Word2Vec的思路。Word2Vec做的事情是在一段文本里经常出现在同一个上下文窗口中的词它们的语义向量应该比较接近。Node2Vec把这个逻辑搬到了图上在图上做随机游走把游走产生的节点序列当成“句子”把节点当成“词”然后套用Word2Vec的Skip-gram模型来训练最终得到每个节点的向量表示。这个思路听起来简单但Node2Vec真正的精髓在于它设计了一种有偏的随机游走策略通过两个参数p和q来灵活控制游走的方向让生成的节点序列既能捕捉到“广度优先”式的同质社区结构也能捕捉到“深度优先”式的结构等价性。这一点是它比DeepWalk等早期方法更强的地方也是为什么这么多年过去了Node2Vec依然是图嵌入领域绕不开的一个基线方法。这篇文章适合谁看如果你已经了解图的基本概念知道什么是节点、边、邻接矩阵也大致听说过Word2Vec那读起来会很顺畅。如果你对Word2Vec也不太熟没关系我会在需要的地方把关键概念解释清楚。整篇内容会从算法原理、参数机制、实操步骤、代码实现、调参经验到常见问题排查尽量讲透让你看完能自己动手跑起来并且知道每一步为什么这么做。2. Node2Vec算法核心机制拆解2.1 随机游走把图变成“句子”的关键一步Node2Vec的第一步是在图上做随机游走。什么叫随机游走假设你站在图上的某个节点上接下来你要随机选择一个邻居节点走过去到了新节点之后再随机选一个邻居继续走如此反复走出一串节点序列。这个过程就像在一个城市的路网里随机散步每到一个路口就随机选一条路继续走。如果只是完全随机地走那就是DeepWalk的做法。DeepWalk用均匀分布的随机游走来生成序列然后直接套Word2Vec。它效果不差但有一个明显的局限它没法控制游走的“风格”。有些图里我们希望游走更多地停留在同一个社区内部把社区结构捕捉出来有些图里我们希望游走能跳出去探索结构上相似但距离较远的节点。DeepWalk没法区分这两种需求而Node2Vec通过引入p和q两个参数把这件事变得可控了。具体来说Node2Vec的随机游走不是均匀随机的而是带权重的。假设当前游走刚从节点t走到了节点v现在站在v上要决定下一步去哪个邻居。对于v的每一个邻居x转移概率不是均等的而是根据x和t之间的关系来加权。这里就涉及两个参数pReturn Parameter返回参数控制回到上一个节点的概率。p越小回到t的概率越大游走倾向于在局部反复徘徊。qIn-out Parameter进出参数控制游走是偏向“向内”还是“向外”。q小于1时游走倾向于走向离t更远的节点偏向深度优先探索q大于1时游走倾向于在t附近打转偏向广度优先探索。这两个参数的组合让Node2Vec可以灵活地在“同质性”和“结构等价性”之间做权衡。所谓同质性就是同一个社区里的节点应该有相似的向量所谓结构等价性就是扮演相似结构角色的节点比如两个不同社区里的中心节点应该有相似的向量。这两种目标在很多场景下是有冲突的Node2Vec通过p和q给了你一个调节旋钮。2.2 有偏游走的概率计算一步步推导这部分是Node2Vec最核心的数学细节我尽量用直白的方式讲清楚。假设游走序列是 ... t → v现在在v节点上候选的下一步节点是v的某个邻居x。未归一化的转移概率定义为π(v, x) α(t, x) * w(v, x)其中w(v,x)是边(v,x)的权重如果是无权图w就是1。α(t,x)就是那个有偏的调节因子它的定义分三种情况如果x就是t也就是走回上一个节点α 1/p如果x和t直接相连x是t的邻居α 1如果x和t不相连既不是t本身也不是t的邻居α 1/q这个设计非常巧妙。你可以这样理解从v出发下一步的候选节点被分成了三类——回到来路、走向来路的邻居、走向更远的地方。p控制第一类的权重q控制第三类的权重第二类始终是1作为基准。当p设得很小比如0.251/p就很大游走强烈倾向于原路返回序列会在局部反复震荡这有助于捕捉非常局部的微观结构。当p设得很大比如41/p就很小游走不太愿意回头更倾向于向前探索。当q设得很小比如0.251/q很大游走倾向于走向那些和t不相连的节点也就是“往外跑”这更像深度优先搜索能探索到更远的节点有助于捕捉全局的结构等价性。当q设得很大比如41/q很小游走倾向于在t的邻居范围内活动这更像广度优先搜索有助于捕捉局部社区的同质性。实际使用中p和q的取值需要根据你的图数据和任务目标来调。常见的起始配置是p1、q1这时候Node2Vec退化成类似DeepWalk的均匀游走。然后根据效果逐步调整。2.3 Skip-gram与负采样从序列到向量有了游走序列之后接下来的步骤就是套用Word2Vec的Skip-gram模型。Skip-gram的核心思想是给定一个中心节点预测它周围窗口内的上下文节点。比如序列是 [A, B, C, D, E]窗口大小为2那么当中心是C时上下文就是[A, B, D, E]。模型的目标是最大化这些上下文节点出现的概率。具体到实现上每个节点会被赋予两个向量一个是作为中心节点时的向量一个是作为上下文节点时的向量。训练过程中对于每一对中心节点上下文节点模型会尝试最大化它们的点积经过softmax后的概率。但直接做softmax的计算量太大了因为节点数量可能几十万上百万分母要对所有节点求和。所以实际中会用负采样技术不去计算完整的softmax而是随机选几个“负样本”节点让模型学会区分真实上下文和随机噪声。这样计算量就大大降低了。训练完成后通常取中心节点的向量作为最终的节点嵌入。也有人把中心向量和上下文向量拼接或平均但标准做法是只用中心向量。向量的维度一般设64到128之间具体取决于图的规模和下游任务的复杂度。2.4 与DeepWalk、LINE等方法的对比Node2Vec不是最早的图嵌入方法但它在DeepWalk的基础上做了关键改进。DeepWalk用的是均匀随机游走没有p和q的调节机制所以它只能捕捉一种固定的游走模式。Node2Vec通过有偏游走让用户可以根据任务需求灵活调整。LINE是另一个早期方法它直接优化一阶和二阶相似度不做随机游走。一阶相似度衡量直接相连节点之间的相似性二阶相似度衡量共享邻居的节点之间的相似性。LINE的优点是目标明确但它的二阶相似度计算需要对每个节点做边采样在大图上效率不如随机游走方法。还有像SDNE这样的深度自编码器方法用神经网络直接学习节点的低维表示。这类方法理论上更强但训练成本高调参也更复杂。Node2Vec的优势在于简单、高效、可扩展而且效果在大多数场景下已经足够好所以它一直是工业界最常用的图嵌入基线之一。方法核心机制优势局限DeepWalk均匀随机游走 Skip-gram简单、可扩展无法控制游走策略Node2Vec有偏随机游走 Skip-gram灵活调节同质/结构等价需要调p和qLINE一阶/二阶相似度优化目标明确、效率高不捕捉高阶结构SDNE深度自编码器表达能力强训练成本高3. 动手实现Node2Vec从零到跑通3.1 环境准备与依赖安装要跑Node2Vec最省事的方式是用现成的库。Python生态里有两个比较常用的选择一个是node2vec这个pip包它封装了完整的Node2Vec流程API很简洁另一个是gensim它提供了Word2Vec的实现你可以自己写随机游走部分然后把序列喂给gensim训练。我建议先用node2vec包快速跑通理解流程之后再考虑自己实现。安装命令很简单pip install node2vec pip install networkx pip install gensim pip install scikit-learn pip install matplotlibnetworkx用来构建和操作图gensim用来做Word2Vec训练node2vec包底层也依赖它scikit-learn用来做下游的聚类或分类验证matplotlib用来可视化。这些库的版本不用太纠结近两年的版本都能跑。提示如果你用的是比较新的Python版本3.10以上建议先创建一个虚拟环境避免和系统里的其他包冲突。node2vec包本身依赖gensim和networkx版本兼容性一般没问题但如果遇到报错优先检查gensim的版本。3.2 构建图数据以空手道俱乐部为例为了演示我用一个经典的图数据集——空手道俱乐部Zacharys Karate Club。这个图有34个节点78条边描述了一个大学空手道俱乐部成员之间的社交关系。它之所以经典是因为后来俱乐部发生了分裂正好可以拿来验证社区发现的效果。import networkx as nx # 加载空手道俱乐部图 G nx.karate_club_graph() # 查看基本信息 print(f节点数: {G.number_of_nodes()}) print(f边数: {G.number_of_edges()}) print(f节点列表: {list(G.nodes())[:10]})这段代码会输出节点数34、边数78。每个节点有一个club属性表示该成员最终归属的派系。这个属性可以用来做下游验证。如果你有自己的图数据比如一个边列表文件可以这样构建import networkx as nx import pandas as pd # 假设edges.csv有两列source, target df pd.read_csv(edges.csv) G nx.from_pandas_edgelist(df, sourcesource, targettarget)如果是带权图可以在from_pandas_edgelist里指定edge_attr参数把权重列传进去。Node2Vec支持带权图游走时会考虑边的权重。3.3 配置并运行Node2Vec用node2vec包跑起来非常直接from node2vec import Node2Vec # 创建Node2Vec对象 node2vec Node2Vec( G, dimensions64, # 嵌入向量维度 walk_length30, # 每条游走序列的长度 num_walks200, # 每个节点作为起点的游走次数 p1, # 返回参数 q1, # 进出参数 weight_keyweight, # 边权重属性名无权图可以不设 workers4 # 并行线程数 ) # 训练模型 model node2vec.fit( window10, # Skip-gram的上下文窗口大小 min_count1, # 忽略出现次数少于该值的节点 batch_words4, # 每个batch处理的词数 epochs10 # 训练轮数 ) # 获取某个节点的向量 vector model.wv[0] print(f节点0的向量维度: {len(vector)}) print(f前10个值: {vector[:10]})这段代码里几个关键参数需要解释一下。dimensions64表示每个节点最终得到一个64维的向量。walk_length30表示每次游走走30步。num_walks200表示每个节点作为起点走200次。所以总的序列数量是34 * 200 6800条每条长度30总共约20万个节点出现次数。这个规模对于34个节点的小图来说已经足够了。window10是Skip-gram的窗口大小表示中心节点前后各看10个节点作为上下文。对于小图窗口可以设大一点因为游走序列本身就不长。epochs10表示训练10轮一般5到10轮就够了再多可能会过拟合。3.4 参数选择背后的计算逻辑很多人跑Node2Vec的时候直接抄默认参数结果效果不好也不知道为什么。这里我把几个关键参数的选择逻辑讲清楚。维度dimensions这个参数决定了嵌入向量的表达能力。维度太低信息压缩太狠区分度不够维度太高容易过拟合而且下游计算量增大。经验值是节点数在几百到几千时用64到128节点数在几万到几十万时用128到256节点数上百万时可以考虑256到512。但也不是绝对的如果你的图结构比较简单64维可能就够了。游走长度walk_length这个参数决定了每次游走能探索多远。太短了捕捉不到远距离的结构信息太长了序列里噪声增多而且计算量增大。一般设20到40之间比较合适。对于直径比较大的图比如社交网络可以设长一点比如40到80对于直径小的图比如分子图20就够了。每节点游走次数num_walks这个参数决定了每个节点被采样的充分程度。次数越多训练数据越多效果通常越好但计算时间也线性增长。一般设10到200之间。小图可以设大一点比如200大图设小一点比如10到20因为大图本身节点多总序列数已经很大了。p和q这是Node2Vec最核心的参数。前面讲过p控制返回概率q控制探索方向。如果你希望捕捉社区结构同质性可以设p1、q2到4让游走更偏向广度优先。如果你希望捕捉结构等价性可以设p1、q0.25到0.5让游走更偏向深度优先。实际调参时建议先用p1、q1跑一个基线然后根据下游任务的效果逐步调整。注意p和q的取值不要走极端。p设得太小比如0.01会导致游走几乎只在两个节点之间来回跳序列多样性极差q设得太小比如0.01会导致游走跑得太远序列里全是远距离节点局部结构信息丢失。一般p和q在0.25到4之间调节就够了。4. 效果验证与下游任务实战4.1 节点分类用嵌入向量训练分类器得到节点嵌入之后最直接的验证方式就是做节点分类。以空手道俱乐部为例每个节点有club属性表示它属于哪个派系。我们可以用嵌入向量作为特征训练一个逻辑回归或随机森林看看分类效果。import numpy as np from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, classification_report # 提取所有节点的嵌入向量 X np.array([model.wv[node] for node in G.nodes()]) # 提取标签 y np.array([G.nodes[node][club] for node in G.nodes()]) # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 训练逻辑回归 clf LogisticRegression(max_iter1000) clf.fit(X_train, y_train) # 预测并评估 y_pred clf.predict(X_test) print(f准确率: {accuracy_score(y_test, y_pred):.4f}) print(classification_report(y_test, y_pred))在空手道俱乐部这个数据集上用Node2Vec嵌入加逻辑回归准确率通常能到90%以上。如果只分两类甚至经常能到100%。这说明嵌入向量确实捕捉到了节点的社区归属信息。4.2 可视化把高维向量降到二维看分布光看准确率不够直观我们可以用t-SNE或PCA把64维的向量降到2维画散点图看看不同类别的节点是否分得开。import matplotlib.pyplot as plt from sklearn.manifold import TSNE # 用t-SNE降维 tsne TSNE(n_components2, random_state42, perplexity10) X_2d tsne.fit_transform(X) # 按类别着色 colors [red if label Mr. Hi else blue for label in y] plt.figure(figsize(8, 6)) plt.scatter(X_2d[:, 0], X_2d[:, 1], ccolors, s100, alpha0.7) for i, node in enumerate(G.nodes()): plt.annotate(str(node), (X_2d[i, 0], X_2d[i, 1]), fontsize8) plt.title(Node2Vec Embedding Visualization (t-SNE)) plt.show()跑出来的图通常能看到两个明显的簇分别对应两个派系。中间会有少数几个节点比较模糊这些往往是两个派系之间的“桥梁”节点它们的归属本身就不太明确。4.3 链路预测判断两个节点是否应该有边链路预测是另一个常见的下游任务。思路是从图中随机隐藏一部分边作为正样本再随机采样一些不存在的边作为负样本然后用节点嵌入计算边的特征比如两个向量的点积、余弦相似度、Hadamard积等训练一个二分类器来预测边是否存在。import random from sklearn.metrics.pairwise import cosine_similarity # 隐藏20%的边作为测试集 edges list(G.edges()) random.shuffle(edges) test_edges edges[:int(len(edges) * 0.2)] train_edges edges[int(len(edges) * 0.2):] # 构建训练图 G_train nx.Graph() G_train.add_nodes_from(G.nodes()) G_train.add_edges_from(train_edges) # 在训练图上重新跑Node2Vec node2vec_train Node2Vec(G_train, dimensions64, walk_length30, num_walks200, p1, q1, workers4) model_train node2vec_train.fit(window10, min_count1, epochs10) # 生成正负样本 def get_edge_features(edge_list, model, label): features [] labels [] for u, v in edge_list: vec_u model.wv[u] vec_v model.wv[v] # 用Hadamard积作为边特征 feat vec_u * vec_v features.append(feat) labels.append(label) return np.array(features), np.array(labels) # 正样本测试集中的边 pos_feat, pos_label get_edge_features(test_edges, model_train, 1) # 负样本随机采样不存在的边 neg_edges [] while len(neg_edges) len(test_edges): u random.choice(list(G.nodes())) v random.choice(list(G.nodes())) if u ! v and not G.has_edge(u, v): neg_edges.append((u, v)) neg_feat, neg_label get_edge_features(neg_edges, model_train, 0) # 合并并训练分类器 X_edge np.vstack([pos_feat, neg_feat]) y_edge np.hstack([pos_label, neg_label]) X_train, X_test, y_train, y_test train_test_split(X_edge, y_edge, test_size0.3, random_state42) clf LogisticRegression(max_iter1000) clf.fit(X_train, y_train) print(f链路预测准确率: {accuracy_score(y_test, clf.predict(X_test)):.4f})链路预测的效果高度依赖于图的结构。如果图本身比较稠密随机负采样可能太简单模型很容易区分。更严谨的做法是用“不连通但距离为2”的节点作为负样本这样难度更大评估也更可靠。4.4 聚类无监督发现社区如果你没有标签可以用K-Means对嵌入向量做聚类看看能不能发现合理的社区结构。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # 尝试不同的K值 for k in range(2, 8): kmeans KMeans(n_clustersk, random_state42, n_init10) labels kmeans.fit_predict(X) score silhouette_score(X, labels) print(fK{k}, 轮廓系数{score:.4f})轮廓系数越高说明聚类效果越好。在空手道俱乐部上K2通常能得到最高的轮廓系数这和实际的两个派系是吻合的。5. 调参经验与性能优化实录5.1 p和q的调参策略别盲目试p和q的调参是Node2Vec最让人头疼的地方因为两个参数组合起来可能性很多。我的经验是分两步走第一步固定p1只调q。q从0.25、0.5、1、2、4这几个值里试。如果下游任务是节点分类通常q2到4效果更好因为偏向广度优先的游走能更好地捕捉社区结构。如果下游任务是链路预测q0.5到1可能更好因为需要捕捉更全局的结构信息。第二步在选定的q附近微调p。p从0.5、1、2、4里试。p对结果的影响通常比q小所以不用花太多时间。如果发现模型对局部结构特别敏感可以适当减小p。实操心得我试过在一个社交网络数据集上p1、q4的配置比默认的p1、q1在节点分类任务上提升了将近8个百分点。但同样的配置换到一个分子图数据集上效果反而下降了。所以没有万能的参数一定要根据具体数据来调。5.2 大规模图的处理技巧当图的节点数超过十万甚至百万时直接跑Node2Vec会遇到内存和时间瓶颈。几个实用的优化方向减少游走次数大图本身节点多每个节点走10次就能产生足够的序列。不需要像小图那样走200次。缩短游走长度大图的直径通常不大社交网络有“六度分隔”现象游走长度设20到30就够了。使用更小的向量维度如果下游任务不复杂64维甚至32维可能就够用了。维度减半训练时间和内存占用都会显著下降。并行化node2vec包的workers参数可以控制并行线程数。设成CPU核心数能明显加速。但注意workers设太大可能会导致内存溢出因为每个线程都会维护自己的游走序列缓冲区。分批训练如果图实在太大可以考虑先把图分成若干子图分别跑Node2Vec然后对齐向量空间。不过子图之间的边信息会丢失效果可能打折扣。5.3 常见报错与排查报错信息可能原因解决方法KeyError: weight图没有weight属性但指定了weight_key去掉weight_key参数或给边添加weight属性RuntimeError: you must first build vocabulary没有调用fit就直接取向量先调用model.fit()再取wvMemoryError图太大或num_walks太大减少num_walks、walk_length或dimensionsValueError: max() arg is an empty sequence图中有孤立节点移除孤立节点或给它们加自环训练速度极慢workers设太小或图太大增大workers减少num_walks孤立节点是Node2Vec的一个常见坑。如果一个节点没有任何边随机游走根本走不出去生成的序列里只有它自己训练出来的向量没有意义。处理方式很简单在跑Node2Vec之前把孤立节点过滤掉。# 移除孤立节点 G.remove_nodes_from(list(nx.isolates(G)))5.4 向量质量评估别只看下游任务下游任务的效果是最终标准但有时候下游任务本身有噪声不好判断嵌入质量。这时候可以用一些内在评估指标节点对相似度对于图中距离近的节点对它们的嵌入向量余弦相似度应该较高距离远的节点对相似度应该较低。可以随机采样一些节点对计算相似度和图上距离的相关性。类比推理类似Word2Vec的“国王-男人女人女王”在图嵌入里也可以做类似的类比。比如在引用网络中“A引用BC引用D”那么vec(A)-vec(B)应该和vec(C)-vec(D)比较接近。不过图上的类比推理不如词向量那么稳定只能作为参考。可视化检查t-SNE图是最直观的。如果同类节点在图上混在一起说明嵌入质量有问题需要调参。6. 进阶方向与变体扩展6.1 从Node2Vec到GraphSAGE、GCNNode2Vec属于“浅层”图嵌入方法每个节点直接学一个向量没有参数共享也没有利用节点特征。如果图上的节点有丰富的属性特征比如用户的年龄、性别、兴趣标签Node2Vec就没法直接利用这些信息。GraphSAGE和GCN这类图神经网络方法就是为了解决这个问题。它们通过聚合邻居节点的特征来更新中心节点的表示能同时利用图结构和节点特征。而且它们学到的是一种“聚合函数”可以泛化到训练时没见过的节点归纳学习而Node2Vec只能处理训练时存在的节点直推学习。不过Node2Vec依然有它的价值。它简单、快、不需要节点特征在很多只有结构信息没有属性信息的场景下依然是首选。而且Node2Vec的嵌入可以作为图神经网络的特征初始化两者结合使用也是常见的做法。6.2 动态图与异质图上的扩展现实中的图往往不是静态的。社交网络里用户关系在变电商图里商品和用户的交互在变。动态图嵌入是一个活跃的研究方向核心挑战是如何在时间维度上保持嵌入的一致性和时效性。异质图是另一个方向。如果图里有多种类型的节点和边比如学术网络里有作者、论文、会议三种节点以及写作、发表、引用等多种边普通的Node2Vec就不够用了。metapath2vec等方法通过定义“元路径”来指导随机游走让游走按照特定的模式进行从而捕捉异质结构。6.3 工业级部署的注意事项如果你打算把Node2Vec部署到生产环境有几个点需要提前考虑增量更新新节点加入时是重新训练整个模型还是只训练新节点重新训练成本高但一致性更好。只训练新节点需要固定已有节点的向量只优化新节点的向量实现起来更复杂。向量存储与检索几十万节点的向量需要高效的存储和相似度检索。可以用FAISS、Annoy这类近似最近邻库来加速。版本管理每次重新训练后向量空间可能会发生变化即使数据没变随机种子不同也会导致结果不同。下游系统如果依赖向量做召回需要做好版本管理和兼容性处理。监控与评估上线后要持续监控嵌入质量。可以定期采样一些节点对检查相似度是否符合预期。如果发现质量下降可能需要重新调参或重新训练。7. 一些踩过的坑和实用建议最后分享几个我在实际项目里踩过的坑都是文档里不会写的。第一个坑随机种子不固定导致结果不可复现。Node2Vec的随机游走和负采样都涉及随机性。如果不固定随机种子每次跑出来的结果都不一样调参的时候根本没法比较。node2vec包本身没有暴露随机种子的参数但你可以通过设置Python的random.seed()和numpy.random.seed()来间接控制。不过要注意gensim的Word2Vec训练也有随机性完全复现比较困难但至少能保证游走序列是可复现的。第二个坑游走序列太长导致训练慢且效果差。我一开始觉得游走越长越好设了100。结果训练时间翻了好几倍效果反而下降了。后来分析发现太长的序列里噪声太多Skip-gram窗口根本覆盖不到那么远的信息反而稀释了局部信号。后来改成30效果和速度都好了很多。第三个坑忽略图的连通性。如果图不是连通的随机游走只能在某个连通分量内部进行不同连通分量之间的节点向量完全没有可比性。这种情况下要么先把连通分量分开处理要么在预处理阶段把图变成连通的比如加一些虚拟边。我在一个用户行为图上就遇到过这个问题后来发现有几个小的连通分量里面的节点向量质量很差最后是把它们单独拿出来处理的。第四个坑下游任务和嵌入目标不匹配。Node2Vec的嵌入是通用的但不同的下游任务对嵌入的要求不一样。节点分类希望同类节点靠近链路预测希望相连节点靠近社区发现希望同社区节点靠近。如果发现效果不好不要只盯着Node2Vec的参数调也要想想下游任务本身的设计是否合理。比如链路预测的负采样策略、分类器的选择、特征工程等都可能影响最终效果。第五个坑盲目追求高维度。我见过有人把维度设到512甚至1024觉得维度越高信息越多。但实际上对于大多数图64到128维已经足够捕捉主要结构信息了。维度太高不仅训练慢还容易过拟合尤其是在标注数据少的情况下。我做过对比实验在同一个数据集上64维和256维的下游任务效果差异不到1个百分点但训练时间差了将近4倍。最后再分享一个小技巧如果你不确定p和q怎么设可以先跑一个p1、q1的基线然后用网格搜索在p∈{0.5, 1, 2}、q∈{0.5, 1, 2, 4}这12个组合里找最优。虽然穷举看起来笨但比凭感觉调靠谱得多。而且Node2Vec在小图上的训练速度很快12个组合跑下来也就几分钟的事。
阅读完成 · 觉得有帮助?
咨询建站