构建双塔推荐系统
Source: Dev.to
请提供您想要翻译的完整文本(包括正文、标题、列表等),我将按照要求保留源链接并将内容翻译成简体中文。谢谢!
概述
我在 POSH(我的电商应用)中使用 Algolia 进行搜索和推荐。效果很好,但费用不断攀升——每一次搜索请求和推荐调用都会累计,因为用户不断浏览商品。
于是我构建了自己的推荐系统,采用 双塔模型(与 YouTube 和 Google 使用的方式相同):
- 一个塔将 产品 表示为向量。
- 另一个塔根据用户行为将 用户 表示为向量。
要获取推荐,我只需找到与用户向量最近的产品。
下面是我构建过程的逐步说明。
数据管道
一切始于通过 Firebase Analytics 捕获的 用户行为:
| 事件 | 含义 |
|---|---|
| Product viewed | 仅浏览 |
| Product clicked | 表示兴趣 |
| Added to cart | 强烈意图 |
并非所有交互都等同,因此我为它们分配 权重:
| 事件 | 权重 |
|---|---|
| View | 0.1 |
| Click | 2.0 |
| Add to cart | 5.0 |
产品向量化
所有产品存储在 Elasticsearch 中。为了使推荐功能生效,每个产品必须表示为 384 维向量。
模型:all-MiniLM-L6-v2 来自 Sentence‑Transformers – 快速、轻量,适用于语义相似度。
向量生成方式
-
将产品属性 合并为一个文本字符串,例如:
Nike Air Max | by Nike | Shoes | Sneakers | Running | Blue color | premium -
该字符串包括:
- 产品名称
- 商家名称
- 类别层级(父级 → 类别 → 子类别)
- 颜色
- 价格档位(预算 / 中档 / 高端 / 奢侈)
-
将字符串输入模型 → 384 维向量。
-
将向量存储在 Elasticsearch 中的
dense_vector字段,以用于相似度搜索。
用户塔架构
用户塔 消费用户最近的交互历史,并输出一个位于与产品向量相同空间的单一向量。
输入
最多 20 条最近的交互,每条包括:
- 产品的 384 维向量
- 交互类型(view / click / add‑to‑cart)
输出
单个 384 维用户向量。
模型工作流
- 嵌入交互类型 并与产品向量拼接。
- 将序列传入 multi‑head attention 层,使模型能够学习哪些交互最重要。
- 应用 recency decay —— 较新的交互获得更高权重。
- 池化 注意后的表示为一个向量并 normalize 它。
得到的用户向量位于所有产品向量的相同空间中。
训练
我使用了 对比学习:
- 对于每个用户:
- 正例:他们实际互动的下一个商品。
- 负例:10 个他们 没有 互动的随机商品。
该损失函数会使用户向量 更接近 正例,更远离 负例。
实时更新
训练是一次性(或周期性)的任务,但用户偏好会不断变化。我使用 AWS SQS 处理更新。
流程
-
交互事件从 Firebase 发送 → 消息进入 SQS:
{ "customer_id": 12345, "product_id": 5678, "event_name": "product_clicked" } -
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
- 通过 Firebase 收集加权事件。
- 使用 Sentence‑Transformer 对商品进行向量化,并存入 Elasticsearch。
- 使用对比学习训练 双塔模型(商品塔 + 用户塔)。
- 通过 SQS 实时更新用户向量。
- 通过余弦相似度 将商品与用户向量进行匹配,排序后提供推荐。
这种方法成本低、速度快,且完全由我掌控。 🚀
在本地子网使用 Elasticsearch — 比访问 Algolia 服务器快得多
自从推出双塔模型以来:
- 订单量提升 40 %
- 用户留存提升 10 %
用户能够找到他们真正想要的商品,并且回访频率更高。
接下来
模型已经奏效,但仍有提升空间:
- 更多事件 – 添加
product_favorited、product_shared和product_purchased,捕获更强的意图信号。 - 商品标签 – 为商品打上 vintage、handmade、streetwear 等属性标签,并利用这些标签对模型进行微调。
结论
你不需要专门的机器学习团队就能构建个性化推荐。双塔架构文档完善,PyTorch 易于上手,Elasticsearch 和 SQS 等工具可以处理基础设施。如果推荐系统的成本侵蚀了利润,自己动手构建可能是值得的。
如果你也构建过类似的系统或有改进此方法的建议,欢迎告诉我。