端到端工作流
一个实际的端到端工作流通常涉及按顺序使用多个算法。本示例展示了如何通过以下步骤创建基于协同过滤的简单产品推荐引擎
-
创建产品和客户图。
-
使用 FastRP 算法计算并添加节点嵌入到图中。
-
根据节点嵌入,使用 k-最近邻 (kNN) 算法计算每对客户的相似度得分。
-
使用 Cypher 查询查找相似客户并推荐产品。
创建图
以下 Cypher 查询在 Neo4j 数据库中创建了一个产品和客户示例图。amount
关系属性表示客户每周在给定产品上花费的平均金额。
CREATE
(dan:Person {name: 'Dan'}),
(annie:Person {name: 'Annie'}),
(matt:Person {name: 'Matt'}),
(jeff:Person {name: 'Jeff'}),
(brie:Person {name: 'Brie'}),
(elsa:Person {name: 'Elsa'}),
(cookies:Product {name: 'Cookies'}),
(tomatoes:Product {name: 'Tomatoes'}),
(cucumber:Product {name: 'Cucumber'}),
(celery:Product {name: 'Celery'}),
(kale:Product {name: 'Kale'}),
(milk:Product {name: 'Milk'}),
(chocolate:Product {name: 'Chocolate'}),
(dan)-[:BUYS {amount: 1.2}]->(cookies),
(dan)-[:BUYS {amount: 3.2}]->(milk),
(dan)-[:BUYS {amount: 2.2}]->(chocolate),
(annie)-[:BUYS {amount: 1.2}]->(cucumber),
(annie)-[:BUYS {amount: 3.2}]->(milk),
(annie)-[:BUYS {amount: 3.2}]->(tomatoes),
(matt)-[:BUYS {amount: 3}]->(tomatoes),
(matt)-[:BUYS {amount: 2}]->(kale),
(matt)-[:BUYS {amount: 1}]->(cucumber),
(jeff)-[:BUYS {amount: 3}]->(cookies),
(jeff)-[:BUYS {amount: 2}]->(milk),
(brie)-[:BUYS {amount: 1}]->(tomatoes),
(brie)-[:BUYS {amount: 2}]->(milk),
(brie)-[:BUYS {amount: 2}]->(kale),
(brie)-[:BUYS {amount: 3}]->(cucumber),
(brie)-[:BUYS {amount: 0.3}]->(celery),
(elsa)-[:BUYS {amount: 3}]->(chocolate),
(elsa)-[:BUYS {amount: 3}]->(milk)
图示如下

