人工智能驱动的搜索(AI-Powered Search)——众包相关性

113 阅读41分钟

本章内容包括:

  • 利用用户的集体洞察力来提高搜索平台的相关性
  • 收集并处理用户行为信号
  • 使用反射智能创建自我调节的模型
  • 构建端到端的信号增强模型

在第一章中,我们介绍了用户意图的维度,包括内容理解、用户理解和领域理解。为了创建一个优化的人工智能驱动的搜索平台,我们需要能够将这些上下文结合起来,以理解用户的查询意图。然而,问题是,我们如何得出这些理解呢?

我们可以从许多信息源中学习:文档、数据库、内部知识图谱、用户行为、领域专家等。有些组织拥有团队,手动为文档打上主题或类别标签,而有些组织甚至使用像Amazon Mechanical Turk这样的工具外包这些任务,通过它们可以从全球各地的人群中众包答案。为了识别网站上的恶意行为或错误,公司通常允许用户报告问题,甚至建议修改。这些都是众包的例子——依靠许多人输入来学习新信息。

在搜索相关性方面,众包可以发挥至关重要的作用,尽管通常重要的是不要通过不断请求帮助来打扰到您的宝贵客户。幸运的是,通常可以通过基于用户行为的方式从用户中间隐性学习。例如,为了发现查询的最相关文档,我们可以查看日志,确定其他用户在进行相同搜索时点击最多的文档。这些点击提供了结果与查询的相关性信号。

在本章中,我们将探索如何收集、分析并从这些信号中生成洞察力来众包相关性。我们还将介绍反射智能过程,介绍三种关键模型:用于流行相关性的信号增强、用于个性化相关性的协同过滤和用于泛化相关性的学习排序。您还将为电商数据集建立索引,并构建您的第一个反射智能模型。

4.1 使用用户信号

每当客户采取行动——例如发出查询或购买产品——这都会提供该用户意图的信号。我们可以记录和处理这些信号,以了解每个用户、不同用户群体或我们整个用户群体的洞察。

本节介绍了如何使用用户信号的强大功能,我们将在全书中使用一个示例电商数据集,并带您了解收集、存储和处理这些信号的过程。

4.1.1 内容 vs. 信号 vs. 模型

在构建搜索引擎时,影响搜索相关性的两个高级数据来源是:内容和信号。大多数内容以文档的形式存在,这些文档可以代表网页、产品列表、计算机文件、图像、视频、事实或任何其他类型的可搜索信息。内容文档通常包含文本或嵌入字段,用于搜索,此外还有表示与内容相关的属性(如作者、大小、颜色、日期等)的其他字段。内容文档的定义特征是,它们包含用户搜索的信息,并理想地包含对其查询的回答。

当用户看到查询结果中的内容时,他们可能会点击某个结果、将其添加到购物车中,或采取其他行动。这些行动就是信号,它们对提供关于用户如何与内容互动的洞察至关重要。这些信号随后可以被汇总并用于构建模型,以改进匹配和排名算法的相关性。信号的定义特征是,它们是用户提供的洞察,用于展示用户希望如何与内容互动。

有时依赖外部数据源或模型作为搜索体验的一部分也是有用的。这可以包括查询知识图谱、引用实体列表,或调用已经在外部数据源上训练的大型语言模型(LLM)或其他基础模型。这些外部模型可以帮助更好地理解用户查询、推理并理解内容,甚至总结或生成新的内容进行返回。尽管我们可以将模型视为搜索引擎的第三种数据来源,但它们是基于内容和/或信号训练的,因此作为这两种原始数据源的派生和精炼表示。

总而言之,我们使用三种主要的信息来源来改进搜索:项目的属性(内容)、用户与内容的互动信号(信号)和外部模型(这些模型源自内容和/或信号)。

在构建人工智能驱动的搜索时,很多任务我们可以通过内容或信号来得出相似的结果,但它们提供了两种不同的相关性视角。在理想的情况下,我们可以应用这两种视角来构建一个更智能的系统,但理解它们的优缺点,以便最好地利用它们,还是很有帮助的。

例如,当尝试查找“driver”(司机)的同义词时,我们可以查看文本内容,寻找那些通常出现在相同文档中的词汇。在这种情况下,我们可能会发现以下词汇(按它们在文档中出现的比例优先排序):“taxi”(40%)、“car”(35%)、“golf”(15%)、“club”(12%)、“printer”(3%)、“linux”(3%)和“windows”(1%)。类似地,我们可以查看搜索“driver”的用户的信号,并根据他们其他搜索中的常见关键词按优先顺序进行汇总,比如“screwdriver”(50%)、“printer”(30%)、“windows”(25%)、“mac”(15%)、“golf”(2%)和“club”(2%)。基于信号和内容得出的列表可能相似,也可能大相径庭。基于内容的方法告诉我们文档中最具代表性的含义,而基于信号的方法则告诉我们用户寻找的最具代表性的含义。

