📍 你在这里 AI 应用 · 第 1 篇 / 共 8 篇

刚进这个阶段?AI 学习路线图 是总入口,零基础请从人工智能简史:AI 是怎么诞生的看起。

← 上一篇 信息熵:抛三次硬币,看懂语言模型凭什么把文本压短 · 下一篇 → 文本向量化:RAG 的基础

RAG 概述

ChatGPT、GLM 等生成式人工智能在文本生成、文本到图像生成等任务中表现出令人印象深刻的性能。但它们也存在固有局限性,包括产生幻觉、缺乏对生成文本的可解释性、专业领域知识理解差,以及对最新知识的了解有限。为了克服这些限制,提高模型的能力,有两种主要途径:一种是微调(Fine Tune)来更新模型,另一种是让他们能够与外部世界互动,以不同的形式和方式获取知识。

微调固然效果好,可以让模型真正的“学会”一些私域知识。但是微调也会带来几个问题:首先,由于生成模型依赖于内在知识(权重),因此模型还是无法摆脱幻觉的产生,在对理解门槛高且准确性要求严格的场景下,这就是完全无法接受的,因为用户很难从回答的表面看出模型是否是在胡说八道。其次,在真实场景中,每时每刻都在产生大量数据,对一个事物的概念会迭代的飞快,如某个政策的解读、某个指标的调整等。而模型微调并不是一个简单的工作,无论是从数据准备、算力资源、微调效果、训练时间等各个角度来看,随时用新产生的数据来进行微调都是不现实的,且最终微调的效果也无法保证,能够做到每月更新一次都已经是很理想的状态。

另一种解决方案,RAG(Retrieval Augmented Generation)为生成式模型提供了与外部世界互动提供了一个很有前景的解决方案。RAG的主要作用类似搜索引擎,找到用户提问最相关的知识或者是相关的对话历史,并结合原始提问(查询),创造信息丰富的prompt,指导模型生成准确输出。其本质上应用了情境学习(In-Context Learning)的原理。

在大型语言模型(LLM)领域,创建一个最小可行产品相对简单,但实现生产级性能和可用性是一个艰巨的任务,特别是构建高性能的RAG系统。RAG已在企业私域知识问答等领域广泛应用,例如,现在主流的各类chat to PDF,chat to doc等应用都是基于RAG来搭建的。

RAG 基本术语:

RAG 基本流程

如下图所示RAG可分为5个基本流程:知识文档的准备;嵌入模型(embedding model);向量数据库;查询检索和生产回答。下面会对每个环节进行详细描述:

知识文档的准备

在构建高效的检索增强生成(RAG)系统过程中,首要任务是准备知识文档。实践中,我们遇到的知识源可能涵盖多种格式,包括但不限于Word文档、TXT文件、CSV数据表、Excel电子表格以及PDF文档、图像和视频资料等。

因此,初步阶段需要采用专门的文档解析工具(如针对PDF文档的文本提取器)或跨模态处理技术(例如光学字符识别(OCR)),将这些多样化的信息资源转换为大型语言模型能够理解的纯文本格式。具体来说,在处理PDF格式时,可以运用PDF文本抽取工具来获取其中的文字内容;而对于图像与视频材料,则可通过OCR技术实现文字信息的自动识别与转换。

嵌入模型

在 RAG 系统中,检索阶段的核心并不是“查关键词”,而是比较向量之间的相似程度。当用户提出一个问题时,问题会被编码成一个向量;与此同时,知识库中的文档也早已被切分并编码为多个知识块向量。检索的本质,就是判断“哪个知识块在语义空间中最接近这个问题”。

但“接近”本身并不是一个唯一的概念。它既可以理解为语义方向是否一致,也可以理解为在向量空间中的实际距离是否足够近。正因为存在这两种不同的理解方式,RAG 系统中才会同时出现 余弦相似度 和 欧式距离 这两类相似度度量方法。

余弦相似度衡量问题向量与知识块向量之间关系的方式,核心关注点在于两个向量的方向是否一致。即使向量长度不同,只要夹角较小,就说明二者在语义上高度相关。

欧式距离是将向量视为高维空间中的点,通过计算两点之间的直线距离来判断相关性。距离越小,表示两个向量在数值空间中越接近。

嵌入模型的构建方法多种多样,以Google开发的Word2Vec模型为例,我们将深入探讨其训练机制。

