一、图算法概述

我们在上一篇文章​​Neo4j图数据科学库(GDS)入门|图管理​​中,讨论了图目录以及如何创建和管理图投影的问题。在本小系列中,将学习GDS中可用的强大图算法集合,以及如何在实际数据上使用它们。我们将从关于算法层和执行模式类内容开始,以便您快速了解一般的使用模式。之后,我们将学习Neo4j中的5大算法类别。

概括起来,共包含如下主要内容:


  • 算法层和执行模式
  • 中心性和重要性算法
  • 路径查找算法
  • 社区检测算法
  • 节点嵌入
  • 相似性算法


二、GDS库算法层与执行模型

(一)简介

在本小节中,您将了解不同的算法层、算法的不同执行模式,以及如何估计在GDS中运行算法所需的内存。

(二)分层

GDS算法分为三层:alpha、beta和production。


  • Production-quality(生产质量)层:表明该算法已经过稳定性和可扩展性测试。这一层中的算法以gds.<algorithm>为前缀。
  • Beta层:表示该算法是生产质量层的候选算法。这一层中的算法以gds.beta.<algorithm>为前缀。
  • Alpha层:表示该算法是实验性的,可能随时更改或删除。这一层中的算法以gds.alpha.<algorithm>为前缀。

(三)执行模式

GDS算法有4种执行模式,它们决定了如何处理算法的结果。


  1. stream:以记录流的形式返回算法的结果。
  2. stats:返回汇总统计信息的单个记录,但不写入Neo4j数据库或修改任何数据。
  3. mutate:将算法的结果写入内存中的图投影,并返回汇总统计信息的单个记录。
  4. write:将算法的结果写回Neo4j数据库,并返回汇总统计的单个记录。

只有生产层算法才能保证所有执行模式的存在。


有关执行模式的更多详细信息和最佳实践,请参阅Neo4j官方的​​运行算法文档​​。

(四)内存估计

随着数据规模的增长,数据科学从业者面临的一个普遍挑战是如何计算支持其分析和机器学习工作流所需的内存。这通常需要大量的实验和反复试验。为了避免这种情况,GDS提供了一个估算过程,允许您在实际执行算法之前估算对数据使用算法所需的内存。要对不同的算法和执行模式使用估计过程,只需使用.estimate命令即可进行估计。


只有生产层算法才能保证所有执行模式中都存在估计过程。


有关内存估计的使用和最佳实践的更多详细信息,请参阅Neo4j官方的​​内存估计文档​​。

(五)总体算法语法

综上所述,所有GDS算法都遵循以下语法:

CALL gds[.<tier>].<algorithm>.<execution-mode>[.<estimate>](
graphName: STRING,
configuration: MAP
)


三、中心性与重要性

(一)简介

中心性(Centrality)算法用于确定图中不同节点的重要性。

中心性的常见使用场景包括:


  • 推荐:确定并推荐内容或产品目录中最具影响力或最受欢迎的项目
  • 供应链分析:找到供应链中最关键的节点,无论是网络中的供应商、制造产品中的原材料还是路线中的港口
  • 欺诈和异常检测:查找具有许多共享标识符的用户,或以其他方式充当多个社区之间桥梁的用户

(二)度中心性示例

度中心性(Degree centrality)是最普遍、最简单的中心性算法之一,用于统计节点具有的关系数。在GDS实现中,专门计算出度中心度(out-degree centrality),即节点传出关系的计数。下面是一个使用度中心性计算每个演员演过的电影数量的示例。

首先创建图投影。

CALL gds.graph.project('proj', ['Actor','Movie'], 'ACTED_IN');

然后流化度中心性:

//get top 5 most prolific actors (those in the most movies)
//using degree centrality which counts number of `ACTED_IN` relationships
CALL gds.degree.stream('proj')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS actorName, score AS numberOfMoviesActedIn
ORDER BY numberOfMoviesActedIn DESCENDING, actorName LIMIT 5

通过计算结果可知,排名靠前的前三演员应该是:"Robert De Niro", "Bruce Willis"和 "Nicolas Cage."

(三)PageRank应用示例