由于我们的最终目标是为用户提供他们正在寻找的内容,因此通常依赖于基于信号得出的含义比基于内容得出的含义更为有效。但如果我们没有很好的内容来映射到基于信号得出的含义呢?我们是使用基于内容的含义,还是基于信号数据尝试推荐其他相关的搜索?如果我们没有足够的信号,或者信号数据不够干净怎么办?我们能否利用基于内容的数据来清理基于信号的数据?

在推荐系统中,我们也会遇到类似的问题。基于内容的推荐使用文档中的属性,但不了解用户,而基于信号的推荐则无法理解内容属性,且在没有足够的互动数据时无法工作。基于内容的推荐可能基于对用户不重要的特征,而基于信号的推荐可能会产生自我强化的循环,用户只与被推荐的项目互动,但这些项目之所以被推荐,是因为用户与它们互动。

理想情况下,我们希望创建一个平衡的系统,能够利用内容派生和信号派生智能的优势。虽然本章主要聚焦于基于信号的众包智能,但本书的一个主要目标是展示如何平衡和结合这两种方法,以实现更优化的人工智能驱动搜索体验。

4.1.2 设置我们的产品和信号数据集(RetroTech)

在本书中,我们将使用各种数据集来探索不同的用例,但拥有一个一致的示例也是很有价值的,随着我们进展,可以以此为基础进行构建。我们将通过拥有一个强大的搜索用例,提供大量数据和用户互动,从而获益,我们将在本节中进行设置。

值得注意的是,本书中的大多数技术适用于几乎所有的搜索用例。决定何时使用特定技术的因素通常更多地取决于内容和信号的量和多样性,而不是具体的用例。

电子商务搜索为人工智能驱动的搜索技术提供了最具体的用例之一,同时也是最容易被读者理解的问题之一,因此我们创建了一个电子商务数据集来帮助我们探索这个领域:RetroTech数据集。

RETROTECH用例

在零售商之间激烈竞争的背景下,销售尖端电子产品、多媒体和技术产品,小型在线企业很难与之竞争。然而,一小部分新兴群体选择避开最新最先进的产品,而是回归几十年前的熟悉技术。RetroTech公司成立的目的就是满足这类独特消费群体的需求,提供那些在今天的货架上很难找到的复古硬件、软件和多媒体产品。

让我们加载RetroTech公司的数据集,以便开始学习文档和用户信号之间的关系,以及如何通过众包智能提高搜索相关性。

加载产品目录

RetroTech网站上有大约50,000种产品可供销售,我们需要将这些产品加载到我们的搜索引擎中。如果您已经构建了用于运行第三章示例的AI驱动搜索代码库,那么您的搜索引擎已经启动并运行。如果没有,构建和运行所有本书示例的说明可以在附录A中找到。

当您的搜索引擎启动后,接下来需要做的是下载本书附带的RetroTech数据集。该数据集包括两个CSV文件,一个包含所有RetroTech的产品,另一个包含RetroTech用户的一年信号数据。以下是产品目录数据集的几行,供您熟悉其格式。

列表4.1 探索RetroTech产品目录

"upc","name","manufacturer","short_description","long_description"
"096009010836","Fists of Bruce Lee - Dolby - DVD", , ,
"043396061965","The Professional - Widescreen Uncut - DVD", , ,
"085391862024","Pokemon the Movie: 2000 - DVD", , ,
"067003016025","Summerbreeze - CD","Nettwerk", ,
"731454813822","Back for the First Time [PA] - CD","Def Jam South", ,
"024543008200","Big Momma's House - Widescreen - DVD", , ,
"031398751823","Kids - DVD", , ,
"037628413929","20 Grandes Exitos - CD","Sony Discos Inc.", ,
"060768972223","Power Of Trinity (Box) - CD","Sanctuary Records", ,

可以看到,产品是通过UPC(统一产品代码)标识的,同时也有名称、制造商、简短描述(用于搜索结果中的预览)和长描述(用于产品详情页上的完整描述)。

由于我们尝试搜索产品,下一步是将它们发送到搜索引擎进行索引。为了启用对RetroTech产品目录的搜索,让我们运行以下代码,发送产品文档到搜索引擎。

列表4.2 将产品文档发送到搜索引擎

products_collection = engine.create_collection("products")
products_dataframe = load_dataframe("data/retrotech/products.csv")
products_collection.write(products_dataframe)

输出:

Wiping "products" collection
Creating "products" collection
Status: Success
Loading Products
Schema:
root
 |-- upc: long (nullable = true)
 |-- name: string (nullable = true)
 |-- manufacturer: string (nullable = true)
 |-- short_description: string (nullable = true)
 |-- long_description: string (nullable = true)

Successfully written 48194 documents

最后,为了验证这些文档是否已经索引并且可以搜索,让我们运行一个示例的关键词搜索。以下列表展示了搜索“ipod”(一个真正的经典设备)的示例。

列表4.3 在产品目录中运行搜索

