🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
在本章中,我们将讨论节点重要性,也称为中心性算法. 正如您将发现的那样,基于给定图形和给定问题的重要性定义,已经开发了几种技术。我们将了解最著名的技术,从度中心性和 Google 使用的 PageRank 算法开始。对于后者,我们将通过一个示例实现并在一个简单的图表上运行它,以充分了解它的工作原理以及何时可以使用它。在发现其他类型的中心性算法(例如中介中心性)之后,我们将在本章结束时解释如何在欺诈检测的上下文中使用中心性算法。在此示例中,我们将首次使用 GDS 中提供的工具从 Cypher 创建投影图,以便在节点之间创建假关系以满足分析需求。
本章将涵盖以下主题:
- 定义重要性
- 计算度中心性
- 了解 PageRank 算法
- 基于路径的中心性度量
- 将中心性应用于欺诈检测
让我们开始吧!
技术要求
在本章中,我们将使用以下工具:
- Neo4j 图形数据库和图形数据科学库
- 本章的代码文件,位于https://github.com/PacktPublishing/Hands-On-Graph-Analytics-with-Neo4j/tree/master/ch6
如果您使用的是Neo4j < 4.0 ,那么GDS插件的最新兼容版本是1.1 ,而如果您使用的是Neo4j ≥ 4.0 ,那么GDS插件的第一个兼容版本是1.2 。
定义重要性
确定图表中最重要的节点取决于您对重要性的定义。此定义本身取决于您要达到的目标。在本章中,我们将研究两个重要的例子:
- 信息在社交网络中传播
- 道路或计算机网络中的关键节点
我们将了解到,这些问题中的每一个都可以通过中心性算法来解决。在本节中,我们将以下图为例来帮助我们理解重要性的不同定义:
这个网络中哪个节点最重要?答案取决于给定上下文中重要的含义。让我们考虑定义重要性的不同方法。
知名度和信息传播
定义重要性最明显的方法是使用影响者概念。大多数有影响力的节点都有很多连接,因此可以很好地传播信息。在社交网络的背景下,有影响力的人通常会获得与希望利用其影响力并让尽可能多的人了解其产品的品牌的广告合同。考虑到这一点,上图中的节点3、5、6和9都具有三个连接,使它们成为最重要的节点。
为了更进一步并尝试解开这四个节点,已经提出了其他方法。其中最著名的是 PageRank 算法,谷歌使用它来对其搜索引擎的结果进行排名。PageRank 更新给定节点的重要性,同时考虑其邻居的重要性。如果一个网页有来自一个重要页面的反向链接,那么与不太重要的页面引用的另一个页面相比,它的重要性将会增加。
这些基于度数的方法有许多应用,从影响到欺诈检测。但是,如果您正在寻找道路或计算机网络中的关键节点,例如,它们并不是最适合的方法。
关键或桥接节点
基于度的中心性算法根据它们的连接来识别重要节点。但是如果节点5消失,我们的图会发生什么?我们最终得到三个不连贯的组件:
- 节点1、2、3和4
- 节点6、7和8
- 节点9、10和11
下图说明了这种新布局:
如您所见,从一个组件到另一个组件的通信将是完全不可能的。例如,考虑节点1和10。他们之间再也没有可能的路径了。在电信或道路网络中,这种情况可能会产生严重后果,从严重的交通拥堵到无法呼叫紧急服务。需要不惜一切代价避免它,这意味着需要识别我们测试图中的节点5等节点,以便更好地保护。因此,节点 5 被称为关键(或桥接)节点。
幸运的是,我们有中心性算法来衡量这种重要性。我们将它们组合在基于路径的类别下。我们将在本章后面讨论的接近性和中介中心性算法属于这一类。
在接下来的两节中,我们将详细介绍度中心性和 PageRank 算法。下一节将更加关注基于路径的中心性的两个示例:接近性和中介性。
计算度中心性
计算度中心性涉及根据节点有多少关系对节点进行排序。这可以使用基本 Cypher 计算或通过 GDS 插件和投影图调用。
公式
度中心性C n定义如下:
Cn = deg(n)
这里,deg(n)表示连接到节点n的边数。
如果您的图是有向的,那么您可以分别将传入和传出度定义为从节点n开始的关系数和以n结尾的关系数。
例如,让我们考虑下图:
节点A有一个传入关系(来自B)和两个传出关系(到B和D),因此其传入度为 1,其传出度为 2。每个节点的度数汇总如下表:
Node | Outgoing degree | Incoming degree | Degree (undirected) |
A | 2 | 1 | 3 |
B | 1 | 3 | 4 |
C | 1 | 0 | 1 |
D | 1 | 1 | 2 |
现在让我们看看如何在 Neo4j 中获得这些结果。您可以使用以下 Cypher 语句创建这个小图:
CREATE (A:Node {name: "A"})
CREATE (B:Node {name: "B"})
CREATE (C:Node {name: "C"})
CREATE (D:Node {name: "D"})
CREATE (A)-[:LINKED_TO {weight: 1}]->(B)
CREATE (B)-[:LINKED_TO]->(A)
CREATE (A)-[:LINKED_TO]->(D)
CREATE (C)-[:LINKED_TO]->(B)
CREATE (D)-[:LINKED_TO]->(B)
Neo4j 中的度中心度计算
仅使用 Cypher 和聚合函数就可以使用 Neo4j 计算连接到节点的边数。例如,以下查询计算每个节点的传出关系数:
MATCH (n:Node)-[r:LINKED_TO]->()
RETURN n.name, count(r) as outgoingDegree
ORDER BY outgoingDegree DESC
在我们在上一节中研究的小图上运行此查询会得到以下结果:
╒══════════╤════════════════╕
│"nodeName"│"outgoingDegree"│
╞══════════╪════════════════╡
│"A" │2 │
├──────────┼────────────────┤
│"B" │1 │
├──────────┼────────────────┤
│"C" │1 │
├──────────┼────────────────┤
│"D" │1 │
└──────────┴────────────────┘
传入度数也可以使用稍微修改的 Cypher 查询来计算,其中关系的方向由于使用<-[]-符号(而不是-[]->)而反转:
MATCH (n:Node)<-[r:LINKED_TO]-()
RETURN n.name as nodeName, count(r) as incomingDegree
ORDER BY incomingDegree DESC
此查询的结果报告在下表中:
╒══════════╤════════════════╕
│"nodeName"│"incomingDegree"│
╞══════════╪════════════════╡
│"B" │3 │
├──────────┼────────────────┤
│"A" │1 │
├──────────┼────────────────┤
│"D" │1 │
└──────────┴────────────────┘
如您所见,节点C缺少中心性结果,该节点未连接到任何其他节点。这可以使用 来修复OPTIONAL MATCH,如下所示:
MATCH (n:Node)
OPTIONAL MATCH (n)<-[r:LINKED_TO]-()
RETURN n.name as nodeName, count(r) as incomingDegree
ORDER BY incomingDegree DESC
这一次,结果包含节点C:
╒══════════╤════════════════╕
│"nodeName"│"incomingDegree"│
╞══════════╪════════════════╡
│"B" │3 │
├──────────┼────────────────┤
│"A" │1 │
├──────────┼────────────────┤
│"D" │1 │
├──────────┼────────────────┤
│"C" │0 │
└──────────┴────────────────┘
但是,使用 GDS 实现要方便得多,它已经处理了这些组件。
使用 GDS 计算传出学位
使用 GDS 时,我们需要定义投影图。在这种情况下,我们可以使用最简单的语法,因为我们想要在其自然方向上添加所有节点及其所有关系:
CALL gds.graph.create("projected_graph", "Node", "LINKED_TO")
然后,我们可以使用这个投影图来运行度中心性算法:
CALL gds.alpha.degree.stream("projected_graph")
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name as nodeName, score
ORDER BY score DESC
此查询的结果在此处复制:
╒══════════╤═══════╕
│"nodeName"│"score"│
╞══════════╪═══════╡
│"A" │2.0 │
├──────────┼───────┤
│"B" │1.0 │
├──────────┼───────┤
│"C" │1.0 │
├──────────┼───────┤
│"D" │1.0 │
└──────────┴───────┘
如果您想计算传入度数,则必须更改投影图的定义。
使用 GDS 计算传入的学位
在 GDS 中,我们需要定义一个投影图,我们可以命名(并保存以备将来在同一会话中使用)或保持未命名。
使用命名投影图
创建一个包含所有反向关系的投影图需要对关系的方向进行详细配置:
CALL gds.graph.create(
"projected_graph_incoming",
"Node",
{
LINKED_TO: {
relationship: "LINKED_TO",
orientation: "REVERSE"
}
}
)
这个新的投影图 ( ) 包含带有标签projected_graph_incoming的节点。它还将具有类型的关系,这将是来自原始图形但在方向上的关系的副本。换句话说,如果原始图包含关系,那么投影图将只包含模式。您可以使用以下查询在此新投影图上运行度中心性算法: NodeLINKED_TO LINKED_TO REVERSE (A)-[:LINKED_TO]->(B)(B)-[:LINKED_TO]->(A)
CALL gds.alpha.degree.stream("projected_graph_incoming")
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name as name, score
ORDER BY score DESC
结果如下:
╒══════════╤═══════╕
│"nodeName"│"score"│
╞══════════╪═══════╡
│"B" │3.0 │
├──────────┼───────┤
│"A" │1.0 │
├──────────┼───────┤
│"D" │1.0 │
├──────────┼───────┤
│"C" │0.0 │
└──────────┴───────┘
如果您的图是无向图,则必须orientation: "UNDIRECTED"在投影图定义中使用参数,就像我们在专门讨论最短路径(第 4 章,图形数据科学库和路径查找)和空间数据(第 5 章)的章节中所做的那样,空间数据)。
使用匿名投影图
GDS 还为我们提供了在没有命名投影图的情况下运行算法的选项。在这种情况下,投影图将在调用 GDS 过程时动态生成。例如,为了获取无向图的节点度数,可以使用以下代码:
CALL gds.alpha.degree.stream(
{
nodeProjection: "Node",
relationshipProjection: {
LINKED_TO: {
type: "LINKED_TO",
orientation: "UNDIRECTED"
}
}
}
) YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name as nodeName, score
ORDER BY score DESC
此查询返回以下结果:
╒══════════╤═══════╕
│"nodeName"│"score"│
╞══════════╪═══════╡
│"B" │4.0 │
├──────────┼───────┤
│"A" │3.0 │
├──────────┼───────┤
│"D" │2.0 │
├──────────┼───────┤
│"C" │1.0 │
└──────────┴───────┘
这将结束本节关于使用 Neo4j 计算度中心性的内容。但是,正如我们在本章第一节中讨论的那样,可以提高度中心性以考虑每个连接的质量。正如我们现在将发现的,这就是 PageRank 算法试图实现的目标。
了解 PageRank 算法
PageRank 算法以谷歌的联合创始人之一拉里佩奇的名字命名。该算法是在 1996 年开发的,用于对搜索引擎的结果进行排名。在本节中,我们将通过逐步构建公式来理解它。然后,我们将在单个图上运行该算法,看看它是如何收敛的。我们还将使用 Python 实现该算法的一个版本。最后,我们将学习如何使用 GDS 从存储在 Neo4j 中的图形中获取这些信息。
建立公式
让我们考虑一下互联网背景下的 PageRank。PageRank 算法依赖于并非所有传入链接都具有相同权重的想法。例如,考虑从纽约时报文章到您博客中文章的反向链接。它比来自每月访问 10 次的网站的链接更重要,因为它会将更多用户重定向到您的博客。所以,我们希望《纽约时报》比低流量的网站更有分量。这里的挑战是评估传入连接的权重。在PageRank算法中,这个权重就是页面的重要性或者页面的排名。因此,我们最终得到了一个递归公式,其中每个页面的重要性都与其他页面进行比较:
PR(A) = PR(N 1 ) + ... + PR(N n )
这里,N i ( i=1...n ) 是指向页面A的页面(传入关系)。
这不是 PageRank 的最终公式。还需要考虑另外两个方面。首先,我们将平衡传入链接对于具有许多传出链接的页面的重要性。就好像每个页面都有相同数量的选票分配给其他人。它可以将所有投票都投给一个页面,在这种情况下链接非常强大,或者在许多页面之间共享它们,从而降低许多链接的重要性。
因此,我们可以像这样更新 PageRank 公式:
PR(A) = PR(N 1 )/C(N 1 ) + ... + PR(N n )/C(N n )
这里,C(N i )是来自N i的传出链接数。
整合以最终得到 PageRank 公式的第二个方面是阻尼因子。它需要更多解释,将在以下小节中介绍。
阻尼系数
最后但同样重要的是,PageRank 算法引入了一个阻尼因子来减轻邻居的影响。这个想法是,当从一个页面导航到另一个页面时,互联网用户通常会点击链接。但在某些情况下,用户可能会感到无聊,或者出于某种原因转到另一个页面。这种情况发生的概率由阻尼因子建模。最初论文的最终 PageRank 公式如下:
PR(A) = (1 - d) + d * (PR(N 1 )/C(N 1 ) + ... + PR(N n )/C(N n ))
通常,阻尼因子d设置为 0.85 左右,这意味着用户在不点击链接的情况下随机导航到页面的概率为1 - d = 15%。
对于没有传出链路的节点,阻尼因子的另一个重要影响是可见的,也称为sinks。如果没有阻尼因子,这些节点将倾向于获取其邻居给出的排名而不将其返回,从而破坏了算法的原理。
正常化
尽管前面的公式来自于 1996 年引入 PageRank 算法的原始论文,但请注意有时会使用另一个公式。在原始公式中,所有节点的秩和加起来为N,即节点数。更新后的公式被归一化为 1 而不是N,写法如下:
PR(A) = (1 - d) / N + d * (PR(N 1 )/C(N 1 ) + ... + PR(N n )/C(N n ))
您可以通过假设所有节点的等级都初始化为1/N来轻松理解这一点。然后,在每次迭代中,每个节点都会将此等级平均分配给它的所有邻居,以使总和保持不变。
这个公式被选为networkx用于图形操作的 Python 包中的 PageRank 实现。但是,Neo4j GDS 使用原始公式。出于这个原因,在以下小节中,我们将使用 PageRank 方程的原始版本。
在示例图上运行算法
PageRank 算法可以用一个迭代过程来实现,在每次迭代中,给定节点的排名会根据其在前一次迭代中的邻居的排名进行更新。重复这个过程,直到算法收敛,这意味着两次迭代之间的 PageRank 差异低于某个阈值。为了理解它是如何工作的,我们首先要在我们在度中心性部分研究的简单图上手动运行它。
为了计算 PageRank,我们需要每个节点的输出度,它只是从节点开始的箭头数。在上图所示的图表中,这些度数如下:
A => 2
B => 1
C => 1
D => 1
为了运行 PageRank 算法(以它的迭代形式),我们需要给每个节点的排名一个初始值。由于我们对给定节点没有任何先验偏好,我们可以使用统一值1初始化这些值。请注意,此初始化保留了 PageRank 算法的归一化:PageRank 在其初始状态的总和仍然等于N,即节点数。
然后,在迭代1期间,页面排名值更新如下:
- 节点 A:接收来自节点B的一个传入连接,因此其更新后的 PageRank 如下:
new_pr_A = (1 - d) + d * (old_pr_B / out_deg_B) = 0.15 + 0.85 * (1 / 1) = 1.0
- 节点 B:具有三个传入连接:
- 一个来自A
- 一个来自C
- 最后一个来自D
因此,它的页面排名更新如下:
new_pr_B = (1 - d)
+ d * (old_pr_A / out_deg_A
+ old_pr_C / out_deg_C
+ old_pr_D / out_deg_D)
= 0.15 + 0.85 * (1 / 2 + 1 / 1 + 1 / 1)
= 2.275
- 节点 C:没有传入连接,因此它的等级更新如下:
new_pr_C = (1 - d) = 0.15
- 节点 D :从A接收一个连接:
new_pr_D = (1 - d) + d * (old_pr_A / out_deg_A) = 0.15 + 0.85 * (1 /2) = 0.575
迭代 2 包括重复相同的操作但更改old_pr值。例如,迭代2后节点B的更新 PageRank如下:
new_pr_B = (1 - d) + d * (old_pr_A / out_deg_A + old_pr_C / out_deg_C + old_pr_D / out_deg_D)
= 0.15 + 0.85 * (1.0 / 2 + 0.15 / 1 + 0.575 / 1)
= 1.191
B在第二次迭代中排名下降很多,而A的排名从 0.575 增加到 1.117。
下表总结了前三个迭代:
迭代/节点 | A | B | C | D |
初始化 | 1 | 1 | 1 | 1 |
0 | 1.0 | 2.275 | 0.15 | 0.575 |
1 | 2.084 | 1.191 | 0.15 | 0.575 |
2 | 1.163 | 1.652 | 0.15 | 1.036 |
何时停止迭代的问题将在下面的小节中得到解答,我们将在其中使用 Python 实现一个 PageRank 版本。
使用 Python 实现 PageRank 算法
为了实现 PageRank 算法,我们需要就图形表示达成一致。为了避免引入其他依赖,我们将通过字典使用简单的表示。图中的每个节点在字典中都有一个键。关联的值包含另一个字典,其键是键中的链接节点。我们在本节学习的图写成如下:
G = {
'A': {'B': 1, 'D': 1},
'B': {'A': 1},
'C': {'B': 1},
'D': {'B': 1},
}
page_rank我们要编写的函数有以下参数:
- G,算法将为其计算 PageRank 的图。
- d, 阻尼系数,默认值为 0.85。
- tolerance, 算法收敛时停止迭代的容差。我们将设置默认值 0.01。
- max_iterations,一个健全性检查,以确保我们不会无限循环,以防算法无法收敛。作为一个数量级,在最初的 PageRank 出版物中,作者报告说,对于包含超过 3 亿条边的图,在大约 50 次迭代后达到了收敛。
这是函数定义:
def page_rank(G, d=0.85, tolerance=0.01, max_iterations=50):
然后,我们需要初始化 PageRank。与上一节类似,我们将值 1 分配给所有节点,因为我们对最终结果没有任何先验信念:
pr = dict.fromkeys(G, 1.0)
我们还计算每个节点的传出链接数,因为这将在稍后使用。根据我们对 graph 的定义,G传出链接的数量就是与每个键相关联的字典的长度:
outgoing_degree = {k: len(v) for k, v in G.items()}
现在我们已经初始化了我们需要的所有变量,我们可以开始迭代过程,我们最多会有max_iter迭代。在每次迭代中,我们会将pr上一次迭代中的字典保存到old_pr字典中并创建一个新pr字典,初始化为0,每次我们找到与节点的传入关系时都会更新该字典,以便old_pr最终包含pr每个节点的更新:
for it in range(max_itererations):
print("======= Iteration", it)
old_pr = dict(pr)
pr = dict.fromkeys(pr.keys(), 0)
下一步是遍历图的每个节点,并使用固定(1-d)/N项及其邻居之一更新其排名:
for node in G:
for neighbor in G[node]:
pr[neighbor] += d * old_pr[node] / outgoing_degree[node]
pr[node] += (1 - d)
print("New PR:", pr)
最后,在每个节点的迭代之后,我们可以比较pr上一次迭代的字典(old_pr)和新的字典(pr)。如果差异的平均值低于tolerance阈值,则算法已经收敛,我们可以返回当前pr值:
# check convergence
mean_diff_to_prev_pr = sum([abs(pr[n] - old_pr[n]) for n in G]) / len(G)
if mean_diff_to_prev_pr < tolerance:
return pr
最后,我们可以在之前定义的图上调用这个新创建的函数:
pr = page_rank(G)
这在第九次迭代后为我们提供了以下输出:
{'A': 1.50, 'B': 1.57, 'C': 0.15, 'D': 0.78}
这个实现对于理解算法很有用,但这是它的唯一目的。在实际应用程序中使用 PageRank 算法时,您将不得不依赖经过优化和测试的解决方案,例如在 Neo4j 的 GDS 插件中实现的解决方案。
使用 GDS 评估 Neo4j 中的 PageRank 中心性
像往常一样,在使用 GDS 时,我们需要定义将用于运行算法的投影图。在这个例子中,我们将在有向图上运行 PageRank 算法,这是投影图的默认行为。如果您在上一节中没有这样做,则可以使用以下查询从具有Node标签和与LINKED_TO类型的关系的节点中创建命名投影图:
CALL gds.graph.create("projected_graph", "Node", "LINKED_TO")
然后我们可以在这个投影图上运行 PageRank 算法。Neo4j中PageRank过程的签名如下:
CALL gds.pageRank(<projected_graph_name>, <configuration>)
配置映射接受以下参数:
- dampingFactor: 一个介于 0 和 1 之间的浮点数,对应于阻尼因子(d在我们的 Python 实现中)。默认值为 0.85。
- maxIterations:最大迭代次数(默认为 20)。
- tolerance:衡量收敛的容差(默认为1E-7)。
当对所有参数使用默认值时,我们可以运行 PageRank 算法并使用以下 Cypher 语句流式传输结果:
CALL gds.pageRank.stream("projected_graph", {}) YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name as nodeName, score
ORDER BY score DESC
流式传输的结果与我们在本章前面通过自定义实现获得的结果相当,即使它们并不严格相同。
比较度中心性和 PageRank 结果
为了理解 PageRank 在做什么,让我们在我们的小型有向图上比较度中心性和 PageRank 算法的结果。这种比较如下表所示:
节点 | 度中心性 | PageRank 中心性 |
A | 2 | 1.44 |
B | 1 | 1.52 |
C | 1 | 0.15 |
D | 1 | 0.76 |
虽然节点A具有最高的传出度 ( 2 ),但它只接收一个链接,这意味着图中只有一个其他页面信任它。另一方面,节点B的传出度为1,但网络的其他三个页面都指向它,使其具有更高的可信度。这就是为什么在 PageRank 中心性下,最重要的节点现在是节点B的原因。
变体
根据期望的目标,PageRank 有几个变体。这些也在 GDS 中实现。
文章排名
ArticleRank 是 PageRank 的一种变体,其中假设来自具有几个传出链接的页面的链接比其他链接更重要的机会更小。这是通过在公式中引入平均度数来实现的:
AR(A) = (1 - d) + d * (AR(N1)/(C(N1) + AVG(C)) + ... + AR(Nn)/(C(Nn) + AVG(C)))
这里,AVG(C)是网络中所有页面的平均传出连接数。
GDS 中的用法与 PageRank 类似:
CALL gds.alpha.articleRank.stream("projected_graph", {})
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name as name, score
ORDER BY score DESC
但是,如图所示,结果彼此更接近:
╒══════════╤═══════╕
│"nodeName"│"score"│
╞══════════╪═══════╡
│"B" │0.37 │
├──────────┼───────┤
│"A" │0.29 │
├──────────┼───────┤
│"D" │0.23 │
├──────────┼───────┤
│"C" │0.15 │
└──────────┴───────┘
个性化的PageRank
在个性化的PageRank中,一些用户定义的节点被赋予了更多的权重。例如,可以通过以下查询来提高节点C的重要性:
MATCH (A:Node {name: "A"})
CALL gds.pageRank.stream("projected_graph", {
sourceNodes: [A]
})
YIELD nodeId, score
RETURN gds.util.asNode(nodeId).name AS nodeName, round(score*100)/100 as score
ORDER BY score DESC
结果列于下表。您可以看到该节点A比在传统的 PageRank 中获得了更多的重要性:
╒══════════╤═══════╕
│"nodeName"│"score"│
╞══════════╪═══════╡
│"A" │0.44 │
├──────────┼───────┤
│"B" │0.34 │
├──────────┼───────┤
│"D" │0.19 │
├──────────┼───────┤
│"C" │0.0 │
└──────────┴───────┘
个性化 PageRank 的应用包括推荐,在这些推荐中,我们已经对给定客户根据他们以前的购买可能喜欢的产品有一些先验知识。
特征向量中心性
PageRank 实际上是另一个中心度度量的变体,称为特征向量中心度。让我在这里通过使用矩阵重建 PageRank 公式来介绍一些数学。
邻接矩阵
为了处理图并模拟其中传播的信息,我们需要一种方法来对复杂的关系模式进行建模。最简单的方法是构建图的邻接矩阵。它是一个二维数组,如果从节点i到节点j有一条边,则第i行和第j列上的元素等于 1,否则为 0。
我们在实现PageRank算法时研究的图的邻接矩阵如下:
第一行的第一个元素对应于A和A之间的边。由于A未连接到自身,因此该元素为 0。第一行的第二个元素包含有关A和B之间的边的信息。由于这两个节点之间存在关系,因此该元素等于 1。类似地,邻接矩阵的第二行包含有关来自节点B的出边的信息。该节点有一条通往A的出边。A对应矩阵的第一列,所以只有第二行的第一个元素为1。
可以从这个矩阵中提取大量信息。例如,我们可以通过对给定行中的值求和来找到每个节点的传出度,通过对给定列中的值求和来找到传入度。
具有归一化行的邻接矩阵如下:
使用矩阵表示法的 PageRank
现在,让我们考虑节点A包含一个我们想要传播给它的邻居的值。我们可以通过一个简单的点积找到该值将如何通过图形传播:
如果所有初始值都初始化为 1,会发生这种情况:
我们就快到了。检索 PageRank 公式的唯一缺失部分是阻尼因子d:
如您所见,我们得到的值与在前几节中找到的值相同。使用这种形式,找到每个节点的等级相当于求解以下方程:
X' = (1-d) + d * M_n * X = X
在这个等式中,X向量的值是每个节点的 PageRank 中心性。
现在,让我们回到特征向量中心性。
特征向量中心性
寻找特征向量中心性涉及求解一个类似但更简单的方程:
MX = λ.X
这里,M是邻接矩阵。用数学术语来说,这意味着找到这个邻接矩阵的特征向量。可能有几种可能的解决方案具有不同的λ值(特征值),但是每个节点的中心性必须为正的约束只留下一个单一的解决方案-特征向量中心性。
由于它与 PageRank 的相似性,它的用例也相似:当您希望节点的重要性受到其邻居重要性的影响时,使用特征向量中心性。
在 GDS 中计算特征向量中心性
特征向量中心性在 GDS 中实现。您可以使用以下代码在您最喜欢的投影图上计算它:
CALL gds.alpha.eigenvector.stream("projected_graph", {}) YIELD nodeId, score as score
RETURN gds.util.asNode(nodeId).name as nodeName, score
ORDER BY score DESC
这结束了关于 PageRank 算法及其变体的长部分。然而,正如我们之前所讨论的,节点重要性不一定与流行度相关。在下一节中,我们将讨论其他中心性算法,例如中介中心性,它可以用来识别网络中的关键节点。
基于路径的中心性度量
正如我们在本章第一节(定义重要性)中所讨论的,邻域法并不是衡量重要性的唯一方法。另一种方法是使用图中的路径。在本节中,我们将发现两个新的中心性指标:接近性和中介中心性。
接近中心性
接近中心性衡量一个节点与图中所有其他节点的平均接近程度。从几何的角度来看,它可以看作是中心性。
正常化
对应的公式如下:
C n = 1 / ∑ d(n, m)
这里,m表示图中所有与n不同的节点,d(n, m)是n和m之间最短路径的距离。
平均而言,更接近所有其他节点的节点将具有低∑ d(n, m),从而导致高中心性。
接近中心性阻止我们比较具有不同节点数量的图的值,因为具有更多节点的图在总和中将具有更多项,因此通过构造降低中心性。为了克服这个问题,使用了归一化的接近中心性:
C n = (N-1) / ∑ d(n, m)
这是为 GDS 选择的公式。
从最短路径算法计算接近度
为了理解如何计算接近度,让我们使用 GDS 的最短路径过程手动计算它。在第 4 章,图数据科学插件和路径查找中,我们发现了单源最短路径算法 ( deltastepping),它可以计算给定起始节点和图中所有其他节点之间的最短路径。我们将使用这个过程来找到从一个节点到所有其他节点的距离总和,然后推断中心性。
为此,我们将使用在了解 PageRank 算法部分中使用的测试图的无向版本。要创建投影图,请使用以下查询:
CALL gds.graph.create("projected_graph_undirected_weight", "Node", {
LINKED_TO: {
type: "LINKED_TO",
orientation: "UNDIRECTED",
properties: {
weight: {
property: "weight",
defaultValue: 1.0
}
}
}
}
)
除了使用我们图的无向版本之外,我们还为每个关系添加了一个权重属性,默认值为 1。
然后我们可以使用如下查询调用 SSSP 算法:
MATCH (startNode:Node {name: "A"})
CALL gds.alpha.shortestPath.deltaStepping.stream(
"projected_graph_undirected_weight",
{
startNode: startNode,
delta: 1,
relationshipWeightProperty: 'weight'
}
) YIELD nodeId, distance
RETURN gds.util.asNode(nodeId).name as nodeName, distance
现在,您可以检查距离是否正确。从这里开始,我们可以应用接近中心性公式:
MATCH (startNode:Node)
CALL gds.alpha.shortestPath.deltaStepping.stream(
"projected_graph_undirected_weight",
{
startNode: startNode,
delta: 1,
relationshipWeightProperty: 'weight'
}
) YIELD nodeId, distance
RETURN startNode.name as node, (COUNT(nodeId) - 1)/SUM(distance) as d
ORDER BY d DESC
结果如下:
╒══════╤════╕
│"node"│"d" │
╞══════╪════╡
│"B" │1.0 │
├──────┼────┤
│"A" │0.75│
├──────┼────┤
│"D" │0.75│
├──────┼────┤
│"C" │0.6 │
└──────┴────┘
就紧密度而言,B是最重要的节点,这是意料之中的,因为它链接到图中的所有其他节点。
我们设法使用 SSSP 过程获得了接近中心性,但是有一种更简单的方法可以从 GDS 获得接近中心性结果。
接近中心性算法
幸运的是,接近中心性是作为 GDS 中的独立算法实现的。我们可以使用以下查询直接获取每个节点的接近中心度:
CALL gds.alpha.closeness.stream("projected_graph_undirected_weight", {})
YIELD nodeId, centrality as score
RETURN gds.util.asNode(nodeId).name as nodeName, score
ORDER BY score DESC
多分量图中的接近中心性
一些图由几个组件组成,这意味着一些节点与其他节点完全断开。我们在第2章“密码查询语言”中构建的美国州图就是这种情况,因为有些州不与任何其他州共享边界。在这种情况下,两个不连接组件中的节点之间的距离是无限的,并且所有节点的中心度下降到 0。因此,GDS 中的中心度算法实现了接近中心度公式的略微修改版本,其中距离之和在同一组件中的所有节点上执行。在下一章,第 7 章,社区检测和相似性措施,我们将发现如何找到属于同一组件的节点。
在下一节中,我们将学习另一种使用基于路径的技术来衡量中心性的方法:中介中心性。
中介中心性
中介性是衡量中心性的另一种方法。我们现在不是对距离求和,而是计算遍历给定节点的最短路径的数量:
C n = ∑ σ(u, v | n) / ∑ σ(u, v)
这里,σ(u, v)是u和v之间最短路径的数量,而σ(u, v | n)是通过n的此类路径的数量。
该措施对于识别网络中的关键节点特别有用,例如道路网络中的桥梁。
它以下列方式与 GDS 一起使用:
CALL gds.alpha.betweenness.stream("projected_graph", {})
YIELD nodeId, centrality as score
RETURN gds.util.asNode(nodeId).name, score
ORDER BY score DESC
gds.betweenness.stream和gds.betweenness.write。
以下是我们的测试图的排序中介中心性:
╒══════════╤═══════╕ │"nodeName"│"score"│ ╞══════════╪═══════╡ │"B" │3.0 │ ├──────────┼───────┤ │"A" │2.0 │ ├──────────┼───────┤ │"C" │0.0 │ ├──────────┼───────┤ │"D" │0.0 │ └──────────┴───────┘
如您所见,B仍然是最重要的节点。但是节点的重要性D降低了,因为两对节点之间的最短路径都没有通过D。
我们现在已经研究了许多不同类型的中心性指标。让我们尝试在同一张图上比较它们,以确保我们了解它们的工作原理以及在哪种情况下使用哪种指标。
比较中心性指标
让我们回到我们在第一部分开始分析的图表。下图显示了我们在本章中研究的几种中心性算法的比较:
如果度中心性无法区分所有具有三个连接的四个节点(节点 3、5、6 和 9),PageRank 能够通过更加重视节点 3 和 6 来使一些节点脱颖而出。和介数都清楚地将节点 5 确定为最关键的节点:如果该节点消失,则图中的路径将完全改变甚至不可能,正如我们在本章第一节中讨论的那样。
中心性算法有许多类型的应用。在下一节中,我们将从欺诈检测用例开始回顾其中的一些。
将中心性应用于欺诈检测
欺诈是私营公司和公共机构损失的主要来源之一。它有许多不同的形式,从复制用户帐户到保险或信用卡欺诈。当然,根据您感兴趣的欺诈类型,识别欺诈者的解决方案会有所不同。在本节中,我们将回顾不同类型的欺诈以及 Neo4j 等图形数据库如何帮助识别欺诈。之后,我们将学习中心性度量(本章的主要主题)如何能够在某些特定情况下提供有关欺诈检测的有趣见解。
使用 Neo4j 检测欺诈
欺诈行为可以有多种形式,并且在不断发展。不怀好意的人可能会窃取信用卡并将大量资金转移到另一个帐户。这种交易可以用传统的统计方法和/或机器学习来识别。这些算法的目标是发现不符合正常预期模式的异常、罕见事件;例如,如果您的信用卡开始在您通常居住地以外的其他国家/地区使用,则该信用卡将高度可疑并可能被标记为欺诈。这就是为什么有些银行会要求您在出国旅行时告知他们,以免您的信用卡被冻结。
想象一个罪犯从 10 亿张信用卡中提取 1 美元,总共盗窃 10 亿美元,而不是一个人一次从一张信用卡中转移 10 亿美元。虽然后一种情况可以通过前面概述的传统方法轻松识别,但前者更难标记。当犯罪分子开始共同行动时,事情变得更加复杂。
欺诈者使用的另一个技巧是建立联盟,称为犯罪团伙。然后他们可以以看似正常的方式一起运作。想象一下两个人合作创建关于他们的汽车保险的虚假索赔。在这些情况下,需要进行更全局的分析,而这正是图表带来很多价值的地方。顺便说一句,如果你看过侦探电影,你会看到墙上挂满了用字符串(通常是红色)连接的便签:这很像一个图表,它说明了图表在刑事犯罪中的有用性活动检测。
现在,让我们回到欺诈的话题并调查一个例子。
使用中心性评估欺诈
在拍卖过程中,卖家以最低价格提出物品,感兴趣的买家不得不相互竞价,从而导致价格上涨。当卖家要求假买家对某些产品出价高出价格时,欺诈可能会发生在这里,只是为了抬高最终价格。
看看下图中重现的简单图形模式:
用户只能与给定的销售互动。让我们用这个模式构建一个简单的测试图:
CREATE (U1:User {id: "U1"})
CREATE (U2:User {id: "U2"})
CREATE (U3:User {id: "U3"})
CREATE (U4:User {id: "U4"})
CREATE (U5:User {id: "U5"})
CREATE (S1:Sale {id: "S1"})
CREATE (S2:Sale {id: "S2"})
CREATE (S3:Sale {id: "S3"})
CREATE (U1)-[:INVOLVED_IN]->(S1)
CREATE (U1)-[:INVOLVED_IN]->(S2)
CREATE (U2)-[:INVOLVED_IN]->(S3)
CREATE (U3)-[:INVOLVED_IN]->(S3)
CREATE (U4)-[:INVOLVED_IN]->(S3)
CREATE (U4)-[:INVOLVED_IN]->(S2)
CREATE (U5)-[:INVOLVED_IN]->(S2)
CREATE (U5)-[:INVOLVED_IN]->(S1)
这个小图如下图所示:
在这种情况下使用 PageRank 等中心性算法背后的想法如下:假设我知道用户 1 ( U1) 是欺诈者,我可以识别他们的犯罪伙伴吗?为了解决这个问题,以用户 1 为源节点的个性化 PageRank 是识别与用户 1 交互更频繁的用户的一个很好的解决方案。
使用 Cypher 投影创建投影图
在我们的拍卖欺诈案例中,用户之间没有直接关系,但我们仍然需要创建一个图来运行中心性算法。GDS 提供了一种解决方案,我们可以使用 Cypher 投影为节点和/或关系创建此类情况的投影图。
为了在用户之间建立虚假关系,如果他们至少一起参加了一次销售,我们认为他们是有联系的。以下 Cypher 查询返回这些用户:
MATCH (u:User)-[]->(p:Product)<-[]-(v:User)
RETURN u.id as source, v.id as target, count(p) as weight
聚合将count用于为每个关系分配权重:他们拥有的销售越常见,两个用户之间的关系越强。
使用 Cypher 创建投影图的语法如下:
CALL gds.graph.create.cypher(
"projected_graph_cypher",
"MATCH (u:User)
RETURN id(u) as id",
"MATCH (u:User)-[]->(p:Product)<-[]-(v:User)
RETURN id(u) as source, id(v) as target, count(p) as weight"
)
这里的要点如下:
- 使用gds.graph.create.cypher程序。
- 节点的投影需要返回 Neo4j 内部节点标识符,可以通过id()函数访问。
- 关系投影必须返回以下内容:
- source:源节点的 Neo4j 内部标识符
- target: 目标节点的 Neo4j 内部标识符
- 将存储为关系属性的其他参数
我们的投影图如下:
- 无向的。
- 加权:如果用户更频繁地相互交互,我们还希望我们的边缘具有更高的权重。
我们现在可以在这个投影图上使用个性化的 PageRank:
MATCH (U1:User {id: "U1"})
CALL gds.pageRank.stream(
"projected_graph_cypher", {
relationshipWeightProperty: "weight",
sourceNodes: [U1]
}
) YIELD nodeId, score
RETURN gds.util.asNode(nodeId).id as userId, round(score * 100) / 100 as score
ORDER BY score DESC
结果如下:
╒════════╤═══════╕
│"userId"│"score"│
╞════════╪═══════╡
│"U1" │0.33 │
├────────┼───────┤
│"U5" │0.24 │
├────────┼───────┤
│"U4" │0.23 │
├────────┼───────┤
│"U2" │0.08 │
├────────┼───────┤
│"U3" │0.08 │
└────────┴───────┘
由于个性化的 PageRank,我们可以说用户 5 是可疑的,紧随其后的是用户 4,而用户 2 和 3 的欺诈行为较少。
在这种情况下,使用 PageRank 与guilt by association的概念相关联。仍然必须谨慎对待结果,并与其他数据源进行双重检查。事实上,拥有相同兴趣的用户更有可能进行更频繁的互动,而不会有任何恶意。
当然,这是一个过于简单的例子。由于用户 5 的可疑行为可以通过查看图表来识别,因此使用 PageRank 肯定是矫枉过正。但是在更大的图表上想象同样的情况;例如,每天包含数百万笔交易的 eBay 用户和拍卖图表。在后一种情况下,本章研究的算法非常有用。
PageRank 甚至还有其他改进,例如专门用于识别其他类型的欺诈者的 TrustRank。Web 垃圾邮件是旨在误导搜索引擎并将流量重定向到它们的页面,即使内容与用户无关。TrustRank 通过标记受信任的站点来帮助识别此类页面。
中心性算法的其他应用
除了欺诈检测之外,中心性算法还可以在许多情况下使用。我们已经讨论过社交网络中的影响者检测。至此,您可能明白了为什么 PageRank 在这种情况下是一个不错的选择,因为它不仅考虑了给定人的人脉,还考虑了这些人脉的联系……在这种情况下,总体而言,如果它从具有较高 PageRank 的节点开始。
生物学和遗传学是中心性应用的另外两个重要领域。例如,当在一些酵母中建立蛋白质相互作用网络时,研究人员可以确定哪些蛋白质对酵母更重要,哪些不重要。例如,中心性的许多其他应用,尤其是 PageRank,已经在遗传学领域进行了探索,以确定基因在某些特定疾病中的重要性。有关生物信息学以及化学和体育领域的更广泛应用列表,请参阅网页之外的 PageRank文章(进一步阅读部分中的链接)。
路径相关的中心性度量可用于任何网络,例如道路或计算机网络,以识别其故障可能对整个系统致命的节点,从而破坏(或减慢)网络两个部分之间的通信。
这不是一个详尽的应用程序列表,根据您的专业领域,您可能会找到这些算法的其他用例。
概括
在本章中,我们研究了定义和测量节点重要性(也称为中心性)的不同方法,使用每个节点的连接数(度中心性、PageRank 及其衍生物)或与路径相关的指标(紧密度和中介中心性) .
为了使用 GDS 的这些算法,我们还研究了定义投影图的不同方法,投影图是 GDS 用来运行算法的图。我们学习了如何使用原生投影和Cypher投影来创建这个投影图。
在本章的最后一节中,我们看到了中心性算法如何帮助欺诈检测的实际应用,假设欺诈者更有可能相互交互。
一个相关主题是图中社区或模式的概念。我们将在下一章对此进行研究。我们将使用不同类型的算法以无监督或半监督的方式在图中查找社区或集群,并识别彼此高度连接的节点组。
练习
这里有几个练习来测试你对本章的理解:
- 修改 PageRank 的 Python 实现以考虑加权边缘。
- 使用 重写此实现networkx,尤其是使用networkx.Graph对象(而不是字典)作为输入。
- 将 PageRank 结果存储在新的节点属性中。
提示:使用gds.pagerank.write程序。
以更一般的方式,我鼓励您通过添加/删除节点和/或关系来修改其中心性结果显示在比较中心性指标部分中的测试图。这将帮助您了解不同的中心是如何演变的,并确保您了解这种演变。
进一步阅读
- 本文详细介绍了最初的 PageRank 想法:S. Brin & L. Page , The anatomy of a large-scale hypertextual Web search engine , Computer Networks and ISDN Systems 30 (1-7); 107-117。
- 有关 Python 中算法的更多信息,您可以参考以下内容:
- 使用 Python 的动手数据结构和算法, B. Agarwal和B. Baka博士, Packt Publishing。
- 以下 GitHub 存储库包含 Python 中许多算法的实现:https ://github.com/TheAlgorithms/Python 。如果 Python 不是你最喜欢的语言,你可以在https://github.com/TheAlgorithms/找到你喜欢的语言。
- 网络安全背景下时间序列中的欺诈检测示例可在Machine Learning for Cybersecurity Cookbook、E. Tsukerman和Packt Publishing 中找到。
- 以下 Neo4j 白皮书给出了一些使用 Neo4j 进行欺诈检测的示例:欺诈检测:发现与图形数据库的连接,白皮书,G. Sadowksi和P. Rathle,Neo4j。它可在White Paper: Fraud Detection免费获得(提供您的联系信息后) 。
- 在 Neo4j 网站上查看与欺诈相关的免费图表要点:https ://neo4j.com/graphgists/?category=fraud-detection 。
- U. Kang等人的论文Centralities in Large Networks: Algorithms and Observations 。展示了一些在大图上计算中心性的有趣方法(Proceedings of the 2011 SIAM International Conference on Data Mining (SDM) | Centralities in Large Networks: Algorithms and Observations)。
- DF Gleich的论文PageRank beyond the web ( https://arxiv.org/abs/1407.5107 ) 列出了 PageRank 在 Google 和搜索引擎之外的一些应用。