提问者:小点点

Neo4j实时推荐性能


我试图了解neo4j在实时推荐系统中的性能。

以下是一个密码查询(取自他们的沙盒),它计算与查询用户“Cynthia Freeman”最相似的前100名用户(余弦距离):

MATCH 
    (p1:User {name: "Cynthia Freeman"})-[x:RATED]->(m:Movie)<-[y:RATED]-(p2:User)
WITH 
    COUNT(m) AS numberMovies, 
    SUM(x.rating * y.rating) AS xyDotProduct,
    SQRT(REDUCE(xDot = 0.0, a IN COLLECT(x.rating) | xDot + a^2)) AS xLength,
    SQRT(REDUCE(yDot = 0.0, b IN COLLECT(y.rating) | yDot + b^2)) AS yLength,
    p1, p2 
WHERE
    numberMovies > 10
RETURN 
    p1.name, p2.name, xyDotProduct / (xLength * yLength) AS sim
ORDER BY sim DESC
LIMIT 100;

如果我的理解是正确的,LIMIT子句背后没有魔法,因为与所有其他用户相比,距离计算仍然需要完成,所以实时解决这个查询似乎有点牵强,除非neo4j在幕后做了一些事情。

在另一个例子中,他们预先计算用户节点之间的这种[: SIMILARITY]关系并将其存储在图中,这样查询前N个最相似的用户就变成了节点的排序。这将直观地使图变得密集,因此与简单地使用相似度矩阵相比没有存储优势。

我是否遗漏了图形数据库(尤其是neo4j)工作方式的一些基本内容?这如何扩展到实时应用程序,其中可能有成千上万的用户,甚至更多的产品与他们交互?


共2个答案

匿名用户

如果您想在数万个或更多节点上使用某种余弦距离度量进行实时推荐,最好将预计算值存储为关系。

至于使图密集,您可以将SIMILAR关系限制为前K个相似节点,并且还可以定义相似性截止阈值,这可以使您的图尽可能稀疏。您只能存储相关结果。因此,例如,在1万节点的图中,如果每个项目都与前10个其他节点有连接,则这不是真正的密集图。如果您还删除从一个节点指向另一个节点并返回的重复关系,则可以删除更多。因此,如果有10k*10k(如果你将关系视为无向关系,则除以2)关系可能,你不会有十亿种可能的关系,但最多只有100k。

Graph Data Science库支持两种计算余弦距离的算法:

第一个朴素版本计算所有对之间的距离,并且可以使用topK相似性Cutoff参数进行调整。

就在最近,kNN算法的优化实现被添加到GDS 1.4预发布中,它使用本文中描述的实现:https://dl.acm.org/doi/abs/10.1145/1963405.1963487

然而,对于10k节点之间相似性的实时计算,可能仍然需要超过100毫秒的时间才能最大限度地提高实时响应,因此使用预先计算的相似性关系是有意义的。

匿名用户

除了@TomažBratanič的好建议之外,您现有的查询可以更高效。它正在为每个p1/p2对执行数学计算,即使是后来因为共享电影数量不超过10而被过滤掉的对。相反,您应该在进行计算之前尝试过滤掉不需要的p1/p2对。

例如:

MATCH
    (p1:User {name: "Cynthia Freeman"})-[x:RATED]->(m:Movie)<-[y:RATED]-(p2:User)
WITH
    COLLECT({xr: x.rating, yr: y.rating}) AS data
    p1, p2
WHERE
    SIZE(data) > 10
WITH
    REDUCE(s = 0, d IN data | s + d.xr * d.yr) AS xyDotProduct,
    SQRT(REDUCE(xDot = 0.0, a IN data | xDot + a.xr^2)) AS xLength,
    SQRT(REDUCE(yDot = 0.0, b IN data | yDot + b.yr^2)) AS yLength,
    p1, p2
RETURN 
    p1.name, p2.name, xyDotProduct / (xLength * yLength) AS sim
ORDER BY sim DESC
LIMIT 100;