def product_search_request(query, param_overrides={}):
  request = {"query": query,
             "query_fields": ["name", "manufacturer", "long_description"],
             "return_fields": ["upc", "name", "manufacturer",
                               "short_description", "score"],
             "limit": 5,
             "order_by": [("score", "desc"), ("upc", "asc")]}
  return request | param_overrides

query = "ipod"
request = product_search_request(query)
response = products_collection.search(**request)
display_product_search(query, response["docs"])

前面提到的ipod搜索结果如图4.1所示,证明我们的产品现在已经被索引并且可以进行搜索。遗憾的是,搜索结果的相关性相当差。

image.png

尽管搜索结果的排名质量还不是很好,但我们已经有了一个开箱即用的“关键词匹配”搜索引擎,可以开始进行改进。我们将以此为基础,并在接下来的书中逐步引入更智能的人工智能驱动的搜索功能。我们的下一步是引入信号数据。

加载信号数据

由于RetroTech运行在您的计算机上,没有实时用户在进行搜索、点击或以其他方式生成信号。因此,我们生成了一个数据集,近似于您在类似的真实世界数据集中可能期望的信号活动。

为了简化处理,我们将把信号存储在搜索引擎中,以便它们既可以在实时搜索场景中访问,也可以用于外部处理。运行以下代码将模拟并索引一些样本信号,我们可以在接下来的章节中使用这些信号。

列表4.4 索引用户信号数据集

signals_collection = engine.create_collection("signals")
signals_collection.write(from_csv("data/retrotech/signals.csv"))

在加载了RetroTech的产品和信号数据后,我们将很快开始探索如何使用信号数据来增强搜索相关性。让我们首先熟悉一下信号数据,以便我们理解信号在现实世界系统中的结构、使用和收集方式。

4.1.3 探索信号数据

不同类型的信号具有不同的属性,需要被记录。对于“查询”信号,我们想记录用户的关键词。对于“点击”信号,我们想记录被点击的文档,以及哪个查询导致了该点击。为了后续分析,我们还希望记录查询后用户返回并可能查看的文档。

为了使示例更具可扩展性并避免为每种新信号类型编写自定义代码,我们在本书中采用了一个通用的格式来表示信号。这个格式可能与您当前记录信号的方式有所不同,但只要您最终能够将信号映射到这种格式,本书中的所有代码都应该能够正常工作,无需针对特定用例进行修改。

我们在本书中使用的信号格式如下:

  • query_id—起源于该信号的查询信号的唯一ID
  • user—表示特定用户的标识符
  • type—信号的类型(例如“query”、“click”、“purchase”等)
  • target—该信号在此signal_time应用的内容
  • signal_time—信号发生的日期和时间

例如,假设一个用户执行了以下一系列操作:

  • 发出了“ipad”查询,并返回了三篇文档(doc1、doc2和doc3)。
  • 点击了doc1。
  • 又返回并点击了doc3。
  • 将doc3添加到购物车。
  • 又进行了“ipad cover”的查询,并返回了两篇文档(doc4和doc5)。
  • 点击了doc4。
  • 将doc4添加到购物车。
  • 购买了购物车中的商品(doc3和doc4)。

这些互动将生成如下表所示的信号。

表4.1 示例信号格式

query_idusertypetargetsignal_time
1u123queryipad2024-05-01-09:00:00
1u123resultsdoc1,doc2,doc32024-05-01-09:00:00
1u123clickdoc12024-05-01-09:00:10
1u123clickdoc32024-05-01-09:00:29
1u123add-to-cartdoc32024-05-01-09:03:40
2u123queryipad cover2024-05-01-09:04:00
2u123resultsdoc4,doc52024-05-01-09:04:00
2u123clickdoc42024-05-01-09:04:40
2u123add-to-cartdoc42024-05-01-09:05:50
1u123purchasedoc32024-05-01-09:07:15
2u123purchasedoc42024-05-01-09:07:15

关于信号格式,有几点需要注意:

  • “query”类型和“results”类型被拆分成了独立的信号。虽然它们在同一时刻发生,但将其拆分有助于保持表格结构的一致性,并避免为查询信号添加额外的结果列。如果用户点击了“下一页”链接或向下滚动页面查看其他结果,这种结构可以让我们创建一个新的信号,而不需要修改原始信号。
  • 每个信号都与最初发起内容互动的“query”信号的query_id相关联。query_id不仅仅是对用户输入的关键词的引用,而是指向特定的“query”信号,它标识了用户查询关键词的时间戳实例。由于相同查询关键词的结果会随时间变化,这使得我们可以更复杂地处理用户如何对特定的结果集做出反应。
  • 大多数信号类型的target包含一个项目,但“results”信号类型包含一个有序的文档列表。结果的顺序对我们在本书后面介绍的一些算法来说很重要,用来衡量相关性。因此,准确保留搜索结果的顺序非常重要。在这种情况下,target是一个有序的文档列表,而不是单一文档。
  • 结账操作为每个商品生成了单独的“purchase”信号,而不是只生成一个“checkout”信号。这样做是为了追踪每个购买是否源自不同的查询。如果需要,还可以添加一个“checkout”信号来跟踪交易,并可能列出两个购买作为target,但这对本书的需求来说是多余的。

