通过数据驱动的查询优化提高搜索相关性

前段时间热播的《天才基本法》中,男女主,用贝叶斯网络来寻找事故发生的可能性最大的地点。在我们的进行搜索优化时,我们也可以用类似的方法来找到能使返回结果最相关的搜索参数的组合。

在构建全文搜索体验(例如FAQ搜索或Wiki搜索)时,有多种方法可以使用Elasticsearch Query DSL来应对挑战。对于全文搜索,我们的武器库中有很多可用的选项,从最简单的match查询到强大的intervals查询

同时,不仅仅是查询类型的选择,您还可以通过调整参数列表来获得优化。虽然 Elasticsearch 对查询参数使用了良好的默认值,但为了提高相关性,我们可以根据基础索引(语料库)中的文档和用户搜索时使用的特定类型的查询字符串来改进这些参数的使用。为了完成这项任务,这篇文章将引导您了解按照结构化和客观的过程优化查询的步骤和技术。

在我们开始之前,让我们考虑这个 multi_match查询示例,它在文档的两个字段上搜索查询字符串:

GET /_search 
{ 
  "query": { 
    "multi_match": { 
      "query": "this is a test", 
      "fields": [ "subject^3", "message" ] 
    } 
  } 
}

在这里,我们使用field boost参数来指定在subject字段上匹配的分数应该被提升并乘以三倍。我们这样做是为了提高查询的整体相关性——对查询最有意义的文档应该尽可能在结果中排序最高。但是我们如何为boost选择一个合适的值呢?当我们面对的不仅是两个字段,而是十几个字段时,又该如何设置 boost 参数?

相关性调整的过程是去理解这些不同参数的影响的过程。在所有你可以调整和调节的参数中,你应该尝试哪些参数,用哪些值,以何种顺序?虽然不应该忽视对评分和相关性调整的深刻理解,但我们如何才能采取更有原则的方法来优化我们的查询?我们可以使用来自用户点击的数据或明确的反馈(例如,对一个结果点赞或者踩)来推动调整查询参数以提高搜索相关性吗?我们可以,所以让我们开始行动吧!

为了配合这篇博文,我们整理了一些示例代码和 Jupyter notebook,引导您完成使用下面概述的技术优化查询的步骤。首先阅读这篇文章,然后转到代码并查看所有正在运行的部分。在撰写本文时,我们使用的是 Elasticsearch 7.10,并且一切都应该适用于任何 Elasticsearch 许可证。

介绍 MS MARCO

为了更好地解释查询参数调整的原理和效果,我们将使用一个名为MS MARCO的公共数据集。MS MARCO 数据集是由Microsoft Research策划的大型数据集,其中包含从网页上抓取的 320 万个文档和来自真实Bing 网络搜索查询的超过 350,000 个查询。MS MARCO 有一些子数据集和相关挑战,因此我们将特别关注文档排名这篇文章中的挑战,因为它最适合传统的搜索体验。挑战在于有效地为 MS MARCO 数据集中的一组选定查询提供最佳相关性排名。该挑战对公众开放,任何研究人员或从业者都可以通过提交自己的尝试来参与,以便为一组查询提出最佳的相关性排名。在这篇文章的后面,您将看到我们使用此处概述的技术取得的成绩。对于当前提交的排名,您可以查看官方排行榜

数据集和工具

现在我们有了一个通过调整查询参数来提高相关性的粗略目标,让我们看看我们将要使用的工具和数据集。首先,让我们对我们想要实现的目标和我们需要的数据进行更正式的描述。

  • 输入:
    • 语料库(索引中的文档)
    • 带参数的搜索查询
    • 已标记的相关数据集
    • 衡量相关性的指标
  • 输出:能使所选指标最大化的查询参数值(query parameter)

已标记的相关数据集

现在你可能在想,"等等,等等,到底什么是已标记的相关数据集,我在哪里可以得到一个?" 简而言之,一个已标记的相关数据集是一组查询结果,这些结果都被贴上了相关性等级的标签。下面是一个非常小的数据集的例子,里面只有一个查询:

