构建LLM应用:搜索&检索(第五部分)

点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”


作者:Vipra Singh

编译:ronghuaiyang

导读

在系列博客中,我们通过检索增强生成(RAG)应用的视角来学习大规模语言模型(LLM)

440b40cee86662da21af3aaf33fb1d00.png

让我们开始在RAG应用程序中探索寻找相关数据的旅程。

当用户输入查询时,过程包括对用户查询进行分词,并使用与嵌入原始数据相同的模型进行嵌入。接着,根据与用户查询的相似度,从知识库中提取相关的信息块。

91ca7b6bc73196c6090c7250d94911f5.png

向量数据库

本篇博客将全面深入地探讨搜索过程的复杂细节。下图具体展示了“搜索”在RAG流程中的位置。

f7c11cf85f435e73bc3deec2bac6c703.png

通过大语言模型(LLM)的上下文窗口提供领域特定信息,检索增强生成(Retrieval Augmented Generation)降低了产生虚假信息的可能性。

1. 介绍

让我们考虑一个常见场景:使用大语言模型(LLM)开发客户支持聊天机器人。通常,团队拥有大量产品文档,其中包含大量关于其产品、常见问题及使用案例的非结构化数据。

这些数据通过一个称为“分块”的过程被分解。数据被拆分后,每一块都被赋予一个唯一的标识符,并嵌入到向量数据库中的高维空间里。这一过程利用先进的自然语言处理技术来理解每个块的上下文和语义意义。

当客户的提问进来时,LLM运用检索算法迅速识别并从向量数据库中提取最相关的数据块。这种检索基于查询与数据块之间的语义相似度,而不仅仅是关键词匹配。

619b95440ece1f06525e1ac22210dc7a.png

上图展示了如何使用提示模板(如图中所示的‘4’)结合搜索与检索生成最终的大语言模型(LLM)提示上下文。上述视图呈现了搜索与检索在LLM应用中最简化的形式:文档被切分为多个块,这些块被嵌入到向量存储中,随后搜索与检索过程依托此上下文框架来塑造LLM的输出。

此方法带来了多方面优势。首先,它显著减少了LLM处理大量数据所需的时间和计算资源,因为模型只需与相关块交互,无需遍历整个文档。

其次,它支持数据库的实时更新。随着产品文档的变化,向量数据库中的相应块可以轻松更新,确保聊天机器人提供的信息始终最新。

最后,通过聚焦于语义相关的块,LLM能够提供更精确且上下文贴切的回复,从而提升客户满意度。

1.1. 搜索&检索中的问题

尽管搜索与检索方法极大提升了LLM的效率与准确性,但仍存在一些潜在的陷阱。早期识别这些问题可以防止它们影响用户体验。

其中一个挑战出现在用户输入的查询与向量存储中的任何块都不紧密匹配时。系统试图在干草堆中找到一根针,但实际上却一无所获。这种匹配缺失的情况,通常由独特性或高度特定的查询引起,可能导致系统不得不依赖可获得的“最相似”块——那些并非完全相关的块。

随之而来的是LLM的次优响应。由于LLM依赖块的相关性来生成回复,缺乏适当匹配可能导致输出与用户查询仅有肤浅联系,甚至完全不相干。

7744431e70970a3654307c6c856be73b.png

LLM给出的无关或质量欠佳的回复会令用户感到沮丧,降低他们的满意度,并最终导致他们对整个系统和产品失去信任。

监控以下三个方面有助于预防这些问题的发生:

查询密度(漂移):查询密度指的是用户查询在向量存储中的覆盖程度。如果查询密度发生显著漂移,这表明我们的向量存储可能未能涵盖用户查询的全部范围,从而导致紧密关联块的短缺。定期监控查询密度使我们能够发现这些缺口或不足之处。有了这些洞察,我们可以通过增加更多相关块或优化现有块来增强向量存储,提高系统根据用户查询获取数据的能力。

排名指标:这些指标评估搜索与检索系统在选择最相关块方面的表现如何。如果排名指标显示出性能下降,这表明系统区分相关与不相关块的能力可能需要改进。

