龙空技术网

AI 驱动的搜索:10 学习对广义搜索相关性进行排名

启辰8 356

前言:

现在大家对“查找算法的比较实验总结”大体比较重视,大家都需要知道一些“查找算法的比较实验总结”的相关知识。那么小编也在网络上搜集了一些有关“查找算法的比较实验总结””的相关文章,希望小伙伴们能喜欢,同学们快快来学习一下吧!

本章涵盖使用机器学习构建通用搜索系统使用机器学习模型在搜索引擎内排名学习排名与其他机器学习方法有何不同构建强大且可推广的排名模型

这是一个随机的星期二。您查看您的搜索日志。搜索范围从沮丧的跑步者polar m430 running watch charger到忧心忡忡的忧郁症患者,weird bump on nose - cancer?再到好奇的电影迷william shatner first film。尽管许多查询都是一次性的,但您知道每个用户都期望令人惊叹的搜索结果。

你感到绝望。您知道,许多查询字符串本身非常罕见。您几乎没有点击数据来了解与这些搜索相关的内容。每一天都变得更具挑战性:趋势、用例、产品、用户界面,甚至语言都在不断发展。当用户似乎不断地用新的搜索方式给我们带来惊喜时,谁能希望建立令人惊叹的搜索呢?

不绝望,还有希望!在本章中,我们介绍通用搜索系统。泛化搜索学习驱动相关性的潜在模式。我们没有记住标题为“Zits:鼻子上的肿块”的文章是查询的答案weird bump on nose - cancer?,而是观察潜在的模式 - 强烈的标题匹配对应于高相关概率。如果我们可以学习这些模式,将它们编码到模型中,那么我们甚至可以为我们从未见过的搜索查询提供相关结果。

本章探讨学习排名(LTR):一种使用机器学习创建广义相关性排名模型的技术。我们将使用搜索引擎准备、训练和搜索 LTR 模型。

10.1 什么是学习排名?

让我们来探讨一下 LTR 到底做了什么。我们将了解 LTR 如何通过查找预测相关性的模式来创建广义相关性。然后我们将探索构建模型的更多具体细节。

回想一下第 3 章中的手动相关性调整。我们观察与相关结果相对应的因素,并以数学方式将这些因素组合成排名函数。排名函数返回一个相关性分数,该分数对结果进行排序,使其尽可能接近我们的理想排名。

例如,考虑一个电影搜索引擎,其文档如清单10.1所示:

清单 10.1。电影《社交网络》的文档显示了在排名函数中使用的潜在有用字段。

{'title': ['The Social Network'], 'overview': ['On a fall night in 2003, Harvard undergrad and computer   [CA]programming genius Mark Zuckerberg sits down at his computer and   [CA]heatedly begins working on a new idea. In a fury of blogging and   [CA]programming, what begins in his dorm room as a small site among   [CA]friends soon becomes a global social network and a revolution in   [CA]communication. A mere six years and 500 million friends later,   [CA]Mark Zuckerberg is the youngest billionaire in history... but for   [CA]this entrepreneur, success leads to both personal and legal   [CA]complications.'], 'tagline': ["You don't get to 500 million friends without making a few   [CA]enemies."], 'release_year': 2010}

本文档来自 TheMovieDB (tmdb) 语料库 ( ),我们将在本章中使用它。如果您希望遵循本章的代码,请使用本章的第一个笔记本来列出 xy

通过无休止的迭代和调整,我们可能会得到一个通用的电影排名函数,如清单 10.2所示。

清单 10.2。结合标题、概述、发布年份权重作为搜索关键词的手动排名功能

q=title:(${keywords})^10 overview:(${keywords})^20 {!func}release_year^0.01

手动优化通用排名以处理许多查询需要付出努力。这种优化非常适合机器学习。

这就是 LTR 的用武之地。学习排名(LTR)采用我们提出的相关因素,并学习最佳排名函数。学习排名有多种形式:从一组简单的线性权重(如此处的提升)到复杂的深度学习模型。

为了掌握诀窍,我们将在本章中构建一个简单的 LTR 模型。我们将在类似于上面的评分函数中找到title、overview、 和的最佳权重。release_year然而,通过这个相对简单的任务,我们将看到开发学习排名解决方案的完整生命周期。

10.1.1 在现实世界中实现排名学习

当我们继续在高层次上定义 LTR 时,让我们快速阐明 LTR 在搜索系统整体图中的位置。然后我们可以查看构建 LTR 模型所需的数据类型。

我们专注于为生产搜索系统构建 LTR,这与研究环境有很大不同。我们不仅需要相关结果,还需要通过主流、易于理解的搜索技术以适当的速度返回结果。

因此,我们的示例重点关注使用 Solr 的 Learning to Rank 插件构建 Learning to Rank。该插件使用搜索引擎中的模型重新排名,提高性能并扩展学习以针对真正的“大数据”问题进行排名。不过,这些课程超出了 Solr 的范围 - Elasticsearch 还有一个社区提供的插件 ( )。Elasticsearch 插件具有几乎相同的概念,其他搜索系统将倾向于遵循本章中概述的相同一般步骤。

图 10.1 概述了开发实用的排序学习解决方案的工作流程。

图 10.1。LTR 系统将我们的训练数据(判断列表)转换为概括相关性排名的模型。

在图 10.2 中,您可能会注意到 LTR 和传统的基于机器学习的分类或回归系统之间的相似之处。但也正是例外才让它变得有趣。表 10.1 映射了传统机器学习目标和排序学习之间的定义。

表 10.1。经典机器学习与学习排名。

概念

传统机器学习

学习排名

训练数据

模型应尝试预测的一组历史或“真实”示例。过去几天的 IE 股价,例如 2021 年 6 月 6 日“Apple”的股价为 125 美元

判断列表判断只是将文档标记为与查询相关或不相关。1在图 10.2 中,对于查询,“Return of the Jedi”被标记为相关(等级为)star wars

特征

我们可以使用这些数据来预测训练数据。IE Apple 拥有 147,000 名员工,收入达 900 亿美元

