如何通过个性化、分群感知排序来提升电商搜索相关性

基于分群的乘法个性化排序

作者:来自 Elastic  Alexander Marquardt

通过在 Elasticsearch 中使用可解释、分群感知的排序来提升电商搜索相关性。了解乘法提升如何在查询时提供稳定、可预测的个性化效果。

Elasticsearch 充满了新功能,可以帮助你为自己的使用场景构建最好的搜索解决方案。了解如何在我们的实践网络研讨会中将这些功能应用到构建现代 Search AI 体验中。你也可以开始免费的云试用,或在本地机器上尝试 Elastic。


概览

在这篇文章中,我们探讨如何使用一种可解释的、乘法提升策略,让 Elasticsearch 的搜索结果对不同电商用户分群更加相关 —— 而且不需要任何机器学习的后处理。

引言:为什么个性化很重要

Elasticsearch 在按文本相关性( BM25 )和语义相关性(向量)对结果进行排序方面非常出色。在电商中,这些是必要的,但还不够。两个人可能输入相同的查询,却合理地期望看到不同的结果:

  • 一个奢侈品购物者搜索 “red lipstick” 时,会希望高端品牌排在前面。
  • 一个预算型购物者则希望更实惠的商品被提升。
  • 一个礼物购买者可能更喜欢热门礼包。

目标是在给定查询的情况下,适度提升与用户分群相匹配的商品,使其在列表中上升,但不会破坏底层的相关性。本文展示如何仅使用 function_score、一个 keyword 字段,以及小幅乘法提升,来在 Elasticsearch 的相关性之上加入分群感知的个性化。

面向分群个性化的乘法提升

分群个性化中的核心挑战是稳定性。你希望与查询高度相关的商品依然保持相关性,并且在匹配用户分群时获得可控、可解释的提升。常见的问题在于个性化信号被加入总分的方式要么:

  • 压过某些查询中的 BM25,要么
  • 在其他情况下几乎没有效果。

出现这种情况的原因是大多数提升方法都使用加法评分。但 BM25 的分值尺度会在不同查询和数据集之间发生巨大变化,因此一个固定的加法调整(例如 “分群匹配加 +2.0”)有时会对 BM25 造成巨大的影响,而有时又微不足道。

我们真正想要的,是一种保证:如果一个商品对查询匹配度高,并且与用户的分群一致,那么它的得分就能按照一个可控的百分比提升,而不受绝对 BM25 分值大小的影响。我们可以通过一种乘法模式来实现这一点:

final_score = BM25 × (1 + cohort_overlap × weight_per_cohort)

本文展示如何使用 Elasticsearch 的 function_score 查询、产品上的 cohorts 字段,以及查询时传入的用户 cohorts 列表来实现这种模式。

在商品目录中建模分群

启用分群感知排序的最简单方式是把分群视为标签。例如,一个商品可能带有如下标签:

  • 口红/Lipstick:["female", "beauty", "luxury"]
  • 男士止汗剂/Men’s deodorant:["male", "personal_care", "sport"]
  • 亮片唇彩/Glitter gloss:["female", "beauty", "youth", "party"]

一个用户或会话则携带根据行为和画像推断出的标签:

  • 高收入女性奢侈品购物者:["female", "beauty", "luxury"]
  • 注重预算的女性购物者:["female", "beauty", "budget"]

分群重叠就是用户/会话与商品之间共享标签的数量。没有加权,没有语义相似度 —— 只是简单的交集。例如,如果用户的 cohorts 是 ["female", "beauty", "budget"],而某款口红的标签是 ["female", "beauty", "luxury"],那么重叠数就是 2。

如果某款男士止汗剂的标签是 ["male", "personal_care", "sport"],那么与同一用户的重叠就是 0。

直觉是:(a) BM25 会根据文档与用户查询的相关程度进行排序,而 (b) 分群重叠会根据商品与用户分群的契合度来提升商品的排序。为了实现这一点,我们将用户分群与商品分群之间的重叠数转换成一个乘法提升,用来对 BM25 进行缩放。

为了避免字段爆炸,我们把所有分群标签放在一个单独的 keyword 字段中,例如:

{
  "product_id": "LIP-001",
  "description": "Premium cherry red lipstick with velvet finish",
  "cohorts": ["female", "beauty", "luxury"]
}

这种方式便于商品运营人员理解,避免出现像 is_female 或 is_luxury 这样成百上千的布尔字段,并且能与 term 过滤高效配合。