用户反馈:鼓励用户提供有关LLM回复质量和相关性的反馈,有助于衡量用户满意度并识别改进领域。定期分析这些反馈可以揭示模式和趋势,进而根据需要调整您的应用程序。

1.2. 优化搜索&检索

在您的LLM驱动型应用程序的整个生命周期中,从构建阶段到后期制作,搜索与检索过程的优化应当是一个持续不断的努力。

在构建阶段,应重视制定一套强有力的测试和评估策略。这种方法使我们能够在早期发现问题并优化策略,为系统奠定坚实的基础。

重点需关注的领域包括:

  • 分块策略:评估信息在这一阶段是如何被拆分和处理的,可以帮助发现性能改进的空间。

  • 检索性能:评估系统信息检索的效能可以指示是否需要采用不同的工具或策略,比如上下文排序(context ranking)或HYDE技术。

发布后,进入后期生产阶段,优化工作应继续进行。即使在启动之后,通过明确的评估策略,我们也能主动识别出任何新出现的问题,并持续改进模型的表现。可以考虑的方法包括:

  • 扩展知识库:增加文档资料能显著提升系统的回复质量。扩大的数据集使我们的LLM能够提供更加准确和个性化的回复。

  • 细化分块策略:进一步调整信息的拆分和处理方式可以带来显著的改善。

  • 增强上下文理解:融入额外的“上下文评估”步骤,帮助系统将最相关的上下文整合进LLM的回复中。

关于这些以及其他持续优化策略的具体细节将在课程的后续部分详述。记住,我们的目标是创建一个不仅能满足用户在启动时的需求,而且能随时间与用户共同发展的系统。

2. 搜索的类型

确实,我们必须认识到向量数据库并非搜索的万能药——它们非常擅长语义搜索,但在许多情况下,传统的关键词搜索能产生更相关的结果,从而提高用户满意度。这是为什么呢?很大程度上是因为基于余弦相似度等指标的排名会导致相似度得分较高的结果排在含有特定输入关键词的部分匹配结果之上,从而降低了对终端用户的关联性。

然而,纯粹的关键词搜索也有其局限性——如果用户输入的词语在语义上与存储的数据相似(但不完全相同),可能有用且相关的结果就不会被返回。鉴于这种权衡,实际应用场景中的搜索与检索需要关键词搜索和向量搜索的结合,其中向量数据库构成了关键组成部分(因为它们存储了嵌入,使得语义相似性搜索成为可能,并能扩展至非常庞大的数据集)。

总结上述要点:

  • 关键词搜索:当用户确切知道他们在寻找什么,并期望搜索结果与他们的搜索词中的确切短语相匹配时,能找到相关且有用的结果。不需要向量数据库。

  • 向量搜索:当用户不确定他们确切寻找的内容时,能找到相关结果。需要向量数据库。

  • 混合(关键词+向量)搜索:通常结合全文关键词搜索和向量搜索的候选结果,并使用交叉编码器模型重新排序。需要文档数据库和向量数据库两者。

这一概念可以通过下图有效地可视化展示:

e9af4dd7a5bc395f56774aa42e9d57c2.png

2.1. 语义搜索

语义搜索旨在通过理解搜索查询的内容来提升搜索的准确性。与仅基于词汇匹配查找文档的传统搜索引擎相比,语义搜索还能够找到同义词。

2.1.1. 背景

语义搜索背后的理念是将语料库中的所有条目——不论是句子、段落还是文档——嵌入到一个向量空间中。在搜索时,查询也会被嵌入到同样的向量空间里,然后从语料库中找出最接近的嵌入向量。这些条目应该与查询有很高的语义重叠,意味着它们在意义上与查询最为接近,从而为用户提供最相关的搜索结果。

2524cfd3f2999e775b0f68c0c79123ad.png

2.2. 对称性 vs. 非对称性语义搜索