用于使相关结果排名高于不相关结果的数据,理想情况下是搜索引擎可以快速计算的值。我们的功能是像清单 10.2title:(${keywords})中的搜索查询

模型

以特征作为输入进行预测的算法。鉴于 Apple 在 2021 年 7 月 6 日拥有 157,000 名员工,收入为 950 亿美元,该模型可能预测该日期的股价为 135 美元

将排名功能(搜索查询)组合在一起,为每个潜在的搜索结果分配相关性分数。结果按分数降序排序,希望将更相关的结果放在第一位

本章按照图 10.2 中的步骤来训练 LTR 模型,如下更深入地概述:

收集判断:我们从点击或其他来源得出判断。我们将在第 11 章中深入讨论这一步骤。特征记录:训练模型时,我们必须将判断与特征结合起来,才能看到整体模式。此步骤要求我们要求搜索引擎存储和计算表示特征的查询。转换为传统机器学习问题:您会发现大多数 LTR 实际上是将排名任务转换为看起来更像表 10.1 中的“传统机器学习”列的内容训练和评估模型:在这里,我们构建模型并确认它确实是可概括的,因此对于它尚未见过的查询将表现良好。存储模型:我们将模型上传到我们的搜索基础设施,告诉搜索引擎使用哪些功能作为输入,并允许用户在搜索中使用它使用模型搜索:我们终于可以使用模型执行搜索了!

本章的其余部分将详细介绍每个步骤,以构建我们的第一个 LTR 实现。让我们开始吧!

10.2 步骤1:判断列表,从训练数据开始

您已经了解了 LTR 的高层次含义,让我们开始深入了解细节。在实现LTR之前,我们首先要了解用于训练LTR模型的数据:判断列表。

判断列表是相关性标签或等级的列表,每个标签或等级都指示文档与查询的相关性。成绩可以有多种形式。现在我们将坚持简单的二元判断- 0 表示不相关文档,1 表示相关文档。

使用Judgment本书代码提供的类,我们social network通过创建Judgment以下内容将“社交网络”标记为与查询相关:

from ltr.judgments import JudgmentJudgment(grade=1, keywords='social network', doc_id=37799)

查看多个查询会更有趣。在清单 10.3中,我们有social network和star wars作为两个不同的查询,电影被评级为相关或不相关。

清单 10.3。social network为查询标记电影相关(等级 = 1)或不相关(等级 = 0)star wars