通过这些原始信号作为构建块,我们现在可以开始思考如何将信号链接起来,开始了解我们的用户及其兴趣。在下一节中,我们将讨论如何在搜索平台中建模用户、会话和请求。

4.1.4 用户、会话和请求建模

在上一节中,我们研究了用户信号的结构,将其视为与原始查询相关联的独立交互列表。我们假设“用户”是一个具有唯一ID的个体,但如何识别和跟踪一个独特的用户呢?此外,一旦你确定了如何追踪独特用户,最好的方式是如何将他们的交互划分为会话,以便理解他们的上下文何时发生变化?

在网页搜索中,用户的概念可能非常灵活。如果你的搜索引擎有经过身份验证的(登录)用户,那么你已经有了一个内部的用户ID来追踪他们。然而,如果你的搜索引擎支持未经身份验证的访问或是公开可用的,那么你会有很多用户在没有正式用户ID的情况下进行搜索。这并不意味着你不能追踪他们;只是这需要对“用户”这一概念有一个更灵活的理解。一个统一的追踪标识符使我们能够将来自同一用户的多个信号联系起来,从而学习他们的互动模式。

如果我们将可追踪信息视为从最持久的用户表示到最不持久的用户表示的层次结构,它大致会是这样的:

  • 用户ID — 一个在多个设备间持久存在的唯一用户ID(已认证用户)
  • 设备ID — 一个在同一设备上的多个会话之间持久存在的唯一ID(例如设备ID或IP地址加设备指纹)
  • 浏览器ID — 一个在同一应用或浏览器中的多个会话间持久存在的唯一ID(持久的Cookie ID)
  • 会话ID — 一个在单个会话中持久存在的唯一ID(例如浏览器的隐身模式下的Cookie)
  • 请求ID — 仅在单个请求中持久存在的唯一ID(例如关闭了Cookie的浏览器)

在大多数现代搜索应用中,尤其是在大多数电子商务应用中,我们通常需要处理所有这些标识符。作为经验法则,你应该尽可能将用户与最持久的标识符相关联——即在列表中尽可能高的位置。请求ID和会话ID之间的链接,以及会话ID和浏览器ID之间的链接,都是通过用户的Cookie实现的,因此最终,浏览器ID(存储在Cookie中的持久唯一ID)是这些标识符的共同基础。

具体来说:

  • 如果用户启用了持久性Cookie,一个浏览器ID可以拥有多个会话ID,每个会话ID可以拥有多个请求ID。
  • 如果用户在每次会话后清除Cookie(例如使用隐身模式),每个浏览器ID只有一个会话ID,每个会话ID可以拥有多个请求ID。
  • 如果用户关闭了Cookie,则每个请求ID都会有一个新的会话ID和新的浏览器ID。

在构建搜索平台时,大多数组织没有正确规划和设计它们的信号追踪机制。如果它们无法将访问者的查询与后续操作关联起来,那么就很难最大化其AI驱动的搜索平台的能力。在某些情况下,有可能事后推导出缺失的信号追踪信息(例如通过使用时间戳将信号建模为可能的会话),但通常最好是事先设计系统,以更好地处理用户追踪,防止潜在的信息丢失。在下一节中,我们将讨论如何利用这些丰富的信号,通过一种被称为“反射智能”的过程来提高相关性。

4.2 引入反射智能

在上一节中,我们讨论了如何捕获用户在与搜索引擎互动时的信号。虽然这些信号本身有助于我们理解搜索引擎的使用情况,但它们也是构建模型的主要输入,这些模型可以持续从用户互动中学习,并使搜索引擎自我调整其相关性模型。在本节中,我们将通过反射智能的概念介绍这些自我调节模型的工作原理。

4.2.1 什么是反射智能?

想象你是一个五金店的员工。有人问你锤子在哪里,你告诉他“二号过道”。几分钟后,你看到同一个人从二号过道走到五号过道,手里没有锤子,然后他又从五号过道走出来,手里拿着一把锤子。第二天,另一个人问你锤子在哪,你再次告诉他“二号过道”,并且观察到几乎相同的行为模式。如果你没有识别出这个模式,并且没有调整你的建议以便为客户提供更好的体验,那你一定是个糟糕的员工。

不幸的是,大多数搜索引擎默认的运行方式就是这样——它们对于每个查询返回一组相对静态的文档,无论每个用户是谁,或者先前的用户如何反应这些文档列表。然而,通过应用机器学习来分析收集到的信号,我们可以了解用户的意图,并将这些知识反映到未来的搜索结果中,从而提高搜索的相关性。这个过程被称为反射智能。