下一个查询从 Neo4j 图创建一个名为 purchases
的内存图。与原始数据唯一的区别是,:BUYS
关系的朝向被丢弃了;这是因为在使用 FastRP 算法时,无向关系是默认选择。
MATCH (source:Person)-[r:BUYS]->(target:Product)
RETURN gds.graph.project(
'purchases',
source,
target,
{
sourceNodeLabels: labels(source),
targetNodeLabels: labels(target),
relationshipType: 'BUYS',
relationshipProperties: r { .amount }
},
{ undirectedRelationshipTypes: ['BUYS'] }
)
向图中添加嵌入
节点嵌入通常用于从图中捕获拓扑信息以进行进一步处理,例如供其他算法使用。
GDS 提供了多种计算嵌入的算法,FastRP 是一个很好的默认选择,可以从它开始。由于嵌入稍后必须可供 kNN 算法使用,因此该算法必须在mutate
模式下运行,才能将它们添加到 purchases
图中。
CALL gds.fastRP.mutate( (1)
'purchases', (2)
{ (3)
embeddingDimension: 4,
iterationWeights: [0.8, 1, 1, 1],
relationshipWeightProperty: 'amount',
randomSeed: 42,
mutateProperty: 'embedding'
}
)
YIELD nodePropertiesWritten
1 | gds.fastRP 算法在 mutate 模式下运行。 |
2 | 用于运行算法并将新节点属性添加到的投影图的名称。 |
3 | 算法的语法部分(Mutate mode 面板)中列出的配置参数。这里,embeddingDimension 设置为 4,因为图很小;iterationWeights 凭经验选择以产生合理结果;relationshipWeightProperty 设置为计算相邻嵌入的加权平均值。添加 randomSeed 以便每次运行都能获得相同的结果,但这对于实际计算并非必需。mutateProperty 是将包含节点嵌入的新节点属性。 |
nodePropertiesWritten |
---|
13 |
您可以像基本工作流示例中一样,通过使用相应的 gds.fastRP.stream
过程并从配置参数中移除 mutateProperty
,在 stream
模式下运行该算法。
计算并写入相似性
有了作为新节点属性 embedding
的嵌入,您可以运行 kNN 算法来计算每对节点之间的相似度得分。在write
模式下运行 kNN 算法,将 score
关系添加到 Neo4j 数据库并在 Cypher 查询中使用它。
CALL gds.knn.write( (1)
'purchases', (2)
{ (3)
nodeProperties: ['embedding'],
nodeLabels: ['Person'],
topK: 2,
sampleRate: 1.0,
deltaThreshold: 0.0,
randomSeed: 42,
concurrency: 1,
writeProperty: 'score',
writeRelationshipType: 'SIMILAR'
}
)
YIELD similarityDistribution
RETURN similarityDistribution.mean AS meanSimilarity (4)
1 | gds.knn 算法在 write 模式下运行。 |
2 | 用于运行算法的投影图的名称。write 模式不会更新内存图。 |
3 | 算法的语法部分(Write mode 面板)中列出的配置参数。nodeLabels 选项设置为 ['Person'],因为我们只对 Person -Person 相似性感兴趣,不希望将此类相似性与例如 Person -Product 相似性混淆。这里,topK 设置为 2,以仅选择源节点的两个最近目标;sampleRate 和 deltaThreshold 分别设置为 1 和 0,因为图很小。设置 concurrency 和 randomSeed 以便每次运行都能获得相同的结果,但这对于实际计算并非必需。这两个 write 属性用于写入一个新的 :SIMILAR 关系,其中包含两个节点之间相似度得分的 score 属性。 |
4 | mean 是返回的 similarityDistribution map 的字段之一。 |
meanSimilarity |
---|
0.8800284068 |
节点之间的平均相似度很高。这是因为相似度分布是在选择每个源节点最相似的 topK
=2 个目标后计算的。此外,该图有两组购买行为非常相似的用户集群:(Brie、Matt 和 Annie)以及(Elsa、Dan 和 Jeff)。为 topK
使用更高的值将导致平均相似度更低。
如果我们要检查人物-产品相似性,可以使用过滤 K-最近邻来实现。
查找最相似的节点
将相似性关系写入 Neo4j 后,您可以使用 Cypher 查找客户对并按其相似度得分进行排名。
MATCH (n:Person)-[r:SIMILAR]->(m:Person)
RETURN n.name AS person1, m.name AS person2, r.score AS similarity
ORDER BY similarity DESCENDING, person1, person2
人物 1 | 人物 2 | 相似度 |
---|---|---|
"Dan" |
"Elsa" |
0.9866833091 |
"Elsa" |
"Dan" |
0.9866833091 |
"Brie" |
"Matt" |
0.9740184546 |
"Matt" |
"Brie" |
0.9740184546 |
"Annie" |
"Matt" |
0.9724045992 |
"Matt" |
"Annie" |
0.9724045992 |
"Annie" |
"Brie" |
0.9154552221 |
"Brie" |
"Annie" |
0.9154552221 |
"Jeff" |
"Annie" |
0.8667784333 |
"Jeff" |
"Matt" |
0.7591181397 |
"Dan" |
"Jeff" |
0.6660436392 |
"Elsa" |
"Jeff" |
0.5712890029 |
查询结果显示,名为“Dan”和“Elsa”的节点非常相似。事实上,它们都连接到三个 :Product
节点,其中两个是相同的(名为“Milk”和“Chocolate”的节点),并且数量相似。“Cookies”产品仅由 Dan 购买,但数量较少,并且由于其在图中的接近性以及部分由于随机性,它与其他产品也具有一定程度的相似性。
进行推荐
协同过滤的基本假设是,客户购买的产品可能对尚未购买它们的相似客户感兴趣。知道“Annie”和“Matt”相似,您可以使用 Cypher 查询为他们每个人进行产品推荐。
MATCH (:Person {name: "Annie"})-->(p1:Product)
WITH collect(p1) AS products
MATCH (:Person {name: "Matt"})-->(p2:Product)
WHERE NOT p2 IN products
RETURN p2.name AS recommendation
推荐 |
---|
"Kale" |
该查询查找“Annie”正在购买的产品,然后选择“Matt”正在购买但“Annie”尚未购买的产品。因此,结果节点“Kale”是为“Annie”推荐的产品。