为什么加法式提升行不通

一个微妙但重要的点是,即使是标准的 boolean 查询,本质上也是加法的。当 Elasticsearch 给文档评分时,主查询(通常是 must)产生的基础 BM25 分数,加上所有匹配的 should 子句的得分,都会以加法方式累加。也就是说,“加法式提升” 不仅体现在 boost 参数上,它是 boolean 评分机制的基础。

基于加法逻辑的个性化表现不一致,因为 BM25 的分值尺度会因查询和数据集而异。例如,三个商品的基础 BM25 分别可能是 12、8、4,但在更新数据集或修改查询后,又可能变成 0.12、0.08、0.04。在这种情况下,一个加法提升(例如 +2.0)会在基础 BM25 分很小时成为主导力量(在得分 0.12 上加 +2.0,相当于提升约 18 倍),而当基础分很大时又几乎可以忽略(在得分 12 上加 +2.0 仅提升约 1.17 倍)。这会导致排序行为不一致且不可预测。

为什么乘法式提升是正确的形状

如果我们应用乘法提升,其形状始终一致:

final_score = BM25 × boost
boost = 1 + overlap × weight_per_cohort

当 weight_per_cohort = 0.1 时,重叠为 2 会产生 1.2 的提升(20% 增幅),重叠为 1 会产生 1.1 的提升(10% 增幅),重叠为 0 则得到 1.0(无变化)。这意味着,只要商品更符合用户分群,就会得到可预测的百分比提升,而不管它的 BM25 得分是 0.01 还是 10.0。BM25 依然是主要信号;分群契合度只是轻柔地重新塑形排序。

function_score 如何带来乘法行为

为了把分群重叠转换成可控的百分比提升,我们需要一种方法,把正常的 BM25 得分按某个倍数放大,例如 1.1、1.2 或 1.3。Elasticsearch 并不支持在标准查询内部直接进行得分相乘,但 function_score 正好提供了这种能力:它允许我们计算一个额外的得分组件,并使用指定的策略(在这个用例中是 “multiply”)将其与基础得分组合。

Elasticsearch 的 function_score 让我们能通过三步实现乘法式分群提升。第一,每个分群匹配贡献一个小的权重(例如 0.1)。第二,我们加入一个 1.0 的基线权重,让最终的乘数永远不会低于 1。第三,我们使用 score_mode: "sum" 把所有分群贡献相加,得到一个表示 (1 + overlap × weight) 的提升因子。最后,我们用 boost_mode: "multiply" 将这个提升因子与 BM25 得分相乘,从而得到我们想要的精确乘法行为。

下面的计算展示了最终得分是如何计算的,其中 BM25 是基础相关性;n 是匹配的分群数量;w 是 weight_per_cohort(例如 0.1);additive baseline = 1.0:

sum_score = baseline + n × w
final_score = BM25 × sum_score

所以,对于 2 个重叠的 cohort 且 w = 0.1:

sum_score = 1.0 + 2 × 0.1 = 1.2
final_score = BM25 × 1.2

这正是我们想要的乘法行为。

整合:索引、数据和基线排序

创建一个简单的索引:

PUT product_catalog
{
  "mappings": {
    "properties": {
      "product_id": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      },
      "cohorts": {
        "type": "keyword"
      }
    }
  }
}

索引几个产品:

POST _bulk
{ "index": { "_index": "product_catalog", "_id": "LIP-001" }}
{ "product_id": "LIP-001", "description": "Premium cherry red lipstick with velvet finish", "cohorts": ["female", "beauty", "luxury"] }
{ "index": { "_index": "product_catalog", "_id": "LIP-002" }}
{ "product_id": "LIP-002", "description": "Affordable matte red lipstick for everyday wear", "cohorts": ["female", "beauty", "budget"] }
{ "index": { "_index": "product_catalog", "_id": "LIP-003" }}
{ "product_id": "LIP-003", "description": "Glitter red gloss for parties and festivals", "cohorts": ["female", "beauty", "youth", "party"] }

“red lipstick”的基线查询可能如下:

POST product_catalog/_search
{
  "size": 5,
  "_source": ["product_id", "description"],
  "query": {
    "multi_match": {
      "query": "red lipstick",
      "fields": ["description"]
    }
  }
}

这会返回纯 BM25 排序(没有任何 cohort 提升)。在这个例子中,LIP-001 和 LIP-002 的分数会非常接近(或相同),因为它们匹配相同的查询词,频率相似,长度也相当。

