我正在尝试在Neo4j上实施基本推荐系统。基本上,我有用户喜欢的用户和艺术家。我想查询“喜欢达米米的用户,也喜欢这些艺术家”。这很容易跟随:
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
虽然这种方法是真的,但它会返回Coldplay,披头士乐队(每个人都喜欢的用户),如下所示:
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)给coldplay一个分数。例如,如果共有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
但是现在,它返回“(没有行)”作为结果。
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艺术家和人之间的关系。你可以假设一个:人只能喜欢:艺术家一次,只有:人可以喜欢:艺术家
答案 0 :(得分:0)
是否有理由使用两个单独的MATCH
条款?使用两个MATCH
子句与使用单个子句具有不同的语义,请参阅uniqueness上Cypher文档中的注释。在目前的情况下,使用两个MATCH
子句允许p2
采用与p
相同的值。
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
答案 1 :(得分:0)
我们可以在这里做一些改进。
了解您的查询可能需要这么长时间才有用。回想一下,Neo4j会返回相当于数据列的行数,并且会在您查询过程中进行构建。在你的第二场比赛之后,正在构建的是由n2组成的行,以及每个喜欢n2的人的每个组合(因为你的第二场比赛在同一组人身上创建了一个笛卡尔积)与每个其他艺术家这些人喜欢。这是一个非常低效的查询(至少在复杂性方面是n ^ 2),并且完全可以预期很长或永不完成的执行时间。
所以让我们解决这个问题。
首先,我们可以完全摆脱第二场比赛来计算n2的喜欢数量。相反(假设:一个人只能喜欢:艺术家一次,而且只有:人们可以喜欢:艺术家)我们可以直接计算: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
编辑以解决明确的要求。此外,更改了查询以使用浮点值进行计数,因此得到的分数是小数而不是int。
我们可以使用类似的方法来获得每个艺术家喜欢的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
然而,这个查询肯定会慢于我提出的第一个查询。提高速度的一种方法是提前在艺术家节点上缓存每位艺术家的相似数量的快照,然后在需要实时计算时使用缓存的值。但是,您需要确定更新缓存值的方式和时间。