我们的设置中一个关键区别在于对称性语义搜索非对称性语义搜索

  • 对称性语义搜索中,我们的查询与语料库中的条目长度大致相同,包含的信息量也相仿。一个例子是搜索类似的问题:查询例如可能是“如何在线学习Python?”而我们想要找到诸如“如何在网络上学习Python?”这样的条目。对于对称任务,理论上我们可以将查询与语料库中的条目互换位置来进行搜索。

  • 非对称性语义搜索中,我们通常有一个简短的查询(如一个问题或几个关键词),而希望找到一个较长的段落作为答案。例如,查询可能是“Python是什么”,我们希望找到这样的段落:“Python是一种解释型、高级、通用的编程语言。Python的设计哲学……”。对于非对称任务,交换查询和语料库中的条目通常是没有意义的。

我们需为不同类型的任务选择合适的模型

适合对称性语义搜索的模型:预训练的句子嵌入模型

适合非对称性语义搜索的模型:预训练的MS MARCO模型

3. 检索算法

3.1. 相似度搜索&最大化边际相关性算法(MMR)

在文档检索的场景中,大多数方法会使用相似度度量,如余弦相似度、欧几里得距离或点积。这些方法都会返回与查询或问题最相似的文档。

但是,如果我们希望检索到的相似文档之间也具有多样性怎么办?这时,最大边际相关性(Maximum Marginal Relevance, MMR)就派上了用场。

MMR的目标是在决定返回哪些文档时,考虑到检索到的文档相互之间的相似度。理论上,这样我们应该能得到一组全面且多样化的文档。

无监督学习的情境下,假设我们的最终关键词短语排名如下:优质产品, 优秀产品, 不错的产品, 卓越产品, 易安装, 优良界面, 轻便等但这种方法存在一个问题,像优质产品, 不错的产品, 卓越产品这样的短语含义相似,都定义了产品的同一属性,且排名靠前。假定我们只有空间展示5个关键词短语,在这种情况下,我们不希望展示所有这些类似的短语。

我们希望充分利用这个有限的空间,使得关键词短语所传达的关于文档的信息足够多样化。不应该让相似类型的短语占据整个空间,而是让用户能看到关于文档的各种信息。

为了实现这一目标,可以采取以下两步策略:

  1. 使用余弦相似度移除冗余短语:首先,通过计算短语间的余弦相似度,找出并移除那些极度相似、表达重复信息的短语,以减少冗余。

  2. 使用MMR重新排序关键词短语:接下来,应用MMR算法对剩余的关键词短语进行重新排序。MMR不仅考虑单个短语与查询的相似度(即相关性),还会考虑短语间差异性(即多样性)。它通过平衡每个候选短语与已选集合的平均相似度(以避免选择过于相似的短语)和与查询的直接相关性来挑选下一个最合适的短语加入结果集,以此达到既相关又多样性的目的。

通过这样的策略,我们能在有限展示空间内最大化信息的多样性和全面性,提供给用户更为丰富和均衡的文档概览。

97b7880f3b51cb4124a527725dcda913.png

MMR

上述内容提到了两种常用的检索方法。其他诸如多查询检索、长上下文重排序、多向量检索器、父文档检索器、自我查询、时间加权向量存储检索等,则属于一些更高级的检索策略,我们将在另外的博文中深入介绍。

现在,让我们来探讨一下下方的检索与重排序流程,并了解这一流程是如何优化结果的。

4. 检索 & 重排序

在语义搜索中,我们已经展示了如何使用Sentence Transformers为查询、句子和段落计算嵌入,以及如何利用这些嵌入进行语义搜索。

对于复杂的搜索任务,比如问答检索,通过采用检索与重排序(Retrieve & Re-Rank)的方法,可以显著提升搜索效果。

4.1. 检索 & 重排序管道

一个运作良好的信息检索/问答检索管道如下。本文提供了所有组件并进行了详细解释:

53bce5d9054bfea0f206f55cf03e6b01.png

面对搜索查询,我们首先使用一个检索系统,该系统会检索出一大列表示,比如100个可能相关的命中项。为了进行检索,我们可以使用词法搜索,例如借助ElasticSearch,或者我们可以使用带有双编码器的密集检索。

然而,检索系统可能返回与搜索查询关联性不强的文档。因此,在第二阶段,我们采用基于交叉编码器重排序器来针对给定的搜索查询对所有候选者的相关性进行打分。

输出结果将是一个排序后的命中项列表,我们可以将其呈现给用户。

