端到端工作流程
一个现实的端到端工作流程通常会涉及按顺序使用多个算法。本示例展示了如何使用以下步骤创建简单的基于协同过滤的产品推荐引擎。
-
创建产品和客户的图。
-
使用 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 |
您可以在stream
模式下运行算法,就像基本工作流程示例中一样,方法是使用相应的 gds.fastRP.stream
过程并从配置参数中删除 mutateProperty
。
计算并写入相似性
有了作为新的 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 地图的字段之一。 |
meanSimilarity |
---|
0.8800284068 |
节点之间的平均相似度很高。这是因为相似度分布是在选择每个源节点的 topK
=2 个最相似目标后计算的。此外,该图有两个用户集群,他们的购买非常相似:(Brie、Matt 和 Annie)和(Elsa、Dan 和 Jeff)。使用更高的 topK
值将产生更低的平均相似度。
如果我们要检查 Person-Product 相似性,可以通过使用过滤后的 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
person1 | person2 | similarity |
---|---|---|
"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
recommendation |
---|
"Kale" |
查询返回节点“Kale”,它是“Annie”已经购买但“Matt”没有购买的产品。这是推荐给“Matt”的产品。