另一种常见的中心性算法是PageRank。PageRank是一种很好的算法,用于测量有向图中节点的影响,尤其是当关系意味着某种形式的移动流时,例如在支付网络、供应链和物流、通信、路由以及网站和链接图中。

PageRank最初是由谷歌联合创始人拉里·佩奇(LarryPage)和谢尔盖·布林(SergeyBrin)于1996年在斯坦福大学(StanfordUniversity)开发的,是一个关于新型搜索引擎的研究项目的一部分。自那以后,Google Search一直使用它在搜索引擎结果中对网页进行排名。

总之,PageRank通过计算来自相邻节点的传入关系的数量来估计节点的重要性,这些关系由这些邻居的重要性和出度中心性加权。基本假设是,更重要的节点可能会有更多来自其他导入节点的传入关系。如果您有兴趣深入挖掘,Neo4j官方的​​PageRank文档​​将提供PageRank的全面技术解释。

下面是一个应用PageRank查找董事中最有影响力的电影的例子,我们将从Director → Actor电影网络中查找从1990年当天或之后发行的电影中获得,并且收入至少为1000万美元的。

首先,创建图投影。在这种情况下,我们可以使用Cypher投影来获得一个图,这里使用的脚本是:

(Person)-[:DIRECTED_ACTOR]→(Person)

可以遍历此图来了解导演和演员之间的影响度。

//drop last graph projection
CALL gds.graph.drop('proj', false);

//create Cypher projection for network of people directing actors
//filter to recent high grossing movies
CALL gds.graph.project.cypher(
'proj',
'MATCH (a:Person) RETURN id(a) AS id, labels(a) AS labels',
'MATCH (a1:Person)-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(a2)
WHERE m.year >= 1990 AND m.revenue >= 10000000
RETURN id(a1) AS source , id(a2) AS target, count(*) AS actedWithCount,
"DIRECTED_ACTOR" AS type'
);

下一个流化PageRank来查找我们创建的导演-演员图网络中最具影响力的前5位人物。

CALL gds.pageRank.stream('proj')
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS personName, score AS influence
ORDER BY influence DESCENDING, personName LIMIT 5

从运行结果来看,前三名应该是:"Robert De Niro", "Greg Kinnear"和 "Sandra Bullock."

(四)其他中心性算法

另外一些GDS生产层中心性算法包括:


  • 中介中心性(Betweenness Centrality
    ):衡量一个节点在图中其他节点之间的位置。它通常用于查找充当从图的一部分到另一部分的桥梁的节点。
  • 特征向量中心性(Eigenvector Centrality):衡量节点的传递影响。与PageRank类似,但仅适用于邻接矩阵的最大特征向量,因此不会以相同的方式收敛,并且更倾向于高度节点。它在某些用例中可能更合适,尤其是那些具有无向关系的用例。
  • 文章排名(Article Rank):PageRank的一种变体,它假设来自低等级节点的关系比来自高等级节点的关系具有更高的影响。

所有产品层的中心性算法的完整列表可以Neo4j官网的​​GDS文档的中心性部分​​找到。


四、度中心性挑战任务

[任务]哪个演员执导的电影最多?


五、路径搜索

(一)简介

路径查找算法主要针对查找两个或多个节点之间的最短路径,或评估路径的可用性和质量。

路径查找的常见使用场景有:


  • 供应链分析:确定原产地和目的地之间或原材料和成品之间的最快路径。
  • 客户旅程问题:分析构成客户体验的事件。例如,在医疗保健领域,这可能是住院患者从入院到出院的经历。

(二)Dijkstra源-目标最短路径

Dijkstra是一种常见的行业标准相似性算法。它计算源节点和目标节点之间的最短路径。与GDS中的许多其他路径查找算法一样,Dijkstra支持加权关系,以便在比较路径时考虑距离或其他成本属性。

下面是使用Dijkstra源-目标最短路径算法查找演员“Kevin Bacon”和“Denzel Washington”之间最短路径的示例。

首先,创建图投影。

CALL gds.graph.project('proj',
['Person','Movie'],
{
ACTED_IN:{orientation:'UNDIRECTED'},
DIRECTED:{orientation:'UNDIRECTED'}
}
);

