Neo4j 与 Cypher 上不常见的常见推荐

Uncommonly common recommendation on Neo4j with Cypher

我正在尝试在 Neo4j 上实现一个基本的推荐系统。基本上,我有用户喜欢的用户和艺术家。我想查询"users who liked damien rice, also liked these artists"。通过以下操作很容易:

MATCH (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"})
RETURN n.artist_name, COUNT(n) AS COUNT
ORDER BY COUNT DESC
LIMIT 30

虽然这种做法有点道理,但是returns Coldplay, The Beatles (用户人气)如下:

n.artist_name        COUNT
coldplay             6193
radiohead            5377
the beatles          3998
death cab for cutie  3647
muse                 3252
the killers          3064
jack johnson         2966

我倾向于找出不常见的建议。我的意图是通过计算 (6193/totalNumberOfLikesForColdplay) 给酷玩打分。例如,如果共有 61930 人喜欢酷玩,那么它的分数为 9163/91630 = 0.1,我想根据这个分数对所有艺术家进行排序。

我尝试了以下方法:

MATCH (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"})
MATCH (n2:Artist {artist_name: "damien rice"})<-[:LIKES]-(p2:Person)
RETURN n.artist_name, COUNT(n)/COUNT(n2) AS SCORE
ORDER BY SCORE DESC
LIMIT 30

但这需要很长时间。我应该输入什么样的查询才能以最有效的方式获得结果?

编辑: 我刚刚意识到我上面尝试的查询不是我想要的。它计算 numberOfPeopleBothLikedColdplay_DamienRice/numberOfPeopleLikedDamienRice numberOfPeopleBothLikedTheBeatles_DamienRice/numberOfPeopleLikedDamienRice 等等

不过我想计算numberOfPeopleBothLikedColdplay_DamienRice/numberOfPeopleLikedColdplay numberOfPeopleBothLikedTheBeatles_DamienRice/numberOfPeopleLikedTheBeatles ...

所以也许它可以更新为

MATCH (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"})
MATCH (n2:Artist {artist_name: n.name})<-[:LIKES]-(p2:Person)
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE
ORDER BY SCORE DESC
LIMIT 30

但是现在,结果 returns 我“(没有行)”。

Edit2: 按照建议,我更新查询如下:

MATCH (p2:Person)-[:LIKES]->(n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->
  (n2:Artist {artist_name: "damien rice"})
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE
ORDER BY SCORE DESC
LIMIT 30

但它仍然永远运行。顺便说一句,我有 292516 位艺术家,359347 个人,17549962 LIKES 艺术家与人之间的关系。你可以假设一个 :Person 只能喜欢一个 :Artist 一次,而且只有 :Persons 可以喜欢 :Artists

是否有使用两个单独的 MATCH 子句的原因?使用两个 MATCH 子句与使用一个子句具有不同的语义,请参阅 Cypher 文档中关于 uniqueness 的注释。在当前情况下,使用两个 MATCH 子句允许 p2p.

取相同的值
MATCH
  (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->
  (n2:Artist {artist_name: "damien rice"})<-[:LIKES]-(p2:Person)
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE
ORDER BY SCORE DESC
LIMIT 30

您也可以在同一个 MATCH 子句中重复变量并获得相同的结果集。例如:

MATCH
  (n:Artist)<-[:LIKES]-(p:Person)-[:LIKES]->(n2:Artist {artist_name: "damien rice"}),
  (n2)<-[:LIKES]-(p2:Person)
RETURN n.artist_name, COUNT(p)/COUNT(p2) AS SCORE
ORDER BY SCORE DESC
LIMIT 30

我们可以在此处进行一些改进。

了解您的查询可能需要这么长时间的原因很有帮助。回想一下 Neo4j returns 相当于数据行的列,这是随着您查询的进行而建立的。在你的第二场比赛之后,正在建立的是由 n2 组成的行,以及喜欢 n2 的人与喜欢 n2 的每个人的每个组合(因为你的第二场比赛在同一组人上创建了笛卡尔积)与其他艺术家被这些人喜欢。这是一个非常低效的查询(至少在复杂度上为 n^2),并且完全可以预期执行时间很长或永远不会结束。

所以让我们解决这个问题。

首先,我们可以完全去掉第二个匹配来计算n2的点赞数。相反(假设一个 :Person 只能喜欢一个 :Artist 一次,并且只有 :Persons 可以喜欢 :Artists)我们可以直接计算 :LIKES 关系的数量。通过首先对其进行重新排序,我们还确保此操作仅对单行数据发生一次,而不是对大量行进行重复。然后我们可以运行进行第一个MATCH。

MATCH (n2:Artist {artist_name: "damien rice"})
WITH n2, SIZE( (n2)<-[:LIKES]-() ) as n2Likes
MATCH (n:Artist)<-[:LIKES]-()-[:LIKES]->(n2)
WITH n, toFloat(COUNT(n))/n2Likes AS SCORE
ORDER BY SCORE DESC
LIMIT 30
RETURN n.artist_name, SCORE

编辑以解决明确的要求。此外,更改查询以使用浮点值进行计数,因此生成的分数是小数而不是整数。

我们可以使用类似的方法来获取每个艺术家的喜欢的 SIZE()。

MATCH (n:Artist)<-[:LIKES]-()-[:LIKES]->(n2:Artist {artist_name: "damien rice"})
WITH n, toFloat(COUNT(n)) as likesBothCnt
WITH n, likesBothCnt, SIZE( ()-[:LIKES]->(n) ) as likesArtist
WITH n, likesBothCnt/likesArtist as SCORE
ORDER BY SCORE DESC
LIMIT 30
RETURN n.artist_name, SCORE

但是这个查询肯定会比我提出的第一个查询慢。提高速度的一种方法是提前在艺术家节点上缓存每个艺术家的点赞数快照,然后在需要实时计算时使用缓存的值。不过,您需要弄清楚如何以及何时更新缓存值。