构建双塔推荐系统

发布: (2026年2月3日 GMT+8 03:05)
8 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您想要翻译的完整文本(包括正文、标题、列表等),我将按照要求保留源链接并将内容翻译成简体中文。谢谢!

概述

我在 POSH(我的电商应用)中使用 Algolia 进行搜索和推荐。效果很好,但费用不断攀升——每一次搜索请求和推荐调用都会累计,因为用户不断浏览商品。

于是我构建了自己的推荐系统,采用 双塔模型(与 YouTube 和 Google 使用的方式相同):

  • 一个塔将 产品 表示为向量。
  • 另一个塔根据用户行为将 用户 表示为向量。

要获取推荐,我只需找到与用户向量最近的产品。

下面是我构建过程的逐步说明。


数据管道

一切始于通过 Firebase Analytics 捕获的 用户行为

事件含义
Product viewed仅浏览
Product clicked表示兴趣
Added to cart强烈意图

并非所有交互都等同,因此我为它们分配 权重

事件权重
View0.1
Click2.0
Add to cart5.0

产品向量化

所有产品存储在 Elasticsearch 中。为了使推荐功能生效,每个产品必须表示为 384 维向量

模型all-MiniLM-L6-v2 来自 Sentence‑Transformers – 快速、轻量,适用于语义相似度。

向量生成方式

  1. 将产品属性 合并为一个文本字符串,例如:

    Nike Air Max | by Nike | Shoes | Sneakers | Running | Blue color | premium
  2. 该字符串包括:

    • 产品名称
    • 商家名称
    • 类别层级(父级 → 类别 → 子类别)
    • 颜色
    • 价格档位(预算 / 中档 / 高端 / 奢侈)
  3. 将字符串输入模型 → 384 维向量。

  4. 将向量存储在 Elasticsearch 中的 dense_vector 字段,以用于相似度搜索。


用户塔架构

用户塔 消费用户最近的交互历史,并输出一个位于与产品向量相同空间的单一向量。

输入

最多 20 条最近的交互,每条包括:

  • 产品的 384 维向量
  • 交互类型(view / click / add‑to‑cart)

输出

单个 384 维用户向量

模型工作流

  1. 嵌入交互类型 并与产品向量拼接。
  2. 将序列传入 multi‑head attention 层,使模型能够学习哪些交互最重要。
  3. 应用 recency decay —— 较新的交互获得更高权重。
  4. 池化 注意后的表示为一个向量并 normalize 它。

得到的用户向量位于所有产品向量的相同空间中。

训练

我使用了 对比学习

  • 对于每个用户:
    • 正例:他们实际互动的下一个商品。
    • 负例:10 个他们 没有 互动的随机商品。

该损失函数会使用户向量 更接近 正例,更远离 负例。


实时更新

训练是一次性(或周期性)的任务,但用户偏好会不断变化。我使用 AWS SQS 处理更新。

流程

  1. 交互事件从 Firebase 发送 → 消息进入 SQS:

    {
      "customer_id": 12345,
      "product_id": 5678,
      "event_name": "product_clicked"
    }
  2. SQS 消费者处理该消息:

    • 从 Elasticsearch 获取商品向量。
    • 加载用户的近期交互历史。
    • 将历史记录输入已训练的 user‑tower 模型。
    • 将新的用户向量保存回 Elasticsearch。

整个管道耗时 毫秒级,因此在用户滚动到下一页时,推荐已经完成刷新。

剪枝:超过 2 天 的交互会被删除,以保持模型专注于近期行为。


Source:

基于余弦相似度的推荐

用户向量和商品向量共享相同的 384 维空间。为了检索相关商品,我使用 script_score 在 Elasticsearch 中计算余弦相似度:

{
  "script_score": {
    "script": {
      "source": "cosineSimilarity(params.user_vector, 'product_vector') + 1.0",
      "params": { "user_vector": userVector }
    }
  }
}
  • + 1.0 将分数平移到正区间,因为余弦相似度可能为负。

回退机制

如果用户尚未拥有向量(新用户或交互不足),系统会回退到默认排序(热度 + 新鲜度)或遵循显式排序(例如价格)。

结果是:有交互历史的已登录用户会获得个性化推荐流,而其他用户仍然会得到合理的默认排序。分页方式保持不变,仅通过相关性重新排序结果。


Source:

结果与收获

  • 不是数据科学家——这是我第一次尝试构建推荐系统。
  • 通过自行托管所有组件(Elasticsearch、PyTorch 模型、SQS 消费者),我摆脱了第三方推荐 API 和托管机器学习的费用。
  • 延迟显著下降,因为所有组件都运行在同一私有网络中——没有外部往返请求。
  • 该系统可以利用现有基础设施进行扩展,并在不产生 Algolia 账单爆炸的情况下提供细粒度、实时的个性化推荐。

TL;DR

  1. 通过 Firebase 收集加权事件
  2. 使用 Sentence‑Transformer 对商品进行向量化,并存入 Elasticsearch。
  3. 使用对比学习训练 双塔模型(商品塔 + 用户塔)。
  4. 通过 SQS 实时更新用户向量
  5. 通过余弦相似度 将商品与用户向量进行匹配,排序后提供推荐。

这种方法成本低、速度快,且完全由我掌控。 🚀

在本地子网使用 Elasticsearch — 比访问 Algolia 服务器快得多

自从推出双塔模型以来:

  • 订单量提升 40 %
  • 用户留存提升 10 %

用户能够找到他们真正想要的商品,并且回访频率更高。

接下来

模型已经奏效,但仍有提升空间:

  • 更多事件 – 添加 product_favoritedproduct_sharedproduct_purchased,捕获更强的意图信号。
  • 商品标签 – 为商品打上 vintagehandmadestreetwear 等属性标签,并利用这些标签对模型进行微调。

结论

你不需要专门的机器学习团队就能构建个性化推荐。双塔架构文档完善,PyTorch 易于上手,Elasticsearch 和 SQS 等工具可以处理基础设施。如果推荐系统的成本侵蚀了利润,自己动手构建可能是值得的。

如果你也构建过类似的系统或有改进此方法的建议,欢迎告诉我。

Back to Blog

相关文章

阅读更多 »