4.2. 检索:双向编码器

词法搜索会在我们的文档集中寻找查询词汇的字面匹配。它无法识别同义词、缩写或拼写变体。相比之下,语义搜索(或密集检索)将搜索查询编码到向量空间中,并检索在向量空间中与其接近的文档嵌入。

0cdc2bfffcda327740ff02623fd80df6.png

语义搜索克服了词法搜索的局限性,能够识别同义词和缩略词

4.3. 重排序器:交叉编码器

检索器必须对包含数百万条目的大型文档集保持高效。但是,它可能返回与查询无关的候选文档。

基于交叉编码器的重排序器可以显著提升最终用户得到的结果。查询和一个可能的文档同时传递给Transformer网络,然后网络输出一个介于0和1之间的单一分数,表示文档对于给定查询的相关程度。

ccb4a1c7a06c78b792f913f87b32b00b.png

交叉编码器的优点在于其更高的性能,因为它们在查询和文档之间执行了注意力机制。

对数千或数百万个(查询,文档)对进行评分会相当缓慢。因此,我们使用检索器来生成例如100个可能的候选集,然后由交叉编码器对这些候选进行重新排序。

0d5b0b3817ecc3a8d1167849c84daa23.png

4.4. 例子脚本

  • retrieve_rerank_simple_wikipedia.ipynb [Colab 版本]: 此脚本使用规模较小的简明英语维基百科作为文档集合,以回答用户的查询或问题。首先,我们将所有维基百科文章拆分为段落,并使用双编码器对它们进行编码。当输入新的查询或问题时,同样使用双编码器对其进行编码,然后根据余弦相似度最高的段落进行检索(参见语义搜索)。接下来,检索到的候选段落由交叉编码器重排序器评分,交叉编码器给出的前5个最高分段落将展示给用户。

  • in_document_search_crossencoder.py: 如果只有少量段落,则不需要检索阶段。例如,当我们想在单个文档内部执行搜索时就会出现这种情况。在此示例中,取关于欧洲的维基百科文章并将其拆分为段落。然后,使用交叉编码器重排序器对搜索查询/问题及所有段落进行评分。与查询最相关的段落将被返回。

4.5. 预训练双向编码器(检索)

双编码器独立地为我们的段落和搜索查询生成嵌入。我们可以这样使用它:

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: 来自Bing搜索引擎的50万条真实用户查询。参见MS MARCO模型

4.6. 预训练交叉编码器(重排器)

对于预训练模型,我们可以参考: MS MARCO Cross-Encoders

5. 信息检索的评估

信息检索(IR)系统的评估对于做出明智的设计决策至关重要。从搜索到推荐,评估指标对于理解检索中什么有效、什么无效是至关重要的。

IR系统的评估指标可以分为两大类在线离线度量。

在线度量 是在IR系统处于在线运行状态下,实际使用过程中收集的。这些指标考虑了用户交互,比如用户是否点击了Netflix推荐的节目,或是从电子邮件广告中点击了某个特定链接(点击率或CTR)。在线度量有很多,但都与某种形式的用户交互有关。

离线度量 则是在部署新IR系统之前,在隔离环境中测量的。这些指标考察的是当使用系统检索项目时,是否返回了一组特定的相关结果。

f6c2cc99b1db8386104e70620d6f4586.png

评估指标可以进一步细分为离线或在线指标。离线指标又可以细分为无序感知或有序感知,我们很快将会解释这两者。

通常会同时使用离线和在线指标来衡量其IR系统的性能。然而,这一过程通常先从使用离线指标预测系统部署前的性能开始。

我们将重点介绍一些最有用且流行的离线指标:

  • 召回率@K(Recall@K)

  • 平均倒数排名(MRR, Mean Reciprocal Rank)

  • 平均精度@K(MAP@K, Mean Average Precision@K)

  • 归一化折损累积增益(NDCG@K, Normalized Discounted Cumulative Gain)

这些指标看似简单,却能为IR系统的性能提供极其宝贵的洞察。