接下来,我们运行Dijkstra最短路径算法:

MATCH (a:Actor)
WHERE a.name IN ['Kevin Bacon', 'Denzel Washington']
WITH collect(id(a)) AS nodeIds
CALL gds.shortestPath.dijkstra.stream('proj', {sourceNode:nodeIds[0], TargetNode:nodeIds[1]})
YIELD sourceNode, targetNode, path
RETURN gds.util.asNode(sourceNode).name AS sourceNodeName,
gds.util.asNode(targetNode).name AS targetNodeName,
nodes(path) as path;

这里的输出结果将是:提供一条凯文·培根(KevinBacon)和丹泽尔·华盛顿(DenzelWashington)之间的4跳路线。

(三)其他路径查找算法

其他GDS生产层路径查找算法可分为以下几个不同的子类别:

两个节点之间的最短路径:


  • 最短路径:Dijkstra的一个扩展,它使用启发式函数来加速计算。
  • Yen算法最短路径:Dijkstra的扩展,允许您查找多条最短路径,前k条。

源节点和多个其他目标节点之间的最短路径:


  • Dijkstra单源最短路径:Dijkstra实现一个源和多个目标之间的最短路径。
  • 增量步进单源最短路径:并行最短路径计算。计算速度比Dijkstra单源最短路径快,但使用更多内存。

源节点和多个其他目标节点之间的常规路径搜索:


  • 广度优先搜索:在每次迭代中,按与源节点的距离增加的顺序搜索路径。
  • 深度优先搜索:在每次迭代中尽可能沿着单个多跳路径进行搜索。

所有产品层的中心性算法的完整列表可以在Neo4j官方​​寻路文档​​中找到。


六、挑战:最短路径问题

[挑战问题]仅使用​​Actor​​​和​​Movie​​​节点以及​​ACTED_IN​​和DIRECTED 关系,求解Kevin Bacon和Peta Wilson之间的最短路径是什么(按关系跳数计算)。

七、社区检测

(一)简介

社区检测算法用于评估如何在图中对节点组进行集群或分区。GDS中的许多社区检测功能都集中于区分和分配ID给这些节点组,以便进行下游分析、可视化或其他处理。

社区检测的常见使用场景包括:


欺诈检测:通过识别频繁进行可疑交易和/或彼此共享标识符的账户,发现欺诈团伙。

客户360:将多个记录和交互消除到单个客户概要文件中,以便组织为每个客户提供一个聚合的真实来源。

市场细分:根据优先级、行为、兴趣和其他标准将目标市场划分为可接近的子组。


(二)Louvain社区检测

常见的社区检测算法是Louvain。Louvain最大化了每个社区的模块化分数,其中模块化量化了向社区分配节点的质量。这意味着,与随机网络中节点的连接程度相比,该方法能够评估社区中节点的连接密度。

Neo4j图数据科学开发进阶篇_最短路径

图1:Louvain模块化优化

Louvain算法通过递归地将社区合并在一起的分层聚类方法优化了这种模块化。有多个参数可用于调整Louvain,以控制其性能以及生成的社区的数量和大小。这包括要使用的最大迭代次数和分层级别,以及用于评估收敛/停止条件的容差参数。Neo4j官方的​​Louvain文档​​更详细地介绍了这些参数和调优。

另一个重要的考虑因素是,Louvain是一个随机算法。因此,重新运行时,社区分配可能会发生一些变化。当图没有一个自然定义良好的社区结构时,运行之间的变化可能会变得更加显著。Louvain包含一个seedProperty参数,可用于分配初始社区ID,并帮助实现运行之间的一致性。此外,如果一致性对于企业开发需求很重要,那么其他社区检测算法(如弱连接组件算法Weakly Connected Components,简称“WWC)会采用更具确定性的分区方法来分配社区,因此在运行之间不会发生变化。

下面是一个运行Louvain以了解电影推荐图中演员和导演社区的示例。

首先创建一个包含电影、演员和导演的图形投影,以无向方向投影关系,因为这最适合Louvain算法。