反射智能的核心在于创建反馈循环,这些循环基于不断变化的用户互动持续学习并改进。图4.2展示了反射智能过程的高层次概述。

image.png

在图4.2中,一个用户(Alonzo)执行搜索,在搜索框中输入查询词“ipad”。一个查询信号被记录,其中包含显示给Alonzo的所有搜索结果列表。Alonzo随后查看了搜索结果列表并采取了两个动作:点击了一个文档(doc22),然后购买了该文档所代表的产品。这两个额外的动作被记录为附加信号。所有Alonzo的信号,以及来自其他所有用户的信号,可以被聚合并通过各种机器学习算法进行处理,以创建学习到的相关性模型。

这些学习到的相关性模型可能会提升某些查询的最热门结果,个性化每个用户的结果,甚至学习哪些文档属性在所有用户中通常最重要。这些模型还可以学习如何更好地理解用户查询,例如识别常见的拼写错误、短语、同义词、其他语言模式以及特定领域的术语。

一旦这些学习到的相关性模型生成出来,它们就可以被部署回生产环境的搜索引擎,并立即应用于提升未来查询的结果。然后这个过程再次开始,下一位用户执行搜索,看到(现在应该已改进的)搜索结果,并与这些结果进行互动。这个过程创造了一个自学习系统,随着每一次额外的用户互动,系统变得越来越聪明、越来越相关,并随着用户兴趣和内容的变化自动调整。

在接下来的几节中,我们将探讨几种反射智能模型的类别,包括信号提升(流行相关性)、协同过滤(个性化相关性)和学习排序(通用相关性)。我们将从最简单和最有效的之一开始:信号提升模型。

4.2.2 通过信号提升实现流行相关性

发送到搜索引擎的最流行查询通常也是从相关性角度最需要优化的查询。幸运的是,由于更流行的查询会生成更多的信号,我们通常可以聚合并提升每个查询中信号数量最多的文档的相关性。这种流行相关性被称为信号提升。它是反射智能中最简单的一种形式,也是提高最流行、查询量最大查询相关性的最有效方法之一。以下是一个未经信号提升处理的搜索示例,查询词为“ipad”,在我们的RetroTech搜索引擎中执行。

列表4.5 执行匹配“ipad”的产品关键词搜索

query = "ipad"
request = product_search_request(query)
response = products_collection.search(**request)
display_product_search(query, response["docs"])

如预期所示,这个查询返回了许多包含“ipad”关键词的文档,包含“ipad”的文档通常排名最高。图4.3展示了这个查询的结果。

image.png

虽然这些结果都在内容中多次包含了“ipad”这个词,但大多数用户可能会对这些结果感到失望,因为它们是配件,而不是搜索中聚焦的主要产品类型。这是仅通过文档文本进行排名时的一个显著限制。然而,对于非常流行的查询来说,很可能许多客户会重复运行相同的查询,并在令人沮丧的搜索结果中挣扎,直到找到他们真正想要的产品。我们可以将这些重复的搜索纳入一个反馈循环,基于新的信号不断更新信号提升模型,如图4.4所示。

image.png

一旦你的产品被索引并且你开始收集用户查询和文档交互的信号,实施信号提升的唯一额外步骤是聚合这些信号,然后将聚合后的信号作为提升项添加到你的查询或文档中。列表4.6演示了通过聚合信号来创建信号提升模型的简单方法。

辅助集合

辅助集合是与你的搜索引擎的主集合并排存放的附加集合,包含其他有用的数据来改善你的搜索应用。在我们的电子商务示例中,我们的主集合是产品集合。已经添加的信号集合可以被视为一个辅助集合。我们将再添加一个辅助集合 signals_boosting,在查询时用于增强我们的查询。在整本书中,我们将介绍许多其他辅助集合,用于存储我们生成的模型的输入和输出。

列表4.6 通过聚合信号创建信号提升模型

signals_collection = engine.get_collection("signals")
create_view_from_collection(signals_collection, "signals")  #1

signals_aggregation_query = """
SELECT q.target AS query, c.target AS doc, #2
COUNT(c.target) AS boost  #2
FROM signals c LEFT JOIN signals q ON c.query_id = q.query_id
WHERE c.type = 'click' AND q.type = 'query'
GROUP BY q.target, doc #2
ORDER BY boost DESC"""

dataframe = spark.sql(signals_aggregation_query)  #3
signals_boosting_collection = \  #4
  engine.create_collection("signals_boosting") #4
signals_boosting_collection.write(dataframe)  #4
  • #1 创建一个视图,使信号集合可以通过SQL查询
  • #2 统计每个文档在每个关键词下的点击总数
  • #3 执行信号聚合SQL查询
  • #4 将结果写入新的 signals_boosting 集合

列表4.6中最重要的部分是 signals_aggregation_query,它被定义为SQL查询以便于阅读。对于每个查询,我们将获取用户在搜索结果中点击的文档列表,并统计每个文档被点击的次数。通过根据每个查询的点击次数对文档进行排序,我们可以得到一个按受欢迎程度排序的文档列表。