我们可以在不同的评估阶段使用这些指标中的一个或多个。在开发Spotify的播客搜索功能时,召回率@K(使用K=1)被用于“评估批次”的训练过程中;而训练完成后,两项指标 — 使用K=30的召回率@KMRR— 则被应用于一个大得多的评估集上。

目前,需理解的关键点是Spotify能够在向客户部署任何内容之前预测系统性能。这使得他们能够实施成功的A/B测试,并显著提升了播客的参与度。

对于这些指标,我们还有两个子分类:有序感知(order-aware)和无序感知(order-unaware)。这指的是结果顺序是否影响指标得分。如果顺序影响得分,则该指标为有序感知;否则,为无序感知。

5.1. 例子

在整篇文章中,我们将使用一个非常小的数据集,包含八张图片。实际上,这个数量很可能会达到数百万乃至更多。

835639ce6416b269eab7c3ef9c301fe9.png

8张图的可能的排序结果

如果我们搜索“盒子里的猫”,我们可能会得到类似上面的结果。数字代表了IR系统预测的每张图片的相关性排名。其他的查询将产生不同的结果顺序。

dd6375d2ca86f31428c030d889f07768.png

示例查询及排名,其中高亮显示了实际相关的结果。

我们可以看到,编号为2457的结果是真正相关的结果。其他结果则不相关,因为它们显示的是没有盒子的猫、没有猫的盒子,或者是一只狗。

5.2. 实际 vs. 预测

在评估IR系统的性能时,我们将比较实际与预测的情况,其中:

  • 实际情况指的是数据集中每个项目的真标签。如果项目与查询相关,它们被视为正例p);如果不相关,则视为反例n)。

  • 预测情况是由IR系统返回的预测标签。如果返回了一个项目,那么它被预测为正例p^);如果没有返回,则预测为反例n^)。

基于这些实际和预测条件,我们创建了一组输出,从中计算出所有的离线指标。这些输出包括真正例、假正例、真反例和假反例。

正例结果关注的是IR系统返回的内容。鉴于我们的数据集,我们让IR系统使用“盒子里的猫”查询返回两个项目。如果返回的是实际相关的结果,这就是一个真正例pp^);如果返回的是不相关的结果,则是一个假正例np^)。

c07601778bf309dc543385c9cb20af8e.png

对于反例结果,我们需要关注IR系统未返回的内容。让我们查询两个结果。任何相关未被返回的项目都是假反例pn^)。那些未被返回的不相关项目则是真反例nn^)。

考虑到以上所有内容,我们可以开始介绍第一个指标了。

5.3. Recall@K

召回率@K是最易解释且最受欢迎的离线评估指标之一。它衡量了相对于整个数据集中存在的相关项目总数(pp^+pn^),系统返回了多少相关项目(pp^)。

4d987e220e04b88d15ce00fea5879d0b.png

这里的K以及所有其他离线指标中的K,指的是IR系统返回的项目数量。在我们的例子中,整个数据集中共有N = 8个项目,因此K可以是[1 ,…, N]之间的任意值。

73983b6cc425030f903adf208dfdb8b4.png

通过召回率@2,我们返回预测的最相关前K = 2个结果

K = 2时,我们的召回率@2得分是通过将返回的相关结果数量除以整个数据集中相关结果的总数来计算的。也就是说:

ea0619770aac5aa8159b7fba3db96413.png

随着K增大和返回项目范围的扩大,召回率@K的得分也会提高。

723a4691035ee2afef5c17ab2bdaa12f.png

随着K的增加,召回率@K的得分会上升,这是因为返回的正例(无论是真是假)数量增多。

我们可以在Python中轻松计算相同的召回率@K得分。为此,我们将定义一个名为recall的函数,该函数接受实际情况预测情况的列表,以及K值,并返回召回率@K的得分。

# recall@k function
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

利用这个方法,我们将复制包含八个图像的数据集,其中实际相关的结果位于第2457的排名位置。

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值会让IR系统更难在召回率@K上取得好成绩。

尽管如此,使用召回率@K也存在一些缺点。通过将K增加到N或接近N,我们每次都能得到完美分数,因此单纯依赖召回率@K可能会产生误导。

