【问题标题】:(poor man's )product recommendation implementation(穷人)产品推荐实施
【发布时间】:2011-10-07 23:48:03
【问题描述】:

我正在尝试为在线商店构建一个穷人推荐系统。 我想实现这种亚马逊“购买此商品的客户也购买了”功能,我读了很多关于它的内容。 我知道有 Apache Mahout 的东西,但我无法以这种方式调整服务器。然后会有谷歌预测 API,但它要花钱,所以我开始自己试验。

我有一个包含 250.000 多个项目的订单历史记录,我编写了一个嵌套的 MySQL 查询来查找包含当前文章的订单,对其他订单项目进行排名并对该表进行排序以进行排名,所以我得到了一组其他人订购的产品连同当前的文章。

问题是,查询可能需要 10 秒 - 所以不能直接使用。 我想到了一个缓存表,但这个查询在 20 分钟后停止(有 60.000 个产品和 250.000 个已订购项目)所以我无法填写该表。

我目前的解决方法如下: 推荐 HTML 通过 AJAX ondocumentready 加载,因此网站加载,而推荐在后台加载。推荐数据被处理一次并存储在文件缓存(PEAR 简单缓存)中,因此下次加载速度更快。因此,如果有人访问该站点并存储一天或一周,则按需生成缓存。

我问我自己和你,这是一种可以接受的方法,还是愚蠢且表现不佳? 将缓存的数据存储在数据库或文件中会更好(我考虑性能和并行命中)。我的意思是,在最坏的情况下,我最终会得到 60.000 个缓存文件。

我更喜欢包含所有数据的预计算表,但正如我所说,这需要很长时间,而且我不知道如何优化它。 (等 SQL Dude 放假回来^^)

感谢任何提示,意见。

顺便说一句。这是查询:

SELECT c.ArtNr as artnr , count(c.ArtNr) as rank, s.ArtNr as parent_artnr
FROM (
SELECT a.ID_order, a.ArtNr
        FROM net_orderposition a
        WHERE a.ArtNr = 'TT-PV0005'
) s
JOIN net_orderposition c 
WHERE s.ID_order = c.ID_order AND s.ArtNr != c.ArtNr
GROUP BY c.ArtNr
ORDER BY rank DESC,c.Stamp DESC
LIMIT 10;

编辑:

我考虑了给出的答案,我认为它们与我最初的想法相似。 上述代码结果如下表:

ID,ParentID , ChildID  , Rank
1, TT-PV0005, TT-PV0040, 220
2, TT-PV0005, TT-PV0355, 135
3, TT-PV0005, TT-PV0450, 134
4, TT-PV0005, TT-PV0451, 89
5, TT-PV0005, RH-01V2  , 83
6, TT-PV0005, TT-PV0041, 83
7, TT-PV0005, TT-PV0353, 82
8, TT-PV0005, TT-PV0037, 80

ParentID 是当前项目,ChildID 是过去与 ParentID 一起订购的项目,Rank 是预计算的孩子订购当前项目的频率的计数。 现在,我可以在每个新订单上更新或插入相关项目,并计算排名(如果它已经存在于数据库中)。 我唯一担心的是,我最终会坐在一张非常大的桌子上。 如果我每周离线一次预先计算,也许这应该不是问题? 但随后我必须优化查询,以便每个项目不需要 10 秒。

你怎么看?

【问题讨论】:

    标签: php mysql ajax recommendation-engine


    【解决方案1】:

    每次有订单时,存储订单中不同商品之间的关系记录。然后执行以下操作:

    SELECT ItemID, COUNT(RelatedItemID) AS RelatedItemCount
    FROM RelatedItems
    WHERE RelatedItemID = @viewingItemID
    GROUP BY ItemID
    ORDER BY RelatedItemCount DESC
    LIMIT 10
    

    您还可以使用通宵流程或其他方式对此进行预总结,并拥有一个表,其中仅包含每个项目 ID 的前 n 个相关项目。

    【讨论】:

    • 那个“关系记录”会是什么样子?在所有的加入和选择之后,我有点愚蠢。
    • 它可以像 OrderID、ItemID、RelatedItemID 一样简单,您可以在其中获得订单中每个唯一项目的交叉连接。因此,如果订单 1 包含项目 2 和 3,您将获得 1, 2, 31, 3, 2
    【解决方案2】:

    要添加到@GalacticCowboy 的答案并填写您的评论位置,@Marcus...

    实现这一点的一个模式是创建一个类似的表:

    RelatedItems
    RelatedItemsId
    purchasedItemId
    relatedItemId
    

    然后,当订单完成(或根据您的要求查看)时,您会将记录写入 RelatedItems 表,其中购买的每件商品都会获得一条记录,其中该 id 是 purchaseItemId。然后所有其他项目将被写为relatedItemId。

    例如,如果我购买了项目 5、9、12 和 19,我将有 12 条记录写入我的表中,如下所示:

    RelatedItemId, PurchasedItemId, RelatedItemId
    1, 5, 9
    2, 5, 12
    3, 5, 19
    4, 9, 5
    5, 9, 12
    6, 9, 19
    7, 12, 5
    8, 12, 9
    9, 12, 19
    10, 19, 5
    11, 19, 9
    12, 19, 12
    

    然后,您可以使用类似于 GalacticCowboy 的查询来获取通常与这些物品一起购买的前 10 件物品。

    请注意,对于这样的任务,这不是最有效的架构,可以对其进行大量调整以减少冗余数据,但鉴于我们对您的系统和整体架构设计知之甚少(以及对某些 SQL 概念的理解似乎不可靠)我不会深入探讨。

    【讨论】:

    • 和@GalacticCowboy,谢谢。我以此为起点进行更多实验。我相信我会找到解决办法的。
    • 完全同意 - 它不是超级高效,但应该比当前方法更高效。
    • 这是一个非常有向图的实现。我相信您可以通过添加一些逻辑并使其成为无向图来减少必须插入的记录数。
    • @Dirk,绝对。人们应该能够制作一个更像“item1,otheritem,count”的模式,您可以在每次购买商品时增加计数。这仍然需要 n*(n-1) 条记录,其中 n 是项目总数。最好是一个表,其中有 item1、item2、count 并且 item1 和 item2 的组合是表的键,然后进行一些更复杂的连接。
    【解决方案3】:

    查看easyrec 它具有您需要的功能并且是免费的。无需调整,您可以使用 Demo 实例,如谷歌分析。我认为使用这个免费的网络服务然后自己编写整个逻辑会容易得多。

    在今天的tweet 中,他们提到他们支持对easyrec 的完整mahout 支持,因此您可以使用easyrec。您可以使用easyrec 的免费网络服务或在您的网络服务器上部署免费的WAR 文件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-31
      • 2010-10-22
      • 2011-09-12
      • 1970-01-01
      • 2014-07-21
      • 2020-07-07
      • 1970-01-01
      相关资源
      最近更新 更多