这里的思路是,用户倾向于选择他们认为最相关的产品,因此如果我们对这些文档进行提升,我们可以期望我们的搜索结果变得更加相关。我们将在下一个列表中通过使用这些聚合计数作为信号提升来验证这一理论。让我们重新访问之前的“ipad”查询。

列表4.7 使用信号提升改进相关性的搜索

def search_for_boosts(query, collection, query_field="query"):
  boosts_request = {"query": query,
                    "query_fields": [query_field],
                    "return_fields": ["query", "doc", "boost"],
                    "limit": 10,
                    "order_by": [("boost", "desc")]}
  response = collection.search(**boosts_request)
  return response["docs"]

def create_boosts_query(boost_documents):
  print(f"Boost Documents: \n{boost_documents}")
  boosts = " ".join([f'"{b["doc"]}"^{b["boost"]}'
                     for b in boost_documents])
  print(f"\nBoost Query: \n{boosts}\n")
  return boosts

query = "ipad"
boost_docs = search_for_boosts(query, signals_boosting_collection)
boosts_query = create_boosts_query(boost_docs)
request = product_search_request(query)
request["query_boosts"] = boosts_query

response = products_collection.search(**request)
display_product_search(query, response["docs"])

提升文档:

[{"query": "ipad", "doc": "885909457588", "boost": 966}, {"query": "ipad", "doc": "885909457595", "boost": 205}, {"query": "ipad", "doc": "885909471812", "boost": 202}, {"query": "ipad", "doc": "886111287055", "boost": 109}, {"query": "ipad", "doc": "843404073153", "boost": 73}, {"query": "ipad", "doc": "635753493559", "boost": 62}, {"query": "ipad", "doc": "885909457601", "boost": 62}, {"query": "ipad", "doc": "885909472376", "boost": 61}, {"query": "ipad", "doc": "610839379408", "boost": 29}, {"query": "ipad", "doc": "884962753071", "boost": 28}]

提升查询:

"885909457588"^966 "885909457595"^205 "885909471812"^202 "886111287055"^109
"843404073153"^73 "635753493559"^62 "885909457601"^62 "885909472376"^61
"610839379408"^29 "884962753071"^28

列表4.7中的查询做了两件值得注意的事:

  1. 它查询了 signals_boosting 辅助集合中的按提升排名的文档,并将这些信号提升转化为另一个查询。
  2. 然后,它将该提升查询作为查询时间的提升项,传递给搜索引擎。在Solr(我们的默认搜索引擎)中,这内部转换为在搜索请求中添加一个提升参数 sum(1,query($boost_query)),将相关性得分乘以1(确保总是增加)加上 boost_query 的计算相关性得分。(如果你想复习如何通过函数和乘法提升影响排名,请参考第3.2节。)

如果你还记得图4.3,最初的“ipad”关键词搜索返回了大多数iPad配件,而不是实际的iPad设备。图4.5展示了应用信号提升后改进的搜索结果。

image.png

新的结果比仅使用关键词的结果要好得多。我们现在看到的产品是用户更有可能寻找的——iPads!你可以预期在搜索引擎中,其他大多数流行查询也会有类似的改进。当然,随着我们向下移动到流行产品列表中的其他项目,信号提升带来的相关性改进将开始下降,而在信号量不足的情况下,我们甚至可能会降低相关性。幸运的是,我们将在后续介绍许多其他技术来改善信号量不足的查询的相关性。

本节的目标是通过一个初步的具体示例,带领你实现一个端到端的反射智能模型。此实现中使用的信号聚合非常简单,尽管如此,结果自有其价值。在实现信号提升模型时,有许多需要考虑的因素和细节——是否在查询时间或索引时间进行提升,如何增加较新信号相对于较旧信号的权重,如何避免恶意用户通过生成虚假信号来提升特定产品在搜索结果中的排名,如何引入和融合来自不同来源的信号等等。我们将在第8章详细讨论这些话题。

现在让我们暂时离开流行相关性和信号提升,讨论几种其他类型的反射智能模型。

4.2.3 通过协同过滤实现个性化相关性

现在让我们看一下名为协同过滤的反射智能方法,我们将其归类为个性化相关性。流行相关性确定哪些结果通常是许多用户中最受欢迎的,而个性化相关性则专注于确定哪些项目对特定用户最相关。

协同过滤是利用一些用户的偏好观察来预测其他用户的偏好。这是推荐引擎中最常用的算法类型,也是许多网站上出现的常见“喜欢这个商品的用户也喜欢这些商品”推荐列表的来源。图4.6展示了协同过滤如何遵循我们在信号提升模型中看到的相同的反射智能反馈循环。

image.png

