假设你是游乐园的运营总监,手里攥着一张园区地图:过山车挨着大摆锤,旋转木马和摩天轮安静地待在亲子区,鬼屋则和 3D 影院共享一条小路。这张地图就是一张典型的图(Graph)——项目是点,路是线。可计算机不懂地图之美,它只认数字。为了让机器也能读懂“过山车和跳楼机性情相近”,我们需要把每个项目压缩成一串数字坐标,这就是图嵌入(Graph Embedding)。DeepWalk 就像园方派出的“路线记录员”:让他们在园区里随机遛弯,把走过的路线记下来。路线上总是一起出现的项目,得到的坐标就会彼此靠近。计算机拿到这些坐标后,就能做推荐、分类、找圈子等任务。这篇文章用游乐园比喻贯穿始终,从零讲懂 DeepWalk 的原理、训练方式与实战价值。
对 AI 从业者来说,图嵌入是把关系型数据接入机器学习的第一步。掌握 DeepWalk,你就能把社交网络、知识图谱、交易网络等复杂结构变成模型可用的向量特征,这也是通往图神经网络的基础。
可视化演示
展示DeepWalk将图节点映射到低维向量的完整流程:随机游走采样节点序列、SkipGram训练学习节点共现关系、嵌入空间可视化及与node2vec对比,交互切换四种模式。
本节配有交互动效(进入视口后自动加载,约 3KB)。
随机游走:图结构序列采样 从每个节点出发执行截断随机游走,生成节点序列作为SkipGram的训练语料 P(v_i | v_0) ∝ 转移概率 游走长度和数量控制采样的覆盖范围与计算开销。
图嵌入思想
想象你手里那张家喻户晓的游乐园地图。过山车与跳楼机之间有一条小路,旋转木马旁紧挨着摩天轮。每一个游乐项目是图上的一个节点,每一条路是一条边。对运营总监来说,地图是直观的;但计算机拿到这张图时,看到的不过是“哪个点和哪个点相连”。要让机器学习模型替我们做决策,得先把这些点变成它熟悉的语言——一串数字。
图嵌入的目标,正是给每个节点生成一张“数字画像”。画像不需要人工设计,而是自动从图结构中学出来。传统做法是维护一张 N×N 的邻接矩阵,N 是节点总数。可真实的社交网络动辄上亿用户,邻接矩阵会稀疏到让人绝望,而且独热式的行向量完全看不出两个节点之间的相似性。DeepWalk 换了个思路:借用词向量的思想,把“图”当成“文章”,把“节点”当成“词”,把节点之间的连接关系当成“语境”,让模型自己把这些语境压缩成低维向量。
在数学上,一张图可以写成一个极简的二元组:
嵌入的本质,是找到一个映射函数:
回到游乐园的场景:一旦每个项目都有了“数字画像”,计算机就能用它们之间的坐标距离来回答“过山车和哪个项目最像”这样的问题。而 DeepWalk 学到画像的方式,正是让路线记录员先去遛弯。
- 映射 f可以理解成“数字画像生成器”,给每个游乐项目签发一张身份证。
- 定义域 \mathcal{V}输入是一个具体节点,比如“过山车”这个点。
- 值域 \mathbb{R}^{d}输出是 d 维空间的坐标,例如 d=16 时,每个项目对应 16 个实数。
随机游走采样
园区请来一批“路线记录员”,他们的任务很简单:从任意一个项目出发,随机选一条路走到下一个项目,再随机走,直到走满规定步数。每走完一圈,记录员手里就多了一张纸条,上面写着一串项目编号,比如“过山车 → 大摆锤 → 鬼屋 → 摩天轮”。这张纸条,就是图领域常说的“游走序列”。
这些纸条有什么用?回想自然语言处理里的一个朴素规律:一句话里经常挨在一起出现的词,意思往往相近。比如“过山车”和“大摆锤”总被游客连着提起,说明它们很可能都属于“刺激项目”。把这一规律搬到图上:一条游走序列就是“一句话”,序列中前后脚出现的节点,就是“相邻的词”。DeepWalk 用海量这样的“句子”作为训练语料,把图结构里的邻居关系,转化为语言模型能消化的共现关系。
具体采样时,有四个关键控制:第一是游走长度(walk length),决定每张纸条最多记几个项目;第二是游走数量(num walks),决定从每个项目出发要派几批记录员;第三是邻域关系保持,路径上相邻或共现的节点,会被视为语义相近;第四是生成方式,每一步都从当前节点的邻居中等概率随机挑选下一个项目。转移概率写作:
记录员们的纸条收得越多,“句子”越丰富,后面训练出的画像就越精准。接下来,我们要把这一堆纸条喂给教练,让他提炼规律。
- 条件概率 P(v_{i+1} \mid v_i)已知当前在第 i 个节点,下一步走到第 i+1 个节点的概率。
- 度 \deg(v_i)v_i 有几条路直接通向邻居,这个数字叫“度”,相当于项目的岔路数量。
- 等概率选择每个邻居被抽中的概率是 1 除以岔路总数,这就是站在路口闭眼乱走的数学表达。
- 无路可走如果两点之间没有边,转移概率直接置 0,表示游客不可能瞬移。
SkipGram训练
现在你手上有了成千上万张游玩路线纸条。一位聪明的教练接过纸条,开始帮每个项目刻画性格。他的原则是:如果一个项目频繁出现在另一个项目的“前后文”里,那它们就很有缘分。比如“鬼屋”总出现在“3D 影院”相邻的位置,教练就推测这两个项目气质接近。
在 DeepWalk 中,这位教练就是 SkipGram 模型。它会用一个可滑动的窗口,在每张纸条上从前往后扫。窗口正中间的项目是“中心词”,窗口内其他项目是“上下文”。模型的任务是:看到中心项目,预测窗口内那些上下文项目出现的概率。如果预测得准,说明中心项目的向量确实浓缩了它的“朋友圈”信息。
训练目标是最小化如下所示的错误:
为了计算方便,SkipGram 假设窗口内各上下文项目互不影响,于是联合概率可以拆成每个上下文分别相乘:
教练利用层次 Softmax 做加速,把原本要遍历全部节点计算概率的做法,改成沿着霍夫曼树走一条路径,复杂度从 O(|V|) 降到 O(log|V|)。训练结束后,每个项目都有了自己的向量,接下来就轮到这些画像登场了。
- 独立性假设窗口里的项目各自独立出现,互不干扰。
- 连乘符号从 -w 到 w 的每个位置都乘一遍,中心位置 k=0 跳过。
- 单项概率每一项 P(v_{j+k} \mid \Phi(v_j)) 表示中心向量对某个上下文的预测概率。
节点向量学习
训练完成后的画面,就像是所有游乐项目都被贴上了一张“气质雷达图”。过山车、跳楼机、大摆锤的画像挤在一处,暗示它们是“刺激三兄弟”;旋转木马、摩天轮、小飞象的画像依偎在一起,属于“亲子温柔区”。画家并没有告诉模型“过山车是刺激的”,但模型从路线的共现规律中,自己悟出了这些隐藏维度。
这些画像有几个特点:第一是低维稠密,每个节点只有几十到几百个连续数值,不像独热编码那样上亿维且几乎全是 0;第二是语义可算,向量之间的夹角、距离可以直接反映节点之间的“亲近程度”;第三是即插即用,下游任务只需把向量当作特征,就能接入分类器、聚类算法或排序模型。
最常用的度量方式是余弦相似度,它只看两个向量的方向是否一致:
有了可比较的向量,乐园区块规划、游客动线优化都不再是拍脑袋。而 DeepWalk 的改进版 Node2Vec,则想让记录员们遛弯时更有主见一些。
- 点积两个向量逐位相乘再求和,衡量它们在同一方向上的投影乘积。
- L2 范数向量长度,即各分量平方和再开方,用来归一化。
- 归一化分母除以两个长度后,结果不受向量长短影响,只保留方向信息。
- 值域余弦值的范围是 [-1, 1],接近 1 意味着两个项目的“画像”高度同向。
与Node2Vec对比
DeepWalk 的记录员是完全“脸盲”的——站在路口时,不管前面是回头路、近邻还是远方,他都一视同仁。但现实中,不同的规划目标需要不同的探索风格:想摸清一个片区的项目构成,最好在小范围内反复逛;想了解整个园区的宏观格局,最好多往远处走。Node2Vec 就是给记录员加装了两个“口味开关”。
这两个开关是参数 p 和 q。p 控制“回退”的吸引力:p 越小,记录员越容易走回刚刚离开的项目;q 控制“远探”的吸引力:q 越小,记录员越喜欢往离上一步很远的节点跑。当 q 较大时,游走近似广度优先搜索(BFS),重点捕捉局部社区结构;当 q 较小时,游走近似深度优先搜索(DFS),重点捕捉全局功能相似性。这一调整只改变了采样策略,训练仍然用 SkipGram。
从转移概率看,Node2Vec 在上一步节点 t 和当前节点 v 的约束下,给候选邻居 x 分配不同的权重:
当 p=q=1 时,三个权重全部等于 1,游走重新变成完全随机,Node2Vec 就退化为 DeepWalk。所以两者不是两个割裂的算法,而是“基础版”与“可调版”的关系。
- 上一步节点 t记录员刚刚离开的那个项目,是判断方向的参照物。
- 候选节点 x当前路口可选的下一个项目。
- 距离 d_{tx}=0候选节点就是 t 本身,即往回走,权重 1/p。
- 距离 d_{tx}=1候选节点与 t 直接相连,权重保持 1,不打折。
- 距离 d_{tx}=2候选节点与 t 隔了两跳,属于“远方”,权重 1/q。
可扩展性设计
如果游乐园规模扩大到千万级项目,地图还能不能靠人工一块块画出来?显然不行。DeepWalk 之所以在工业界受欢迎,恰恰因为它对大图非常友好,几乎不做“重活”。
首先,DeepWalk 从不显式构造 N×N 邻接矩阵,随机游走只访问局部邻居,天然避免全图展开。其次,每个记录员的遛弯路线互不依赖,可以分派给多个线程甚至多台机器并行处理,最后汇总成语料即可。再次,训练阶段使用层次 Softmax,用二叉树结构替代全量归一化,省掉了对千万个节点逐一计算概率的巨大开销。总体复杂度可以写成:
正因为复杂度可控,DeepWalk 才能处理千万级节点的社交网络或交易网络。有了这批“路线记录员”和高效训练管线,图向量才能真正落地到业务场景。
- 节点总数 |\mathcal{V}|图中的项目个数,复杂度随节点数线性增长。
- 游走次数 t从每个节点出发重复采样的次数,决定语料大小。
- 游走长度 l每条序列包含的节点个数,决定上下文视野。
- 嵌入维度 d最终向量的长度,影响存储与计算成本。
- 线性关系复杂度与边数无关,这一点让 DeepWalk 区别于许多基于矩阵分解的方法。
应用场景展望
当每个节点都拥有了向量画像,游乐园和互联网的各种“聪明功能”就可以上线了。运营方最关心的三件事:给新项目自动分类、预测该在哪两个项目之间修路、找到园区里隐藏的“主题片区”——分别对应图学习中的节点分类、链路预测和社区发现。
节点分类很简单:把节点向量当作特征,训练一个逻辑回归或小分类器,就能自动判断“刺激型 / 亲子型 / 观赏型”。链路预测则依赖向量之间的亲和度,两个节点向量点积越大,越可能在未来产生连接。社区发现更直接:对向量做 K-Means 或 DBSCAN 聚类,每一簇就是一个潜在的“圈子”。
链路预测的数学形式通常写成:
回顾整个游乐园:记录员遛弯得到路线,教练从路线中学出画像,画像再服务于分类、预测和聚类。DeepWalk 作为这一切的起点,它的价值并不在于模型多复杂,而在于用最简单的随机游走加 SkipGram,为图数据打开了一条通往机器学习世界的大路。
- 点积衡量两个节点画像的匹配程度。
- sigmoid 函数把任意实数映射到 (0,1) 区间,输出可以当作概率。
- 概率形式输出越大,越有理由预测 u 和 v 之间应该出现一条边。
AI 应用场景
社交网络好友推荐
阶段:召回阶段
解决的问题:传统协同过滤只能看用户与内容的历史交互,难以利用“好友的好友”这种社交链路信息。DeepWalk 把用户当作节点,把关注关系当作边,学习出每个用户的向量画像,再通过向量相似度快速召回候选好友。
具体案例:某兴趣社区将“关注关系”建成 2000 万节点的有向图,用 DeepWalk 训练 64 维用户向量,把最相似的 50 个用户作为“你可能认识的人”候选集,再交给精排模型,最终新好友转化率提升了 23%。
金融交易网络反欺诈
阶段:无监督特征学习 + 聚类
解决的问题:欺诈团伙的单个账户看起来行为正常,但账户之间的资金往来异常密集,形成“抱团”结构。把账户作为节点、转账作为边,用 DeepWalk 学习嵌入后,再对向量做聚类,就能把可疑的密集交易团伙识别出来。
具体案例:风控团队按月构建转账图,对 DeepWalk 学出的向量做 DBSCAN 聚类,成功发现多组“空转资金小团体”,这些账户在传统规则引擎下完全不会触发告警。
知识图谱实体分类
阶段:表示学习 + 少样本分类
解决的问题:知识图谱每周新增大量实体,人工标注成本极高。先用 DeepWalk 从图结构预训练实体向量,再借助少量标注样本训练分类头,就能标注剩余的海量实体。
具体案例:电商商品知识图谱中,工程团队用 DeepWalk 预训练了商品节点向量,仅用 1000 个标注样本训练分类器,便给近千万商品自动打上“3C / 美妆 / 食品”等类目标签,准确率达到 91%。
Python 演示
"""DeepWalk 图嵌入最小演示"""import randomimport numpy as np try: import networkx as nxexcept ImportError: raise SystemExit("请先安装 networkx: pip install networkx") try: from gensim.models import Word2Vecexcept ImportError: raise SystemExit("请先安装 gensim: pip install gensim") def random_walk(graph, start, walk_length): walk = [start] for _ in range(walk_length - 1): neighbors = list(graph.neighbors(walk[-1])) if not neighbors: break walk.append(random.choice(neighbors)) return walk def build_corpus(graph, num_walks, walk_length): sentences = [] nodes = list(graph.nodes()) for _ in range(num_walks): random.shuffle(nodes) for node in nodes: sentences.append([str(x) for x in random_walk(graph, node, walk_length)]) return sentences def deepwalk_embed(graph, dim=16, num_walks=10, walk_length=20, window=5): sentences = build_corpus(graph, num_walks, walk_length) model = Word2Vec( sentences=sentences, vector_size=dim, window=window, min_count=0, sg=1, hs=1, workers=4, seed=42, ) return {node: model.wv[str(node)] for node in graph.nodes()} def main(): graph = nx.Graph() graph.add_edges_from([ (0, 1), (1, 2), (2, 3), (3, 0), # 刺激系小团体 (4, 5), (5, 6), (6, 7), (7, 4), # 亲子系小团体 (2, 4), # 连接两个团体的路 ]) embeddings = deepwalk_embed(graph, dim=8, num_walks=5, walk_length=10) v0, v4 = embeddings[0], embeddings[4] v6, v7 = embeddings[6], embeddings[7] cos04 = float(np.dot(v0, v4) / (np.linalg.norm(v0) * np.linalg.norm(v4))) cos67 = float(np.dot(v6, v7) / (np.linalg.norm(v6) * np.linalg.norm(v7))) print("节点0 与 节点4 的余弦相似度:", round(cos04, 4)) print("节点6 与 节点7 的余弦相似度:", round(cos67, 4)) if __name__ == "__main__": main()
random_walk(graph, node, walk_length)实现核心采样:从当前节点随机选择邻居,逐步生成一条游走序列。sentences.append([str(x) for x in random_walk(graph, node, walk_length)])把一次游走转换成一串节点编号组成的“句子”,加入语料库。random.shuffle(nodes)每轮遍历前打乱节点顺序,让不同起点都有机会作为开头,语料更均衡。model = Word2Vec(...)使用 Gensim 的 Word2Vec 完成 SkipGram 训练。sg=1, hs=1指定 SkipGram 模型和层次 Softmax,与 DeepWalk 论文的技术路线一致。return {node: model.wv[str(node)] for node in graph.nodes()}把训练好的向量按节点导出,方便下游任务直接取用。
常见误区
误区我觉得 DeepWalk 既然能做节点分类,肯定需要很多标注数据才能训练。
正解DeepWalk 训练嵌入时完全不需要标签。它只用图结构本身做自监督学习:随机游走自动生成序列,序列中同一窗口内的共现关系就是监督信号。标签只在后续“节点分类”阶段才用到,属于下游任务。
为何常见:人们容易把“训练嵌入”和“训练分类器”两个阶段混为一谈,误以为嵌入也依赖标注。
误区把图嵌入理解成给每个节点编号,然后转成一个 0/1 形式的独热向量。
正解独热向量是 N 维且稀疏的,任意两个节点的独热向量正交,看不出任何相似性。DeepWalk 学出的是低维稠密向量,每一维都是连续实数,经过 SkipGram 拟合共现关系后,向量之间的方向差异才带有语义。
为何常见:很多入门资料一提到“向量”就想到 one-hot 编码,忽略了嵌入是“学习出来”的连续表示。
误区既然它要训练类似 Word2Vec 的模型,肯定只能在几百个节点的图上跑。
正解DeepWalk 的复杂度约为 O(|V| × t × l × d),与边数基本解耦。随机游走可以在多线程并行生成,层次 Softmax 又避免了全量节点归一化,因此能扩展到百万甚至千万级节点的图。
为何常见:把“训练词向量”与“慢速”画等号,忽略了它在线性时间下同样能处理大规模语料的特性。
误区Node2Vec 名字都不一样,一定是把训练模型彻底换掉了。
正解Node2Vec 的嵌入训练依然使用 SkipGram,唯一区别在于随机游走时多了 p 和 q 两个参数,用来调整每一步的转移概率。当 p=q=1 时,Node2Vec 退化为标准 DeepWalk。
为何常见:Node2Vec 论文大量讨论 BFS/DFS 和参数调节,容易让人误以为它连训练目标也一起改了。
面试题
Q1. DeepWalk 最核心的学习目标是什么?
- A.预测节点是否异常
- B.学习节点的低维向量← 正确答案
- C.预测两个节点间连线
- D.生成新的随机游走序列
答案:B。DeepWalk 是一种图嵌入方法,目标是让每个图节点获得一个低维实数向量。A、C 属于下游任务,D 只是中间采样步骤,都算不上 DeepWalk 的学习目标。
Q2. 随机游走环节的输出是什么?
- A.一列节点类别标签
- B.一串节点编号序列← 正确答案
- C.一张图的全量邻接矩阵
- D.一组边的权重矩阵
答案:B。随机游走从一个节点出发,按邻居关系随机前进,最终输出一串节点编号组成的序列。这串序列被当作训练语料中的“句子”,并不是标签,也不是矩阵。
Q3. DeepWalk 借用下列哪个模型来完成训练?
- A.Transformer 的注意力编码
- B.卷积神经网络的图像特征
- C.Word2Vec 的 SkipGram 模型← 正确答案
- D.支持向量机的分类决策边界
答案:C。DeepWalk 的核心灵感来自自然语言处理中的 Word2Vec,具体使用 SkipGram:把节点当作词,把窗口内的共现关系当作监督信号,训练得到节点向量。
Q4. 滑窗大小 w 变大,最直接的影响是什么?
- A.上下文窗口变宽语义更全局← 正确答案
- B.随机游走语料规模随之增大
- C.每个节点向量维度显著变少
- D.目标函数改为平方误差损失
答案:A。w 决定了中心节点左右各取多少节点作为上下文。w 越大,能看到的共现节点越多,学到的语义更偏向全局;它并不直接扩大语料规模,也不会改变向量维度或损失函数。
Q5. 比较两个节点的嵌入向量,最常用的相似度是?
- A.余弦相似度度量← 正确答案
- B.曼哈顿距离度量
- C.编辑距离计算法
- D.马氏距离度量法
答案:A。图嵌入的语义相似体现在向量方向一致,因此常用余弦相似度,把点积除以两向量长度,得到 [-1,1] 的结果。编辑距离面向字符串,马氏距离用于带协方差的数据,曼哈顿距离更偏向坐标轴差异。
Q6. 关于 DeepWalk,下列说法正确的有哪些?
- A.游走过程完全不依赖节点标签← 正确答案
- B.随机游走序列只能串行生成
- C.相邻节点共现概率被模型捕获← 正确答案
- D.节点向量必须等于度中心性
答案:AC。DeepWalk 是自监督方法,游走只依赖图结构,A 正确;游走彼此独立、可以并行生成,B 错误;SkipGram 正是为了拟合窗口内共现概率,C 正确;向量是训练出来的,不等于节点的度中心性,D 错误。
Q7. 关于 Node2Vec,下列说法正确的有哪些?
- A.引入 p 和 q 调节游走概率← 正确答案
- B.可以平衡局部和全局探索← 正确答案
- C.训练仍使用 SkipGram 模型← 正确答案
- D.需要为节点准备人工标签
答案:ABC。Node2Vec 用 p 控制回退权重、q 控制远探权重,从而在 BFS 式局部探索与 DFS 式全局探索之间取得平衡;但训练目标依然是 SkipGram,同样不需要人工标签,D 错误。
Q8. 以下哪项是 DeepWalk 能扩展到大规模图的关键?
- A.层次 Softmax 降低计算开销← 正确答案
- B.对节点进行独热编码表示
- C.基于全图的邻接矩阵训练
- D.深度神经网络逐层预训练
答案:A。层次 Softmax 把全量节点归一化变成二叉树上的一次搜索,复杂度从 O(|V|) 降到 O(log|V|)。独热编码和全图邻接矩阵都是高维稀疏方案,只会加剧内存和计算开销。
Q9. 关于 DeepWalk 与 Node2Vec 的关系,正确的是?
- A.DeepWalk 是 Node2Vec 的特例情况
- B.p 越大回退到上一个点概率越低← 正确答案
- C.q 越大越倾向 DFS 式全局探索
- D.Node2Vec 与 DeepWalk 使用不同目标
答案:B。Node2Vec 在 p=q=1 时退化为 DeepWalk,A 说反了。p 越大,回退权重 1/p 越小,B 正确;q 越大,远探权重 1/q 越小,反而偏向 BFS 式局部探索;两者训练目标都是 SkipGram,D 错误。
Q10. 在千万级图上应用 DeepWalk,合理的优化策略有哪些?
- A.使用层次 Softmax 替代全量 Softmax← 正确答案
- B.把随机游走任务分片交给多线程并行← 正确答案
- C.把整张邻接矩阵一次性加载到显存
- D.对高频节点进行负采样加速训练← 正确答案
答案:ABD。层次 Softmax 或负采样都能避免对全部节点归一化,A、D 正确;随机游走互不依赖、天然可并行,B 正确;千万级图的邻接矩阵是 O(N²) 量级,整张加载到显存不现实,C 错误。