{
  "id": "query1",
  "value": "tuning Elasticsearch for relevance",
  "results": [
    { "rank": 1, "id": "doc2", "label_id": 2, "label": "relevant" },
    { "rank": 2, "id": "doc1", "label_id": 3, "label": "very relevant" },
    { "rank": 3, "id": "doc8", "label_id": 0, "label": "not relevant" },
    { "rank": 4, "id": "doc7", "label_id": 1, "label": "related" },
    { "rank": 5, "id": "doc3", "label_id": 3, "label": "very relevant" }
  ]
}

在这个例子中,我们每个文档标注了其相关性的强弱。(3) very relevant, (2) relevant, (1) related, 以及 (0) not relevant。这些标签是任意的,你可以选择不同的尺度,但上面的四个标签是很常见的。获得这些标签的一个方法是来自于人类的评判。一群人可以查看你的搜索查询记录,并为每个结果提供一个标签。这可能相当耗费时间,所以许多人选择直接从用户那里收集这些数据。他们记录用户的点击,并使用点击模型将点击活动转换成相关性标签

这个过程的细节远远超出了这篇博文的范围,但可以看看关于点击模型的介绍和研究(附录:1,2,3)。一个好的开始是为了分析而收集的点击事件,一旦你有足够的用户行为数据,再看点击模型。请查另一篇博客文章使用 Elasticsearch 和 Elastic Stack 分析在线搜索相关性指标以了解更多信息。

MS MARCO 文档数据集

正如在介绍中所讨论的,为了演示的目的,我们将使用MS MARCO排名挑战的文档和相关数据集,其中包含我们需要的一切:语料库和已标记的相关数据集。

MS MARCO 最初是为了对问答 (Q&A) 系统进行基准测试而构建的,数据集中的所有查询实际上都是某种形式的问题。例如,您不会找到任何看起来像“rules Champions League”这样的典型关键字查询的查询。相反,您会看到查询字符串,例如“欧洲冠军联赛的足球规则是什么?”(“What are the rules of football for the UEFA Champions League?”)。由于这是一个问答数据集,因此已标记的相关性数据集看起来也有些不同。由于问题通常只有一个最佳答案,因此结果要么是有“relevant”标签 ( 1),要么啥都没有。文档非常简单,只包含三个字段:urltitlebody。这是文档的示例(片段):

编号: D2286643
网址: http://www.answers.com/Q/Why_is_the_Manhattan_Proj...
标题:为什么曼哈顿计划很重要?
正文: Answers.com® Wiki Answers® 类别历史、政治与社会历史战争和军事历史第二次世界大战 <snip> 这是战争中第二个最秘密的项目(密码工作是第一个)。这是战争中最高优先级的项目,代号“银牌”被分配给它,并压倒了所有其他战时优先级。它花费了 2,000,000,000 美元。<snip> 编辑 Mike M 656 贡献在二战中美国的回答 为什么曼哈顿项目被命名为曼哈顿项目?曼哈顿项目的第一部分参与了位于曼哈顿的一座建筑的地下室。编辑 Pat Shea 3,370 在战争和军事历史中回答的贡献 曼哈顿项目的秘密项目是什么?曼哈顿计划是二战项目的代号,目的是制造第一颗核武器——原子弹。

如您所见,文档已被处理过,HTML 标记已被删除,但它们有时可能包含各种元数据。正如我们在上面看到的,对于用户生成的内容尤其如此。

衡量搜索相关性

我们在这篇博文中的目标是建立一种系统化的方法来调整查询参数,以提高我们搜索结果的相关性。为了衡量我们在实现这一目标方面的表现如何,我们需要定义一个指标来捕捉给定搜索查询的结果满足用户需求的程度。换句话说,我们需要一种衡量相关性的方法。幸运的是,我们已经在 Elasticsearch 中为此提供了一个名为Rank Evaluation API的工具。该 API 允许我们获取上述数据集并计算许多搜索相关性指标之一。