mini_judg_list=[    # for 'social network' query    Judgment(grade=1, keywords='social network', doc_id='37799'),    [CA]# The Social Network    Judgment(grade=0, keywords='social network', doc_id='267752'),    [CA]# #chicagoGirl    Judgment(grade=0, keywords='social network', doc_id='38408'),    [CA]# Life As We Know It    Judgment(grade=0, keywords='social network', doc_id='28303'),    [CA]# The Cheyenne Social Club    # for 'star wars' query    Judgment(grade=1, keywords='star wars', doc_id='11'),    [CA]# Star Wars, A New Hope    Judgment(grade=1, keywords='star wars', doc_id='1892'),    [CA]# Return of the Jedi    Judgment(grade=0, keywords='star wars', doc_id='54138'),    [CA]# Star Trek Into Darkness    Judgment(grade=0, keywords='star wars', doc_id='85783'),    [CA]# The Star    Judgment(grade=0, keywords='star wars', doc_id='325553'),    [CA]# Battlestar Galactica]

您可以看到,在清单 10.3中,我们将“Star Trek”和“Battlestar Galatica”标记为与 不相关star wars,但将“Return Of The Jedi”标记为相关。

您可能会问自己 - 这些成绩是从哪里来的!?由电影专家手工标记?基于用户点击?好问题!根据用户与搜索结果的交互创建良好的训练集对于让 LTR 正常运行至关重要。为了批量获取训练数据,我们通常使用称为点击模型的算法从点击流量中获取这些标签。由于这一步非常基础,因此我们用第 11 章的全部内容来深入探讨该主题。

每个判断还有一个features向量,可以用来训练模型。向量中的第一个特征features可以对应于titleBM25 分数,第二个特征对应于overviewBM25 分数,依此类推。然而,我们还没有填充features向量,因此如果您尝试检查它们,它们目前将为空:

In: mini_judg_list[0].featuresOut: []

让我们利用搜索引擎来收集一些特征吧!

10.3 步骤 2 - 特征记录和工程

有了我们的训练数据,我们现在需要了解什么可以预测相关性。例如,通过注意“我们判断中的相关结果对应于强标题匹配”之类的模式。确切地说,如何定义“标题匹配”就是特征工程的全部内容。在本节中,您将了解特征到底是什么,以及如何使用现代搜索引擎从语料库中设计和提取这些特征。

就 LTR 而言,特征是文档、查询或查询-文档关系的某些数字属性。特征是我们用来构建排名函数的数学构建块。您已经看到了具有清单 10.2中的功能的手动排名功能:字段中关键字的分数title就是这样的功能之一。release_year和关键字分数也是如此overview。

q=title:(${keywords})^10 overview:(${keywords})^20 {!func}release_year^0.01

当然,您最终使用的功能可能更复杂或特定于领域,例如求职中的通勤距离,或者查询和文档之间的某些知识图关系。当用户搜索时您可以相对快速地计算的任何内容都可能是一个合理的功能。

特征记录采用判断列表并计算每个标记的查询-文档对的特征。如果我们计算查询的清单 10.2中每个组件的值,social network我们将得到类似于表 10.2 的结果。

表 10.2。social network为相关(等级=1)/不相关(等级=0)文档的关键字记录的特征

年级

电影

title:(${keywords})

overview:(${keywords})

{!func}release_year

1

社交网络

8.243603

3.8143613

2010.0

0

#芝加哥女孩

0.0

6.0172443

2013.0

0

我们所知道的生活

0.0

4.353118

2010.0

0

夏延社交俱乐部

3.4286604

3.1086721

1970.0

机器学习算法可能会检查表 10.2 中的特征值并收敛到良好的排名函数。仅从表 10.2 中的数据来看,这种算法似乎可能会产生一个排名函数,该函数的特征权重较高,title而其他特征的权重较低。

然而,在讨论算法之前,我们需要检查生产搜索系统中的特征日志工作流程。

10.3.1 在现代搜索引擎中存储特征

支持 LTR 的现代搜索引擎(例如 Solr 和 Elasticsearch)可以帮助我们存储、管理和提取特征。让我们具体研究一下 Solr 如何执行此任务。

Solr LTR 跟踪特征存储中记录的特征- 命名特征列表。至关重要的是,我们以与搜索引擎执行模型的方式一致的方式记录训练特征。LTR 插件的大部分工作是帮助存储和管理功能并保持事物的一致性。

如清单 10.4所示,在 Solr 中创建特征存储是一个简单的 HTTP PUT。在这里,我们创建三个特征:标题字段相关性得分title_bm25、概述字段相关性得分overview_bm25和字段的值release_year。这里的 BM25 对应于第 3 章中定义的基于 BM25 的评分,Solr 使用术语频率和其他索引统计数据对文本字段中的术语匹配进行评分的默认方法。

清单 10.4。为学习排名创建三个特征。

feature_set = [    {      "name" : "title_bm25", #1      "store": "movies", #2      "class" : "org.apache.solr.ltr.feature.SolrFeature",      "params" : {        "q" : "title:(${keywords})" #3      }    },    {      "name" : "overview_bm25", #4      "store": "movies",      "class" : "org.apache.solr.ltr.feature.SolrFeature",      "params" : {        "q" : "overview:(${keywords})"      }    },    {      "name" : "release_year", #5      "store": "movies",      "class" : "org.apache.solr.ltr.feature.SolrFeature",      "params" : {        "q" : "{!func}release_year"}}]requests.put(';,             json=feature_set)

前两个功能是参数化的:它们各自采用搜索关键字(即social network, star wars)并在相应字段上执行搜索。最终功能只是检索电影的发行年份(纯粹是文档功能)。请注意,其中使用的语法params只是一个 Solr 查询,让您可以利用 Solr 广泛的 Query DSL 的全部功能来制作功能。

10.3.2 Solr 语料库中的日志功能

将特征加载到 Solr 后,我们的下一个重点将是记录判断列表中每一行的特征。在完成最后一点管道之后,我们可以训练一个模型,该模型可以观察每个查询的相关文档和不相关文档之间的关系。

对于判断列表中的每个唯一查询,我们需要提取查询的分级文档的特征。在social network上面的迷你判断列表中,我们有一个相关文档 ( 37799) 和三个不相关文档 ( 267752、38408和28303)。

查询的功能日志记录示例如social network清单 10.5所示。

清单 10.5。记录我们的特征存储中的值,以获取查询判断列表中存在的文档social network。

logging_solr_query = { "fl": "id,title,[features store=movies efi.keywords=\"social network\"]", #1 'q': "id:37799 OR id:267752 OR id:38408 OR id:28303", #2 'rows': 10, 'wt': 'json'}resp = requests.post(';,                     data=logging_solr_query)resp.json()

结果

  'docs': [{'id': '38408',    'title': 'Life As We Know It',    '[features]': 'title_bm25=0.0,overview_bm25=4.353118,    [CA]release_year=2010.0'}, #1   {'id': '37799',    'title': 'The Social Network',    '[features]': 'title_bm25=8.243603,overview_bm25=3.8143613,    [CA]release_year=2010.0'},   {'id': '28303',    'title': 'The Cheyenne Social Club',    '[features]': 'title_bm25=3.4286604,overview_bm25=3.1086721,    [CA]release_year=1970.0'},   {'id': '267752',    'title': '#chicagoGirl',    '[features]': 'title_bm25=0.0,overview_bm25=6.0172443,    [CA]release_year=2013.0'}]}}

清单 10.5中的导入语法fl是传递给(field list) 的方括号。此语法告诉 Solr 向每个文档响应添加一个计算字段,其中包含指定特征存储的特征数据(此处movies)。该efi参数代表外部特征信息,用于传递查询关键字(此处social network)以及计算每个特征所需的任何其他查询时信息。请注意,在响应中,我们检索了我们请求的 4 个文档中的每一个,以及为该文档计算的每个特征。

social network通过一些普通的 Python 簿记(列出 xy),我们可以从该响应中填写训练集中查询的特征。我们最终得到清单 10.6,一个部分填写的训练集。在这里,为查询填写特征social network;查询仍然需要功能star wars

清单 10.6。判断列表,仅附加查询的特征social network

[Judgment(grade=1,qid=1,keywords=social network,doc_id=37799,features=[[CA]8.243603, 3.8143613, 2010.0],weight=1), #1 Judgment(grade=0,qid=1,keywords=social network,doc_id=267752,features=[ [CA]0.0, 6.0172443, 2013.0],weight=1), Judgment(grade=0,qid=1,keywords=social network,doc_id=38408,features=[ [CA]0.0, 4.353118, 2010.0],weight=1), #2 Judgment(grade=0,qid=1,keywords=social network,doc_id=28303,features=[ [CA]3.4286604, 3.1086721, 1970.0], weight=1), Judgment(grade=1,qid=2,keywords=star wars,doc_id=11,features=[], [CA]weight=1), #3 Judgment(grade=1,qid=2,keywords=star wars,doc_id=1892,features=[], [CA]weight=1), #3 Judgment(grade=0,qid=2,keywords=star wars,doc_id=54138,features=[], [CA]weight=1), #3 Judgment(grade=0,qid=2,keywords=star wars,doc_id=85783,features=[], [CA]weight=1), #3 Judgment(grade=0,qid=2,keywords=star wars,doc_id=325553,features=[], [CA]weight=1)] #3

In Listing 10.6, as we might expect, the first feature value corresponds to the first feature in our fe在清单 10.6中,正如我们所期望的,第一个特征值对应于特征存储中的第一个特征 ( title_bm25);我们的特征存储中的第二个特征的第二个值 ( overview_bm25),依此类推。然后,我们对查询重复清单 10.6 ,记录相应的分级文档,从而得到完整记录的训练集,如清单 10.7star wars所示。

清单 10.7。为查询star wars和附加特征的判断列表social network。

[Judgment(grade=1,qid=1,keywords=social network,doc_id=37799,[CA]features=[8.243603, 3.8143613, 2010.0],weight=1), Judgment(grade=0,qid=1,keywords=social network,doc_id=267752, [CA]features=[0.0, 6.0172443, 2013.0],weight=1), Judgment(grade=0,qid=1,keywords=social network,doc_id=38408, [CA]features=[0.0, 4.353118, 2010.0],weight=1), Judgment(grade=0,qid=1,keywords=social network,doc_id=28303, [CA]features=[3.4286604, 3.1086721, 1970.0],weight=1), Judgment(grade=1,qid=2,keywords=star wars,doc_id=11, [CA]features=[6.7963624, 0.0, 1977.0],weight=1), #1 Judgment(grade=1,qid=2,keywords=star wars,doc_id=1892, [CA]features=[0.0, 1.9681965, 1983.0],weight=1), Judgment(grade=0,qid=2,keywords=star wars,doc_id=54138, [CA]features=[2.444128, 0.0, 2013.0],weight=1), Judgment(grade=0,qid=2,keywords=star wars,doc_id=85783, [CA]features=[3.1871135, 0.0, 1952.0],weight=1), Judgment(grade=0,qid=2,keywords=star wars,doc_id=325553, [CA]features=[0.0, 0.0, 2003.0],weight=1)]

然而,只有两个查询,每个查询有一些判断,这并不是那么有趣。在本章中,我们将使用包含大约 100 个电影查询的判断列表,每个查询包含大约 40 部被评级为相关/不相关的电影。这个更大的训练集的加载和记录功能的代码本质上重复了清单 10.7中所示的 Solr 请求。我们不会在这里重复冗长的代码(您可以在清单 xy 中找到它)。该功能日志记录的最终结果看起来就像清单 10.7一样,只是根据一个更大的判断列表创建的。

接下来我们将考虑如何将排名问题作为机器学习问题来处理。

10.4 步骤 3 - 将 LTR 转化为传统的机器学习问题

在本节中,我们将探讨作为机器学习问题的排名。这将帮助我们了解如何将现有的经典机器学习知识应用于我们的学习排名任务。

学习排序的任务是查看查询的许多相关/不相关的训练示例,然后构建一个模型,将相关文档放在顶部,将不相关文档推到底部。每个训练示例本身没有太大价值,而仅与它在查询中与同行一起排序的方式有关。图 10.2 显示了此任务,其中包含两个查询。目标是找到一个可以使用这些特征对结果进行正确排序的评分函数。

图 10.2。学习排名是将每个查询的结果集按理想顺序放置,而不是预测各个相关性等级。这意味着我们需要将每个查询视为一个单独的案例。

将 LTR 与更经典的逐点机器学习任务进行对比:如前面表 10.2 中提到的预测公司股价之类的任务。在这里,我们可以单独评估模型在每个训练示例上的准确性。只要观察一家公司,我们就知道我们对该公司股价的预测有多好。将显示逐点任务的图 10.3 与图 10.2 进行比较。请注意,在图 10.3 中,学习函数尝试直接预测股票价格,而对于 LTR,函数的输出仅对于相对于查询的对等项进行排序才有意义。

图 10.3。逐点机器学习尝试优化各个点(例如股票价格或温度)的预测。搜索相关性是与逐点预测不同的问题。相反,我们需要优化按搜索查询分组的示例的排名。

LTR 似乎与逐点机器学习截然不同。但大多数 LTR 方法都使用巧妙的炼金术将排名转变为逐点机器学习任务,例如股票价格预测。

在下一节中,我们将通过探索名为 SVMRank 的流行 LTR 模型来了解一个模型用于转换排名任务的方法。

10.4.1 SVMRank:将排序转换为二元分类

title_bm25LTR 的核心是模型:学习相关性/不相关性与、等特征之间关系的实际算法。overview_bm25在本节中,我们将探索一个这样的模型,SVMRank,首先了解“SVM”到底代表什么,然后深入研究如何使用它来构建一个出色的、可推广的 LTR 模型。

SVMRank将相关性转化为二元分类问题。二元分类只是意味着使用可用功能将项目分类为两个类别之一(例如“相关”与“不相关”;“成人”与“儿童”;“猫”与“狗”)。

SVM或支持向量机是执行二元分类的一种方法虽然我们不会深入研究 SVM,但您无需成为机器学习专家就可以跟上讨论。尽管如此,您可能想从Luis Serrano 的《Grokking Machine Learning 》( )等书中更深入地了解 SVM。

直观上,SVM 会找到最好的、最通用的线(或真正的超平面)来在两个类之间绘制。如果尝试建立一个模型来预测动物是狗还是猫,我们可能会查看已知狗或猫的身高和体重,并画一条线分隔两类,如图 10.4 所示。

图 10.4。SVM 示例:动物是狗还是猫?该超平面(线)根据高度和重量这两个特征将这两种情况分开。很快您就会看到我们如何执行类似的操作来分离查询的相关和不相关搜索结果。

在类之间绘制一条良好的线,或分离超平面,试图最大限度地减少它在训练数据中所犯的错误(猫一侧的狗较少,反之亦然)。我们还想要一个可概括的超平面,这意味着它可能会很好地对训练期间未见过的动物进行分类。毕竟,如果模型不能对新数据进行预测,那么它有什么用呢?它不是很人工智能驱动!

关于支持向量机需要了解的另一个细节是它们对我们的特征范围很敏感。例如,想象一下如果我们的高度特征是毫米而不是如图 10.5 所示的厘米。它迫使数据在 x 轴上伸展,并且分离的超平面看起来完全不同!

图 10.5。受某一特征范围影响的分离超平面。这导致支持向量机对特征范围很敏感,因此我们需要对特征进行归一化,这样某个特征就不会对模型产生不当影响。

当我们的数据标准化时,SVM 效果最佳。标准化只是意味着将特征强制到可比较的范围内。我们将通过将 0 映射到特征值的平均值来标准化我们的数据。因此,如果平均值release_year为 1990 年,则 1990 年发行的电影将归一化为 0。我们还将 +1/-1 映射到高于或低于平均值的一个标准差。因此,如果电影发行年份的标准差是22年,那么 2012 年的电影就变成了1.0;1968年的电影变成了-1.0. 我们可以在训练数据中重复此操作title_bm25并使用这些特征的平均值和标准差。overview_bm25这有助于在寻找分离超平面时使特征更具可比性。

有了这个简短的背景知识,SVM 能否帮助我们以某种方式将相关文档与不相关文档区分开来?即使对于以前从未见过的查询?是的!这正是 SVMRank 希望做到的。让我们来看看 SVMRank 的工作原理。

10.4.2 将 LTR 训练数据转换为二元分类

对于LTR,我们必须将任务从排名重新构建为传统的机器学习任务。在本节中,我们将探讨 SVMRank 如何将排名转换为适合 SVM 的二元分类任务。

在开始之前,让我们检查一下步骤 2 结束时完整记录的训练集,以查找我们最喜欢的两个查询star wars和social network。在本节中,我们将仅关注两个功能(title_bm25和overview_bm25),以帮助我们以图形方式探索功能关系。图 10.6 显示了每个分级star wars和social network文档的这两个特征,标记了训练集中的一些著名电影。

图 10.6。记录的特征得分social network和star wars查询

首先,标准化LTR特征

我们首先要标准化每个功能。清单 10.8获取步骤 2 中记录的输出并将特征标准化为normed_judgments.

清单 10.8。将我们记录的 LTR 训练集标准化为标准化训练集

means, std_devs, normed_judgments = normalize_features(logged_judgments)logged_judgments[360], normed_judgments[360]

结果

(Judgment(grade=1,qid=11,keywords=social network,doc_id=37799,  [CA]features=[8.243603, 3.8143613, 2010.0],weight=1, #1 Judgment(grade=1,qid=11,keywords=social network,doc_id=37799,   [CA]features=[4.482941696779275, 2.100049660821875, 0.8347155270825962],   [CA]weight=1) #2

您可以看到清单 10.8的输出首先显示了记录的标题和概述的 BM25 分数(8.243603、3.8143613),以及发布年份(2010)。然后对这些特征进行归一化,其中 8.243603title_bm25对应于高于平均值 4.4829 个标准差title_bm25,对于每个特征依此类推。

我们在图 10.7 中绘制了标准化特征。这看起来与图 10.6 非常相似,只是每个轴上的比例不同。

图 10.7。标准化star wars和social network分级的电影。图表中的每个增量都是高于或低于平均值的标准差。

接下来,我们将把排名变成一个二元分类学习问题,以将相关结果与不相关结果分开。

其次,计算两两差异

通过标准化数据,我们将特征强制限制在一致的范围内。现在,我们的 SVM 不应该因恰好具有很大范围的特征而产生偏差。在本节中,我们准备将任务转换为二元分类问题,为我们训练模型奠定基础!

SVM Rank 使用成对变换将 LTR 重新表述为二元分类问题。成对简单地意味着将排名转变为最小化查询的无序对的任务。

在本节的其余部分中,我们将仔细介绍 SVMRank 的成对算法,如清单 10.9中的伪代码所示。在此之前,让我们先从高层讨论一下该算法。该算法获取每个查询的判断,并将其与同一查询的所有其他判断进行比较。feature_deltas它计算该查询的每个相关和不相关对之间的特征差异 ( )。添加到 时feature_deltas,如果第一个判断比第二个判断更相关,则用+1in predictor_deltas- 进行标记,反之亦然。最后,该算法从旧训练集构建了一个全新的训练集: 和feature_deltas,predictor_deltas更适合二元分类。

清单 10.9。SVMRank 训练数据转换的伪代码。

foreach query in queries:   foreach judged_document_1 in query.judgments:      foreach judged_document_2 in query.judgments:         if judged_document_1.grade > judged_document_2.grade:            predictor_deltas.append(+1) #1            feature_deltas.append( judged_document_1.features -              [CA]judged_document_2.features) #2         else if  judged_document_1.grade < judged_document_2.grade:            predictor_deltas.append(-1) #3            feature_deltas.append( judged_document_1.features -              [CA]judged_document_2.features) #2

图 10.9 绘制了图 10.8 中的和数据feature_deltas的结果,突出显示了一些显着的成对差异。social networkstar wars

图 10.8。SVMRank 变换后的成对差异social network和star wars文档以及候选分离超平面。

您会注意到相关减去不相关的成对增量 ( +) 往往位于右上角。这意味着与不相关的文档相比,相关文档具有更高的title_bm25和。overview_bm25

好吧,有很多东西需要消化!不用担心 - 婴儿学步!如果我们仔细地、逐步地​浏览几个示例,您将看到该算法如何构造图 10.9 中的数据点。该算法会比较每个查询的相关和不相关文档。因此,首先让我们比较查询中的两个文档(“Network”和“The Social Network”)social network,如图 10.9 所示。

图 10.9。比较“网络”和“社交网络”进行查询social network

“社交网络”的特点是:

[4.483, 2.100]  # title_bm25 is 4.483 stddevs above mean, overview_bm25 is[CA]2.100 stddevs above mean

“网络”的特点是:

[3.101, 1.443]  # title_bm25 is 3.101 stddevs above mean, overview_bm25 is[CA]1.443 stddevs above mean

然后,我们在清单 10.10中的“The Social Network”和“Network”之间插入增量。

清单 10.10。将“社交网络”与“网络”增量标记为 feature_deltas

predictor_deltas.append(+1)feature_deltas.append( [4.483, 2.100] - [3.101, 1.443]) # appends [[CA]1.382, 0.657]

用英语重述清单 10.10,我们可能会说,这是电影“社交网络”的一个示例,对于此查询,它比另一部电影“网络”更相关social network。有趣的!让我们看看它们有何不同!当然,数学中的“差异”意味着减法,我们在这里就是这样做的。啊,是的,在进行差异后,我们看到“社交网络”的title_bm25标准差比“网络”高 1.382 个标准差;overview_bm25高出 0.657 个标准差。事实上,请注意+图 10.8 中的“社交网络 - 网络”,显示了增量中的点 [1.382, 0.657]。

该算法还会注意到“网络”的相关性不如“社交网络”,如图 10.10 所示。

图 10.10。将“网络”与“社交网络”进行比较以进行查询social network

正如清单 10.9中一样,我们在代码中捕获了这两个文档之间的相关性差异。然而,这一次是相反的方向(不相关减去相关)。因此,我们看到相同的值也就不足为奇了,但都是负面的。

predictor_deltas.append(-1)feature_deltas.append([3.101, 1.443] - [4.483, 2.100] ) # [-1.382, -0.657]

在图 10.11 中,我们对查询的两个文档进行另一个相关-不相关比较social network,将另一个比较附加到新的训练集。

图 10.11。将“社会种族灭绝”与“社交网络”进行比较以进行查询social network

我们在清单10.11 中展示了附加图 10.11 的两个比较方向,添加一个正值(当首先列出更相关的文档时)和一个负值(当首先列出不太相关的文档时)。

清单 10.11。将“社会种族灭绝”和“社交网络”添加到逐点训练数据中

# Positive examplepredictor_deltas.append(+1)feature_deltas.append( [4.483, 2.100] - [2.234, -0.444]) # [2.249, 2.544]# Negative examplepredictor_deltas.append(-1)feature_deltas.append([2.234, -0.444] - [4.483, 2.100]  ) # [-2.249, -2.544]

一旦我们迭代与查询匹配的文档之间的每个成对差异social network以创建逐点训练集,我们就会继续记录其他查询的差异。图 10.12 显示了第二个查询的差异,这次比较了与查询匹配的文档的相关性star wars。

图 10.12。《侠盗一号:星球大战电影》与《明星!》的比较对于查询“星球大战”。

# Positive examplepredictor_deltas.append(+1)feature_deltas.append( [2.088, 1.024] - [1.808, -0.444]) #1# Negative examplepredictor_deltas.append(-1)feature_deltas.append([1.808, -0.444] - [2.088, 1.024]) #2

我们继续计算相关文档与不相关文档的特征值之间的差异的过程,直到我们一起计算出训练和测试查询的所有成对差异。

您可以在图 10.8 中看到,正示例显示正title_bm25增量,并且可能显示稍微正overview_bm25增量。如果我们计算 100 个查询的完整数据集的增量,这一点就会变得更加清晰,如图 10.13 所示。

图 10.13。完整的训练集,超平面将相关文档与不相关文档分开。我们看到了一个模式!

有趣的!现在很容易直观地识别出较大的标题 BM25 分数匹配与与查询相关的文档高度相关,并且具有较高的概述 BM25 分数至少在某种程度上呈正相关。

值得退后一步询问一下这种排名公式是否适合您的领域。其他 LTR 方法有自己的方法来执行将成对比较映射到分类问题的技巧,但深入了解您选择的解决方案的执行情况非常重要。其他方法(例如 LambdaMART)执行自己的技巧,但它们也可以直接优化经典搜索相关性排名指标,例如精度或折扣累积增益 (DGC)。

接下来,我们将介绍如何训练强大的模型来捕获完全转换的排名数据集中的模式。

10.5 第 4 步——训练(和测试!)模型

好的机器学习显然需要大量的数据准备!幸运的是,您已经到达了我们实际训练模型的部分!通过上一节中的feature_deltas和predictor_deltas,我们现在有了一个适合训练经典机器学习模型的训练集。该模型将让我们预测文档何时可能相关:甚至对于尚未见过的查询和文档!

10.5.1 将分离超平面的向量转化为评分函数

我们已经了解了 SVMRank 的分离超平面如何将不相关的示例与相关的示例进行分类和分离。您可能会想,“这很酷,但我们的任务实际上是为我们的特征找到‘最佳’权重,而不仅仅是对文档进行分类!”。让我们看看如何使用这个超平面对搜索结果进行实际评分!

事实证明,分离超平面也为我们提供了学习最佳权重所需的信息。任何超平面都是由与该平面正交的向量定义的。因此,当 SVM 机器学习库发挥作用时,它实际上让我们了解每个特征应具有的权重,如图 10.14 所示。

图 10.14。具有候选分离超平面的完整训练集,显示定义超平面的正交向量。注意正交向量指向相关方向。

想想这个正交向量代表什么。这个向量指向相关性的方向!它说相关的例子是这样的,不相关的例子是相反的方向。这个向量肯定表明title_bm25对相关性有很大的影响,而 的影响较小overview_bm25。这个向量可能是这样的:

[0.65,0.40]

我们使用清单 10.9中的算法来计算在不相关/相关示例之间执行分类所需的增量。如果我们根据这些数据训练 SVM,如清单 10.12所示,该模型会为我们提供定义分离超平面的向量。

清单 10.12。使用 scikit learn 训练线性 SVM

from sklearn import svmmodel = svm.LinearSVC(max_iter=10000, verbose=1) #1model.fit(feature_deltas, predictor_deltas) #2model.coef_ #3

结果:

array([[0.40512169, 0.29006365, 0.14451721]])

清单 10.12训练一个SVM使用相应的(标准化的 和 特征中的增量)来分离(记住predictor_deltas它们是+1和s)。所得模型是与分离超平面正交的向量。正如预期的那样,它显示出对 的权重较大,对 的权重较温和,对 的权重较弱。-1feature_deltastitle_bm25overview_bm25release_yeartitle_bm25overview_bm25release_year

10.5.2 模型试驾

该模型如何作为排名函数发挥作用?假设用户输入查询wrath of khan。wrath of khan相对于使用该模型的关键字,该模型如何对文档“星际迷航 II:可汗之怒”进行评分?非标准化特征向量表明 的标题和概述具有很强的匹配性wrath of khan:

[5.9217176, 3.401492, 1982.0]

将其归一化后,每个特征值都比每个特征的平均值高/低许多标准差:

[3.099, 1.825, -0.568]

我们只需将每个标准化特征与其相应的coef_值相乘即可。将它们加在一起,得到一个相关性分数:

(3.099 * 0.405) + (1.825 * 0.290) + (-0.568 * 0.1445) = 1.702

对于我们的查询,该模型相对于“星际迷航 II:可汗之怒”如何对“星际迷航 III:寻找斯波克”进行排名wrath of khan?希望不会那么高!事实上它没有:

[0.0,0.0,1984.0] # Raw Features[-0.432, -0.444, -0.468] # Normalized features(-0.432 * 0.405) + (-0.444 * 0.290) + (-0.468 * 0.1445) = -0.371

该模型似乎得到了我们怀疑的朝向顶部的正确答案。

10.5.3 验证模型

通过 1-2 次查询进行尝试可以帮助我们发现问题,但我们更喜欢采用更系统的方法来检查模型是否具有泛化性。毕竟,我们希望在晚上睡觉时知道搜索可以处理该模型尚未见过的查询!

LTR 和经典机器学习之间的一个区别是,我们通常评估查询,而不是单个数据点,以证明我们的模型是有效的。因此,我们将在查询级别执行测试/训练拆分。它可以让我们发现有问题的查询类型。我们将使用简单的精度指标进行评估,计算前 N 个相关结果(在我们的例子中 N=4)的比例。您应该选择最适合您自己的用例的相关性指标。

首先,我们将随机地将查询放入测试或训练集中,如清单 10.13所示。

清单 10.13。在查询级别进行简单的测试/训练分割。

all_qids = list(set([j.qid for j in normed_judgments]))random.shuffle(all_qids)proportion_train=0.1test_train_split_idx = int(len(all_qids) * proportion_train) #1test_qids=all_qids[:test_train_split_idx]train_qids=all_qids[test_train_split_idx:]train_data = []; test_data=[]for j in normed_judgments: #2    if j.qid in train_qids:        train_data.append(j)    elif j.qid in test_qids:        test_data.append(j)

通过训练数据分割,我们可以执行步骤 3 中的成对变换技巧。然后我们可以仅使用清单 10.14中的训练数据进行重新训练。

清单 10.14。仅根据训练数据进行训练

train_data_features, train_data_predictors = pairwise_transform(train_data)from sklearn import svmmodel = svm.LinearSVC(max_iter=10000, verbose=1)model.fit(train_data_features, train_data_predictors) #1model.coef_[0]

现在我们已经保留了测试数据。就像一个好老师一样,我们不想给“学生”所有的答案。我们想看看模型除了死记硬背训练样例之外是否还学到了其他东西。

在清单 10.15中,我们使用测试数据评估我们的模型。此代码循环遍历每个测试查询,并使用模型对每个测试判断进行排名(rank函数省略)。然后,它计算前 4 个判断的精度。

清单 10.15。我们的模型可以推广到训练数据之外吗?

def eval_model(test_data, model):    tot_prec = 0    num_queries = 0    for qid, query_judgments in groupby(test_data, key=lambda j: j.qid): #1        query_judgments = list(query_judgments)        ranked = rank(query_judgments, model) #2        tot_relevant = 0        for j in ranked[:4]: #3            if j.grade == 1:                tot_relevant += 1        query_prec = tot_relevant/4.0        tot_prec += query_prec        num_queries += 1    return tot_prec / num_queries

在多次运行中,您应该期望大约 0.3 - 0.4 的精度。对于我们的第一次迭代来说还不错,我们只是猜测了一些功能(title_bm25、overview_bm25和release_year)!

在 LTR 中,您始终可以回顾之前的步骤,看看哪些地方可以改进。这次精度测试是我们第一次能够系统地评估我们的模型,因此现在是重新审视这些功能以了解如何在后续运行中提高精度的自然时机。一直返回到步骤 2。查看哪些示例位于分离超平面的错误一侧。例如,如果您查看图 10.8,第三部星球大战电影“绝地归来”符合标题中没有关键字匹配的相关文档的模式。在没有标题的情况下,可以添加哪些其他功能来帮助捕捉电影属于特定系列(例如《星球大战》)?也许有一个 TMDB 电影属性表明我们可以尝试一下?

不过,现在让我们采用刚刚构建的模型,看看如何将其部署到生产中。

10.6 步骤 5 和 6 - 上传模型并搜索

在本节中,我们最终向 Solr 介绍我们的模型。然后我们就可以看到我们努力的成果:实际的搜索结果!

最初,我们提出的目标是为手动排名函数(如清单 10.2中的函数)找到“理想”的提升:

q="title:(${keywords})^10 overview:(${keywords})^20[CA]{{!func}}release_year^0.01"

这个手动函数确实将每个特征乘以一个权重(提升),并对结果求和。但事实证明我们实际上并不希望 Solr 乘以原始特征值。相反,我们需要对特征值进行标准化。

幸运的是,Solr LTR 允许我们存储线性模型以及特征归一化统计数据。我们保存了每个功能的means和std_devs,Solr 可以使用它来标准化任何正在评估的文档的值。我们只需要在存储模型时向 Solr 提供这些信息,就像清单 10.16中所做的那样。

清单 10.16。将模型上传到 Solr,并对每个特征进行归一化和权重。

PUT {  "store": "movies", #1  "class": "org.apache.solr.ltr.model.LinearModel",  "name": "movie_titles",  "features": [    {      "name": "title_bm25", #2      "norm": {        "class": "org.apache.solr.ltr.norm.StandardNormalizer", #3        "params": { #3          "avg": "1.5939970007512951", #3          "std": "3.689972140122766" #3        }      }    },    {      "name": "overview_bm25",      "norm": {        "class": "org.apache.solr.ltr.norm.StandardNormalizer",        "params": {          "avg": "1.4658440933160637",          "std": "3.2978986984657808"        }      }    },    {      "name": "release_year",      "norm": {        "class": "org.apache.solr.ltr.norm.StandardNormalizer",        "params": {          "avg": "1993.3349740932642",          "std": "19.964916628520722"        }      }    }  ],  "params": {    "weights": {      "title_bm25": 0.40512169, #4      "overview_bm25": 0.29006365,      "release_year": 0.14451721    }  }}

请注意,模型与特征存储关联,因此在 Solr 中评估模型时可以查找特征名称来计算它们。

最后,我们可以使用 Solr 的 LTR 查询解析器发出搜索,如清单 10.17 所示。

清单 10.17。使用我们的模型对 60,000 个文档进行排名以供搜索harry potter

request = {    "fields": ["title", "id", "score"],    "limit": 5,    "params": {      "q": "{!ltr reRankDocs=60000 model=movie_model efi.keywords=\      [CA]"harry potter\"}", #1    }}resp = requests.post(';, json=request)resp.json()["response"]["docs"]

结果:

{'id': '570724', 'title': ['The Story of Harry Potter'], 'score': 2.786719,[CA]'_score': 2.786719}{'id': '116972', 'title': ['Discovering the Real World of Harry Potter'],[CA]'score': 2.5646381, '_score': 2.5646381}{'id': '672', 'title': ['Harry Potter and the Chamber of Secrets'],[CA]'score': 2.3106465, '_score': 2.3106465}{'id': '671', 'title': ["Harry Potter and the Philosopher's Stone"],[CA]'score': 2.293983, '_score': 2.293983}{'id': '393135', 'title': ['Harry Potter and the Ten Years Later'],[CA]'score': 2.2162843, '_score': 2.2162843}

请注意清单 10.17中使用了术语“rerank”。正如这意味着,LTR 通常作为另一个查询之上的第二阶段发生。在清单 10.17中没有初始查询。那么在这种情况下会发生什么呢?该模型按照文档的索引顺序(基本上是随机的)应用于文档。从结果中可以看出,该模型对于我们的查询似乎很有效,因为它对完整的 60,000 个文档进行了排名。

不过,通常我们不想对如此大的结果集进行重新排名。我们宁愿使用简单的评分(例如 BM25 或简单的 edismax 查询)进行快速基线匹配,然后选择利用 LTR 对较少数量的候选文档进行更昂贵的重新排名。清单 10.18演示了如何实现基线搜索,使用我们的 LTR 模型对前 500 名重新排名。

清单 10.18。重新排名前 500 名,以便在初始快速排名计算的热门结果之后movie_model进行搜索。harry potter

request = {    "fields": ["title", "id", "score"],    "limit": 5,    "params": {      "rq": "{!ltr reRankDocs=500 model=movie_model efi.keywords=\      [CA]"harry potter\"}", #1      "qf": "title overview", #2      "defType": "edismax", #2      "q": "harry potter" #2    }}resp = requests.post(';, json=request)resp.json()["response"]["docs"]
10.6.1 关于 LTR 性能的说明

唷,你已经做到了!正如您所看到的,构建真实的 LTR 模型需要执行许多步骤。让我们通过一些关于 LTR 系统中其他实际性能限制的想法来结束这个循环:

模型复杂性——模型越复杂,就越准确。更简单的模型可以更快、更容易理解,但可能不太准确。在这里,我们坚持使用一个非常简单的模型(一组线性权重)。想象一下一个复杂的深度学习模型,它的效果如何?复杂性值得吗?它会具有普遍性吗?重新排名深度- 重新排名越深,您可能会发现更多可能是隐藏宝石的其他文档。另一方面,重新排名越深,模型在实时搜索引擎集群中对结果进行评分的计算周期就越多。特征复杂性- 如果您在查询时计算非常复杂的特征,它们可能会对您的模型有所帮助。然而,它们会减慢评估和搜索响应时间。特征数量- 具有许多特征的模型可能会带来更高的相关性。然而,计算每个文档的每个特征也需要更多的时间,所以问问自己哪些特征是至关重要的。许多学术 LTR 系统使用数百个。实际的 LTR 系统通常将其归结为几十个。当您继续添加附加功能时,您几乎总是会看到收益递减,因此根据功能数量选择正确的截止阈值非常重要。10.7 冲洗并重复

恭喜!您已经完成了一个完整的学习排名周期!但与许多数据问题一样,您可能需要继续迭代该问题。您总是可以做一些新的事情来改进。

在第二次迭代中,您可能会开始考虑以下一些注意事项:

新的和更好的功能。模型是否在某些类型的查询或示例上表现不佳?比如title没有title提及的搜索(star wars《绝地归来》的标题中没有提及。什么功能可以捕获这些?)。我们能否结合第 1-9 章的经验教训来构建更高级的功能?训练数据覆盖所有特征。更多功能的另一面是更多的训练数据。当您增加想要尝试的功能时,您应该想知道您的训练数据是否在每个不同的功能组合中都有足够的相关/不相关文档的示例。否则你的模型将不知道如何利用特征来解决问题。不同的模型架构。我们使用了一个相对简单的模型,期望特征与相关性线性相关。然而相关性往往是非线性的。搜索 iPad 的购物者可能会期待最新发布的 Apple iPad,除非他们添加“电缆”一词,如“iPad 电缆”。对于该查询,他们可能只想要他们能找到的最便宜的电缆。

在下一章中,我们将重点讨论良好 LTR 的基础:伟大的判断!这将使您对迭代更有信心,随着您的成熟度的提高,突破自动化此过程的界限。

10.8 总结学习排名 (LTR) 使用强大的机器学习技术构建可应用于所有搜索的通用排名函数在 Solr LTR 中,功能通常对应于 Solr 查询。Solr LTR 允许您存储和记录特征,以用于训练和稍后应用排名模型。我们在使用哪些特征来概括相关性方面拥有极大的自由。特征可以是查询的属性(如术语数量)、文档的属性(如流行度)或查询与文档之间的关系(如 BM25 或其他相关性分数)。为了做好 LTR 并应用众所周知的机器学习技术,我们通常将相关性排名问题重新表述为经典的机器学习问题。SVMRank 在标准化特征值上创建简单的线性权重,这是 LtR 之旅的良好第一步为了真正有用,我们需要我们的模型能够概括出它所学到的东西!我们可以通过将一些训练数据放入测试集中来确认 LTR 可以泛化。然后,我们可以使用测试数据来评估模型的普遍性。将 LTR 模型加载到搜索引擎后,请务必考虑性能(如速度)与相关性的权衡。现实生活中的搜索系统两者都需要!

标签: #查找算法的比较实验总结