像信号提升一样,协同过滤也涉及一个持续的反馈循环。信号被收集,基于这些信号构建模型,使用这些模型生成推荐,然后这些推荐的互动再次被记录为附加信号。协同过滤方法通常生成一个用户-项目交互矩阵,将每个用户映射到每个项目(文档),用户与项目之间的关系强度基于正向交互(如点击、购买、评分等)的强度。

如果交互矩阵被充分填充,就可以从中推断出任何用户或项目的推荐。这是通过直接查找与同一项目有交互的其他用户,然后提升这些用户也与之互动的其他项目(类似于信号提升)。然而,如果用户-项目交互矩阵过于稀疏,则通常需要应用矩阵分解方法。

矩阵分解是将用户-项目交互矩阵分解为两个矩阵的过程:一个将用户映射到潜在特征(或因子),另一个将这些潜在因子映射到项目。这类似于我们在第3章中描述的降维方法,我们从使用多个精确关键词表示食品项目(一个包含倒排索引中每个单词特征的向量)转换为使用更少的有意义维度(一个包含八个特征的向量描述食品项目),并将数据压缩成这些维度。这种矩阵分解使得能够从有限的信号数据中提取出用户对项目的偏好,以及项目之间的相似度,通过将数据压缩到更少的、有意义的维度中,这些维度更好地概括了项目之间的相似性。

在协同过滤的矩阵分解上下文中,潜在因子代表了我们文档的属性,这些属性被学习为跨用户共享兴趣的重要指示器。通过根据这些因子匹配其他文档,我们利用众包来找到匹配相同共享兴趣的其他相似文档。

尽管协同过滤可以非常强大地学习用户的兴趣和品味,完全基于众包相关性,但它存在一个主要缺陷,称为冷启动问题。这种情况发生在返回结果依赖于信号的存在时,但从未生成过信号的新文档将不会被返回。这就创造了一个困境,新内容由于尚未生成任何信号(而信号是展示内容的必要条件),因此不太可能被展示给用户(而这恰恰是生成信号的前提)。在一定程度上,信号提升模型也展示了类似的问题,已经流行的文档往往会获得更高的提升,从而获得更多的信号,而未见过的文档继续未能获得信号提升。这一过程创造了一个自我强化的循环,可能导致搜索结果缺乏多样性。这个问题被称为展示偏差,我们将在第12章中展示如何克服它。

你还可以通过其他方式生成推荐,例如通过基于内容的推荐,我们将在下一章(第5.4.6节)探讨。然而,协同过滤的独特之处在于,它可以在不需要了解文档内容的情况下,学习用户对其他文档的偏好和品味。这是因为所有决策完全是通过观察用户与内容的互动,并根据这些观察来确定相似度的强度。我们将在第9章实施个性化搜索时,深入探讨协同过滤。

除了仅使用流行和个性化的相关性模型(这些模型在文档已经有信号时效果最好),搜索引擎还可以受益于更通用的相关性模型,这种模型可以应用于所有搜索和文档。这在很大程度上有助于解决冷启动问题。接下来,我们将探讨如何通过一种叫做“学习排序”的技术,将众包相关性进行泛化。

4.2.4 通过学习排序实现通用相关性

由于信号提升(流行相关性,第4.2.2节)和协同过滤(个性化相关性,第4.2.3节)仅适用于已经拥有信号的文档,因此在文档获得流量之前,许多查询无法从中受益。这时,学习排序作为一种通用相关性的方法就显得尤为重要。

学习排序(LTR),也称为机器学习排序,是构建和使用排名分类器的过程,该分类器可以评估任何文档与任何查询的匹配程度。你可以将排名分类器视为一个经过训练的相关性模型。与手动调整搜索提升和其他参数不同,LTR过程通过训练一个机器学习模型,能够理解文档的重要特征,然后对搜索结果进行适当评分。图4.7展示了实施LTR的总体流程。

image.png

在LTR系统中,和信号提升以及协同过滤一样,采用相同的高层次反射智能过程(参见图4.2)。不同之处在于,LTR可以使用相关性评判列表(将查询映射到理想排序文档集的映射)来自动训练一个相关性模型,然后该模型可以普遍应用于所有查询。你会发现,图4.7中“构建排名分类器”步骤的输出是一个相关性特征模型(如title_match_any_terms、is_known_category、popularity和content_age),这个模型会定期部署到生产环境的搜索引擎中,以增强搜索结果的排名。一个非常简单的机器学习排名模型中的特征可能是这样的可读格式,但并没有要求排名分类器必须是可解释的或可解释的,许多基于深度学习的高级排名分类器并非如此。

在图4.7中,请注意,实时用户流程从查询“ipad”开始。然后,初始搜索结果通过已部署的学习排序分类器进行处理,返回最终重新排序的搜索结果。由于排名分类器通常比传统的关键词排序相关性模型更智能,且使用了更复杂的排名参数,因此它通常太慢,无法用排名分类器对搜索引擎中的所有匹配文档进行评分。相反,LTR通常会使用一个初始的、更快的排名函数(如BM25)来找到前N个文档(通常是数百或数千个文档),然后仅对这部分文档运行排名分类器。也可以将排名分类器作为主要的相关性函数,而不是应用这种重新排序技术,但更常见的是看到采用重新排序的方法,因为它通常更快,同时仍然能够获得大致相同的结果。