CALL gds.graph.project('proj', ['Movie', 'Person'], {
ACTED_IN:{orientation:'UNDIRECTED'},
DIRECTED:{orientation:'UNDIRECTED'}
});

然后,我们就可以运行Louvain算法了。在这里,我们将在mutate模式下运行Louvain以保存社区ID,并返回关于社区计数、分布、模块化分数的高级统计信息,以及Louvain如何处理图形的信息。

CALL gds.louvain.mutate('proj', {mutateProperty:'communityId'})

我们可以使用流操作验证投影中的communityId节点属性。

CALL gds.graph.streamNodeProperty('proj','communityId', ['Person'])
YIELD nodeId, propertyValue
WITH gds.util.asNode(nodeId) AS n, propertyValue AS communityId
WHERE n:Person
RETURN n.name, communityId LIMIT 10

(三)其他社区检测算法

下面是其他一些生产层社区检测算法。所有社区检测算法的完整列表可以在​​社区检测算法文档​​中找到。


  • 标签传播:与Louvain的意图类似,并行性好的快速算法,适用于大型图形。
  • 弱连接组件(WCC):将图划分为多个连接节点集,以便:
  • 每个节点都可以从同一集中的任何其他节点访问
  • 不同集合中的节点之间不存在路径
  • 三角形计数:计算每个节点的三角形数,可以用来检测社区的凝聚力和图的稳定性。
  • 局部聚类系数:计算图中每个节点的局部聚类系数,该系数是节点如何与其邻居聚类的指标。


八、节点嵌入


图嵌入是将属性图转换为一个向量(图)或者一组向量(顶点)。好的嵌入应该尽可能的捕获图拓扑结构、顶点之间的关系以及其他一些关于图/子图/顶点的信息。尽可能多的捕获相关属性会产生更好的嵌入,对下游任务会很有帮助。一般来说,我们可以将图嵌入大致分为两类:

  • 顶点嵌入:将图中的每一个顶点向量化表示。当我们想在节点层次上进行可视化或预测任务的时候就会做顶点嵌入,比如说在2维平面上可视化顶点或者基于顶点之间的相似度预测顶点之间是否连接。
  • 图嵌入:将整个图表示成一个向量。当我们对整个图做预测任务或是与其他图比较,又或者可视化整张图,例如比较分子结构时, 我们就进行图嵌入。

(一)概念

节点嵌入的目标是计算节点的低维向量表示,使得向量之间的相似度(例如点积)近似于原始图中节点之间的相似度。这些向量也称为嵌入,对于探索性数据分析、相似性度量和机器学习非常有用。

下图说明了节点嵌入背后的概念,即在图中靠得很近的节点最终在二维嵌入空间中靠得很近。因此,嵌入从图中提取结构,即n维邻接矩阵,并将其近似为每个节点的二维向量。由于显著降低了维数,嵌入向量更有效地用于下游过程。例如,它们可以用于聚类分析,也可以用作训练节点分类或链接预测模型的特征。

Neo4j图数据科学开发进阶篇_GDS库_02

图1:节点嵌入背后的概念

当然,在实际问题中,节点嵌入通常会大于2维,通常会达到数百或更大,尤其是当应用于具有数百万或数十亿节点的大型图时。节点嵌入也不必严格根据图中的节点接近度来确定相似度。虽然基于关系跳数和公共邻居中距离的相似度在应用中可能最常见,但节点嵌入在计算嵌入向量时还可以考虑节点属性和其他“全局视图”节点属性。

(二)用例分析

节点嵌入具有跨多个用例的应用程序,从推荐系统到异常和欺诈检测、实体解析和其他形式的知识图完成。

节点嵌入向量本身并不提供什么结论,它们是为了启用或扩展其他分析支持而创建的。常见工作流包括:


  • 探索性数据分析(EDA),例如可视化TSNE图中的嵌入,以更好地了解图形结构和潜在的节点群集
  • 相似性度量:节点嵌入允许您使用K最近邻(KNN)或其他技术在大型图中缩放相似性推断。这对于扩展基于内存的推荐系统(例如协作过滤的变体)非常有用。它还可以用于欺诈检测等领域的半监督技术——例如,我们可能希望生成类似于一组已知欺诈实体的线索。
  • 用于机器学习的功能:节点嵌入向量自然地作为各种机器学习问题的功能插入。例如,在在线零售商的用户购买图中,我们可以使用嵌入来训练机器学习模型,以预测用户下一步可能对购买什么产品感兴趣。