关键是相对排序;具体的数值分数可能会因分片配置、分析器差异或 Elasticsearch 版本而不同。

Product IDDescriptionBM25 score
LIP-001Premium cherry red lipstick with velvet finish0.603535
LIP-002Affordable matte red lipstick for everyday wear0.603535
LIP-003Glitter red gloss for parties and festivals0.13353139

Persona A:高收入奢侈品购物者

假设我们知道 Persona A 属于以下 cohorts:

["female", "beauty", "luxury"]

我们将其转换为一组 cohort 过滤器,每个过滤器有一个小权重,再加上一个基线因子:

GET product_catalog/_search
{
  "explain": true,
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query": "red lipstick",
          "fields": ["description"]
        }
      },
      "functions": [
        { "filter": { "term": { "cohorts": "female" }},  "weight": 0.1 },
        { "filter": { "term": { "cohorts": "beauty" }},  "weight": 0.1 },
        { "filter": { "term": { "cohorts": "luxury" }},  "weight": 0.1 },
        { "weight": 1.0 }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}

对于这个 persona,LIP-001(“Premium cherry red lipstick with velvet finish”)匹配 “female”、“beauty” 和 “luxury”,意味着 cohort 重叠为 3,因此提升因子为 1.3。另一方面,LIP-002 和 LIP-003 匹配 “female” 和 “beauty”,提升因子为 1.2。

Product IDDescriptionBase BM25 scoreBoost factorNew score
LIP-001Premium cherry red lipstick with velvet finish0.6035351.3x (30%)0.7845955
LIP-002Affordable matte red lipstick for everyday wear0.6035351.2x (20%)0.724242
LIP-003Glitter red gloss for parties and festivals0.133531391.2x (20%)0.16023767

如同预期,对于这个奢侈品用户,奢侈口红(LIP-001)获得了最大的提升,倾向于在结果中超过类似的替代品。

Persona B:注重预算的购物者

一个注重预算的购物者可能属于以下 cohorts:

["female", "beauty", "budget"]

这个用户的查询几乎与之前的查询相同,只是 cohort 值现在反映的是 “budget” 而不是 “luxury”:

GET product_catalog/_search
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query": "red lipstick",
          "fields": ["description"]
        }
      },
      "functions": [
        { "filter": { "term": { "cohorts": "female" }},  "weight": 0.1 },
        { "filter": { "term": { "cohorts": "beauty" }},  "weight": 0.1 },
        { "filter": { "term": { "cohorts": "budget" }},  "weight": 0.1 },
        { "weight": 1.0 }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}

对于这个 persona,LIP-002(“Affordable matte red lipstick for everyday wear”)匹配 “female”、“beauty” 和 “budget”,意味着 cohort 重叠为 3,因此提升因子为 1.3。另一方面,LIP-001 和 LIP-003 匹配 “female” 和 “beauty”,提升因子为 1.2。

Product IDDescriptionBase BM25 scoreBoost factorNew score
LIP-002Affordable matte red lipstick for everyday wear0.6035351.3x (30%)0.7845955
LIP-001Premium cherry red lipstick with velvet finish0.6035351.2x (20%)0.724242
LIP-003Glitter red gloss for parties and festivals0.133531391.2x (20%)0.16023767

如同预期,对于这个注重预算的用户,预算口红(LIP-002)获得了最大的提升,倾向于在结果中超过类似的替代品。

如何动态构建 cohort 过滤器(Python 示例)

通常,你会在查询时根据用户/会话的 profile 注入 cohort 过滤器。例如:

user_cohorts = ["female", "beauty"]
functions = [
    { "filter": { "term": { "cohorts": cohort } }, "weight": 0.1 }
    for cohort in user_cohorts
]
# add baseline multiplier
functions.append({ "weight": 1.0 })

在 keyword 字段上使用 term 过滤器速度快,对分片缓存友好,并且在 _explain API 中完全可见,该 API 会准确显示哪些过滤器被触发以及应用了哪些权重。

Cohort 分配如何工作

Cohort 分配有意留在 Elasticsearch 外部,也不在本文范围内。然而,来源可能包括:

  • 浏览事件(“has viewed lipstick” → beauty)
  • 性别推断(来自偏好或营销 profile)
  • 设备特征(mobile shopper)
  • 位置(“urban buyer”)
  • 历史购买
  • 营销细分
  • 个性化 cookies