为了实现这一点,API 在已标记相关性数据集上的执行查询,并将每个查询的结果与标记 结果进行比较,以计算相关性指标,例如精确度召回率平均倒数排名 (_mean reciprocal rank,_MRR)。在我们的案例中,MS MARCO 文档排名挑战已经选择了前100 个结果 (MRR@100)的平均倒数排名(MRR ) 作为相关性指标。这对于问答数据集是有意义的,因为 MRR 只关心结果集中第一个正确答案的排名。它用排名的倒数(1 / rank) 进行计算,并在所有查询中平均它们。(即已标记的文档,在返回结果中是第一个结果则分数为 1 ,是第二个匹配则分数为 0.5,第 n 个匹配则分数为 1/n,如果没有匹配的句子分数为0。最终的分数为所有得分之和)

MRR formula

通过下图,我们可获得一个形象的认识:

MRR 计算示例

搜索模板

现在我们已经确定了如何借助 Rank Evaluation API 来衡量相关性,接下来,我们需要看看如何修改查询参数以允许我们尝试不同的值。回想一下介绍中的基本示例,我们如何在字段 multi_match上设置subject字段的boost值:

GET /_search 
{ 
  "query": { 
    "multi_match": { 
      "query": "this is a test", 
      "fields": [ "subject^3", "message" ] 
    } 
  } 
}

当我们使用Rank Evaluation API时,我们指定指标、已标记的相关性数据集,以及可选的用于每个查询的搜索模板。下面描述的方法实际上非常强大,因为我们可以依赖搜索模板。实际上,我们可以将任何我们可以在搜索模板中参数化的东西变成我们可以优化的参数。这是另一个multi_match查询,但使用 MS MARCO 文档数据集中的真实字段并为每个查询设置一个 boost 参数:

{ 
  "query": { 
    "multi_match": { 
      "query": "{{query_string}}", 
      "fields": [ 
        "url^{{url_boost}}", 
        "title^{{title_boost}}", 
        "body^{{body_boost}}" 
      ] 
    } 
  } 
}

当我们运行 Rank Evaluation API 时,query_string将被替换,而每次我们想要测试新参数值时都会设置这些boost参数。同样的,如果您使用具有不同参数的查询,例如 tie_breaker,则可以使用相同的模板来修改参数。(查看搜索模板文档以获取更多详细信息)

参数优化

接下来,我们会将所有这些部分放在一起。我们已经看到了每个必要的组件:

  • 语料库
  • 已标记的数据集
  • 衡量相关性的指标
  • 带参数的搜索模板

对于本示例,我们将使用 Python 脚本将所有这些拼接在一起,以向Rank Evaluation API 提交请求并编排工作流程。工作流程非常简单:向Rank Evaluation API 提交带有参数值的调用以进行尝试,获取指标分数 (MRR@100),记录产生该指标分数的参数值,并通过选择新的参数值进行迭代来尝试。最后,我们返回产生最佳度量分数的参数值。此工作流程是一个参数优化过程,我们在其中寻找能最大化指标分数的一组参数。

参数优化工作流程

在图 3 的工作流程中,我们可以看到我们所有数据集和工具所处的位置,Rank Evaluation API 占据中心位置,以运行查询并通过提供的指标衡量相关性。我们唯一没有涉及的是如何在每次迭代中选择要尝试的参数值。在接下来的部分中,我们将讨论选择参数值的两种不同方法:网格搜索贝叶斯优化。不过,在讨论方法之前,我们需要介绍参数空间的概念

参数空间

当我们谈论参数(例如,来自我们上面的示例的:url_boosttitle_boostbody_boost)以及它们可以采用的可能值时,我们使用术语参数空间。参数空间是指所有参数组合的可能值的世界。在参数优化的背景下(挑选出能使某些指标或分数最大化的参数值),搜索参数是我们的自变量,相关性指标是我们的因变量。让我们举一个简单的例子,即一个大小为2的参数空间或一个二维参数空间,参数为x和y。由于我们只有两个维度,我们可以很容易地将这个参数空间绘制成三维图。在这种情况下,我们使用一个等高线图,其中第三个维度,即颜色梯度,代表相关性指标分数。颜色梯度从蓝色是最低的指标得分,黄色是最高的指标得分(越高越好)