(三)FastRP算法

GDS提供了一种节点嵌入技术的自定义实现,称为快速随机投影(Fast Random Projection),简称FastRP。FastRP利用概率抽样技术生成图的稀疏表示,从而可以非常快速地计算嵌入向量,其质量堪与传统随机遍历和神经网络技术(如Node2vec和GraphSage)生成的嵌入向量相媲美。这使得FastRP成为在GDS中开始探索嵌入图的最佳选择。

FastRP支持多个调整参数,在实际应用中,这些参数可能是需要考虑的重要因素。下面有几个值得注意的问题:


  • embeddingDimension:适用于GDS中的所有节点嵌入算法。控制嵌入向量的长度。设置此参数是降维和精度之间的权衡。更大的嵌入维度将更准确地捕获图结构,但生成和生成嵌入向量所需的时间也会更长,从而需要更多的内存和计算来处理下游。嵌入维度的选择在很大程度上取决于图中的节点数。由于嵌入可以编码的信息量受到其维数的限制,较大的图往往需要较大的嵌入维数。典型值是128-1024范围内的二次方。值至少为256可以在100K个节点的顺序的图上获得良好的结果。
  • IterationWeights:这控制两个方面:中间嵌入的迭代次数,以及它们对最终节点嵌入的相对影响。该参数是一个数字列表,表示每个数字的一次迭代,其中数字是应用于该迭代的权重。默认值为[0.0,1.0,1.0]。通常,与第i次迭代相对应的中间嵌入包含依赖于长度为i的路径可到达的节点的特征。

还有其他参数控制规范化强度和节点自身影响。像往常一样,您可以在文档中详细引用这些内容。

关于FastRP的最后一个重要注意事项是,虽然我们在这里不讨论它,但它能够在生成嵌入时考虑节点和关系属性的权重。这对于生成嵌入向量非常有用,这些嵌入向量封装了来自加权图结构和数据中其他属性/属性的信号。像往常一样,您可以在文档中更详细地参考参数和各种配置。


(四)FastRP应用举例

下面是一个在电影图中的人物节点上基于他们所扮演和/或导演的电影生成FastRP嵌入的示例。

像往常一样,我们将从图投影开始:

CALL gds.graph.project('proj', ['Movie', 'Person'], {
ACTED_IN:{orientation:'UNDIRECTED'},
DIRECTED:{orientation:'UNDIRECTED'}
});

之后,我们将运行FastRP。出于演示目的,我们将只使用嵌入维度64。我们可以选择在此处设置一个随机种子,以控制运行之间的一致性。

CALL gds.fastRP.stream('proj',  {embeddingDimension:64, randomSeed:7474})
YIELD nodeId, embedding
WITH gds.util.asNode(nodeId) AS n, embedding
WHERE n:Person
RETURN id(n), n.name, embedding LIMIT 10

理论上,这些嵌入可用于相似性度量,以了解哪些演员最相似,并可用于内容推荐系统中,以根据用户最近观看的电影的演员和/或导演向用户推荐电影。

(五)其他节点嵌入算法

GDS还实现了Node2Vec(基于图中的随机游动计算节点的向量表示)和GraphSage(基于节点属性和图结构计算节点嵌入的归纳建模方法)。有关此二种算法的描述,请参考文后引用处,此处暂时省略讨论。

九、相似性

(一)简介

顾名思义,相似性算法用于推断节点对之间的相似性。在GDS中,这些算法大量运行在图投影上。当根据用户指定的度量和阈值识别相似的节点对时,将在该对之间绘制具有相似性得分属性的关系。根据运行算法时使用的执行模式,可以将这些相似性关系流化、变异为内存中的图形或写回数据库。

