使用判断列表评估搜索查询的相关性
针对在 Elasticsearch 中开展可扩展的搜索测试,探究如何构建判定列表以客观评估搜索查询相关性,并提升召回率等性能指标。
Elasticsearch is packed with new features to help you build the best search solutions for your use case. Learn how to put them into action in our hands-on webinar on building a modern Search AI experience. You can also start a free cloud trial or try Elastic on your local machine now.
从事搜索引擎开发的工程师们常常会遇到同一个问题:业务团队对某次特定搜索结果并不满意,因为他们期望排在搜索结果首位的文档,实际却出现在结果列表的第三或第四位。
然而,当你解决这一问题时,却可能因无法手动测试所有情况而不经意间破坏其他查询的功能。但你或你的 QA 团队该如何测试,以确认某一项查询的改动是否会对其他查询产生连锁反应呢?或者更关键的是,你们要如何确保所作的改动确实优化了某项查询呢?
转向系统性评估
这个时候,判断列表就可以派上用场。与其在每次更改时依赖手动和主观测试,不如定义一组与业务案例相关的固定查询及其相关结果。
这一组(测试用例或数据)将成为基准参照。每次实施改动时,你都用它来评估搜索效果是否确实得到了提升。
这种方法的价值在于:
消除不确定性:无需再费心猜测所做的更改是否会影响其他查询;数据会直接告诉你答案。
停止人工测试:一旦判定集被记录下来,测试便会自动执行。
佐证更改:你可以展示出明确的指标,以佐证某项更改所带来的益处。
如何开始建立判断列表
最简单的开始方式之一是获取具有代表性的查询,并手动选择相关文件。有两种方法可以列出此列表:
二元判断:与查询关联的每一份文档都会被赋予一个简单标签:相关(通常标注分数为“1”)和不相关(标注分数为“0”)。
分级判断:在此情境下,每份文档会依据不同等级获得相应分数。例如:采用 0 至 4 分的评分量表,类似于李克特量表,其中 0 分表示“完全不相关”,4 分表示“完全相关”,中间还设有“相关”“有点相关”等不同程度表述。
当搜索意图具有明确界限时,二元判断(是/否)十分奏效,即判断该文档是否应出现在搜索结果中?
当存在模糊地带时,分级判断更为实用:某些结果相较于其他结果更优,因此你可以将结果划分为“优秀”“良好”和“毫无价值”等不同等级,并运用能体现结果排序权重及用户反馈的评估指标。然而,分级量表也存在弊端:不同评审者对评分等级的使用方式可能存在差异,这会导致判断结果的一致性降低。并且,由于分级指标对高分赋予了更大的权重,即便是一个微小的改动(比如将某项评分从 4 分改为 3 分),也可能在指标上引发远超评审者预期的巨大波动。这种额外引入的主观性使得分级判断结果更具干扰性,且随时间推移愈发难以把控。
我需要自己对文件分类吗?
不一定,因为有多种不同方法创建判定列表,且每种方法各有其优缺点:
明确判断:在这种情况下,领域专家会逐一审阅每个查询/文档,并手动判定其相关性(或相关程度)。尽管此方法能确保质量并实现把控,但其可扩展性较差。
隐式判断:采用这种方法时,你会依据真实用户行为(如点击量、跳出率、购买行为等)来推断相关文档。此方法可实现数据的自动收集,但可能存在偏差。例如,用户往往更倾向于点击排名靠前的结果,即便这些结果并不相关。
AI 生成的判断:最后这种方法是借助模型(如 LLM)自动评估查询和文档,人们通常称之为LLM 陪审团。其优势在于速度快且易于扩展,不过数据质量取决于所用模型的性能,以及大语言模型训练数据与您业务需求的契合程度。与人工评分一样,LLM 评审团也可能引入自身偏见或出现前后不一致的情况,因此,必须对照一小部分可信判断结果来验证其输出结果。LLM 模型本质上具有概率性,所以即便将温度参数设置为 0,也常见同一结果被 LLM 模型给出不同评分的情况。
以下是一些选择最佳方法来构建判断集的建议:
明确界定哪些仅用户能恰当判断的要素对你而言至关重要(例如价格、品牌、语言、风格以及产品细节等)。如果这些要素至关重要,则至少需针对判断列表中的部分内容获取明确的判断结果。
当你的搜索引擎已有足够流量时,可运用隐式判断,即借助点击量、转化率以及停留时长等指标来洞察使用趋势。不过,你仍需谨慎解读这些数据,将其与显式判断结果进行对比,以规避潜在偏差(例如用户往往更倾向于点击排名靠前的结果,即便排名靠后的结果更具相关性)。
为解决这一问题,位置偏差消除技术会对点击数据进行调整或重新加权,以更准确地反映用户的真实兴趣。以下是一些方法:
结果随机排序:针对部分用户调整搜索结果的排序,以此估算结果位置对点击量的影响。
点击模型包括动态贝叶斯网络 DBN 和用户浏览模型 UBM。这些统计模型会借助滚动行为、停留时长、点击顺序以及返回结果页等模式,来估算用户点击行为反映真实兴趣(而非仅受结果位置影响)的概率。
示例:电影评分应用
准备工作
要运行此示例,需要一个正在运行的本地或部署在 Elastic Cloud 上(托管或无服务器)的 Elasticsearch 8.x 集群,以及访问 REST API 或 Kibana 的权限。
想象有一款应用程序,用户可以在其中上传自己对电影的看法,还可以搜索要观看的电影。由于文本由用户自己撰写,因此可能存在拼写错误和表达方式上的多种差异。因此,搜索引擎必须能够解读这种多样性,并为用户提供有用的结果。
为能在不影响整体搜索行为的前提下对查询进行迭代优化,贵公司业务团队基于最常执行的搜索查询,创建了以下二元判断集:
查询 | DocID | 文本 |
|---|---|---|
迪卡普里奥的表演 | doc1 | 迪卡普里奥在《荒野猎人》中的表演令人惊叹。 |
迪卡普里奥的表演 | doc2 | 《盗梦空间》中,莱昂纳多·迪卡普里奥饰演了他最具标志性的角色之一。 |
迪卡普里奥的表演 | doc3 | 布拉德·皮特在这部犯罪惊悚片中表现出色。 |
迪卡普里奥的表演 | doc4 | 一部充满惊险动作、视觉效果惊艳的冒险大片。 |
让人热泪盈眶的悲伤电影 | doc5 | 这是一个令人心碎的关于爱与失去的故事,让我哭了好几个小时。 |
让人热泪盈眶的悲伤电影 | doc6 | 有史以来最催泪的电影之一,记得带上纸巾! |
让人热泪盈眶的悲伤电影 | doc7 | 让你捧腹大笑的轻松喜剧 |
让人热泪盈眶的悲伤电影 | doc8 | 一部充满动作与激情的科幻史诗巨作。 |
正在创建索引:
PUT movies
{
"mappings": {
"properties": {
"text": {
"type": "text"
}
}
}
}批量请求:
POST /movies/_bulk
{ "index": { "_id": "doc1" } }
{ "text": "DiCaprio performance in The Revenant was breathtaking." }
{ "index": { "_id": "doc2" } }
{ "text": "Inception shows Leonardo DiCaprio in one of his most iconic roles." }
{ "index": { "_id": "doc3" } }
{ "text": "Brad Pitt delivers a solid performance in this crime thriller." }
{ "index": { "_id": "doc4" } }
{ "text": "An action-packed adventure with stunning visual effects." }
{ "index": { "_id": "doc5" } }
{ "text": "A heartbreaking story of love and loss that made me cry for hours." }
{ "index": { "_id": "doc6" } }
{ "text": "One of the saddest movies ever made -- bring tissues!" }
{ "index": { "_id": "doc7" } }
{ "text": "A lighthearted comedy that will make you laugh." }
{ "index": { "_id": "doc8" } }
{ "text": "A science-fiction epic full of action and excitement." }以下是该应用程序正在使用的 Elasticsearch 查询:
GET movies/_search
{
"query": {
"match": {
"text": {
"query": "DiCaprio performance",
"minimum_should_match": "100%"
}
}
}
}从判断到指标
就其本身而言,判断列表并不提供太多信息;它们只是我们查询结果的期望。它们真正的优势在于,当我们使用它们来计算客观指标以衡量我们的搜索性能时。
如今,大多数常用指标包含
精度:衡量所有搜索结果中真正相关的结果比例。
召回率:衡量搜索引擎在检索出的 x 个结果中,找到的相关结果所占的比例。
折损累积增益(DCG):用于衡量结果排序的质量,该指标基于最相关的结果应排在前列这一原则进行评估。
平均倒数排名(MRR):用于衡量首个相关结果所处的排名位置情况 。在列表中越靠前,其分数就越高。
以同样的电影评分应用程序为例,我们将计算召回率指标,看看我们的查询是否遗漏了任何信息。
在 Elasticsearch 中,我们可以通过排名评估 API,使用判断列表来计算指标。该 API 将判断列表、查询以及想要评估的指标作为输入,并返回一个数值,该数值是对查询结果与判断列表进行对比后得出的结果。
让我们针对已提出的这两个查询运行判定列表:
POST /movies/_rank_eval
{
"requests": [
{
"id": "dicaprio-performance",
"request": {
"query": {
"match": {
"text": {
"query": "DiCaprio performance",
"minimum_should_match": "100%"
}
}
}
},
"ratings": [
{
"_index": "movies",
"_id": "doc1",
"rating": 1
},
{
"_index": "movies",
"_id": "doc2",
"rating": 1
},
{
"_index": "movies",
"_id": "doc3",
"rating": 0
},
{
"_index": "movies",
"_id": "doc4",
"rating": 0
}
]
},
{
"id": "sad-movies",
"request": {
"query": {
"match": {
"text": {
"query": "sad movies that make you cry",
"minimum_should_match": "100%"
}
}
}
},
"ratings": [
{
"_index": "movies",
"_id": "doc5",
"rating": 1
},
{
"_index": "movies",
"_id": "doc6",
"rating": 1
},
{
"_index": "movies",
"_id": "doc7",
"rating": 0
},
{
"_index": "movies",
"_id": "doc8",
"rating": 0
}
]
}
],
"metric": {
"recall": {
"k": 10,
"relevant_rating_threshold": 1
}
}
}我们将向 rank_eval 发送两个请求:一个针对莱昂纳多·迪卡普里奥查询,另一个针对悲伤电影查询每个请求均包含一个查询及其对应的判定列表(评分)。我们无需对所有文档进行评分,因为未纳入评分范围的文档将被视为未作判定。在进行计算时,召回率仅考虑“相关文档集”,即那些在评分中被认定为相关的文档。
在此情形下,针对莱昂纳多·迪卡普里奥的查询召回率为 1,而悲伤电影查询的召回率为 0。这意味着对于第一个查询,我们能够获取到所有相关结果,而第二个查询则未获取到任何相关结果。因此,平均召回率为 0.5。
{
"metric_score": 0.5,
"details": {
"dicaprio-performance": {
"metric_score": 1,
"unrated_docs": [],
"hits": [
{
"hit": {
"_index": "movies",
"_id": "doc1",
"_score": 2.4826927
},
"rating": 1
},
{
"hit": {
"_index": "movies",
"_id": "doc2",
"_score": 2.0780432
},
"rating": 1
}
],
"metric_details": {
"recall": {
"relevant_docs_retrieved": 2,
"relevant_docs": 2
}
}
},
"sad-movies": {
"metric_score": 0,
"unrated_docs": [],
"hits": [],
"metric_details": {
"recall": {
"relevant_docs_retrieved": 0,
"relevant_docs": 2
}
}
}
},
"failures": {}
}或许我们对 minimum_should_match 参数设置得过于严苛了,因为要求查询中的所有词汇都必须在文档中出现,这很可能会导致我们遗漏掉一些相关结果。不妨去掉 minimum_should_match 参数,这样只要文档中包含查询语句里的任意一个词汇,该文档就会被视为相关结果。
POST /movies/_rank_eval
{
"requests": [
{
"id": "dicaprio-performance",
"request": {
"query": {
"match": {
"text": {
"query": "DiCaprio performance"
}
}
}
},
"ratings": [
{
"_index": "movies",
"_id": "doc1",
"rating": 1
},
{
"_index": "movies",
"_id": "doc2",
"rating": 1
},
{
"_index": "movies",
"_id": "doc3",
"rating": 0
},
{
"_index": "movies",
"_id": "doc4",
"rating": 0
}
]
},
{
"id": "sad-movies",
"request": {
"query": {
"match": {
"text": {
"query": "sad movies that make you cry"
}
}
}
},
"ratings": [
{
"_index": "movies",
"_id": "doc5",
"rating": 1
},
{
"_index": "movies",
"_id": "doc6",
"rating": 1
},
{
"_index": "movies",
"_id": "doc7",
"rating": 0
},
{
"_index": "movies",
"_id": "doc8",
"rating": 0
}
]
}
],
"metric": {
"recall": {
"k": 10,
"relevant_rating_threshold": 1
}
}
}如你所见,通过在两个查询中的其中一个里移除 minimum_should_match 参数,现在两个查询的平均召回率都达到了 1。
{
"metric_score": 1,
"details": {
"dicaprio-performance": {
"metric_score": 1,
"unrated_docs": [],
"hits": [
{
"hit": {
"_index": "movies",
"_id": "doc1",
"_score": 2.0661702
},
"rating": 1
},
{
"hit": {
"_index": "movies",
"_id": "doc3",
"_score": 0.732218
},
"rating": 0
},
{
"hit": {
"_index": "movies",
"_id": "doc2",
"_score": 0.6271719
},
"rating": 1
}
],
"metric_details": {
"recall": {
"relevant_docs_retrieved": 2,
"relevant_docs": 2
}
}
},
"sad-movies": {
"metric_score": 1,
"unrated_docs": [],
"hits": [
{
"hit": {
"_index": "movies",
"_id": "doc7",
"_score": 2.1307156
},
"rating": 0
},
{
"hit": {
"_index": "movies",
"_id": "doc5",
"_score": 1.3160692
},
"rating": 1
},
{
"hit": {
"_index": "movies",
"_id": "doc6",
"_score": 1.190063
},
"rating": 1
}
],
"metric_details": {
"recall": {
"relevant_docs_retrieved": 2,
"relevant_docs": 2
}
}
}
},
"failures": {}
}总而言之,移除 minimum_should_match: 100% 这一条件后,我们得以使两个查询均实现完美召回率。
我们做到了!对不对?
没那么快!
通过提升召回率,我们能够获取到更广泛的结果范围。然而,每一次调整都意味着需要权衡取舍。这正是为何要定义完整的测试用例,并运用不同指标来评估各项更改的原因。
使用判断列表和指标可以防止您在进行更改时盲目行事,因为现在您有数据可以支持这些更改。验证不再是手动和重复的,您可以在多个用例中测试您的更改。此外,A/B 测试允许您实时测试哪种配置最适合您的用户和业务案例,从而实现从技术指标到实际指标的完整循环。
使用判断列表的最终建议
运用判定列表开展工作,不仅关乎评估测量,更在于构建一个能让你自信迭代优化的框架。为实现这一目标,可遵循以下建议:
从小处着手,但一定要开始行动。你无需准备 10000 个查询,且每个查询都配有 50 个判断列表。你只需找出 5 到 10 个对业务场景最为关键的查询,并明确你期望在结果顶部看到的文档即可。这已经能为你提供一个基础。通常,你应优先从热门查询以及无结果的查询入手开展工作。你也可以先使用像精确率这样易于配置的指标进行测试,然后再逐步尝试更复杂的指标。
与用户核实。在生产环境中通过 A/B 测试对数据指标进行补充验证。如此一来,你便能知晓那些在指标上表现良好的更改是否也切实产生了实际影响。
保持列表有效性。你的商业案例会不断变化,关键问题也会随之变化。定期更新判断以反映新的需求。
使其成为流程的一部分。将判断列表整合到开发管道之中。确保每次配置更改、同义词添加或文本分析操作,都能自动对照基础列表进行验证。
将技术知识与战略相结合。不要仅仅满足于衡量精确率或召回率等技术指标。要利用评估结果为业务成果提供决策依据。




