作者:来自 Elastic Alexander Marquardt, Honza Král 及 Taylor Roy
了解如何在不破坏治理的情况下,在 Elasticsearch 中创建个性化电商搜索体验。本文解释了如何提升用户此前购买过的商品,以及如何基于用户群组特定策略和用户 profiles 激活相应策略。
刚接触 Elasticsearch?欢迎参加我们的 Elasticsearch 入门网络研讨会。你也可以立即开始免费的云试用,或者现在就在你的本地机器上尝试 Elastic。
本系列的第 1 部分 到第 5 部分描述了一个受治理的控制平面,它会在查询产品目录之前完成意图分类、约束执行、策略冲突解析,以及路由到合适的检索策略。到目前为止,所有描述的机制都会以相同方式对待所有购物者。对于 “chocolate” 的搜索,无论购物者是纯素食者、为孩子生日购物的父母,还是遵守 halal 饮食规范的消费者,都会得到相同的受治理结果集。
本文引入了两种个性化机制,它们在不改变架构的情况下扩展了这一受治理控制平面。这两种机制都会与第 1 到第 5 部分中的治理层进行乘法叠加:策略仍然会触发,约束仍然会被执行,冲突仍然会被解析,而个性化信号会被组合进同一个受治理查询中,从而确保 Elasticsearch 返回的结果已经完成个性化。
第一种机制会提升购物者此前购买过的商品。第二种机制会基于购物者 profile 激活特定用户群组的策略。它们共同表明,个性化并不是附加在搜索旁边的独立系统,也不是作为检索后处理应用的机制;它是策略驱动控制平面的自然扩展。
如果想深入了解本文所使用个性化技术背后的数学原理,请参阅《无需 ML 后处理的 Elasticsearch 个性化搜索》以及《Elasticsearch 中的用户群组感知排序》。
如果想观看一个关于如何利用购买历史为回访客户提升搜索结果的实时演示,请观看视频《可解释的个性化:利用购买历史提升搜索》。
个体购买历史提升
最简单的个性化形式之一,也是最有效的形式之一:如果某个购物者以前购买过某个产品,那么当他搜索相关内容时,就对该产品进行提升。一个经常购买特定品牌巧克力曲奇的购物者,在搜索 “cookies” 时,应该看到这些曲奇排名更高,这不是因为模型预测了偏好,而是因为存在直接的行为证据。
工作原理
当搜索请求包含用户标识(例如用户处于已登录会话中)时,控制平面会使用线程池并行运行两个 Elasticsearch 查询:
-
针对 policy index 的 percolator 查询(与第 3 和第 4 部分中描述的治理查找相同)
-
针对 user_purchases index 的购买历史查询,通过 term(user_id) 过滤到特定用户,然后将当前搜索字符串与该用户的商品标题进行匹配
这两个查询是并发执行的(彼此不等待),因此个性化查找不会给治理流水线带来显著延迟。
购买历史查询在将当前搜索字符串与已存储的商品标题匹配时,会使用 Elasticsearch 的文本分析能力(词干提取、分词等)。这意味着搜索 “cookies” 可以通过标准文本分析匹配到过去购买的“brownie cookies”,而不需要精确字符串匹配。
计算提升权重
并非所有历史购买都应该获得相同的提升。权重会考虑两个直觉因素:购买频率以及购买的时间新近程度。一个上周购买了 15 次的商品,其信号远强于六个月前只购买过一次的商品。权重计算对频率使用对数缩放(避免单一高频商品压制其他结果),对时间使用指数衰减(使较旧购买自然减弱影响)。
关于提升公式的数学细节,请参阅《无需 ML 后处理的 Elasticsearch 个性化搜索》。
如何进入查询
购买历史的提升会作为查询的最外层评分层进行组合,它包裹在治理策略过滤器以及第 3 和第 4 部分的提升逻辑之外,同时也包括业务信号提升(例如利润率和热度,我们将在第 7 部分探讨)。这意味着如果某个产品被治理策略移除,它不会因为购买历史提升而重新出现。
治理控制的是结果集合;个性化只调整集合内部的排序。
没有购买历史的商品不会被惩罚。它们仍然保持受治理的排序,只是当存在相关购买历史时,有历史记录的商品会在同等条件下排名更靠前。
为什么每次搜索都要查询 Elasticsearch?
购买历史查询会在每次搜索时从 Elasticsearch 中执行,而不是在应用层进行缓存。这是一个有意的设计选择。因为该查询需要将当前搜索字符串与商品标题进行匹配,而这一过程使用的是 Elasticsearch 的文本分析流水线(text analysis pipeline)。这样系统就可以复用同一套支持搜索的词干提取、分词以及语言处理能力。如果使用内存缓存,就必须重新实现这些分析逻辑,或者接受更粗糙的匹配效果。
为了理解这种排序的重要性,可以考虑一个场景:某个购物者曾经购买过 orange juice,而现在搜索 “oranges”。购买历史查询会通过文本分析,将 “orange juice”与 “oranges” 匹配,并为该商品计算一个提升权重。但治理层已经将 “oranges” 限制在生鲜(produce)类别,因此会过滤掉 orange juice。此时,虽然 orange juice 在购买历史中获得了 boost,但由于它已经不在受治理的结果集中,这个 boost 不会产生任何影响。用户最终看到的是新鲜橙子,它们的排序由相关性和个性化共同决定,而治理层的约束依然有效。
性能成本是很低的:购买历史索引本身很小(用户的购买记录通常只有几十到几百条,而不是数百万条),并且该查询与 percolator 查询并行执行,因此不会增加关键路径的延迟。
“spring water” 的无用户历史示例查询
如果是未登录用户,或者从未购买过 “spring water” 的用户进行搜索,他们可能会看到类似如下的结果:
示例用户购买历史
另一方面,一个名为 Carol 的用户,其购物历史包含以下商品:
使用上述购买历史搜索 “spring water” 的示例
如果 Carol 搜索 “spring water”,她将看到反映其过往购买行为的个性化结果。根据上面的购买历史,她曾购买 “Carbonated Spring Water”(绿色瓶装)大约 40 次,最近一次是在两天前。因此,当她搜索 “spring water” 时,该商品会被提升排序,因为系统已知她偏好该产品。注意,在未个性化的结果中,Rubicon spring water 原本是排名第一的结果。
群组感知策略激活
个体购买历史在面对有明确行为模式的回访用户时效果很好。但许多购物者是新用户、匿名用户,或者他们的浏览行为并不符合既有习惯。对于这些用户,用户群组(cohort)成员身份提供了另一种个性化方式 —— 基于 “这个用户是谁”,而不是 “这个用户做过什么”。
素食(vegan)用户搜索“chocolate”时,应该优先看到素食巧克力。遵守 halal 饮食规范的用户搜索“snacks”时,应优先展示 halal 认证产品。注重健康的用户搜索“yogurt”时,应优先提升含益生菌的产品。
将用户群组作为策略,而不是商品标签
商品本身已经包含其正常属性,例如 dietary_restrictions: ["vegan"] 或 dietary_restrictions: ["halal"]。关键问题在于:是谁来定义 “用户群组” 和 “商品属性” 之间的映射逻辑。
一种简单但不理想的方法,是在应用层或搜索模板中硬编码这种映射:如果用户是 vegan,就对 dietary_restrictions: "vegan" 做 boost。但这正是第 1 部分中提到的应用层 “意大利面式” 逻辑,会带来同样的运维问题:新增用户群组或修改用户群组定义,都需要改代码。
受治理的控制平面将用户群组逻辑保留在策略引擎中。一个 cohort policy 会连接两件事:用户的 cohort 成员身份(例如 “vegan”)与商品属性(例如 dietary_restrictions: “vegan”)。该策略定义了这种关联:当属于 vegan cohort 的用户进行搜索时,对 dietary_restrictions 包含 “vegan” 的商品进行提升排序。
因为 用户群组(cohort)逻辑存在于策略引擎中,而不是应用代码中,这意味着:
-
添加一个新的用户群组(cohort)只需要创建一条新策略;不需要重新对商品进行索引(reindexing)。
-
用户群组(cohort)策略可以使用完整的规则引擎:它们可以添加过滤条件、应用软提升(soft boost)、扩展同义词、改变检索策略,或执行策略能够支持的任何其他操作。
-
用户群组(cohort)行为通过与所有其他策略相同的管理 UI 进行管理:运营人员(merchandiser)可以通过第 2 部分描述的 Author → Test → Promote 工作流来创建、测试并发布用户群组(cohort)策略。
示例:vegan 用户群组(cohort)策略
运营人员创建一个用户群组(cohort)策略,具有以下特征:
-
用户群组(cohorts):["vegan"]
-
匹配条件:匹配任意查询(或特定商品类别)
动作:对 dietary_restrictions: "vegan" 施加 soft boost,权重为 2
用户群组(cohort)激活的工作原理
每一条策略文档都有一个 cohorts 字段。适用于所有购物者、与任何用户群组(cohort)无关的通用策略可以将该字段留空,这些策略在控制平面内部会被自动赋值为 "_all"。而用户群组(cohort)特定的策略则会存储目标用户群组名称,例如 ["vegan", "kosher", “sweet_tooth”]。
当搜索请求包含用户 profile 时,控制平面会为 percolator 查询构建一个简单的 terms 过滤器:
`{ "terms": { "cohorts": ["_all", "vegan", "health_conscious"] } }`AI写代码
这个单一过滤器会包含所有通用策略,以及该用户群组(cohort)的专属策略。_all 这个哨兵值使它成为一个干净的 “包含型过滤器”:不需要使用 must_not 或 exists 查询来处理 “策略没有 cohort 限制” 的情况。
随后 percolator 会像往常一样评估策略匹配。唯一的区别在于候选策略集合已经被缩小到与该购物者用户群组(cohort)相关的那些策略。其余所有下游流程(级联变换、字段级冲突解析、已消费短语追踪)都与第 3 和第 4 部分描述的非个性化流程完全一致。
非素食(standard)用户搜索 “chocolate” 的结果
当非素食用户搜索 chocolate 时,他们的结果中不会应用任何素食用户群组(cohort)提升。因此,他们通常会在顶部看到非素食巧克力,例如如下结果:
当搜索 “chocolate” 的是 vegan 用户群组(cohort)用户时的结果
当一个属于 vegan 用户群组(cohort)的购物者搜索 “chocolate” 时,该策略会被纳入 percolator 候选策略集合中。它会被匹配,并且控制平面会对 vegan 认证的巧克力应用 soft boost。
该提升是乘法型的:vegan 巧克力的排名会更高,但非 vegan 巧克力不会被完全排除,因为上述过滤被定义为 soft boost,这一点我们在本系列第 3 部分中已经详细说明过。
然而,如果购物者明确搜索 “Hershey milk chocolate”,即使 vegan 提升仍然会被应用,但它可能会被 Hershey milk chocolate 产品更强的文本相关性所抵消。
不属于 vegan 用户群组(cohort)的购物者在搜索同一查询时,永远不会看到“vegan 用户群组(cohort)”策略;该策略不在他们的候选策略集合中。治理层是完全一致的;唯一不同的是被激活的策略集合。
带购买历史的用户群组(cohort)
一个属于 vegan 用户群组(cohort)且拥有大量购买历史的购物者,会同时触发 vegan 用户群组(cohort)策略激活以及购买历史提升。
对于新用户或匿名用户,仅凭“隐含的用户群组(cohort)成员身份”就可以提供有意义的个性化,而不需要任何行为数据(例如,一个匿名用户可能只搜索过 vegan 产品,因此被系统归类为 vegan 用户群组(cohort)成员)。
在账号创建时自我标识为 halal-observant 的购物者,会在第一次搜索时立即获得 halal 定制化的结果。
个性化层如何组合
function_score 各层的嵌套顺序非常重要,从内到外依次是:
- 基础查询层:关键词或语义匹配(named queries,例如 fulltext_match、title_phrase_match)
- 治理策略层:作为 bool.filter 的硬过滤,以及 function_score 中的软提升(第 3 和第 4 部分)
- 业务信号提升层:利润率和热度等提升(第 7 部分将介绍)
- 购买历史提升层:最外层 function_score
这种顺序保证:
-
治理层控制结果集合(哪些内容可以出现)
-
业务信号在集合内部调整排序(从商家视角优化优先级)
-
购买历史进一步基于个体行为调整排序(从购物者视角优化优先级)
每一层都会对上一层进行乘法式包裹,因此各个信号不是冲突关系,而是叠加增强关系。
这在运维上的意义
通过受治理的控制平面实现个性化,可以保留第 1 和第 2 部分中描述的所有运维特性:
-
零部署变更(Zero-deploy changes)
用户群组(cohort)策略可以通过管理 UI 创建、测试和发布。新增 dietary 用户群组或调整提升权重都不需要代码变更或工程介入。 -
可审计性(Auditability)
每一条用户群组(cohort)策略都是独立的版本化文档。当运营人员询问“为什么 vegan 产品在这个用户的搜索中排名更高?”时,可以精确定位到具体策略,并在调试面板中看到该查询触发的所有策略。 -
冲突解决(Conflict resolution)
用户群组(cohort)策略参与第 3 部分描述的逐字段冲突解析。如果 cohort 的 category boost 与 campaign policy 的 category override 冲突,会通过统一的优先级和策略框架进行确定性解决,不需要特殊处理。 -
可度量性(Measurability)
因为用户群组(cohort)策略是独立且可单独开关的,其对转化率、点击率和加购率的影响可以像系统中的任何其他策略一样被独立衡量。
本系列的下一部分内容
下一篇文章将探索受治理控制平面的另一个维度:如何通过策略(policies)按查询调整利润率(margin)和热度(popularity)的提升,将经济优化从静态配置转变为一种治理决策。
请参阅第 7 部分:由查询治理的经济优化:按查询进行利润率与热度提升
将受治理的电商搜索付诸实践
本文中描述的个性化模式(个体购买历史提升和用户群组(cohort)感知策略激活)由 Elastic Services Engineering 设计并实现,作为我们可复用的电商搜索加速器的一部分。这两种机制都集成在本系列中描述的受治理控制平面架构之中。请联系 Elastic 专业服务。
加入讨论
如果你对搜索治理、检索策略或电商搜索架构有疑问,欢迎加入更广泛的 Elastic 社区讨论。
这篇内容对你有帮助吗?