参数空间等高线图

网格搜索

为了生成我们在上面看到的图,我们使用两个参数的所有可能排列调用排名评估 API(Rank Evaluation API),并每次存储返回的相关性指标分数。在伪代码中,它可能看起来像这样:

for x in [1, 2, 3, 4, 5]: 
    for y in [1, 2, 3, 4, 5]: 
       params = {‘x’: x, ‘y’: y} 
       score = rank_eval(search_template, relevance_data, 
                         metric, params)

然后,这个双循环将生成一个相关性指标分数表:

    1  2  3  4  5 
5:  0  5  7  3  0 
4:  5  7  9  7  5 
3:  0  7  9  10 4 
2:  0  5  7  9  0 
1:  0  2  6  5  2

如图 4 所示的参数空间,其中xy是大小为 5 的范围,这意味着我们调用排名评估 API 25 ( 5*5) 次。如果我们将每个参数范围的大小加倍为 10,我们将得到大小为 100 ( 10*10) 的参数空间。如果我们增加参数的数量,比如 z 维度,但保持范围相同,我们会得到更多的排名评估 API 执行次数 : 125次 ( 5*5*5)。

当参数空间很小时,网格搜索是一个不错的选择,因为它很详尽——它会测试所有可能的组合。然而,随着对具有高基的参数空,该方法对排名评估 API 的调用次数有时呈指数增长,网格搜索变得不切实际,因为它可能会将优化查询所需的时间增加到数小时甚至数天。请记住,在调用排名评估 API 时,它将执行我们数据集中的所有查询。这可能需要在每次调用时运行成百上千个查询,对于大型语料库或复杂的搜索查询,即使在大型 Elasticsearch 集群上也可能非常耗时。

贝叶斯优化

一种计算效率更高的参数优化方法是贝叶斯优化。贝叶斯优化不是像在网格搜索中那样尝试所有可能的参数值组合,而是根据之前的相关性指标分数来决定接下来要尝试哪些参数值。贝叶斯优化将寻找尚未看到但看起来可能包含更好相关性指标分数的参数空间区域。以下面的参数空间为例。

带有测量参数标记的参数空间等高线图

在上图中,我们随机放置了 10 个黑色X标记。红色X标记了参数空间中具有最大度量分数的点。基于随机的黑色X标记,我们已经可以对参数空间有所了解。左下角和右下角的X标记看起来不是很有希望的区域,可能不值得在该区域测试更多参数值。如果我们查看参数空间的顶部,我们可以看到一些具有更高度量分数的点。我们在这四个方面看到了特别高的价值X标记在中间,这看起来是一个更有希望的区域,可以在其中找到我们的最大度量分数。贝叶斯优化将使用这些初始随机点以及它尝试的任何后续点,并应用一些统计数据来选择下一个要尝试的参数值。统计数据为我们工作——感谢贝叶斯

结果

使用这里概述的技术,并基于一系列对各种分析器、查询类型和优化的评估,我们在MS MARCO文档排名的挑战上比基线、未优化的查询有了一些改进。所有实验的完整细节和解释可以在我们分享的的Jupyter notebook中找到,但下面你可以看到我们的结果总结。我们看到,从简单的、未经优化的查询,到通过参数优化,获得具有更高的MRR@100分数的查询(越高越好,每个notebook的最佳分数都以红色标出)。这告诉我们,我们确实可以利用数据和有原则的方法,通过优化查询参数来提高搜索的相关性!

参考笔记本

实验

MRR@100

0 - Analyzers

默认分析器,组合每个match字段

0.2403

自定义分析器,组合每个match字段

0.2504

默认分析器,multi_match cross_fields(默认参数)

0.2475

自定义分析器,multi_match cross_fields(默认参数)

0.2683

默认分析器,multi_match best_fields(默认参数)

0.2714

自定义分析器,multi_match best_fields(默认参数)

0.2873

1 - Query tuning

multi_match cross_fields基线:默认参数

0.2683

multi_match cross_fields调整(逐步)tie_breaker:,minimum_should_match

0.2841