另一个问题是,它是一个无序感知指标。这意味着如果我们使用召回率@4,并在一个排名位置返回一个相关结果,我们将与在第四个排名位置返回相同结果获得相同的得分。显然,在更高的排名位置返回实际相关结果更好,但召回率@K无法反映这一点。

5.4. 平均倒数排名(MRR, Mean Reciprocal Rank)

平均倒数排名(MRR, Mean Reciprocal Rank)是一个有序感知指标,这意味着与召回率@K不同,它认为在排名第一的位置返回一个实际相关的结果比在排名第四的位置返回得分更高。

MRR的另一个特点是,它是基于多个查询计算得出的。具体计算方式为:

f3ae8c47e9404230726e7e11690a44a1.png

Q是查询的数量,q是一个具体的查询,而rank-q是查询q的第一个实际相关结果的排名。我们将逐步解释这个公式。

以我们之前的例子为例,即用户搜索“盒子里的猫”。我们再增加两个查询,这样我们就有Q=3。

534cbb22a81b801fb08c19aa42154d06.png

我们在计算MRR得分时执行三个查询。

我们为每个查询q计算排名倒数1/rankq。对于第一个查询,第一个实际相关的图片在位置被返回,因此排名倒数是1/2。现在,让我们计算所有查询的排名倒数:

4bad63ceb58f94326a441dfd782ac3d1.png

接下来,我们对所有查询q=[1,..., Q](例如,我们的三个查询)的排名倒数求和:

cebb0961bb7ea790d214a8c232e0b5be.png

由于我们计算的是平均倒数排名(MRR),我们必须通过将总倒数排名除以查询数量Q来取平均值:

6ff1e5bb2bb6ca547ec606fcee871403.png

现在,让我们将这个过程转化为Python代码。我们将重现同样的场景,其中Q=3,并使用相同的实际相关结果。

# relevant results for query #1, #2, and #3
actual_relevant = [
    [2, 4, 5, 7],
    [1, 4, 5, 7],
    [5, 8]
]
# number of queries
Q = len(actual_relevant)

# calculate the reciprocal of the first actual relevant rank
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}")
# calculate mrr
mrr = 1/Q * cumulative_reciprocal
# generate results
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并不是一个好的指标。例如,如果我们想向用户推荐大约10件商品,我们会要求IR系统检索10个项目。我们可能会在排名第一位返回一个实际相关的商品,而不再返回其他相关商品。十个中有九个是不相关的商品,这是一个糟糕的结果,但MRR会给出完美的1.0得分。

另一个较小的缺点是,相比召回率@K这样的简单指标,MRR的可解释性较差。然而,与其他许多评估指标相比,它的可解释性仍然更强。

5.5. 平均精度均值(MAP, Mean Average Precision)

平均精度均值@KMAP@K, Mean Average Precision@K)是另一个流行的有序感知指标。乍一看,它的名字似乎有些奇怪,平均均值?这是有道理的,我们保证。

计算MAP@K有几个步骤。我们从另一个称为精度@K的指标开始:

c54fc7afb216a4f0abc81e3d7959966b.png

你可能会觉得这看起来与召回率@K非常相似,确实如此!唯一的区别是我们在这里将召回率中的pn^换成了np^。这意味着我们现在只考虑返回项目中的实际相关和非相关结果。在使用精度@K时,我们不考虑未返回的项。

58c4df030d77bfb7d7dd2fe4d196482a.png

K = 2时,召回率@K和精度@K计算的区别。

让我们回到“盒子里的猫”的例子。我们将返回K=2个项,并计算精度@2。

f419bcaded4d894e5703343d31a993e0.png

K=2时,计算精度@K

ef15da905ad82d2e28161ea47315cad2.png

注意,在精度@K中,分母始终等于K。现在我们已经得到了精度@K的值,接下来进行计算平均精度@KAP@K)的下一步:

17ed4c45396407f08f56cda683515583.png

注意,对于AP@K,我们对k=[1,..., K]的所有值计算精度@k的平均得分。这意味着对于AP@5,我们计算k=[1,2,3,4,5]时的精度@k

我们之前没有见过rel-k参数。这是一个相关性参数,对于AP@K而言,当第k个项相关时,它的值等于1;不相关时,值为0