所有这些都是输入信号,但 Elasticsearch 中的评分机制保持不变。Elasticsearch 不需要知道你是如何推断这些 segment 的。这种关注点分离让 Elasticsearch 专注于排序,而你的应用或数据科学层负责推断 segment 的逻辑。

如何选择合适的提升权重

在我们的示例中,每个 cohort 使用 0.1。这个值是可调的。保持在 0.05 到 0.20 之间可能会有良好效果。你应该根据以下因素进行 A/B 测试权重:

  • 目录多样性
  • 每个产品的 cohort 标签数量
  • BM25 的可变性
  • 业务目标(收入 vs. 发现 vs. 个性化)

限制每个产品分配的 cohort 数量

给一个产品分配 20 个 cohort 标签会导致:

  • 信号噪声
  • 商家操纵(“给所有东西打上 luxury 标签”)
  • 可解释性丧失
  • 过度提升

作为起点(需通过你自己的测试确认),我们建议:

  • 每个产品大约 5 个 cohort。
  • 可选的离线验证步骤(ingest pipeline、CI 脚本或索引时检查),当分配超过 5 个标签时发出警告或阻止。

针对用户的自定义 cohort 提升

到目前为止,我们的示例假设每个 cohort 的贡献相同。实际上,有些用户对某些 segment 偏好很强。在某些情况下,你可能知道某些 cohort 对特定用户尤其重要。例如:

  • 几乎总是购买奢侈品牌的用户
  • 一直选择预算选项的用户

你可以通过为每个 cohort 分配不同权重来实现,而不是固定的 0.1。例如,如果你的应用检测到一个 “super-luxury” 购物者,那么可以如下修改 function scoring:

"functions": [
  { "filter": { "term": { "cohorts": "female" }},  "weight": 0.1 },
  { "filter": { "term": { "cohorts": "beauty" }},  "weight": 0.1 },
  { "filter": { "term": { "cohorts": "luxury" }},  "weight": 0.2 },
  { "weight": 1.0 }
]

在上述示例中,匹配 “female” 或 “beauty” 各增加 +0.1,而匹配 “luxury” 增加 +0.2。在这个示例中,匹配所有三个 cohort 的产品将得到:

boost = 1.0 + 0.1 + 0.1 + 0.2 = 1.4

这仍然完全可解释,你可以记录配置(“对于这个用户,luxury 的重要性是其他 cohort 的 2 倍”)。此外,_explain API 会显示这些数值如何具体贡献到最终分数。

结论:

这种原生 Elasticsearch 的 cohort 个性化方法仅使用轻量级元数据和标准查询构造,同时保持可解释性、稳定性以及对相关性模型的业务控制。它提供精准、可预测的相关性,确保业务目标不会牺牲搜索结果质量。

实现总结

如果你想在生产环境中采用这种模式,高层步骤如下:

  1. 给每个产品添加一个单一 keyword 字段(cohorts),包含 3–5 个 cohort 标签。
  2. 在应用逻辑中计算用户/会话的 cohorts(来自浏览、购买历史、CRM 等),并随查询传递。
  3. 在查询中注入动态 function_score 过滤器,每个用户 cohort 一个,每个带一个小权重(例如 0.1),再加上基线权重(1.0)。
  4. 将现有 BM25 查询包裹在 function_score 中,使用 score_mode: "sum" 和 boost_mode: "multiply" 应用乘法提升。
  5. 根据 A/B 实验调优每个 cohort 权重(通常 0.05–0.20),确保 BM25 保持主要信号。

这些步骤让你可以在现有搜索相关性基础上干净地叠加 cohort 个性化,无需脚本、ML 模型或重大架构更改。

接下来做什么?

这个模式是如何直接在查询中构建复杂相关性规则的强大示例,确保速度和可靠性。

  • 更快实现自定义个性化:如果你准备部署并优化这一高级 cohort 个性化策略,或解决其他复杂相关性挑战,我们的团队可以帮助你快速构建、调优并投入运行 Elasticsearch 解决方案。联系 Elastic Services 获取实现此方案及其他高级搜索技术的帮助。

  • 加入讨论:关于高级相关性技术和实现的一般问题,可加入更广泛的 Elastic Stack 社区,参与搜索讨论。

原文:https://www.elastic.co/search-labs/blog/ecommerce-search-relevance-cohort-aware-ranking-elasticsearch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值