相似性的常见用例包括:


  • 欺诈检测:通过分析一组新用户帐户与标记帐户的相似程度,发现潜在的欺诈用户帐户
  • 推荐系统:在在线零售商店中,识别与用户当前查看的商品配对的商品,以告知用户印象并提高购买率
  • 实体解析:根据活动或图中的标识信息,标识彼此相似的节点

(二)GDS中的相似性算法

GDS有两种主要的相似性算法:


  • 节点相似度:根据图中共享相邻节点的相对比例确定节点之间的相似度。在可解释性很重要的地方,节点相似性是一个很好的选择,您可以将比较范围缩小到数据的一个子集。缩小范围的示例包括只关注单个社区、新添加的节点或特定接近感兴趣子图的节点。
  • K-最近邻(KNN):基于节点属性确定相似度。当适当调整时,GDS KNN实现可以很好地扩展到大型图上的全局推理。它可以与嵌入和其他图算法结合使用,根据图中的接近度、节点属性、社区结构、重要性/中心性等确定节点之间的相似性。

(三)相似性度量的选择

节点相似度和KNN都提供了不同相似度度量之间的选择。节点相似度可以在杰卡德(jaccard)相似度重叠(overlap)相似度之间进行选择。KNN度量的选择由节点属性类型决定。整数列表受jaccard和overlap的约束,浮点数列表受余弦相似性、皮尔逊(pearson)和欧几里德的约束。使用不同的度量标准当然会改变相似度得分,并略微改变解释。您可以在节点相似性文档中阅读关于​​节点相似性​​​的不同度量的更多信息,也可以在​​K-最近邻文档​​中阅读关于KNN的不同度量的更多信息。

(四)控制比较范围

将图中的每个节点与其他节点进行比较是一项计算复杂度约为O(n^2)的昂贵任务。节点相似度和KNN的GDS实现都有内部机制,可以智能地选择节点对进行比较,从而使它们工作得更快,扩展得更好。它们还具有可由用户调整的参数,以调整如何对节点对进行采样和选择以进行比较。

节点相似度有一个节点的degreeCutOff参数,允许您为要选择的节点设置度中心度的下限。

KNN具有各种参数,可以调整这些参数以影响节点比较的速度与完整性权衡,包括采样率、初始采样器方法、迭代之间的随机连接计数以及其他一些参数。您可以在​​K-最近邻文档​​中阅读有关这些如何工作的更多信息。

(五)结果控制范围

对于相似性比较,我们可能还希望控制返回结果的数量,以便只考虑最相关的节点对。节点相似度和KNN都有一个topK参数,用于限制每个节点返回的相似度比较数。通过节点相似性,还可以全局限制结果,而不仅仅是基于每个节点。

(六)KNN应用示例

让我们根据上面“节点嵌入”计算出的嵌入,并根据演员和导演参与的电影,使用它们来确定演员和导演之间的相似性。在此,我们可以使用以下命令重新生成投影:

CALL gds.graph.project('proj', ['Movie', 'Person'], {
ACTED_IN:{orientation:'UNDIRECTED'},
DIRECTED:{orientation:'UNDIRECTED'}
});

然后,我们将像上面介绍的那样运行FastRP(当然不是在mutate模式下),这样嵌入将保存在投影中。

CALL gds.fastRP.mutate('proj',  {
embeddingDimension:64,
randomSeed:7474,
mutateProperty:'embedding'
})

之后,我们可以运行相似性计算。在此,我们将使用系统默认的余弦度量策略。为了便于演示,我将topK限制为1,以便我们可以看到每个节点的顶部对。

CALL gds.knn.stream('proj', {nodeLabels:['Person'], nodeProperties:['embedding'], topK:1})
YIELD node1, node2, similarity
RETURN gds.util.asNode(node1).name AS actorName1,
gds.util.asNode(node2).name AS actorName2,
similarity
LIMIT 10

(七)相似性函数

除了节点相似度和KNN算法外,GDS还提供了一组函数,可用于使用各种相似度度量计算两个数字数组之间的相似度,包括jaccard、overlap、pearson、cosine相似度等。完整的文档可以在​​相似函数文档​​中找到。如果您希望一次测量单个选定节点对之间的相似性,而不是计算整个图上的相似性,则这些函数非常有用。


十、主要参考资源