【问题标题】:Neo4j real-time recommendation performanceNeo4j 实时推荐性能
【发布时间】:2020-10-20 10:34:26
【问题描述】:

我正在尝试了解 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)工作方式的一些基本知识?这如何扩展到实时应用程序,其中可能有数以万计的用户,以及他们与之交互的更多产品?

【问题讨论】:

    标签: performance neo4j cypher recommendation-engine


    【解决方案1】:

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

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

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

    first naive version 计算所有对之间的距离,可以使用topKsimilarityCutoff 参数进行调整。

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

    但是,对于 10k+ 个节点之间的相似性的实时计算,您可能仍需要超过 100 毫秒才能最大化实时响应,因此使用预先计算的相似性关系是有意义的。

    【讨论】:

      【解决方案2】:

      除了@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;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-22
        • 1970-01-01
        • 2010-11-05
        • 2017-12-27
        相关资源
        最近更新 更多