Word2Vec模型提供了两种主要的学习策略:连续词袋模型(Continuous Bag of Words, CBOW)和Skip-gram模型。为了简化讨论,本例将聚焦于CBOW模型。
CBOW模型的核心理念是在给定目标词汇上下文的前提下预测该中心词汇。例如,在句子“The cat sat on the mat”中,若“sat”被选作目标词汇,则其余词汇作为上下文信息用于预测“sat”。具体训练步骤如下:

  1. One-Hot编码转换:首先,对每个上下文词汇(如“the”, “cat”, “on”, “the”, “mat”)进行 One-Hot 编码。这种编码方式生成一个高维度稀疏向量,其中仅与特定词汇对应的元素值为1,其余均为0。比如,“the”的One-Hot表示可能为[1, 0, 0, 0, 0]等。
  2. 词向量生成:随后,每一个One-Hot向量都将乘以一个大小为(V \times N)的权重矩阵(W)((V)代表词汇表规模,(N)指定了词嵌入的空间维度)。通过此操作,每个词汇都被映射到一个密集向量空间内的位置。接着,所有这些生成的词向量被累加并平均化,形成一个综合反映整个上下文语境的向量(e)(即投影层中的1×N向量)。
  3. 输出层计算:接下来,利用另一个权重矩阵(W')对向量(e)执行线性变换,并应用Softmax函数来标准化结果,从而得到一个长度为(V)的概率分布向量。该向量展示了模型对于每个词汇成为当前窗口内中心词的可能性估计。
  4. 参数更新:最后一步涉及根据实际中心词与模型预测之间的差异调整权重矩阵(W)和(W')。这一过程通常采用梯度下降法实现,旨在最小化损失函数,促使模型逐渐优化其预测能力。

除了Word2Vec之外,还有诸如BERT、GPT系列等更为先进的预训练语言模型,它们借助更复杂的神经网络架构捕捉文本数据中的深层次语义关联。总之,嵌入技术在自然语言处理领域扮演着至关重要的角色,它不仅能够高效地表达词语间的复杂关系,同时也极大地提升了信息检索系统的准确性和相关性。

向量数据库

向量数据库主要功能是高效地存储和检索向量形式的数据。这类数据通常以多维数组的形式存在,能够表示各种信息,如文本、图像或音频等经过转换后的特征向量。
当使用 RAG 系统时,首先会通过一种称为嵌入模型的技术将原始数据转换成向量格式。这些模型能够捕捉到输入数据的关键特征,并将其编码为固定长度的向量。之后,所有由嵌入模型产生的向量都会被保存在一个专门为此目的构建的向量数据库中。这样的做法有几个明显的优势:

  1. 高效的相似性搜索:向量数据库采用了多种算法和技术来优化向量之间的相似度计算过程,使得即使是在非常大的数据集上也能快速找到与给定查询最接近的结果。
  2. 大规模数据处理能力:由于采用了专门针对高维度数据结构进行优化的设计,向量数据库能够有效地管理和查询包含数百万甚至数十亿条记录的大规模数据集合。
  3. 灵活性与可扩展性:随着业务需求的变化和发展,向量数据库提供了灵活的架构支持,允许轻松添加新的数据源或者调整现有索引策略,同时保持良好的性能表现。

查询检索

再经过上述几个步骤的准备后,我们就可以开始进行处理用户查询了。首先,用户的问题会被输入到嵌入模型中进行向量化处理。然后,系统会在向量数据库中搜索与该问题向量语义上相似的知识文本或历史对话记录并返回。

生成回答

最终将用户提问和上一步中检索到的信息结合,构建出一个提示模版,输入到大语言模型中,静待模型输出答案即可。

RAG 评估指标

RAG(Retrieval-Augmented Generation)系统的评估需要从检索和生成两个阶段分别评估。

若不细分,也可通过一下核心指标进行粗判:

检索阶段核心指标

  1. 精确率(Precision)

定义:检索到的相关文档占检索文档总数的比例。

公式:Precision = 检索到的相关文档数 / 检索文档总数

示例:

  • 检索10篇文档,其中7篇相关 → Precision = 0.7
  • 评估检索结果的相关性,值越高说明检索越精准

应用场景:评估检索结果的相关性,适用于对精度要求高的场景。

  1. 召回率(Recall)

定义:检索到的相关文档占所有相关文档的比例。

公式:Recall = 检索到的相关文档数 / 所有相关文档数

示例:

  • 总共有20篇相关文档,检索到12篇 → Recall = 0.6
  • 评估检索的完整性,值越高说明覆盖越全面

应用场景:评估检索的完整性,适用于对覆盖率要求高的场景。

  1. F1分数(F1-Score)

定义:精确率和召回率的调和平均数,平衡两者关系。

公式:F1 = 2 × (Precision × Recall) / (Precision + Recall)

特点:

  • 在精确率和召回率之间取得平衡
  • 当两者都重要时,F1是综合评估的最佳选择

应用场景:需要同时考虑精确率和召回率的综合评估。

  1. NDCG(归一化折损累积增益)

定义:考虑排序位置的检索质量指标。

公式:NDCG@k = DCG@k / IDCG@k

特点:

  • 对排序位置敏感,位置越靠前权重越高
  • 更符合实际应用需求(用户更关注Top结果)

应用场景:评估Top-K检索结果的排序质量,是检索系统最重要的排序指标。

生成阶段核心指标

  1. BLEU 分数

定义:基于n-gram匹配的文本相似度评估指标。

特点:计算快速,适合大规模评估。不考虑语义,只关注表面匹配。

应用场景:评估生成文本与参考答案的表面相似度。

  1. ROUGE 分数

定义:基于n-gram重叠的召回率评估指标。

主要变体:

  • ROUGE-N:基于n-gram重叠的召回率
  • ROUGE-L:基于最长公共子序列
  • ROUGE-W:基于加权最长公共子序列

应用场景:文本摘要、问答任务评估。

  1. BERTScore

定义:基于BERT嵌入的语义相似度评估。

特点:考虑语义相似度,不依赖表面匹配,更符合人类评估标准,但计算成本较高。

应用场景:需要语义理解的生成评估,评估质量更准确。

  1. 事实准确性(Factual Accuracy)

定义:生成内容的事实正确性,RAG系统最重要的质量指标之一。

评估方法:

  1. 与检索文档的一致性检查:生成内容是否与检索到的文档一致
  2. 外部知识库验证:通过外部知识库验证事实
  3. 人工事实核查:专业标注者进行事实核查

重要性:RAG系统的核心价值在于提供准确的事实信息,事实准确性直接影响系统可信度。

  1. 相关性(Relevance)

定义:生成内容与查询的相关程度。

评估方法:

  • 语义相似度计算
  • 关键词匹配
  • 人工评估

应用场景:确保生成内容回答了用户的问题。

RAG 优化

刚才讲述的流程仅仅为基本的 RAG 流程,但是其中的各个环节均有着极大的优化空间。下面我们将从上述5个环节中穿插12个具体优化策略来依次讲解。下面介绍的方法均在AI开发框架 langchain 和 LLamaIndex 中有具体实现,具体操作方法可参考官方文档。

数据清洗

高性能RAG系统依赖于准确且清洁的原始知识数据。一方面为了保证数据的准确性,我们需要优化文档读取器和多模态模型。特别是处理如CSV表格等文件时,单纯的文本转换可能会丢失表格原有的结构。因此,我们需引入额外的机制以在文本中恢复表格结构,比如使用分号或其他符号来区分数据。另一方面我们也需要对知识文档做一些基本数据清洗其中可以包括:

  1. 基本文本清理:规范文本格式,去除特殊字符和不相关信息。除重复文档或冗余信息。
  2. 实体解析:消除实体和术语的歧义以实现一致的引用。例如,将“LLM”、“大语言模型”和“大模型”标准化为通用术语。
  3. 文档划分:合理地划分不同主题的文档,不同主题是集中在一处还是分散在多处?如果作为人类都不能轻松地判断出需要查阅哪个文档才能来回答常见的提问,那么检索系统也无法做到。
  4. 数据增强:使用同义词、释义甚至其他语言的翻译来增加语料库的多样性。
  5. 用户反馈循环:基于现实世界用户的反馈不断更新数据库,标记它们的真实性。
  6. 时间敏感数据:对于经常更新的主题,实施一种机制来使过时的文档失效或更新。

分块处理

在RAG系统中,文档需要分割成多个文本块再进行向量嵌入。在不考虑大模型输入长度限制和成本问题情况下,其目的是在保持语义上的连贯性的同时,尽可能减少嵌入内容中的噪声,从而更有效地找到与用户查询最相关的文档部分。如果分块太大,可能包含太多不相关的信息,从而降低了检索的准确性。相反,分块太小可能会丢失必要的上下文信息,导致生成的回应缺乏连贯性或深度。在RAG系统中实施合适的文档分块策略,旨在找到这种平衡,确保信息的完整性和相关性。一般来说,理想的文本块应当在没有周围上下文的情况下对人类来说仍然有意义,这样对语言模型来说也是有意义的。

分块方法的选择:

  1. 固定大小的分块:这是最简单、也最直接的一种方式。做法是为每个分块设定固定的字符数或 token 数,并在相邻分块之间保留一定的重叠区域,以避免语义在边界处被截断。这种方式实现成本低、计算开销小,适合结构较为规整或对语义连续性要求不高的文本场景。不过,由于它并不关心文本本身的语义结构,可能会在段落或句子中间强行切分。
  2. 内容分块:根据文本本身的结构进行切分。例如,按照标点符号(句号、分号)或段落进行分割,或者直接使用 NLTK、spaCy 等 NLP 工具提供的句子分割能力。这种方式能更好地保留语义完整性,但在面对长句或结构复杂的文本时,仍然可能出现分块大小不均的问题。
  3. 递归分块:递归分块是实际工程中最常用、也最灵活的一种方法。它通过一组由“粗到细”的规则,逐层拆解文本,直到满足预期的块大小。以 LangChain 中的实现为例,通常会先按段落分隔符(\n\n)进行切分;如果得到的块仍然过大,再使用单换行符(\n)继续拆分;如果还不满足要求,则进一步按空格或句号进行分割。这个过程会不断递归,直到每个分块的大小落在合理范围内。这种方式的优势在于灵活性强,可以根据文本密集程度动态调整粒度:信息密集的部分切得更细,信息稀疏的部分则保留更大的块。相应的挑战是,需要提前设计好分割规则,否则容易引入复杂度。
  4. 从小到大分块:这种思路不局限于单一分块粒度,而是对同一文档生成多种大小的分块,并将它们全部存入向量数据库中,同时维护好分块之间的层级关系。在检索阶段,可以根据查询需求在不同粒度之间进行递归或回溯搜索,从而兼顾细节和全局上下文。不过,这种方式会引入大量重复内容,对存储空间和索引成本的要求更高。
  5. 特殊结构分块:对于结构化或半结构化内容,通用的分块方式往往效果有限。这类场景通常需要使用专门的分割器,以保留原有结构信息。LangChain 已经内置了多种特殊分块器,例如针对 Markdown、LaTeX 文档,以及主流编程语言代码的分割器。这类分块方式非常适合技术文档和代码类知识库的构建。

分块大小的选择:

分块大小并不存在一个“放之四海而皆准”的固定值,它本质上是在 语义完整性、检索精度和上下文利用率 之间做权衡。实际选择时,可以从以下几个维度来考虑。

  1. 先看模型的上下文窗口:分块大小首先要受限于大模型的上下文长度。每个分块最终都会和用户问题一起送入模型,如果分块本身过大,就会快速吃掉上下文预算,导致可用信息变少。因此,一个常见经验是:单个分块不超过模型上下文窗口的 10%~20%,并预留空间给用户问题和多条检索结果。
  2. 语义完整性比“刚好塞满”更重要:分块的目标不是把上下文窗口用满,而是保证每个块表达的是一个相对完整的语义单元。
    • 分块太小:信息被切碎,检索结果可能只命中局部细节,缺乏背景,回答容易断章取义。
    • 分块太大:虽然上下文完整,但相似度检索会被大量无关内容稀释,相关性反而下降。
  3. 结合文本类型调整粒度:不同类型的文本,对分块大小的容忍度差异很大。
    • 技术文档 / 博客:通常以“一个概念或一个小节”为单位,块可以相对大一些。
    • 代码或 API 文档:更适合小块,保证函数、类、配置项的边界清晰。
    • 长对话或日志:需要更细粒度的分块,否则关键信息容易被埋没。
  4. 为检索结果数量预留空间:在 RAG 场景中,通常不会只取一个分块,而是 Top-K 多条一起送入模型。这意味着:分块越大,可同时放入上下文的块就越少。在实践中,可以反推分块大小:(上下文窗口 − 问题 token 数) ÷ Top-K ≈ 单块上限
  5. 合理使用重叠(overlap):分块重叠是用来缓解边界语义丢失的问题,但并不是越多越好。
    • 重叠太少:上下文容易在切分处断裂
    • 重叠太多:存储和计算成本明显上升,且信息冗余
    • 经验上,重叠大小控制在分块大小的 10%~30% 较为合理。

理论值并非绝对正确,分块大小一定要结合实际查询进行验证。

可以通过以下方式迭代优化:

  • 对比不同分块大小下的召回率和命中率
  • 人工评估回答是否出现“答非所问”或“信息不全”
  • 在固定数据集上做 A/B 测试

嵌入模型

嵌入模型能帮助我们把文本转换成向量,显然不同的嵌入模型带来的效果也不尽相同,例如,之前讨论的Word2Vec模型,尽管功能强大,但存在一个重要的局限性:其生成的词向量是静态的。一旦模型训练完成,每个词的向量表示就固定不变,这在处理一词多义的情况时可能导致问题。比如,“我买了一张光盘”,这里“光盘”指的是具体的圆形盘片,而在“光盘行动”中,“光盘”则指的是把餐盘里的食物吃光,是一种倡导节约的行为。语义完全不一样的词向量却是固定的。

相比之下,引入自注意力机制的模型,如 BERT,能够提供动态的词义理解。这意味着它可以根据上下文动态地调整词义,使得同一个词在不同语境下有不同的向量表示。在之前的例子中,“光盘”这个词在两个句子中会有不同的向量,从而更准确地捕捉其语义。

有些项目为了让模型对特定垂直领域的词汇有更好的理解,会嵌入模型进行微调。但在这里我们并不推荐这种方法,一方面其对训练数据的质量有较高要求,另一方面也需要较多的人力物力投入,且效果未必理想,最终得不偿失。在这种情况下,对于具体应该如何选择嵌入模型,推荐参考Hugging Face推出的嵌入模型排行榜MTEB(https://huggingface.co/spaces/mteb/leaderboard)。这个排行榜提供了多种模型的性能比较,能帮助我们做出更明智的选择。同时,要注意并非所有嵌入模型都支持中文,因此在选择时应查阅模型说明。

元数据

当在向量数据库中存储向量数据时,某些数据库支持将向量与元数据(即向量化前的数原始数据)一同存储。为向量添加元数据标注是一种提高检索效率的有效策略,它在处理搜索结果时发挥着重要作用。

例如,日期就是一种常见的元数据标签。它能够帮助我们根据时间顺序进行筛选。设想一下,如果我们正在开发一款允许用户查询他们电子邮件历史记录的应用程序。在这种情况下,日期最近的电子邮件可能与用户的查询更相关。然而,从嵌入的角度来看,我们无法直接判断这些邮件与用户查询的相似度。通过将每封电子邮件的日期作为元数据附加到其嵌入向量中,我们可以在检索过程中优先考虑最近日期的邮件,从而提高搜索结果的相关性。此外,我们还可以添加诸如章节或小节的引用,文本的关键信息、小节标题或关键词等作为元数据。这些元数据不仅有助于改进知识检索的准确性,还能为最终用户提供更加丰富和精确的搜索体验。

多级索引

当元数据不足以区分不同的上下文时,我们可以试试多重索引技术。其核心就是把大量数据和信息按类别分开,并在不同层次上组织起来,让管理和搜索变得更有效。简单来说,不是只用一个索引来处理所有事情,而是创建多个索引,每个索引针对不同类型的数据和查询需求。比如,有一个索引专门处理摘要类问题,另一个解决直接找答案的问题,还有一个是考虑时间因素的。这样,RAG系统可以根据查询的具体情况和上下文选择最适合的索引来检索数据,提高准确性和速度。不过,要实现这一点,还需要一个多级路由机制。

多级路由机制的作用是确保每个查询都能被快速引导到最合适的索引上去。根据查询的特点(比如复杂度、需要的信息类型等),它会被送到一个或几个特定的索引那里去。这不仅提高了效率,还优化了资源使用,保证了对各种查询的精准匹配。举个例子,如果有人问“最新的科幻电影推荐”,RAG系统可能会先把它送到关注当前热点话题的索引,然后再通过专注于娱乐和影视内容的索引来生成推荐结果。

总的来说,多级索引和路由技术可以帮助我们更好地处理大规模数据,更精确地提取信息,从而改善用户体验和系统的整体性能。

索引/查询算法

我们可以利用索引来筛选数据,但最终仍需从这些筛选后的数据中检索出相关的文本向量。由于向量数据的规模庞大且结构复杂,寻找绝对最优解在计算上往往成本极高,有时甚至是不可行的。此外,大型模型本质上并不具备完全确定性,它们在搜索过程中追求的是语义上的相似性——即达到合理的匹配度即可。从实际应用的角度来看,这种做法是合理且高效的。

例如,在推荐系统中,用户通常不会察觉或过于关注每个推荐项是否为绝对的最佳匹配;他们更关心的是推荐结果总体上是否与其兴趣相符。因此,查找与查询向量完全一致的项目通常不是主要目标,而是要找到“足够接近”或“相似”的项目,这便是近似最近邻搜索(Approximate Nearest Neighbor, ANN)技术的核心理念。采用这种方法不仅能够满足实际需求,还为检索过程中的优化提供了广阔的空间。

接下来,我们将介绍几种常见的向量搜索算法,以帮助大家根据具体应用场景做出合适的选择。

聚类

回想一下,当我们在网上购物时,通常不会在所有商品中盲目搜索,而是会选择进入特定的商品分类,比如“电子产品”或“服饰”,在一个更加细分的范畴内寻找心仪的商品。这个能帮我们大大缩小搜索范围。同样这种思路,聚类算法可以帮我们实现这个范围的划定。

比如可以用 K-mean 算法把向量分为数个簇,当用户进行查询的时候,我们只需找到距离查询向量最近的簇,然后再这个簇中进行搜索。当然聚类的方法并不保证一定正确,如下图,查询距离黄色簇的中心点更近,但实际上距离查询向量最近的,即最相似的点在紫色类。

有一些缓解这个问题的方法,例如增加聚类的数量,并指定搜索多个簇。然而,任何提高结果质量的方法都不可避免地会增加搜索的时间和资源成本。实际上,质量和速度之间存在着一种权衡关系。我们需要在这两者之间找到一个最优的平衡点,或者找到一个适合特定应用场景的平衡。不同的算法也对应着不同的平衡。

位置敏感哈希

沿着缩小搜索范围的思路,位置敏感哈希算法是另外一种实现的策略。在传统的哈希算法中,我们通常希望每个输入对应一个唯一的输出值,并努力减少输出值的重复。然而,在位置敏感哈希算法中,我们的目标恰恰相反,我们需要增加输出值碰撞的概率。这种碰撞正是分组的关键,哈希值相同的向量将被分配到同一个组中,也就是同一个“桶”里。此外,这种哈希函数还需满足另一个条件:空间上距离较近的向量更有可能被分入同一个桶。这样在进行搜索时,只需获取目标向量的哈希值,找到相应的桶,并在该桶内进行搜索即可。

量化乘积

乘积量化(Product Quantization, PQ)提供了一种有效手段来实现这一目标。类似于图像处理领域中的有损压缩策略——通过合并相邻像素以降低存储需求——我们也可以采用聚类方法的变体,即使用每个簇的中心点来近似表示该簇内的所有向量。尽管这种方法会导致原始向量的具体数值信息有所损失,但鉴于聚类中心与簇内成员间的高度相关性以及可通过增加簇的数量来进一步缩小这种差异,大部分原始信息仍然能够得到保留。

此方案带来的益处十分显著:通过给这些中心点分配唯一的编码,我们可以用一个简单的数字代替复杂的多维向量进行存储,从而极大地减少了所需的空间。同时,建立一个码本,其中包含每个中心向量及其对应的编码,使得在需要访问特定向量时,只需根据其编码快速查找相应的中心向量即可。即使经过这样处理后的向量不再完全等同于原向量,但正如前文所述,对于大多数应用场景而言,这种近似是可以接受的。将向量映射到其所在簇的中心点的过程被称为量化。

但是你可能会发现码本又引入了额外的开销。码本大小表面上与向量数量相关,但码本增长的根本原因在于向量维度。随着维度升高,空间结构变得更加稀疏且复杂,单个聚类中心所能覆盖的有效区域急剧缩小。为了保持相同的量化精度,所以我们需要更多的聚类数量来减少丢失的细节,就必须引入更多的码本条目,因此高维向量量化在工程上才会面临严重的规模问题。最终这个码本所需的内存会变得非常恐怖,甚至超越量化本身所节省下来的内存。

解决这个问题的办法是把高维的向量分割成多个低维的子向量,然后在这些低维的子向量中独立量化。比如把128维的向量分解成8个16维的子向量,然后独立的在8个独立的子空间进行量化形成自己子码本。

每个16维的空间只需要256个聚类就可以得到不错的效果,并且每个子码本将变得很小,即便8个子码本加在一起的大小也仍然足够的小,实际上我们是在把码本大小增长从指数模型分解为加法模式。最后,我们还可以把前面的提速度方法和减内存的乘积量化法结合使用,同时实现速度和内存的优化。

分层导航小世界

从用户的角度来看,内存消耗可能并非最关键的因素。用户更重视的是应用程序的性能表现,具体来说就是响应速度和答案质量。分层导航小世界(Hierarchical Navigable Small World, NSW)算法正是通过牺牲一定的内存资源来换取更快的查询速度与更高的准确度的一种解决方案。

该算法的设计理念类似于“六度分隔理论”,即任何两个人之间最多只需要通过六个中间人即可建立联系。将这一概念映射到向量空间中,我们可以把每个数据点视为一个人,并将搜索过程想象成是从一个点到达另一个点的过程。在执行查询时,首先选取一个起始节点A,接着找到与A相连且最接近目标向量的下一个节点B;然后继续以相同的方式从B出发寻找更近的匹配项,直至达到某一点C,在这一点上,所有直接相连的邻居都不比它本身更靠近查询目标为止。因此,点C就被认为是与查询最为相似的结果。

查询转换

在 RAG 系统中,用户的查询问题被转化为向量,然后在向量数据库中进行匹配。不难想象,查询的措辞会直接影响搜索结果。如果搜索结果不理想,可以尝试以下几种方法对问题进行重写,以提升召回效果:

  1. 结合历史对话的重新表述:在向量空间中,对人类来说看似相同的两个问题其向量大小并不一定很相似。我们可以直接利用LLM 重新表述问题来进行尝试。此外,在进行多轮对话时,用户的提问中的某个词可能会指代上文中的部分信息,因此可以将历史信息和用户提问一并交给LLM重新表述。
  2. 假设文档嵌入(HyDE):HyDE的核心思想是接收用户提问后,先让LLM在没有外部知识的情况下生成一个假设性的回复。然后,将这个假设性回复和原始查询一起用于向量检索。假设回复可能包含虚假信息,但蕴含着LLM认为相关的信息和文档模式,有助于在知识库中寻找类似的文档。
  3. 退后提示(Step Back Prompting):如果果原始查询太复杂或返回的信息太广泛,我们可以选择生成一个抽象层次更高的“退后”问题,与原始问题一起用于检索,以增加返回结果的数量。例如,原问题是“某某在特定时期去了哪所学校”,而退后问题可能是关于他的“教育历史”。这种更高层次的问题可能更容易找到答案。
  4. 多查询检索/多路召回(Multi Query Retrieval):使用LLM生成多个搜索查询,特别适用于一个问题可能需要依赖多个子问题的情况。

通过这些方法,RAG系统能够更精准地处理和响应复杂的用户查询,从而提升整体的搜索效率和准确性。

检索参数

把查询问题准备好了,可以进入向量数据库进行检索。在具体的检索过程中,我们可以根据向量数据库的特定设置来优化一些检索参数,以下是一些常见的可设定参数:

  1. 稀疏和稠密搜索权重: 稠密搜索即通过向量进行搜索。然而,在某些场景下可能存在限制,此时可以尝试使用原始字符串进行关键字匹配的稀疏搜索。一种有效的稀疏搜索算法是最佳匹配 25(BM25),它基于统计输入短语中的单词频率,频繁出现的单词得分较低,而稀有的词被视为关键词,得分会较高。我们可以结合稀疏和稠密搜索得出最终结果。向量数据库通常允许设定两者对最终结果评分的权重比例,如 0.6 表示 40% 的得分来自稀疏搜索,60% 来自稠密搜索。
  2. 结果数量(topK): 检索结果的数量是另一个关键因素。足够的检索结果可以确保系统覆盖到用户查询的各个方面。在回答多方面或复杂问题时,更多的结果提供了丰富的语境,有助于RAG系统更好地理解问题的上下文和隐含细节。但需注意,结果数量过多可能导致信息过载,降低回答准确性并增加系统的时间和资源成。
  3. 相似度度量方法:计算两个向量相似度的方法也是一个可选参数。这包括使用欧式距离和 Jaccard 距离计算两个向量的差异,以及利用余弦相似度衡量夹角的相似性。通常,余弦相似度更受青睐,因为它不受向量长度的影响,只反映方向上的相似度。这使得模型能够忽略文本长度差异,专注于内容的语义相似性。需要注意的是,并非所有嵌入模型都支持所有度量方法,具体可参考所用嵌入模型的说明。

高级检索策略

在处理大型文档或复杂查询时,有效地利用语言模型(LLM)以提高响应质量和效率是至关重要的。为了达到这一目标,需要在向量数据库检索之上如何具体开发或改进整个系统的策略。

  1. 上下文压缩:我们提到过当文档文块过大时,可能包含太多不相关的信息,传递这样的整个文档可能导致更昂贵的LLM调用和更差的响应。上下文压缩的思想就是通过LLM的帮助根据上下文对单个文档内容进行压缩,或者对返回结果进行一定程度的过滤仅返回相关信息。
  2. 句子窗口搜索:相反,文档文块太小会导致上下文的缺失。其中一种解决方案就是窗口搜索,该方法的核心思想是当提问匹配好分块后,将该分块周围的块作为上下文一并交给LLM进行输出,来增加LLM对文档上下文的理解。
  3. 父文档搜索:无独有偶,父文档搜索也是一种很相似的解决方案,父文档搜索先将文档分为尺寸更大的主文档,再把主文档分割为更短的子文档两个层级,用户问题会与子文档匹配,然后将该子文档所属的主文档和用户提问发送给LLM。
  4. 自动合并:自动合并是在父文档搜索上更进一步的复杂解决方案。同样地,我们先对文档进行结构切割,比如将文档按三层树状结构进行切割,顶层节点的块大小为1024,中间层的块大小为512,底层的叶子节点的块大小为128。而在检索时只拿叶子节点和问题进行匹配,当某个父节点下的多数叶子节点都与问题匹配上则将该父节点作为结果返回。
  5. 多向量检索:多向量检索同样会给一个知识文档转化成多个向量存入数据库,不同的是,这些向量不仅包括文档在不同大小下的分块,还可以包括该文档的摘要,用户可能提出的问题等等有助于检索的信息。在使用多向量查询的情况下,每个向量可能代表了文档的不同方面,使得系统能够更全面地考虑文档内容,并在回答复杂或多方面的查询时提供更精确的结果。例如,如果查询与文档的某个具体部分或摘要更相关,那么相应的向量就可以帮助提高这部分内容的检索排名。
  6. 多代理检索:多代理检索,简而言之就是选取我们提及的12大优化策略中的部分交给一个智能代理合并使用。就比如使用子问题查询,多级索引和多向量查询结合,先让子问题查询代理把用户提问拆解为多个小问题,再让文档代理对每个字问题进行多向量或多索引检索,最后排名代理将所有检索的文档总结再交给LLM。这样做的好处是可以取长补短,比如,子问题查询引擎在探索每个子查询时可能会缺乏深度,尤其是在相互关联或关系数据中。相反,文档代理递归检索在深入研究特定文档和检索详细答案方面表现出色,以此来综合多种方法解决问题。需要注意的是现在网络上存在不同结构的多代理检索,具体在多代理选取哪些优化步骤尚未有确切定论,我们可以结合使用场景进行探索。
  7. Self-RAG:自反思搜索增强是一个全新RAG框架,其与传统RAG最大的区别在于通过检索评分(令牌)和反思评分(令牌)来提高质量。它主要分为三个步骤:检索、生成和批评。Self-RAG首先用检索评分来评估用户提问是否需要检索,如果需要检索,LLM将调用外部检索模块查找相关文档。接着,LLM分别为每个检索到的知识块生成答案,然后为每个答案生成反思评分来评估检索到的文档是否相关,最后将评分高的文档当作最终结果一并交给LLM。

重排模型(Re-ranking)

在完成语义搜索的优化步骤后,我们能够检索到语义上最相似的文档,但不知你是否注意到一个关键问题:语义最相似是否总代表最相关?答案是不一定。例如,当用户查询“最新上映的科幻电影推荐”时,可能得到的结果是“科幻电影的历史演变”,虽然从语义上这与科幻电影相关,但并未直接回应用户关于最新电影的查询。

重排模型可以帮助我们缓解这个问题,重排模型通过对初始检索结果进行更深入的相关性评估和排序,确保最终展示给用户的结果更加符合其查询意图。这一过程通常由深度学习模型实现,如Cohere模型。这些模型会考虑更多的特征,如查询意图、词汇的多重语义、用户的历史行为和上下文信息等。

举个例子,对于查询“最新上映的科幻电影推荐”,在首次检索阶段,系统可能基于关键词返回包括科幻电影的历史文章、科幻小说介绍、最新电影的新闻等结果。然后,在重排阶段,模型会对这些结果进行深入分析,并将最相关、最符合用户查询意图的结果(如最新上映的科幻电影列表、评论或推荐)排在前面,同时将那些关于科幻电影历史或不太相关的内容排在后面。这样,重排模型就能有效提升检索结果的相关性和准确性,更好地满足用户的需求。

在实践中,使用RAG构建系统时都应考虑尝试重排方法,以评估其是否能够提高系统性能。

提示词

大型语言模型的解码器部分通常基于给定输入来预测下一个词。这意味着设计提示词或问题的方式将直接影响模型预测下一个词的概率。这也给了我们一些启示:通过改变提示词的形式,可以有效地影响模型对不同类型问题的接受程度和回答方式,比如修改提示语,让LLM知道它在做什么工作,是十分有帮助的。

为了减少模型产生主观回答和幻觉的概率,一般情况下,RAG系统中的提示词中应明确指出回答仅基于搜索结果,不要添加任何其他信息例如,可以设置提示词如:“你是一名智能客服。你的目标是提供准确的信息,并尽可能帮助提问者解决问题。你应保持友善,但不要过于啰嗦。请根据提供的上下文信息,在不考虑已有知识的情况下,回答相关查询。”当然也可以根据场景需要,也可以适当让模型的回答融入一些主观性或其对知识的理解。此外,使用少量样本(few-shot)的方法,将想要的问答例子加入提示词中,指导LLM如何利用检索到的知识,也是提升LLM生成内容质量的有效方法。这种方法不仅使模型的回答更加精准,也提高了其在特定情境下的实用性。

大语言模型

最后一步——LLM 生成回答。LLM 是生成响应的核心组件。与嵌入模型类似,可以根据自己的需求选择 LLM,例如开放模型与专有模型、推理成本、上下文长度等。此外,可以使用一些LLM开发框架来搭建 RAG 系统,比如,LlamaIndex 或 LangChain。这两个框架都拥有比较好用的 debugging 工具,可以让我们定义回调函数,查看使用了哪些上下文,检查检索结果来自哪个文档等等。

参考资料

  1. 读懂RAG这一篇就够了,万字详述RAG的5步流程和12个优化策略
— 感谢阅读 —

一起交流

分享你的思考,让讨论更进一步。