multi_match cross_fields调整(逐步):所有参数

0.3007

multi_match cross_fields调整(多合一 v1):所有参数

0.2945

multi_match cross_fields调优(一体机 v2,精炼参数空间):所有参数

0.2993

multi_match cross_fields调整(多合一 v3,随机):所有参数

0.2966

2 - Query tuning - best_fields

multi_match best_fields基线:默认参数

0.2873

multi_match best_fields调谐(多合一):所有参数

0.3079

更新(2020 年 11 月 25 日):我们的正式提交已在,我们在评估查询集(仅用于排行榜排名)上获得了 MRR@100为0.268的分数。这为我们提供了排行榜上非神经(不使用深度学习)排名器的最佳分数!敬请期待更多的更新。更新(2021 年 1 月 20 日):我们对第一次提交进行了改进,增加了一种称为doc2query (T5)的技术,以及其他一些较小的修复和改进。通过新的提交,我们在 MRR@100的评估查询集上取得了这比我们之前的提交提高了+0.032!分数:0.300 。 更多细节可以在项目自述文件和随附的 Jupyter 笔记本。

成功指南

我们现在已经看到了两种优化查询的方法,以及我们可以在 MS MARCO 文档排名挑战中取得什么样的结果。为了帮助您成功优化自己的查询,请牢记以下提示和一般准则。

  • 由于这些方法是数据驱动的,因此拥有足够的质量数据非常重要。数据与方法同样重要。“足够”通常意味着至少有数百个带有标记结果的查询。“质量”意味着数据应该是准确的,并且可以代表您尝试改进和优化的查询类型。
  • 这并不能完全替代手动相关性调整:调试分数、构建良好的分析器、了解您的用户及其信息需求等。
  • 贝叶斯优化对其自身的参数很敏感。观察您需要多少次总迭代,以及用多少次随机初始化来作为种子。如果你有一个大的参数空间,你应该考虑用分步的方法来分解。
  • 小心使用大参数空间的过度拟合。考虑交叉验证以帮助纠正此问题,但请注意,您现在需要自己在 Python 中执行此操作。
  • 将为特定的语料库和查询集调整参数。除非其他语料库和查询集的一般统计数据足够相似,否则它们可能不会转移。这也可能意味着您需要定期重新调整以保持最佳参数。

结论

这篇博文中有很多内容,我们希望您能一直关注我们直到现在!代码示例和 Jupyter 笔记本非常具体地展示了如何通过和调整查询。原理不仅限于查询参数,因此还有一个示例笔记本展示了如何调整BM25 参数。我们希望您有机会查看这些示例并自己进行一些实验。我们在使用隔离的大型Elastic Cloud集群方面取得了最大的成功,我们可以在需要时启动和关闭这些集群。现在继续前进,使用您自己的数据来优化您的查询!不要忘记将您的问题或成功案例带到我们的论坛

引用

1 单击Aleksandr Chuklin、Ilya Markov 和 Maarten de Rijke 的 Web 搜索模型

2 转换模型: Doug Turnbull 构建学习以对训练数据进行排名

3 Olivier Chapelle 用于 Web 搜索排名的动态贝叶斯网络点击模型

4我们在单个排名阶段对所有文档进行排名,然后选择前 100 名进行 MRR。这被认为是一种“完全排名”的方法,而不是“重新排名”,它只尝试从预先设定的结果列表中重新排名前 1,000 个候选文档。

5这些结果是截至本文发布之日的最新结果,但如果我们继续尝试新技术,项目README将包含最新结果。

本站文章资源均来源自网络,除非特别声明,否则均不代表站方观点,并仅供查阅,不作为任何参考依据!
如有侵权请及时跟我们联系,本站将及时删除!
如遇版权问题,请查看 本站版权声明
THE END
分享
二维码
海报
通过数据驱动的查询优化提高搜索相关性
在构建全文搜索体验(例如FAQ搜索或Wiki搜索)时,有多种方法可以使用Elasticsearch Query DSL来应对挑战。对于全文搜索,我们的武器库中有...
<<上一篇
下一篇>>