本文1W8000字,在这篇文章中,我们探讨了检索增强生成(RAG)应用程序中的搜索过程,重点介绍了使用向量数据库的语义搜索。我们强调了其减少处理时间和支持实时更新等优势,同时也指出了面临的挑战,比如对独特查询可能给出不太理想的回复。预防这些问题的策略包括监控查询密度和收集用户反馈。优化工作应贯穿构建和生产后阶段,其中扩展知识库是重要的一环。此外,还讨论了搜索类型、语义搜索,以及检索与重排流程,并推荐了预训练模型和诸如召回率@K 等离线评估指标。有些内容是翻译自 HuggingFace 和一些论文,更多 LLM 架构文章点击查看:LLM 架构专栏
欢迎加入大模型交流群:加群链接 https://docs.qq.com/doc/DS3VGS0NFVHNRR0Ru#
公众号【柏企阅文】
欢迎大家点赞转发收藏和关注,您的推荐是我坚持下去的动力~~

让我们开始探索检索增强生成(RAG)应用中相关数据的搜索过程。当用户输入查询时,这个过程包括对用户查询进行分词,并使用与嵌入原始数据相同的模型进行嵌入。随后,根据与用户查询的相似性,从知识库中提取相关的文本块。
向量数据库
本文将全面探讨搜索过程的复杂性。下图展示了 “搜索” 在整个 RAG 流程中的确切位置。
检索增强生成通过大语言模型(LLM)的上下文窗口提供特定领域的信息,降低了幻觉的可能性。
1. 引言
让我们考虑一个常见的场景:使用大语言模型开发客户支持聊天机器人。通常,团队拥有大量的产品文档,其中包含大量关于产品、常见问题和用例的非结构化数据。
这些数据通过一个称为 “分块” 的过程被分解成小块。数据分解后,每个小块被赋予一个唯一的标识符,并嵌入到向量数据库的高维空间中。这个过程利用了先进的自然语言处理技术来理解每个小块的上下文和语义含义。
当客户的问题进来时,大语言模型使用检索算法从向量数据库中快速识别并获取最相关的文本块。这种检索是基于查询和文本块之间的语义相似性,而不仅仅是关键词匹配。
这种方法有几个优点。首先,它显著减少了大语言模型处理大量数据所需的时间和计算资源,因为它只需要与相关的文本块交互,而不是整个文档。
其次,它允许对数据库进行实时更新。随着产品文档的更新,向量数据库中相应的文本块可以很容易地更新,这确保了聊天机器人始终提供最新的信息。
最后,通过关注语义相关的文本块,大语言模型可以提供更精确、更符合上下文的回复,从而提高客户满意度。
1.1 搜索与检索的问题
虽然搜索和检索方法大大提高了大语言模型的效率和准确性,但它并非没有潜在的缺陷。尽早发现这些问题可以防止它们影响用户体验。
当用户输入的查询与向量存储中的任何文本块都不太匹配时,就会出现一个挑战。系统就像在大海捞针,却根本找不到针。这种不匹配通常是由独特或高度特定的查询引起的,可能会导致系统使用 “最相似” 但并非完全相关的文本块。
反过来,这会导致大语言模型给出不太理想的回复。由于大语言模型依赖于文本块的相关性来生成回复,缺乏合适的匹配可能会导致输出与用户查询只是略微相关,甚至完全不相关。
大语言模型不相关或质量不佳的回复会让用户感到沮丧,降低他们的满意度,并最终导致他们对系统和整个产品失去信任。
监测以下三个主要方面有助于预防这些问题:
- 查询密度(漂移):查询密度指的是向量存储对用户查询的覆盖程度。如果查询密度显著漂移,这表明我们的向量存储可能没有涵盖用户查询的全部范围,导致缺乏紧密相关的文本块。定期监测查询密度使我们能够发现这些差距或不足。有了这些洞察,我们可以通过纳入更多相关文本块或优化现有文本块来扩充向量存储,提高系统响应用户查询获取数据的能力。
- 排名指标:这些指标用于评估搜索和检索系统在选择最相关文本块方面的表现。如果排名指标显示性能下降,这表明系统区分相关和不相关文本块的能力可能需要改进。
- 用户反馈:鼓励用户对大语言模型回复的质量和相关性提供反馈,有助于评估用户满意度并确定需要改进的地方。定期分析这些反馈可以找出模式和趋势,然后根据需要调整应用程序。
1.2 优化搜索与检索
在基于大语言模型的应用程序的整个生命周期中,从构建阶段到生产后阶段,搜索和检索过程的优化都应该是一项持续的工作。
在构建阶段,应注重开发强大的测试和评估策略。这种方法使我们能够尽早发现潜在问题并优化策略,为系统奠定坚实的基础。
需要关注的关键领域包括:
- 分块策略:评估在这个阶段信息是如何分解和处理的,有助于发现性能方面的改进空间。
- 检索性能:评估系统检索信息的能力,可以表明我们是否需要采用不同的工具或策略,如上下文排名或 HYDE。
在发布后,进入生产后阶段,优化工作仍应继续。即使在发布后,通过明确的评估策略,我们也可以主动发现任何新出现的问题,并继续提高模型的性能。可以考虑以下方法:
- 扩展知识库:添加文档可以显著提高系统的回复质量。扩展的数据集使大语言模型能够提供更准确、更有针对性的回复。
- 优化分块策略:进一步修改信息的分解和处理方式可以带来显著的改进。
- 增强上下文理解:加入额外的 “上下文评估” 步骤,有助于系统在大语言模型的回复中纳入最相关的上下文。
本课程的后续部分将详细介绍这些以及其他持续优化的策略。请记住,我们的目标是创建一个不仅在发布时满足用户需求,而且能够随着时间推移与用户共同发展的系统。
2. 搜索类型
我们必须记住,向量数据库并不是搜索的万能药 —— 它们在语义搜索方面表现出色,但在许多情况下,传统的关键词搜索可以产生更相关的结果,提高用户满意度。为什么会这样呢?这在很大程度上是因为基于余弦相似度等指标进行排名,会使相似度得分较高的结果排在可能包含特定输入关键词的部分匹配结果之上,降低了这些结果对最终用户的相关性。
然而,纯粹的关键词搜索也有其局限性 —— 如果用户输入的术语与存储的数据在语义上相似(但不完全相同),那么潜在有用和相关的结果可能不会被返回。由于这种权衡,现实世界中搜索和检索的用例需要结合关键词搜索和向量搜索,其中向量数据库是关键组成部分(因为它们存储嵌入,支持语义相似性搜索,并且可以扩展到非常大的数据集)。
总结上述要点:
- 关键词搜索:当用户知道自己在寻找什么,并期望结果与搜索词中的精确短语匹配时,关键词搜索可以找到相关、有用的结果。它不需要向量数据库。
- 向量搜索:当用户不确定自己具体在寻找什么时,向量搜索可以找到相关结果。它需要向量数据库。
- 混合(关键词 + 向量)搜索:通常会结合全文关键词搜索和向量搜索的候选结果,并使用交叉编码器模型对它们进行重新排名。它既需要文档数据库,也需要向量数据库。
这可以通过下面的图表有效地可视化:
2.1 语义搜索
语义搜索旨在通过理解搜索查询的内容来提高搜索准确性。与传统搜索引擎仅基于词汇匹配查找文档不同,语义搜索还可以找到同义词。
2.1.1 背景
语义搜索的核心思想是将我们语料库中的所有条目,无论是句子、段落还是文档,都嵌入到向量空间中。
在搜索时,查询也被嵌入到相同的向量空间中,并找到语料库中最接近的嵌入。这些条目应该与查询在语义上有很高的重叠度。
2.2 对称与非对称语义搜索
对于我们的设置来说,对称与非对称语义搜索是一个关键区别:
- 对称语义搜索:我们的查询和语料库中的条目长度大致相同,内容量也相同。例如,搜索相似的问题:我们的查询可能是 “如何在线学习 Python?”,我们想要找到一个类似 “如何在网上学习 Python?” 的条目。对于对称任务,我们有可能交换查询和语料库中的条目。
- 非对称语义搜索:我们通常有一个简短的查询(如一个问题或一些关键词),并且我们想要找到一个较长的段落来回答这个查询。例如,查询 “什么是 Python”,我们想要找到段落 “Python 是一种解释型、高级和通用的编程语言。Python 的设计哲学……”。对于非对称任务,交换查询和语料库中的条目通常没有意义。
我们必须根据任务类型选择合适的模型。
- 适合对称语义搜索的模型:预训练句子嵌入模型
- 适合非对称语义搜索的模型:预训练 MS MARCO 模型
3. 检索算法
3.1 相似性搜索(普通搜索)与最大边际相关性(MMR)
在检索文档时,大多数方法会使用余弦相似度、欧几里得距离或点积等相似性度量。所有这些方法都会返回与我们的查询 / 问题最相似的文档。
然而,如果我们想要相似但又彼此不同的文档呢?这就是最大边际相关性(MMR)发挥作用的地方。
它的目标是在确定返回哪些文档时,考虑检索到的文档之间的相似程度。理论上,我们应该得到一组全面、多样的文档。
在无监督学习的情况下,假设我们最终的关键词短语排名如下:优质产品、很棒的产品、不错的产品、优秀的产品、安装简便、界面美观、重量轻等。但这种方法存在一个问题,像 “优质产品”“不错的产品”“优秀的产品” 这些短语相似,描述了产品的同一属性,并且排名较高。假设我们只有显示 5 个关键词短语的空间,在这种情况下,我们不想展示所有这些相似的短语。
我们希望合理利用这个有限的空间,使关键词短语所展示的关于文档的信息足够多样化。相似类型的短语不应占据整个空间,这样用户可以看到关于文档的各种信息。
- 使用余弦相似度去除冗余短语
- 使用 MMR 对关键词短语重新排名
以上是两种广泛使用的检索方法。其他方法,如多查询检索、长上下文重排序、多向量检索器、父文档检索器、自查询、时间加权向量存储检索等,是一些先进的检索策略,我们将在另一篇博客文章中介绍。
现在,让我们讨论下面的检索和重排流程,看看它是如何提升结果的。
4. 检索与重排
在语义搜索中,我们已经展示了如何使用句子转换器为查询、句子和段落计算嵌入,以及如何将其用于语义搜索。
对于复杂的搜索任务,例如问答检索,使用检索与重排可以显著提高搜索效果。
4.1 检索与重排流程
一个适用于信息检索 / 问答检索的流程如下。本文将提供并解释所有组件:
给定一个搜索查询,我们首先使用一个检索系统,检索出例如 100 个可能与查询相关的结果列表。对于检索,我们可以使用词汇搜索,例如使用 ElasticSearch,或者我们可以使用带有双编码器的密集检索。
然而,检索系统可能会检索到与搜索查询不太相关的文档。因此,在第二阶段,我们使用基于交叉编码器的重排器,对给定搜索查询的所有候选文档进行相关性评分。
输出将是一个排序后的结果列表,我们可以呈现给用户。
4.2 检索:双编码器
词汇搜索在我们的文档集合中查找查询词的字面匹配。它不会识别同义词、首字母缩写词或拼写变体。相比之下,语义搜索(或密集检索)将搜索查询编码到向量空间中,并检索在向量空间中接近的文档嵌入。
语义搜索克服了词汇搜索的缺点,可以识别同义词和首字母缩写词。
4.3 重排器:交叉编码器
对于包含数百万条目的大型文档集合,检索器必须高效。然而,它可能会返回不相关的候选文档。
基于交叉编码器的重排器可以显著提高最终呈现给用户的结果质量。查询和一个可能的文档同时被输入到 Transformer 网络中,然后网络输出一个介于 0 到 1 之间的分数,表示该文档与给定查询的相关程度。
交叉编码器的优点是性能更高,因为它们在查询和文档之间执行注意力机制。
对数千或数百万个(查询,文档)对进行评分会相当缓慢。因此,我们使用检索器创建一组例如 100 个可能的候选文档,然后由交叉编码器对其进行重排。
4.4 预训练双编码器(检索)
双编码器分别为我们的段落和搜索查询生成嵌入。我们可以这样使用它:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('model_name')
docs = ["My first paragraph. That contains information", "Python is a programming language."]
document_embeddings = model.encode(docs)
query = "What is Python?"
query_embedding = model.encode(query)
- MS MARCO:来自必应搜索引擎的 50 万个真实用户查询。查看MS MARCO 模型
4.5 预训练交叉编码器(重排器)
对于预训练模型,我们可以参考:MS MARCO交叉编码器
5. 信息检索的评估
评估信息检索(IR)系统对于做出明智的设计决策至关重要。从搜索到推荐,评估指标对于理解检索中哪些有效、哪些无效至关重要。
信息检索系统的评估指标可以分为两类:在线指标或离线指标。
- 在线指标:在信息检索系统在线实际使用过程中收集。这些指标考虑用户交互,例如用户是否点击了 Netflix 推荐的节目,或者是否点击了电子邮件广告中的特定链接(点击率或 CTR)。有许多在线指标,但它们都与某种形式的用户交互相关。
- 离线指标:在部署新的信息检索系统之前,在隔离环境中进行测量。这些指标关注使用系统检索项目时,是否返回了特定的一组相关结果。
评估指标可以分为离线指标或在线指标。离线指标可以进一步分为无序敏感指标或有序敏感指标,我们很快会解释。
组织通常同时使用离线指标和在线指标来衡量其信息检索系统的性能。然而,首先要使用离线指标在部署前预测系统的性能。
我们将重点介绍最有用和最流行的离线指标:
- 召回率@K
- 平均倒数排名(MRR)
- 平均精度均值@K(MAP@K)
- 归一化折损累计增益(NDCG@K)
这些指标看似简单,但却能为信息检索系统的性能提供宝贵的见解。
我们可以在不同的评估阶段使用这些指标中的一个或多个。在开发 Spotify 的播客搜索时,在 “评估批次” 训练期间使用召回率@K(K = 1),训练后,使用更大的评估集同时计算召回率@K 和 MRR(K = 30)。
目前,要知道 Spotify 能够在向客户部署任何内容之前预测系统性能。这使他们能够成功进行 A/B 测试,并显著提高播客的参与度。
我们还可以将这些指标进一步细分为两类:有序敏感指标和无序敏感指标。这指的是结果的顺序是否会影响指标得分。如果是,则该指标是有序敏感指标;否则,它是无序敏感指标。
5.1 示例
在本文中,我们将使用一个非常小的包含八张图片的数据集。在现实中,这个数字可能是数百万甚至更多。
如果我们搜索 “盒子里的猫”,可能会返回类似上面的结果。数字代表信息检索系统预测的每张图片的相关排名。其他查询会产生不同顺序的结果。
我们可以看到,结果 2、4、5 和 7 是实际相关的结果。其他结果不相关,因为它们显示的是没有盒子的猫、没有猫的盒子或一只狗。
5.2 实际与预测
在评估信息检索系统的性能时,我们将对实际情况和预测情况进行比较,其中:
- 实际情况:指数据集中每个项目的真实标签。如果一个项目与查询相关,则标记为正(p);如果与查询无关,则标记为负(n)。
- 预测情况:是信息检索系统返回的预测标签。如果一个项目被返回,则预测为正($\hat{p}$);如果未被返回,则预测为负($\hat{n}$)。
根据这些实际和预测情况,我们可以生成一组输出,进而计算所有离线指标,即真正例、假正例、真反例和假反例。
正例结果关注信息检索系统返回的内容。以我们的数据集为例,我们让信息检索系统使用 “盒子里的猫” 这个查询返回两个项目。如果返回的是实际相关的结果,这就是一个真正例($p\hat{p}$);如果返回的是不相关的结果,则是一个假正例($n\hat{p}$)。
对于反例结果,我们必须查看信息检索系统未返回的内容。假设我们查询两个结果,任何相关但未被返回的内容都是假反例($p\hat{n}$),而未被返回的不相关项目则是真反例($n\hat{n}$)。
了解了这些之后,我们就可以开始介绍第一个指标了。
5.3 召回率@K
召回率@K 是最易于理解且最受欢迎的离线评估指标之一。它衡量的是返回的相关项目数量($p\hat{p}$)与整个数据集中相关项目的总数($p\hat{p} + p\hat{n}$)的比例。
这里的 K 以及其他所有离线指标中的 K,都表示信息检索系统返回的项目数量。在我们的示例中,整个数据集中总共有 N = 8 个项目,因此 K 可以是 1 到 8 之间的任意值。
以召回率@2 为例,我们返回预测的前 K = 2 个最相关的结果。
当 K = 2 时,召回率@2 的得分计算方法是:返回的相关结果数量除以整个数据集中相关结果的总数,即:
随着 K 值的增加和返回项目范围的扩大,召回率@K 的得分会提高。
随着 K 值增加,返回的正例(无论是真正例还是假正例)增多,召回率@K 的得分也会随之增加。
我们可以在 Python 中轻松计算相同的召回率@K 得分。为此,我们定义一个名为 recall 的函数,该函数接受实际情况列表、预测情况列表和一个 K 值作为参数,并返回召回率@K 得分。
def recall(actual, predicted, k):
act_set = set(actual)
pred_set = set(predicted[:k])
result = round(len(act_set & pred_set) / float(len(act_set)), 2)
return result
使用这个函数,我们来模拟包含 8 张图片的数据集,其中实际相关结果的排名位置为 2、4、5 和 7。
actual = ["2", "4", "5", "7"]
predicted = ["1", "2", "3", "4", "5", "6", "7", "8"]
for k in range(1, 9):
print(f"Recall@{k} = {recall(actual, predicted, k)}")
输出:
Recall@1 = 0.0
Recall@2 = 0.25
Recall@3 = 0.25
Recall@4 = 0.5
Recall@5 = 0.75
Recall@6 = 0.75
Recall@7 = 1.0
Recall@8 = 1.0
优点和缺点
召回率@K 无疑是最容易解释的评估指标之一。我们知道,完美的分数意味着所有相关项目都被返回了。并且,较小的 k 值会让信息检索系统更难在召回率@K 上取得好成绩。
不过,使用召回率@K 也存在一些缺点。通过将 K 增加到 N 或接近 N,我们每次都能得到一个完美的分数,所以仅仅依赖召回率@K 可能会产生误导。
另一个问题是,它是一个无序敏感指标。这意味着,如果我们使用召回率@4,在排名第一的位置返回一个相关结果,与在排名第四的位置返回相同结果,得分是一样的。显然,在更高排名返回实际相关结果更好,但召回率@K 无法体现这一点。
5.4 平均倒数排名(MRR)
平均倒数排名(MRR)是一个有序敏感指标,这意味着,与召回率@K 不同,在排名第一的位置返回实际相关结果的得分要高于在排名第四的位置返回。
MRR 的另一个特点是,它是基于多个查询计算得出的。其计算公式为:
其中,Q 是查询的数量,q 是一个特定的查询,$rank_q$ 是查询 q 的第一个实际相关结果的排名。下面我们逐步解释这个公式。
以上文 “盒子里的猫” 搜索为例,我们再添加两个查询,这样就有 Q = 3 个查询。
在计算 MRR 分数时,我们执行这三个查询。
对于每个查询 q,我们计算其排名倒数 $1/rank_q$。对于第一个查询,第一个实际相关的图片在位置二被返回,所以排名倒数是 $1/2$。下面我们来计算所有查询的倒数排名:
接下来,我们将所有这些查询 q(例如,我们的三个查询)的倒数排名相加:
由于我们计算的是平均倒数排名(MRR),所以必须将总倒数排名除以查询数量 Q,得到平均值:
现在我们用 Python 实现这个计算过程。我们使用相同的实际相关结果,模拟 Q = 3 的情况。
actual_relevant = [
[2, 4, 5, 7],
[1, 4, 5, 7],
[5, 8]
]
Q = len(actual_relevant)
cumulative_reciprocal = 0
for i in range(Q):
first_result = actual_relevant[i][0]
reciprocal = 1 / first_result
cumulative_reciprocal += reciprocal
print(f"query #{i+1} = 1/{first_result} = {reciprocal}")
mrr = 1/Q * cumulative_reciprocal
print("MRR =", round(mrr,2))
输出:
query #1 = 1/2 = 0.5
query #2 = 1/1 = 1.0
query #3 = 1/5 = 0.2
MRR = 0.57
不出所料,我们计算出的 MRR 分数也是 0.57。
优点和缺点
MRR 有其独特的优点和缺点。它是有序敏感的,对于那些第一个相关结果的排名很重要的应用场景(如聊天机器人或问答系统)来说,这是一个巨大的优势。
另一方面,MRR 只考虑了第一个相关项目的排名,而不考虑其他相关项目。这意味着,对于像推荐系统或搜索引擎这样需要返回多个项目的应用场景,MRR 并不是一个好的指标。例如,如果我们想向用户推荐大约 10 个产品,让信息检索系统检索 10 个项目。我们可能只在排名第一的位置返回一个实际相关的项目,而没有返回其他相关项目。十个项目中有九个不相关,这显然是一个很糟糕的结果,但 MRR 却可能给出完美的 1.0 分。
另外,与召回率@K 这样更简单的指标相比,MRR 的可解释性较差。不过,它仍然比许多其他评估指标更容易解释。
5.5 平均精度均值(MAP)
平均精度均值@K(MAP@K)是另一个受欢迎的有序敏感指标。乍一看,这个名字有点奇怪,均值的平均值?但它其实是有意义的。
计算 MAP@K 需要几个步骤。我们先从另一个指标 “精度@K(Precision@K)” 开始:
你可能觉得这看起来和召回率@K 很相似,确实如此!唯一的区别是,这里我们把召回率中的 $p\hat{n}$ 换成了 $n\hat{p}$。这意味着,我们现在只考虑返回项目中的实际相关和不相关结果,而在计算精度@K 时不考虑未返回的项目。
以 “盒子里的猫” 为例,我们返回 K = 2 个项目,并计算精度@2:
注意,精度@K 的分母始终等于 K。现在我们有了精度@K 的值,接下来进入计算平均精度@K(AP@K)的下一步:
$[AP@K=\frac{\sum_{k = 1}^{K}Precision@k\times rel_k}{\text{相关结果的总数}}]$
这里的 $rel_k$ 参数我们之前没见过。它是一个相关性参数,对于 AP@K 来说,当第 k 个项目相关时,$rel_k$ 等于 1;当它不相关时,$rel_k$ 等于 0。
我们使用 k = 1 到 K 迭代计算精度@k 和 $rel_k$。
由于我们将精度@k 和 $rel_k$ 相乘,所以只需要考虑第 k 个项目相关时的精度@k。
根据这些值,对于每个查询 q,我们可以计算出 K = 8 时的 AP@$K_q$ 分数:
每个单独的 AP@$K_q$ 计算都会为每个查询 q 生成一个平均精度@K 分数。要得到所有查询的平均精度均值@K(MAP@K)分数,我们只需将所有 AP@$K_q$ 分数之和除以查询数量 Q:
最终我们得到的 MAP@K 分数为 0.48。用 Python 计算所有这些值的代码如下:
actual = [
[2, 4, 5, 7],
[1, 4, 5, 7],
[5, 8]
]
Q = len(actual)
predicted = [1, 2, 3, 4, 5, 6, 7, 8]
k = 8
ap = []
for q in range(Q):
ap_num = 0
for x in range(k):
act_set = set(actual[q])
pred_set = set(predicted[:x+1])
precision_at_k = len(act_set & pred_set) / (x+1)
if predicted[x] in actual[q]:
rel_k = 1
else:
rel_k = 0
ap_num += precision_at_k * rel_k
ap_q = ap_num / len(actual[q])
print(f"AP@{k}_{q+1} = {round(ap_q,2)}")
ap.append(ap_q)
map_at_k = sum(ap) / Q
print(f"mAP@{k} = {round(map_at_k, 2)}")
输出:
AP@8_1 = 0.54
AP@8_2 = 0.67
AP@8_3 = 0.23
mAP@8 = 0.48
得到的 MAP@K 分数同样为 0.48。
优点和缺点
MAP@K 是一个简单的离线指标,它让我们可以考虑返回项目的顺序,非常适合那些期望返回多个相关项目的应用场景。
MAP@K 的主要缺点是 $rel_k$ 相关性参数是二元的。我们必须将项目视为相关或不相关,它不允许项目之间存在相关性程度上的差异。
5.6 归一化折损累计增益(NDCG@K)
归一化折损累计增益@K(NDCG@K)是另一个有序敏感指标,它可以从一些更简单的指标推导得出。首先是累计增益(CG@K),其计算方式如下:
这里的 $rel_k$ 变量与之前有所不同。它是一个相关性等级范围,0 表示最不相关,某个更高的值表示最相关。等级的数量并不重要,在我们的示例中,使用的范围是 0 到 4。
我们使用 $rel_k$ 根据项目与特定查询 q 的相关性对每个项目进行排名。
我们再用另一个例子来尝试应用这个指标。我们使用与之前类似的包含 8 张图片的数据集。圆圈中的数字代表信息检索系统的预测排名,菱形代表实际排名。
要计算位置 K 处的累计增益(CG@K),我们将预测排名 K 之前的相关性分数相加。当 K = 2 时:
需要注意的是,CG@K 是无序敏感的。如果我们交换图片 1 和图片 2 的位置,当 K≥2 时,尽管将更相关的项目排在了前面,但得分仍然相同。
为了解决这种对顺序不敏感的问题,我们对该指标进行修改,创建了 DCG@K,在公式中添加了 $log_2(1 + k)$ 形式的惩罚项:
现在,当我们计算 DCG@2 并交换前两张图片的位置时,会得到不同的分数。
from math import log2
relevance = [0, 7, 2, 4, 6, 1, 4, 3]
K = 8
dcg = 0
for k in range(1, K+1):
rel_k = relevance[k-1]
dcg += rel_k / log2(1 + k)
使用有序敏感的 DCG@K 指标意味着,更优的交换结果会返回更好的分数。
不幸的是,DCG@K 分数很难解释,因为它们的范围取决于我们为数据选择的 $rel_k$ 范围。我们使用归一化 DCG@K(NDCG@K)指标来解决这个问题。
NDCG@K 是标准 NDCG 的一种特殊修改,它会忽略排名大于 K 的结果。这种修改在衡量搜索性能的应用场景中非常普遍。
NDCG@K 使用理想 DCG@K(IDCG@K)排名对 DCG@K 进行归一化。对于 IDCG@K,我们假设最相关的项目排名最高,并且按照相关性顺序排列。
计算 IDCG@K 只需要对分配的排名进行重新排序,然后执行相同的 DCG@K 计算:
ideal_relevance = sorted(relevance, reverse=True)
idcg = 0
for k in range(1, K+1):
rel_k = ideal_relevance[k-1]
idcg += rel_k / log2(1 + k)
现在,计算 NDCG@K 所需做的就是用 IDCG@K 分数对 DCG@K 分数进行归一化:
dcg = 0
idcg = 0
for k in range(1, K+1):
rel_k = relevance[k-1]
ideal_rel_k = ideal_relevance[k-1]
dcg += rel_k / log2(1 + k)
idcg += ideal_rel_k / log2(1 + k)
ndcg = dcg / idcg
使用 NDCG@K,我们得到了一个更易于解释的结果 0.41。我们知道,1.0 是所有项目排名完美(例如,IDCG@K)时能得到的最好分数。
优点和缺点
NDCG@K 是评估信息检索系统,尤其是网络搜索引擎时最受欢迎的离线指标之一。这是因为 NDCG@K 针对高度相关的文档进行了优化,是有序敏感的,并且易于解释。
然而,NDCG@K 也有一个明显的缺点。我们不仅需要知道哪些项目与特定查询相关,还需要知道每个项目与其他项目相比相关性的高低,这意味着对数据的要求更加复杂。
这些就是一些评估信息检索系统时最受欢迎的离线指标。单个指标可以很好地指示系统性能。如果想要更准确地评估检索性能,我们可以像 Spotify 使用召回率@1、召回率@30 和 MRR@30 那样,使用多个指标。
在 A/B 测试中,这些指标与在线指标配合使用效果最佳,A/B 测试是将检索系统部署到实际应用之前的重要环节。不过,这些离线指标是任何检索项目的基础。
无论是在开发第一个产品的原型,还是在评估谷歌搜索的最新迭代,使用这些指标评估我们的检索系统都有助于我们尽可能地部署最好的检索系统。
结论
在这篇文章中,我们探讨了检索增强生成(RAG)应用程序中的搜索过程,重点介绍了使用向量数据库的语义搜索。我们强调了其减少处理时间和支持实时更新等优势,同时也指出了面临的挑战,比如对独特查询可能给出不太理想的回复。预防这些问题的策略包括监控查询密度和收集用户反馈。优化工作应贯穿构建和生产后阶段,其中扩展知识库是重要的一环。此外,还讨论了搜索类型、语义搜索,以及检索与重排流程,并推荐了预训练模型和诸如召回率@K 等离线评估指标。总体而言,本文强调了在 RAG 应用程序中持续优化和全面评估的必要性。
评论