7d4c93ee6c53661fc4464874aee05a4e.png

我们迭代地使用k=[1,...,K]计算精度@krel-k

由于我们对精度@krel-k进行乘法运算,因此我们只需考虑第k个项相关的精度@k

e3ed2ea72f8140ef868bc87eb279aaee.png

在三个查询q = [1, …, Q]下,所有相关项的精度@k和rel_k值。

0cc309b60fe38752f1504b0774c4e04d.png

每个单独的AP@Kq计算都会为每个查询q产生一个单一的平均精度@KAP@K)得分。要获取所有查询的平均平均精度@KMAP@K)得分,我们只需除以查询的数量Q

e83ee702fc79a940a273a74423ce569c.png

这样我们就得到了最终的MAP@K得分为0.48。要用Python计算所有这些,我们编写:

# initialize variables
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 = []

# loop through and calculate AP for each query q
for q in range(Q):
    ap_num = 0
    # loop through k values
    for x in range(k):
        # calculate precision@k
        act_set = set(actual[q])                                                                                                                                   
        pred_set = set(predicted[:x+1])
        precision_at_k = len(act_set & pred_set) / (x+1)
        # calculate rel_k values
        if predicted[x] in actual[q]:
            rel_k = 1
        else:
            rel_k = 0
        # calculate numerator value for ap
        ap_num += precision_at_k * rel_k
    # now we calculate the AP value as the average of AP
    # numerator values
    ap_q = ap_num / len(actual[q])
    print(f"AP@{k}_{q+1} = {round(ap_q,2)}")
    ap.append(ap_q)
# now we take the mean of all ap values to get mAP
map_at_k = sum(ap) / Q
# generate results
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, Normalized Discounted Cumulative Gain)

归一化的折损的累积的增益@K(NDCG@K)是我们可以从几个更简单的指标推导出的另一个有序感知指标。从累积增益CG@K)开始,计算如下:

225b4a757c87f5e41dd8146e92d37154.png

这次的rel-K变量有所不同。它是一个相关性等级范围,其中0是最不相关的,而某个较高的值是最相关的。等级的数量并不重要;在我们的例子中,我们使用0→4的范围。

13768eb7d97f9c174ffb751af85cbb1f.png

使用rel_k,我们根据项目与特定查询q的相关性对每个项进行排名。

让我们尝试将此应用到另一个例子中。我们使用与之前类似的八个图像的数据集。圈起来的数字代表IR系统预测的排名,而菱形代表rel-k实际排名。

44309be6fc97bca095adf264b79117cc.png

一个小型数据集,其中包含了预测排名(圆圈)和实际排名(菱形)。

为了计算位置K处的累积增益(CG@K),我们对预测排名K之前的关联性得分进行求和。当K=2时:

ca857f5a84ef6b7e0cce56e53ea997d9.png

需要注意的是,CG@K并不具备有序感知能力。如果我们交换图片12的位置,尽管将更相关的项目放在首位,但在K≥2时,我们仍会得到相同的得分。

edf59f24f3867b397d328c313f501bae.png

图像1和2进行了交换

294edeaa10ce922e5d983d4d6057c903.png

为了解决这种缺乏有序感知的问题,我们修改指标创建DCG@K,在公式中以*log_2(1+k)*的形式添加惩罚项:

3afb5260479d3d176dcaa617539876f4.png

现在,当我们计算DCG@2并交换前两张图片的位置时,我们得到的得分是不同的:

b94ea76a849b8c0c6a5f1cae43ed1272.png

from math import log2
# initialize variables
relevance = [0, 7, 2, 4, 6, 1, 4, 3]
K = 8
dcg = 0
# loop through each item and calculate DCG
for k in range(1, K+1):
    rel_k = relevance[k-1]
    # calculate DCG
    dcg += rel_k / log2(1 + k)

3448db71542395fa39bc4ab5907390ce.png

使用query#1时DCG@K得分随着K的增加而增加

使用有序感知DCG@K指标意味着,经过优选交换的结果会得到更好的得分。