LTR可以使用显式的相关性评判(由专家手动创建)或隐式评判(从用户信号中推导出来),或者两者的组合。我们将在第10至12章中介绍从显式和隐式评判列表中实现LTR的示例。

4.2.5 其他反射智能模型

除了深入探讨信号提升(第8章)、协同过滤(第9章)和学习排序(第10章),我们将在本书中探索许多其他种类的反射智能模型。在第6章中,我们将探讨如何通过挖掘用户查询自动学习特定领域的短语、常见拼写错误、同义词和相关术语;在第11至12章中,我们将探索从用户互动中自动学习相关性评判的方法,以便自动生成有趣的机器学习方法的训练数据。

一般来说,每个用户与内容之间的互动都会创建一个连接——图中的一个边——我们可以利用这个连接来理解新兴的关系并推导出更深刻的洞察。图4.8展示了通过探索这个互动图,我们可以学习到的一些不同关系。相同的输入信号数据可以通过不同的信号聚合和机器学习方法进行处理,来学习:

  • 用户与项目之间的相似性(用户-项目推荐)
  • 项目与其他项目之间的相似性(项目-项目推荐)
  • 基于特定属性的偏好,可以生成用户兴趣的画像
  • 查询与项目之间的相似性

image.png

我们将在接下来的章节中继续探讨这些技术,但值得记住的是,信号数据包含了大量潜在的洞察,通常与用户互动的文档内容提供的价值不相上下。反射智能和众包不仅限于我们描述的信号提升、协同过滤和学习排序技术,它们还可以从内容中提取,正如我们将在下一节讨论的那样。

4.2.6 从内容中进行众包

虽然我们通常认为众包是要求用户提供输入,但正如本章所示,隐式反馈在许多用户信号的聚合中,往往能提供与信号数据一样多甚至更多的价值。虽然本章完全聚焦于使用用户信号进行众包,但同样重要的是指出,内容本身也可以作为你AI驱动搜索平台的众包智能。

例如,如果你试图了解文档的总体质量,可以查看客户评论来生成产品评分,或查看产品是否被报告为恶意或垃圾信息。如果客户留下了评论,你可以对文本运行情感分析算法,标注评论是积极的、中立的还是消极的。根据检测到的情感,你可以相应地提升或惩罚源文档。这一过程本质上是从用户提交的内容中提取信号,因此它仍然是众包的一种形式,只不过是来自用户提供的其他内容。

我们提到,在第6章中我们将探讨如何挖掘用户信号,以自动学习特定领域的术语(短语、拼写错误、同义词等)。正如你可以利用用户查询和互动来学习这些术语,你还应该意识到,文档通常是由人写的,因此术语之间的非常相似的关系也会反映在写作内容中。我们将在下一章进一步探讨这些基于内容的关系。

最著名的搜索算法之一是Page Rank算法——这个突破性的算法最初使得Google成为最相关的网页搜索引擎。Page Rank超越了任何给定网页的文本,而是观察所有其他网页创建者的隐性行为,看看他们是如何链接到其他网页的。通过衡量传入和传出的链接,可以衡量网页的“质量”,假设网站更倾向于链接到更高质量、更具权威性的来源,而这些高质量的来源不太可能链接到低质量的来源。这种超越单一文档内容、将其与其他文档关联的理念——无论是通过它们之间的直接链接、用户评论或反馈、任何其他用户互动,还是不同文档中术语的使用方式——都极其强大。利用所有可用信息(无论是关于内容还是用户的)来构建一个高度相关的AI驱动搜索引擎,是一门艺术和科学。在第5章中,我们将探讨知识图谱的概念,以及如何利用文档之间隐含链接中嵌入的某些关系,来自动进一步加深领域理解。

总结

  • 内容、信号和模型(由内容和信号派生)是为AI驱动的搜索引擎提供“燃料”的三大主要来源,其中信号是众包相关性的主要来源。
  • 反射智能是创建学习反馈循环的过程,通过每次用户互动的改进并将学到的智能反映回去,从而持续提高未来结果的相关性。
  • 信号提升是一种“流行相关性”的形式,通常对查询量最大、最流行的查询产生最大影响。
  • 协同过滤是一种“个性化相关性”的形式,它可以通过用户与项目的互动模式来学习用户偏好或项目之间关系的强度,并基于这些学习到的关系推荐相似的项目。
  • 学习排序(LTR) 是一种“通用相关性”的形式,它是基于相关性评判列表(将查询映射到正确排序的文档)训练排名分类器的过程。LTR可以应用于排名所有文档,并避免冷启动问题。
  • 其他种类的反射智能存在,包括使用内容(而不仅仅是信号)进行众包相关性的技术。