【问题标题】:How to speed up Collaborative Filtering in Neo4j Cypher?如何加快 Neo4j Cypher 中的协同过滤?
【发布时间】:2016-11-07 08:56:55
【问题描述】:

这是基于 Neo4j 文档中的示例 Cypher:

MATCH (user:User)-[:ORDERS]->(:Product)<-[:ORDERS]-(otherUser:User)-[:ORDERS]->(recommended:Product)
WHERE NOT (user)-[:ORDERS]->(recommended)
  AND user.id = 171
RETURN distinct recommended.id, count(distinct otherUser.id) as frequency
ORDER BY frequency DESC
LIMIT 200

而以下是我所做的改进:

MATCH (user:User)-[:ORDERS]->(p:Product)
WHERE user.id = 171
WITH DISTINCT p, user
MATCH (p)<-[:ORDERS]-(otherUser:User)
WITH DISTINCT otherUser, user
MATCH (otherUser)-[:ORDERS]->(recommended:Product)
WHERE NOT (user)-[:ORDERS]->(recommended)
RETURN distinct recommended.id, count(distinct otherUser.id) as frequency
ORDER BY frequency DESC
LIMIT 200

两者都返回相同的结果,但第二个运行速度快 6 倍。 (但在我的 Macbook 上仍然需要 3 秒)

  1. 为什么第二个跑得更快?
  2. 如何进一步加快速度?

【问题讨论】:

    标签: neo4j cypher collaborative-filtering


    【解决方案1】:

    您的查询获得了p 产品(您不想推荐的产品),但最终丢弃了它们。这些p 节点可以用来与recommended 节点进行比较,而不是删除它们,从而避免处理WHERE NOT (user)-[:ORDERS]-&gt;(recommended) 所需的额外数据库命中(每次都必须重新扫描user 的每个订单)。这应该会显着加快您的查询速度。

    试试这个:

    MATCH (user:User)-[:ORDERS]->(p:Product)<-[:ORDERS]-(otherUser:User)
    WHERE user.id = 171
    WITH COLLECT(DISTINCT otherUser) AS others, COLLECT(DISTINCT p) AS sharedProds
    UNWIND others AS other
    MATCH (other)-[:ORDERS]->(recommended:Product)
    WHERE NOT recommended IN sharedProds
    RETURN DISTINCT recommended.id, count(DISTINCT other) as frequency
    ORDER BY frequency DESC
    LIMIT 200;
    

    另外,我假设User 节点具有唯一的id 值,所以我使用count(DISTINCT otherUser) 而不是count(DISTINCT otherUser.id),这应该会更快。

    【讨论】:

    • 太好了,它的运行速度明显更快!小修正:RETURN DISTINCT recommended.id, count(DISTINCT other) as frequency
    • 我想知道你为什么需要在那里使用UNWIND?与以下有什么不同? MATCH (user:User)-[:ORDERS]-&gt;(p:Product)&lt;-[:ORDERS]-(otherUser:User) WHERE user.id = 171 WITH distinct otherUser, COLLECT(DISTINCT p) AS sharedProds MATCH (otherUser)-[:ORDERS]-&gt;(recommended:Product) WHERE NOT recommended IN sharedProds WITH DISTINCT recommended, COUNT(DISTINCT otherUser) AS frequency RETURN DISTINCT recommended.id, frequency ORDER BY frequency DESC LIMIT 200
    • 如果您想在一个别名中获得不同products 的整个集合,您也必须聚合other。要每行保留一个otherUser,只需执行COLLECTUNWIND,然后每一行都有一个otherUserProducts 的完整列表。但是,对于您的查询,我相信这是不必要的,因此您的编辑也应该可以正常工作(但当然是配置文件并查看哪个更好)
    【解决方案2】:

    第一个查询可能比必要的要慢,因为它将DISTINCT 应用于属性,而不是节点,这会强制数据库在可能删除它们之前查找每个节点的属性。如果id 属性对单个User 节点是唯一的(Product 节点也是如此),这将降低效率。如果您可以假设没有User 与另一个User 共享id,并且没有Product 与另一个id 共享id,这将是同一查询的更快版本:

    MATCH (user:User)-[:ORDERS]->(:Product)<-[:ORDERS]-(otherUser:User)-[:ORDERS]->(recommended:Product)
    WHERE NOT (user)-[:ORDERS]->(recommended)
      AND user.id = 171
    WITH recommended, COUNT(DISTINCT otherUser) AS frequency
    RETURN recommended.id, frequency
    ORDER BY frequency DESC
    LIMIT 200
    

    您的查询将DISTINCT 应用于每个步骤的节点,因此它避免了此属性查找问题(这通常是查询中最昂贵的部分,尤其是大型查询)。但是,像这样手动切割一条长路径可能会减慢查询速度,因为规划器可能无法看到它可以对整个路径执行的优化。几乎在所有情况下,单条长路径都比多条短路径更可取。

    至于如何更快,请记住,图表可能会占用大量资源,因此如果您在笔记本电脑或小型云实例上并行运行它,不妨看看 performance tuning 以了解如果你可能有点阻塞你的数据库。

    编辑:如果你有一个密集连接的图(:Users 之间有很多共享的:Product 节点),那么你可能会在第一个查询中生成不必要的结果行。如果 PROFILE 在查询中间显示大量结果行,请尝试以下替代方法:

    MATCH (user:User {id:171})-[:ORDERS]->(:Product)<-[:ORDERS]-(otherUser:User)
    WITH DISTINCT user, otherUser
    MATCH (otherUser)-[:ORDERS]->(recommended:Product)
    WHERE NOT (user)-[:ORDERS]->(recommended)
    WITH recommended, COUNT(DISTINCT otherUser) AS frequency
    RETURN recommended.id, frequency
    ORDER BY frequency DESC
    LIMIT 200
    

    这可确保每个otherUser 节点只检查一次推荐,而第一个查询将针对他们与用户共享的每个:Product 处理每个otherUser。这就是PROFILE 可以帮助你找到的关于你的图表的信息;找到结果行数激增的步骤,看看是否有办法将其分解以减少总行数。

    【讨论】:

    • 太棒了!它比第一个快 2 到 3 倍!但仍然是第二个的两倍。
    • 我必须在您的设置中查看查询的 PROFILE 才能进一步说明,在这种情况下可能会产生影响的实际因素。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-28
    • 2017-06-14
    • 1970-01-01
    相关资源
    最近更新 更多