不幸的是,DCG@K得分很难解释,因为其范围取决于我们为数据选择的rel-k范围。我们使用归一化DCG@KNDCG@K)指标来解决这个问题。

NDCG@K是标准NDCG的一个特殊修改版本,它会截断所有排名大于K的结果。这种修改在衡量搜索性能的应用案例中非常常见。

NDCG@K使用Ideal DCG@KIDCG@K)排名对DCG@K进行归一化。对于IDCG@K,我们假设最相关的项目排名最高,并按相关性顺序排列。

计算IDCG@K只需要重新排序分配的排名,并执行相同的DCG@K计算:

2821803b708dd07055e32e5d1a27f2b2.png

# sort items in 'relevance' from most relevant to less relevant
ideal_relevance = sorted(relevance, reverse=True)

print(ideal_relevance)

idcg = 0
# as before, loop through each item and calculate *Ideal* DCG
for k in range(1, K+1):
    rel_k = ideal_relevance[k-1]
    # calculate DCG
    idcg += rel_k / log2(1 + k)

7e311b111395e2a312545e65fa5680b1.png

随着K增加,使用查询#1的结果顺序计算的DCG@K得分与IDCG@K得分的对比。

现在,我们只需要使用IDCG@K得分对DCG@K得分进行归一化,就可以计算NDCG@K了:

38f7c5dec168d0db8ea174e2e922355a.png

dcg = 0
idcg = 0

for k in range(1, K+1):
    # calculate rel_k values
    rel_k = relevance[k-1]
    ideal_rel_k = ideal_relevance[k-1]
    # calculate dcg and idcg
    dcg += rel_k / log2(1 + k)
    idcg += ideal_rel_k / log2(1 + k)
    # calcualte ndcg
    ndcg = dcg / idcg

144da4ac9d87b459285e24dd22dda566.png

随着K增加,通过使用IDCG@K对DCG@K进行归一化来计算NDCG@K得分。

使用NDCG@K,我们得到了一个更易解释的结果0.41,我们知道1.0是我们可以通过所有项目完美排名(例如,IDCG@K)获得的最好的得分。

优缺点

NDCG@K是评估IR系统,特别是网络搜索引擎时最流行的离线指标之一。这是因为NDCG@K优化了高度相关的文档,具有有序感知的能力,并且易于解释。

然而,NDCG@K有一个显著的缺点。我们不仅需要知道哪些项目与特定查询相关,还需要知道每个项目是否比其他项目更相关或不太相关;数据需求更为复杂。

dda6e266f5e8de0a728f10c0d9fea48f.png

适用于其他指标的数据示例(左)以及NDCG@K所需更复杂数据的示例(右)。

这些是最受欢迎的用于评估信息检索系统的离线指标。单个指标可以很好地指示系统性能。为了对检索性能有更大的信心,我们可以使用多种指标,就像Spotify使用recall@1、recall@30和MRR@30一样。

在A/B测试中,这些指标最好与在线指标结合使用,这是在将检索系统部署到全局之前的下一步。然而,这些离线指标是任何检索项目背后的基础。

无论我们正在原型设计我们的第一个产品,还是评估Google搜索的最新迭代,使用这些指标评估我们的检索系统将帮助我们部署尽可能最好的检索系统。

结论

在这篇博文中,我们探讨了检索增强生成(RAG)应用中的搜索流程,特别强调了使用向量数据库的语义搜索。它突出了诸如减少处理时间和实时更新等优势。面临的挑战包括对独特查询响应不佳。预防问题的策略包括监控查询密度和收集用户反馈。优化工作应集中在构建和后期制作阶段,包括扩展知识库。博文还讨论了搜索的类型、语义搜索以及检索与重排管道。建议使用预训练模型和诸如召回率@K等离线评估指标。总的来说,它强调了RAG应用中持续优化和全面评估的必要性。

70946eb72ed22a952514edabb99a19e3.png

—END—

英文原文:https://medium.com/@vipra_singh/building-llm-applications-retrieval-search-part-5-c83a7004037d

d6be7f838492cbc18701f5f20b86890d.jpeg

请长按或扫描二维码关注本公众号

喜欢的话,请给我个在看吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值