即使是最先进的生成式 AI 模型,也只能基于它们已训练的数据生成响应。它们无法准确回答超出训练数据范围的问题。生成式 AI 模型“并不知道它们不知道的内容”!这会导致不准确或不恰当的输出,有时被称为幻觉、偏见,或简单地说是胡言乱语。
检索增强生成(RAG)是一种框架,通过将基于检索的方法与生成模型结合来应对这一限制。它实时从外部数据源中检索相关数据,并使用这些数据生成更准确且更符合上下文的响应。集成了 RAG 检索器的生成式 AI 模型,凭借前所未有的高效性和强大性能,正在革新这一领域。RAG 的一大优势在于其适应性。无论是文本、图像还是音频,RAG 都能无缝应用于任何数据类型。正因这种多样性,使得 RAG 生态系统成为增强生成式 AI 能力的可靠高效工具。
然而,项目经理已经面临着广泛的生成式 AI 平台、框架和模型,如 Hugging Face、Google Vertex AI、OpenAI、LangChain 等。新增的 RAG 框架和平台(如 Pinecone、Chroma、Activeloop、LlamaIndex 等)将进一步增加复杂性。所有这些生成式 AI 和 RAG 框架往往重叠,产生了难以计数的可能配置。因此,为特定项目找到合适的模型和 RAG 资源配置,对项目经理来说是一项挑战。这没有灵丹妙药,挑战极大,但一旦实现,其回报也是巨大的!
本章开始时,我们将从宏观上定义 RAG 框架。接着,我们将定义三种主要的 RAG 配置:简单 RAG、高级 RAG 和模块化 RAG。我们还将比较 RAG 和微调,并确定何时使用这些方法。RAG 必须存在于一个生态系统中,我们将在本章中设计并描述该生态系统。数据需要来源并被处理,检索需要一个有组织的环境来获取数据,而生成式 AI 模型则有输入限制。
最后,我们将深入到本章的实操部分。从头开始编写一个 Python 程序来运行入门级的简单 RAG,使用关键词搜索和匹配。我们还将编写一个使用向量搜索和基于索引的高级 RAG 系统。最后,我们将构建一个兼顾简单和高级 RAG 的模块化 RAG。到本章结束时,您将获得 RAG 框架的理论理解,并具备构建 RAG 驱动的生成式 AI 程序的实践经验。通过这种实操方式,您将加深理解,为接下来的章节做好准备。
简而言之,本章涵盖以下主题:
- 定义 RAG 框架
- RAG 生态系统
- 用 Python 实现简单的关键词搜索和匹配 RAG
- 用 Python 实现基于向量搜索和索引的高级 RAG
- 构建模块化 RAG 程序
让我们从定义 RAG 开始。
什么是 RAG?
当生成式 AI 模型无法准确回答时,有人称其为“幻觉”或产生“偏见”。简单来说,它只是生成了无意义的内容。归根结底,这是因为在经典的模型配置问题之外,当模型的训练中未包含所需信息时,它无法提供适当的响应。这种困惑通常导致模型输出一系列概率最高但并不准确的内容。
RAG 从生成式 AI 结束的地方开始,提供了 LLM 模型所缺乏的信息,以便生成准确的回答。RAG 框架(由 Lewis 等人于 2020 年提出)专为 LLM 设计。RAG 框架将执行优化的信息检索任务,而生成系统会将这些信息添加到输入(用户查询或自动提示)中,以生成更优的输出。RAG 框架的高级概述可以在下图中总结:
想象你是一名图书馆里的学生,你需要写一篇关于 RAG 的文章。就像 ChatGPT 或其他任何 AI 助手一样,你已经学会了如何阅读和写作。与任何大型语言模型(LLM)一样,你经过了充分的训练,可以阅读高级信息、总结内容并撰写文章。然而,就像 Hugging Face、Vertex AI 或 OpenAI 中的任何超人 AI 一样,你仍然有很多不知道的事情。
在检索阶段,你会在图书馆中查找你需要的主题书籍(如图 1.1 的左侧)。然后,你回到座位上,自己或和同学一起执行检索任务,从这些书中提取所需的信息。在生成阶段(如图 1.1 的右侧),你开始撰写你的文章。你就像一个由 RAG 驱动的生成式人类代理,与 RAG 驱动的生成式 AI 框架相似。
当你继续撰写关于 RAG 的文章时,会遇到一些难以理解的主题。你没时间去查阅所有相关信息!作为一个生成式人类代理,你被困住了,就像生成式 AI 模型一样。你可能会尝试写点什么,就像生成式 AI 模型在输出内容缺乏逻辑时所做的一样。但是,与你相似的是,生成式 AI 代理也无法意识到内容的准确性,直到有人批改你的文章并给出评分,你才能知道你的文章水平。
在这个时候,你达到了极限,决定转向 RAG 生成式 AI 助手,以确保获得正确的答案。然而,你对各种 LLM 模型和 RAG 配置的数量感到困惑。你首先需要了解可用资源以及 RAG 的组织方式。接下来,我们将介绍主要的 RAG 配置。
基础、高级和模块化 RAG 配置
一个 RAG 框架必须包含两个主要组件:检索器和生成器。生成器可以是任何 LLM 或基础多模态 AI 平台或模型,如 GPT-4o、Gemini、Llama 或数百种初始架构的变体。检索器可以是任何新兴的框架、方法和工具,例如 Activeloop、Pinecone、LlamaIndex、LangChain、Chroma 等。
现在的问题是确定三种类型的 RAG 框架(Gao 等人,2024)中的哪一种适合项目的需求。我们将在本章的“基础、高级和模块化 RAG 编码”部分中以代码示例展示这三种方法:
- 基础 RAG:这种类型的 RAG 框架不涉及复杂的数据嵌入和索引。例如,可以通过关键词访问适量的数据,以增强用户输入并获得令人满意的响应。
- 高级 RAG:这种 RAG 涉及更复杂的场景,例如应用向量搜索和基于索引的检索。高级 RAG 可以通过多种方法实现,能够处理多种数据类型以及结构化或非结构化的多模态数据。
- 模块化 RAG:模块化 RAG 扩展了应用场景,包含任何涉及基础 RAG、高级 RAG、机器学习和完成复杂项目所需的算法的情况。
然而,在进一步深入之前,我们需要决定是实现 RAG 还是对模型进行微调。
RAG 与微调
RAG 并不总是微调的替代方案,微调也不能总是取代 RAG。如果在 RAG 数据集中积累了过多的数据,系统可能会变得过于臃肿,难以管理。另一方面,我们无法使用动态的、不断变化的数据(如每日天气预报、股市行情、公司新闻及各种日常事件)对模型进行微调。
决定是否实施 RAG 或微调模型取决于参数化与非参数化信息的比例。模型从头训练或微调与 RAG 之间的根本区别可以用参数化和非参数化知识来概括:
- 参数化:在一个 RAG 驱动的生成式 AI 生态系统中,参数化部分指的是通过训练数据学习的生成式 AI 模型的参数(权重)。这意味着模型的知识存储在这些学习到的权重和偏置中。原始的训练数据被转化为一种数学形式,我们称之为参数化表示。本质上,模型“记住”了从数据中学到的内容,但数据本身并未被显式存储。
- 非参数化:相比之下,RAG 生态系统中的非参数化部分涉及存储可以直接访问的显式数据。这意味着数据始终可用,并可以在需要时进行查询。与参数化模型将知识间接嵌入在权重中不同,RAG 的非参数化数据允许我们直接查看和使用每次输出的实际数据。
RAG 和微调之间的区别在于生成式 AI 模型必须处理的静态(参数化)和动态(非参数化)不断演变的数据的数量。过度依赖 RAG 的系统可能会变得臃肿且难以管理,而过度依赖微调的生成式模型则会表现出无法适应每日信息更新的不足。
如图 1.2 所示,在 RAG 驱动的生成式 AI 项目中,项目经理需要在实施非参数化(显式数据)RAG 框架之前评估生态系统中经过训练的参数化生成式 AI 模型的潜力。同样,RAG 组件的潜力也需要仔细评估。
最终,在 RAG 驱动的生成式 AI 生态系统中,加强检索器和生成器之间的平衡取决于项目的具体需求和目标。RAG 和微调并不是互斥的。
RAG 可以与微调结合使用,以提高模型的整体效率,微调作为一种方法,可以增强 RAG 框架中检索和生成组件的性能。在第 9 章《增强 AI 模型:微调 RAG 数据和人类反馈》中,我们将微调部分检索数据。
接下来,我们将了解一个由 RAG 驱动的生成式 AI 系统如何涉及到包含多个组件的生态系统。
RAG 生态系统
由 RAG 驱动的生成式 AI 是一个可以通过多种配置实现的框架。RAG 框架运行在一个广泛的生态系统中,如图 1.3 所示。然而,无论遇到多少种检索和生成框架,其核心可以归结为以下四个领域及相关问题:
- 数据:数据来自哪里?数据是否可靠?数据是否足够?是否存在版权、隐私和安全问题?
- 存储:数据在处理前或处理后将如何存储?将存储多少数据?
- 检索:如何检索到正确的数据,以在生成模型处理之前增强用户输入?哪种类型的 RAG 框架适合项目?
- 生成:哪种生成式 AI 模型适合选定的 RAG 框架类型?
数据、存储和生成领域在很大程度上取决于所选择的 RAG 框架类型。在做出选择之前,我们需要评估在所实施的生态系统中参数化和非参数化知识的比例。图 1.3 展示了 RAG 框架,包括主要组件,不论所实施的 RAG 类型如何。
- 检索器 (D) 负责数据的收集、处理、存储和检索
- 生成器 (G) 负责输入增强、提示工程和生成
- 评估器 (E) 负责数学指标、人类评估和反馈
- 训练器 (T) 负责初始的预训练模型和模型微调
这四个组件各自依赖于其相应的生态系统,共同构成了整体的 RAG 驱动的生成式 AI 流水线。接下来的部分中,我们将使用 D、G、E 和 T 来指代这些领域。让我们从检索器开始。
检索器 (D)
RAG 生态系统的检索器组件负责数据的收集、处理、存储和检索。因此,RAG 生态系统的起点是一个数据摄取过程,其第一步是收集数据。
收集 (D1)
在当今世界,AI 数据就像我们的媒体播放列表一样多样化。它可以是博客文章中的一段文字,也可以是一个表情包,甚至是耳机中播放的最新热曲。而这还不止于此——数据文件本身也是形形色色的。想想那些包含各种细节的 PDF、网页、直奔主题的纯文本文件、整齐有序的 JSON 文件、动听的 MP3 音乐、MP4 格式的视频,或者 PNG 和 JPG 格式的图片。
此外,这些数据中有很大一部分是非结构化的,分布方式也很复杂和不可预测。幸运的是,许多平台如 Pinecone、OpenAI、Chroma 和 Activeloop 提供了现成的工具来处理和存储这些错综复杂的数据。
处理 (D2)
在多模态数据处理的收集阶段 (D1),可以通过网络爬虫或其他信息来源从网站上提取各种类型的数据,如文本、图像和视频。接着,对这些数据对象进行转换以创建统一的特征表示。例如,数据可以被分块(分解为更小的部分)、嵌入(转换为向量)和索引,以增强其可检索性和检索效率。
我们将从本章的“Python 中构建混合自适应 RAG”部分开始介绍这些技术,并在后续章节中继续构建更复杂的数据处理功能。
存储 (D3)
在流水线的这一阶段,我们已经从互联网上收集并开始处理大量多样化的数据——视频、图片、文本,应有尽有。接下来,该如何处理这些数据以使其有用呢?
这正是像 Deep Lake、Pinecone 和 Chroma 等向量存储发挥作用的地方。可以将它们视为“超级智能图书馆”,不仅存储数据,还将数据转换为数学实体(向量),以支持强大的计算功能。它们还可以应用各种索引方法和其他技术,以实现快速访问。
与将数据保存在静态的电子表格和文件中不同,我们将其转化为一个动态的、可搜索的系统,能够为聊天机器人到搜索引擎等应用提供支持。
检索查询 (D4)
检索过程由用户输入或自动输入 (G1) 触发。
为了快速检索数据,我们将其转换为合适的格式后加载到向量存储和数据集中。然后,通过结合关键词搜索、智能嵌入和索引,可以高效地检索数据。例如,余弦相似度可以找到密切相关的项,确保搜索结果不仅快速,而且高度相关。
数据检索后,我们将对输入进行增强。
生成器 (G)
在 RAG 生态系统中,输入和检索之间的界限较为模糊,如图 1.3 中展示的 RAG 框架和生态系统所示。用户输入 (G1)(无论是自动化还是人工)与检索查询 (D4) 互动,以在将其发送给生成模型之前增强输入。
生成流程始于输入。
输入 (G1)
输入可以是一批自动化任务(例如处理电子邮件),也可以是通过用户界面 (UI) 提供的人工提示。这种灵活性使得 AI 可以无缝集成到各种专业环境中,从而提高各行业的生产力。
带有人类反馈的增强输入 (G2)
可以在输入中添加人类反馈 (HF),如评估器 (E) 部分下的“人类反馈 (E2)”中所述。人类反馈使 RAG 生态系统具有很强的适应性,并提供了对数据检索和生成式 AI 输入的完全控制。在本章的“Python 中构建混合自适应 RAG”部分,我们将构建带有人类反馈的增强输入。
提示工程 (G3)
检索器 (D) 和生成器 (G) 都高度依赖提示工程,以准备生成式 AI 模型需要处理的标准和增强消息。提示工程将检索器的输出和用户输入整合在一起。
生成和输出 (G4)
生成式 AI 模型的选择取决于项目的目标。Llama、Gemini、GPT 等模型可以满足不同的需求。然而,提示必须符合每个模型的规范。本书将实现的 LangChain 等框架,通过提供可适应的接口和工具,有助于简化各种 AI 模型在应用程序中的集成。
评估器 (E)
我们通常依赖数学指标来评估生成式 AI 模型的性能。然而,这些指标只提供了部分信息。重要的是要记住,AI 效能的最终考验在于人类评估。
指标 (E1)
与任何 AI 系统一样,模型的评估离不开数学指标,例如余弦相似度。这些指标确保检索到的数据是相关且准确的。通过量化数据点之间的关系和相关性,它们为评估模型的性能和可靠性提供了坚实的基础。
人类反馈 (E2)
无论是否是由 RAG 驱动的生成式 AI 系统,也无论数学指标看似多么充足,任何系统都不能回避人类评估。最终,只有人类评估能决定为人类用户设计的系统是被接受还是被拒绝,是受到赞扬还是批评。
自适应 RAG 引入了人类、真实生活、实用性的反馈因素,从而提升了 RAG 驱动的生成式 AI 生态系统的性能。我们将在第 5 章《通过专家人类反馈提升 RAG 性能》中实现自适应 RAG。
训练器 (T)
一个标准的生成式 AI 模型通过大量通用数据进行预训练。然后,我们可以使用特定领域的数据对模型进行微调 (T2)。
我们将在第 9 章《增强 AI 模型:微调 RAG 数据和人类反馈》中更进一步,将静态 RAG 数据整合到微调过程中。我们还将整合人类反馈,这些反馈提供了有价值的信息,可以通过人类反馈强化学习 (RLHF) 的一种变体整合到微调过程中。
现在我们准备好用 Python 编写入门级的基础、高级和模块化 RAG 代码。
基础、高级和模块化 RAG 代码实现
本节通过基础教育示例介绍基础、高级和模块化 RAG。程序构建了关键词匹配、向量搜索和基于索引的检索方法。使用 OpenAI 的 GPT 模型,程序基于输入查询和检索到的文档生成响应。
本笔记本的目标是使对话代理能够回答有关 RAG 的一般性问题。我们将从头开始在 Python 中逐步构建检索器,并在八个代码部分中运行生成器,使用 OpenAI 的 GPT-4o,将其分为两个部分:
第 1 部分:基础与基本实现
- 设置 OpenAI API 环境
- 使用 GPT-4o 的生成函数
- 使用文档列表 (db_records) 进行数据设置
- 用户输入的查询
第 2 部分:高级技术与评估
- 用于衡量检索响应的检索指标
- 具有关键词搜索和匹配功能的基础 RAG
- 使用向量搜索和基于索引搜索的高级 RAG
- 实现灵活检索方法的模块化 RAG
首先,在 GitHub 仓库中打开 RAG_Overview.ipynb。我们将从笔记本的基础开始,并探索基本实现。
第 1 部分:基础与基本实现
在本节中,我们将设置环境、创建生成器函数、定义格式化响应的函数,并定义用户查询。
第一步是安装环境。
以下笔记本的实现标题遵循代码结构。因此,您可以在笔记本中跟随代码,或阅读本独立部分。
1. 环境
主要安装的包是 OpenAI,用于通过 API 访问 GPT-4o:
!pip install openai==1.40.3
确保冻结您安装的 OpenAI 版本。在 RAG 框架生态系统中,我们需要安装多个包来运行高级 RAG 配置。稳定安装后,我们会冻结安装的包版本,以最大限度减少我们实现的库和模块之间的潜在冲突。
安装 openai 后,如果还没有账户,您需要在 OpenAI 上创建一个账户并获取 API 密钥。在运行 API 前请确保查看费用和付费计划。
获取密钥后,将其存储在安全的位置,并通过以下代码从 Google Drive(或您选择的任何其他方法)读取:
# API Key
# 将密钥存储在文件中并读取(可以直接在笔记本中输入,但会被旁边的人看到)
from google.colab import drive
drive.mount('/content/drive')
您可以使用 Google Drive 或任何其他方法来存储密钥。可以从文件中读取密钥,也可以选择直接在代码中输入密钥:
f = open("drive/MyDrive/files/api_key.txt", "r")
API_KEY = f.readline().strip()
f.close()
# 设置 OpenAI 密钥
import os
import openai
os.environ['OPENAI_API_KEY'] = API_KEY
openai.api_key = os.getenv("OPENAI_API_KEY")
完成上述操作后,我们的项目主资源已经设置完成。接下来,我们将为 OpenAI 模型编写生成函数。
2. 生成器
代码中导入了 openai 生成内容,还导入了 time 用于测量请求时间:
import openai
from openai import OpenAI
import time
client = OpenAI()
gptmodel = "gpt-4o"
start_time = time.time() # 请求前开始计时
现在我们创建一个带有说明和用户输入的提示的函数:
def call_llm_with_full_text(itext):
# 将所有行连接为一个字符串
text_input = '\n'.join(itext)
prompt = f"Please elaborate on the following content:\n{text_input}"
函数将尝试调用 gpt-4o,并为模型添加额外信息:
try:
response = client.chat.completions.create(
model=gptmodel,
messages=[
{"role": "system", "content": "You are an expert Natural Language Processing exercise expert."},
{"role": "assistant", "content": "1. You can explain and read the input in detail."},
{"role": "user", "content": prompt}
],
temperature=0.1 # 低温度确保更精确的回答
)
return response.choices[0].message.content.strip()
except Exception as e:
return str(e)
请注意,此场景下的提示信息保持通用,以确保模型灵活。温度设置较低(0.1)以提供更精确的回答。如果希望系统更具创造性,可以将温度设置为更高值,例如 0.7。然而在这种情况下,建议要求精确的响应。
调用生成式 AI 模型时,我们可以添加 textwrap 以将响应格式化为段落:
import textwrap
def print_formatted_response(response):
# 设置文本换行的宽度
wrapper = textwrap.TextWrapper(width=80)
wrapped_text = wrapper.fill(text=response)
# 输出格式化响应并添加标题和尾注
print("Response:")
print("---------------")
print(wrapped_text)
print("---------------\n")
生成器现在已准备好在需要时被调用。由于生成式 AI 模型的概率性质,每次调用可能会产生不同的输出。
程序现在实现数据检索功能。
3. 数据
数据收集包含文本、图像、音频和视频。在此笔记本中,我们将专注于通过基础、高级和模块化配置进行数据检索,而不是数据收集。我们将在第 2 章《使用 Deep Lake 和 OpenAI 实现 RAG 嵌入向量存储》中收集和嵌入数据。因此,我们假设所需数据已经过处理,即已收集、清理并分割为句子。我们还假设过程已将句子加载到名为 db_records 的 Python 列表中。
此方法说明了我们在 RAG 生态系统部分描述的三个方面,以及图 1.3 中描述的系统组件:
- 检索器 (D) 包含三个数据处理组件:收集 (D1)、处理 (D2) 和存储 (D3),它们是检索器的准备阶段。
- 检索查询 (D4) 因此独立于检索器的前三个阶段(收集、处理和存储)。
- 数据处理阶段通常在激活检索查询之前独立完成,我们将在第 2 章中实现这一点。
该程序假设数据处理已经完成且数据集已就绪:
db_records = [
"Retrieval Augmented Generation (RAG) represents a sophisticated hybrid approach in the field of artificial intelligence, particularly within the realm of natural language processing (NLP).",
# 其他句子...
]
我们可以显示数据集的格式化版本:
import textwrap
paragraph = ' '.join(db_records)
wrapped_text = textwrap.fill(paragraph, width=80)
print(wrapped_text)
输出将合并 db_records 中的句子以便于显示,但 db_records 本身保持不变。
程序现在可以处理查询。
4. 查询
检索器 (图 1.3 中的 D4) 查询过程取决于数据的处理方式,但查询本身只是用户输入或其他 AI 代理的自动输入。尽管我们希望用户能提供优质输入,但在现实生活中,意外输入会导致不可预测的行为。因此,我们必须构建能够处理不精确输入的系统。
在本节中,我们假设组织中的数百位用户听说过与“LLM”和“向量存储”相关的“RAG”一词。他们中的许多人希望了解这些术语的含义,以便跟上部门内正在部署的对话代理。几天后,他们听到的术语在记忆中模糊了,因此他们向对话代理(此处为 GPT-4o)提出以下查询,试图解释他们记得的内容:
query = "define a rag store"
在这种情况下,我们会将该程序主题的主查询存储在 query 中,它表示检索器和生成器之间的交汇点,并触发 RAG 的一种配置(基础、高级或模块化)。配置的选择取决于每个项目的目标。
程序接受查询并将其发送到 GPT-4o 模型进行处理,然后显示格式化输出:
# 调用函数并打印结果
llm_response = call_llm_with_full_text(query)
print_formatted_response(llm_response)
输出非常有趣。即使是最强大的生成式 AI 模型也无法猜测对 AI 一无所知的用户在试图找到什么。在此情况下,GPT-4o 会按输出片段所示回答:
Response:
---------------
Certainly! The content you've provided appears to be a sequence of characters
that, when combined, form the phrase "define a rag store." Let's break it down
step by step:…
输出似乎像是“幻觉”,但真的是这样吗?用户以一个初学者的良好意图编写了查询,试图学习新主题。GPT-4o 也尽力在有限上下文中基于其概率算法生成内容,可能每次运行都会产生不同响应。然而,GPT-4o 对查询保持谨慎,因为它并不清晰,因此以请求用户提供更多上下文的输出结束:
…Would you like more information or a different type of elaboration on this content?…
用户感到困惑,不知道该怎么办,而 GPT-4o 正在等待进一步指令。软件团队需要采取措施!
生成式 AI 基于概率算法,因此提供的响应可能每次运行时略有不同。
这就是 RAG 出场拯救局面的时刻。我们将在整个笔记本中保留此查询,看看 RAG 驱动的 GPT-4o 系统能否做得更好。
第 2 部分:高级技术与评估
在第 2 部分中,我们将介绍基础、高级和模块化 RAG。目标是介绍这三种方法,而非处理复杂文档,这将在本书的后续章节中实现。
首先让我们定义检索指标,用于衡量我们检索到的文档的准确性。
1. 检索指标
本节探讨检索指标,首先关注余弦相似度在评估文本文档相关性中的作用。接着,我们将通过加入同义词扩展和文本预处理来实现增强的相似度指标,以提高文本之间相似度计算的准确性。
我们将在第 7 章《使用 Wikipedia API 和 LlamaIndex 构建可扩展的基于知识图谱的 RAG》的“指标计算与展示”部分中进一步探讨更多的指标。
在本章中,我们从余弦相似度开始。
余弦相似度
余弦相似度测量两个向量之间的角度余弦值。在我们的案例中,这两个向量是用户查询和语料库中的每个文档。
程序首先导入所需的类和函数:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
TfidfVectorizer 导入了一个类,用于将文本文档转换为 TF-IDF 特征矩阵。词频-逆文档频率 (TF-IDF) 量化一个词对文档在集合中的重要性,区分通用词与特定文本中的重要词。TF-IDF 因此会使用文档内词频和语料库内逆词频来量化词在文档中的重要性。cosine_similarity 导入了我们将用于计算向量之间相似度的函数。
calculate_cosine_similarity(text1, text2) 函数用于计算查询 (text1) 和数据集的每条记录之间的余弦相似度。
函数通过向量化器将查询文本 (text1) 和数据集中的每条记录 (text2) 转换为向量。然后计算并返回两个向量之间的余弦相似度:
def calculate_cosine_similarity(text1, text2):
vectorizer = TfidfVectorizer(
stop_words='english',
use_idf=True,
norm='l2',
ngram_range=(1, 2), # 使用单词和双词组合
sublinear_tf=True, # 应用次线性 TF 缩放
analyzer='word' # 可尝试 'char' 或 'char_wb' 进行字符级特征分析
)
tfidf = vectorizer.fit_transform([text1, text2])
similarity = cosine_similarity(tfidf[0:1], tfidf[1:2])
return similarity[0][0]
该函数的主要参数包括:
stop_words='english': 忽略常见的英文单词以聚焦有意义的内容use_idf=True: 启用逆文档频率加权norm='l2': 对每个输出向量应用 L2 归一化ngram_range=(1, 2): 考虑单词和双词组合sublinear_tf=True: 应用对数词频缩放analyzer='word': 在词级进行分析
余弦相似度在某些情况下可能有限。当遇到模糊查询时,由于它严格基于文本的向量表示的角度进行相似度测量,可能无法处理。如果用户提出模糊问题,比如“什么是 rag?”而数据库主要包含关于 AI 中的“RAG”(检索增强生成)的信息,而不是“破布”,余弦相似度分数可能会很低。这是因为数学模型缺乏上下文理解,无法区分“rag”的不同含义。它只基于文本中相似词的出现和频率计算相似度,而无法理解用户意图或查询的广泛上下文。因此,即使可用数据集内的回答技术上准确,如果查询的上下文没有很好地反映在数据中,余弦相似度可能无法准确地反映相关性。
在这种情况下,我们可以尝试增强的相似度。
增强相似度
增强相似度引入利用自然语言处理工具的计算,以更好地捕捉词语之间的语义关系。通过使用 spaCy 和 NLTK 等库,它对文本进行预处理以减少噪声,使用 WordNet 扩展词的同义词,并基于扩展后的词汇的语义丰富性计算相似度。该方法通过考虑比典型直接比较方法更广泛的上下文,旨在提高两个文本相似性评估的准确性。
代码包含四个主要函数:
get_synonyms(word): 获取给定词的同义词preprocess_text(text): 将所有文本转为小写,词形还原,并过滤停用词和标点符号expand_with_synonyms(words): 通过添加同义词增强词列表calculate_enhanced_similarity(text1, text2): 计算预处理并扩展同义词的文本向量之间的余弦相似度
calculate_enhanced_similarity(text1, text2) 函数接受两个文本并最终返回处理并扩展同义词后的文本的余弦相似度分数。该分数基于文本的语义内容和增强后的词集合来量化文本相似性。
代码首先下载并导入必要的库,然后从 calculate_enhanced_similarity(text1, text2) 开始运行四个函数:
import spacy
import nltk
nltk.download('wordnet')
from nltk.corpus import wordnet
from collections import Counter
import numpy as np
# 加载 spaCy 模型
nlp = spacy.load("en_core_web_sm")
增强相似度在指标方面更进一步。然而,将 RAG 与生成式 AI 集成会带来多种挑战。
无论我们实施哪种指标,都会面临以下限制:
- 输入与文档长度:用户查询通常较短,而检索到的文档较长且内容丰富,使直接相似度评估变得复杂。
- 创造性检索:系统可能会选择符合用户期望的长文档,但由于意料之外的内容排列导致得分较低。
- 需要人类反馈:人类判断往往对准确评估检索内容的相关性和有效性至关重要,因为自动化指标可能无法完全捕捉用户的满意度。我们将在第 5 章《通过专家人类反馈提升 RAG 性能》中探讨 RAG 的这一关键方面。
我们始终需要在数学指标和人类反馈之间找到适当的平衡。
现在我们可以创建一个基础 RAG 示例。
2. 基础 RAG
基础 RAG 中的关键词搜索和匹配在拥有明确文档的组织中可能非常高效,例如法律和医学文档。这些文档通常有明确的标题或标签(如图像)。在此基础 RAG 函数中,我们将实现关键词搜索和匹配。为此,我们将在代码中应用简单的检索方法:
- 将查询拆分为独立的关键词
- 将数据集中的每条记录拆分为关键词
- 确定共同匹配的长度
- 选择得分最高的记录
生成方法将:
- 使用检索查询的结果增强用户输入
- 请求生成模型(在此为 gpt-4o)
- 显示响应
让我们编写关键词搜索和匹配函数。
关键词搜索与匹配
最佳匹配函数首先初始化最佳分数:
def find_best_match_keyword_search(query, db_records):
best_score = 0
best_record = None
接着,将查询拆分为关键词。每条记录也拆分为单词以找到共同的词语,测量共同内容的长度并找到最佳匹配:
# 将查询拆分为独立关键词
query_keywords = set(query.lower().split())
# 遍历 db_records 中的每条记录
for record in db_records:
# 将记录拆分为关键词
record_keywords = set(record.lower().split())
# 计算共同关键词的数量
common_keywords = query_keywords.intersection(record_keywords)
current_score = len(common_keywords)
# 如果当前得分高于最佳得分,则更新最佳得分和记录
if current_score > best_score:
best_score = current_score
best_record = record
return best_score, best_record
现在调用函数,格式化响应并打印:
# 假设 'query' 和 'db_records' 已在 Colab 笔记本的前一部分中定义
best_keyword_score, best_matching_record = find_best_match_keyword_search(query, db_records)
print(f"Best Keyword Score: {best_keyword_score}")
#print(f"Best Matching Record: {best_matching_record}")
print_formatted_response(best_matching_record)
此笔记本的主查询将为 query = "define a rag store" 以查看每种 RAG 方法是否产生可接受的输出。
关键词搜索在数据集的句子列表中找到了最佳记录:
Best Keyword Score: 3
Response:
---------------
A RAG vector store is a database or dataset that contains vectorized data points.
---------------
让我们运行指标。
指标
我们在本章的“1. 检索指标”部分中创建了相似度指标。我们首先应用余弦相似度:
# 余弦相似度
score = calculate_cosine_similarity(query, best_matching_record)
print(f"Best Cosine Similarity Score: {score:.3f}")
输出的相似度较低,如本章“1. 检索指标”部分所述。用户输入较短,而响应较长且完整:
Best Cosine Similarity Score: 0.126
增强相似度将产生更高的得分:
# 增强相似度
response = best_matching_record
print(query,": ", response)
similarity_score = calculate_enhanced_similarity(query, response)
print(f"Enhanced Similarity:, {similarity_score:.3f}")
使用增强功能后的得分更高:
define a rag store : A RAG vector store is a database or dataset that contains vectorized data points.
Enhanced Similarity:, 0.642
查询的输出现在将增强用户输入。
增强输入
增强输入是用户输入与通过关键词搜索检测到的数据集中最佳匹配记录的连接:
augmented_input = query + ": " + best_matching_record
必要时显示增强输入以便于维护:
print_formatted_response(augmented_input)
输出显示增强输入已准备就绪:
Response:
---------------
define a rag store: A RAG vector store is a database or dataset that contains
vectorized data points.
---------------
输入现在准备好用于生成过程。
生成
我们现在准备调用 GPT-4o 并显示格式化响应:
llm_response = call_llm_with_full_text(augmented_input)
print_formatted_response(llm_response)
响应的片段显示 GPT-4o 理解了输入并提供了有趣且相关的回答:
Response:
---------------
Certainly! Let's break down and elaborate on the provided content: ### Define a
RAG Store: A **RAG (Retrieval-Augmented Generation) vector store** is a
specialized type of database or dataset that is designed to store and manage
vectorized data points…
基础 RAG 在许多情况下已经足够。然而,如果文档数量过多或内容变得更复杂,高级 RAG 配置将提供更好的结果。接下来让我们探讨高级 RAG。
3. 高级 RAG
随着数据集规模扩大,关键词搜索方法可能会运行时间过长。例如,如果我们有数百个文档,每个文档包含数百个句子,仅使用关键词搜索将变得极具挑战性。使用索引可以将计算负担减少到数据总量的一小部分。
在本节中,我们将超越使用关键词搜索文本的方式,了解 RAG 如何将文本数据转换为数值表示,以提高搜索效率和处理速度。与直接解析文本的传统方法不同,RAG 首先将文档和用户查询转换为向量,即加速计算的数值形式。简单来说,向量是一个表示文本各种特征的数字列表。简单的向量可以计数单词出现频率(词频),而更复杂的向量(称为嵌入)可以捕捉更深层的语言模式。
本节将实现向量搜索和基于索引的搜索:
- 向量搜索:我们将数据集中的每个句子转换为数值向量。通过计算查询向量(用户查询)与这些文档向量之间的余弦相似度,可以快速找到最相关的文档。
- 基于索引的搜索:在这种情况下,所有句子都使用 TF-IDF(词频-逆文档频率)转换为向量,这是一种统计方法,用于评估一个词对文档在集合中的重要性。这些向量充当矩阵中的索引,从而无需完全解析每个文档即可快速进行相似度比较。
让我们从向量搜索开始,看看这些概念的实际应用。
3.1 向量搜索
向量搜索将用户查询和文档转换为向量形式的数值,使得在处理大量数据时能够通过数学计算更快地检索相关数据。
程序遍历数据集的每条记录,通过计算查询向量与数据集中每条记录的余弦相似度找到最佳匹配文档:
def find_best_match(text_input, records):
best_score = 0
best_record = None
for record in records:
current_score = calculate_cosine_similarity(text_input, record)
if current_score > best_score:
best_score = current_score
best_record = record
return best_score, best_record
然后代码调用向量搜索函数并显示找到的最佳记录:
best_similarity_score, best_matching_record = find_best_match(query, db_records)
print_formatted_response(best_matching_record)
输出令人满意:
Response:
---------------
A RAG vector store is a database or dataset that contains vectorized data
points.
与基础 RAG 类似,响应是找到的最佳结果。这表明没有万能的解决方案。每种 RAG 技术都有其优点。指标将证实这一观察结果。
指标
对于相似性方法的指标,与基础 RAG 相同,因为检索到了相同的文档:
print(f"Best Cosine Similarity Score: {best_similarity_score:.3f}")
输出为:
Best Cosine Similarity Score: 0.126
使用增强相似度后,得到的输出与基础 RAG 相同:
# 增强相似度
response = best_matching_record
print(query,": ", response)
similarity_score = calculate_enhanced_similarity(query, best_matching_record)
print(f"Enhanced Similarity:, {similarity_score:.3f}")
输出证实了这一趋势:
define a rag store : A RAG vector store is a database or dataset that contains vectorized data points.
Enhanced Similarity:, 0.642
那么,如果它产生与基础 RAG 相同的输出,为什么还要使用向量搜索呢?在小数据集上,一切看起来都很简单。但当我们处理数百万条复杂文档的数据集时,关键词搜索将无法捕捉到向量能够捕捉到的细微差异。现在让我们将检索到的信息与用户查询合并。
增强输入
我们不加任何其他辅助信息,将检索到的信息添加到用户查询中并显示结果:
# 调用函数并打印结果
augmented_input = query + ": " + best_matching_record
print_formatted_response(augmented_input)
我们只在用户查询和检索到的信息之间添加了一个空格,其他没有变化。输出令人满意:
Response:
---------------
define a rag store: A RAG vector store is a database or dataset that contains
vectorized data points.
---------------
现在让我们看看生成式 AI 模型如何响应此增强输入。
生成
我们现在使用增强输入调用 GPT-4o 并显示格式化输出:
# 调用函数并打印结果
augmented_input = query + best_matching_record
llm_response = call_llm_with_full_text(augmented_input)
print_formatted_response(llm_response)
响应合理,如以下摘录所示:
Response:
---------------
Certainly! Let's break down and elaborate on the provided content: ### Define a RAG Store: A **RAG (Retrieval-Augmented Generation) vector store** is a specialized type of database or dataset that is designed to store and manage vectorized data points…
虽然向量搜索通过逐一遍历记录显著加快了找到相关文档的过程,但其效率可能随着数据集的增大而降低。为了解决此扩展性问题,基于索引的搜索提供了更先进的解决方案。接下来让我们看看基于索引的搜索如何加速文档检索。
3.2 基于索引的搜索
基于索引的搜索并不是将用户查询的向量与文档内容的直接向量进行比较,而是与代表该内容的索引向量进行比较。
程序首先导入所需的类和函数:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
TfidfVectorizer 导入了一个类,将文本文档转换为 TF-IDF 特征矩阵。TF-IDF 使用文档中的词频来量化单词在文档中的重要性。该函数通过余弦相似度函数找到最佳匹配,以计算查询与矩阵加权向量之间的相似度:
def find_best_match(query, vectorizer, tfidf_matrix):
query_tfidf = vectorizer.transform([query])
similarities = cosine_similarity(query_tfidf, tfidf_matrix)
best_index = similarities.argmax() # 获取最高相似度分数的索引
best_score = similarities[0, best_index]
return best_score, best_index
该函数的主要任务包括:
- 转换查询:使用提供的向量化器将输入查询转换为 TF-IDF 向量格式
- 计算相似度:计算查询向量与
tfidf_matrix中所有向量之间的余弦相似度 - 识别最佳匹配:找到结果中最高相似度分数的索引 (
best_index) - 提取最佳分数:提取最高的余弦相似度分数 (
best_score)
输出是找到的最佳相似度分数和最佳索引。
以下代码首先调用数据集的向量化器,然后通过索引搜索最相似的记录:
vectorizer, tfidf_matrix = setup_vectorizer(db_records)
best_similarity_score, best_index = find_best_match(query, vectorizer, tfidf_matrix)
best_matching_record = db_records[best_index]
最后显示结果:
print_formatted_response(best_matching_record)
系统找到与用户输入查询最相似的文档:
Response:
---------------
A RAG vector store is a database or dataset that contains vectorized data
points.
---------------
可以看到,在运行 GPT-4o 之前,模糊的用户查询在检索层面上已生成可靠的输出。
程序中的后续指标与基础和向量搜索的高级 RAG 相同。这是正常的,因为找到的文档与用户输入查询最接近。从第 2 章《使用 Deep Lake 和 OpenAI 实现 RAG 嵌入向量存储》开始,我们将引入更复杂的 RAG 文档。目前,我们来看看影响词在向量中表示方式的特征。
特征提取
在使用该文档增强输入之前,运行以下单元格,该单元格再次调用 setup_vectorizer(records) 函数,但会显示矩阵,以便您可以看到其格式。以下摘录显示了句子中的单词“accurate”和“additional”的格式:
一个黑白图像显示数字,描述为中等置信度的自动生成
图 1.4:矩阵的格式
现在让我们增强输入。
增强输入
我们只需将查询与最佳匹配记录以最简方式合并,以查看 GPT-4o 的反应并显示输出:
augmented_input = query + ": " + best_matching_record
print_formatted_response(augmented_input)
输出接近向量搜索的结果,且检索方法更快:
Response:
---------------
define a rag store: A RAG vector store is a database or dataset that contains
vectorized data points.
---------------
我们现在将该增强输入传入生成式 AI 模型。
生成
现在,我们使用增强输入调用 GPT-4o 并显示输出:
# 调用函数并打印结果
llm_response = call_llm_with_full_text(augmented_input)
print_formatted_response(llm_response)
初始模糊查询的用户可以得到合适的响应,如下摘录所示:
Response:
---------------
Certainly! Let's break down and elaborate on the given content: --- **Define a RAG store:** A **RAG vector store** is a **database** or **dataset** that contains **vectorized data points**. --- ### Detailed Explanation: 1. **RAG Store**: - **RAG** stands for **Retrieval-Augmented Generation**. It is a technique used in natural language processing (NLP) where a model retrieves relevant information from a database or dataset to augment its generation capabilities…
此方法在特定领域的封闭环境(如组织内)中效果很好。而在开放环境中,用户可能需要在提交请求前提供更多的详细信息。
在本节中,我们看到 TF-IDF 矩阵预先计算了文档向量,从而无需重复向量转换即可实现更快、并行的比较。我们已了解向量和基于索引的搜索如何改进检索。然而,在一个项目中,根据需要检索的文档,我们可能需要应用基础和高级 RAG。现在让我们看看模块化 RAG 如何改进我们的系统。
4. 模块化 RAG
在实现 RAG 时,我们应该使用关键词搜索、向量搜索还是基于索引的搜索?每种方法都有其优点。选择将取决于多个因素:
- 关键词搜索适合简单的检索
- 向量搜索适用于语义丰富的文档
- 基于索引的搜索在处理大量数据时提供速度优势
然而,这三种方法完全可以在一个项目中结合使用。例如,在一种场景下,关键词搜索可以帮助找到明确标注的文档标签,如 PDF 文件的标题和标注的图像,然后进行处理。接着,基于索引的搜索会将文档分组到索引子集内。最后,检索程序可以在索引数据集中搜索,找到一个子集,并仅使用向量搜索在有限数量的文档中找到最相关的文档。
在本节中,我们将创建一个 RetrievalComponent 类,可以在项目的每个步骤中调用,以执行所需的任务。代码总结了本章中构建的三种方法,我们可以通过 RetrievalComponent 的主要成员为其总结这些方法。
以下代码初始化类时选择搜索方法,并在需要时准备一个向量化器。self 指代类的当前实例,用于访问它的变量、方法和函数:
def __init__(self, method='vector'):
self.method = method
if self.method == 'vector' or self.method == 'indexed':
self.vectorizer = TfidfVectorizer()
self.tfidf_matrix = None
在此情况下,激活了向量搜索。
fit 方法从记录中构建一个 TF-IDF 矩阵,适用于向量或索引搜索方法:
def fit(self, records):
if self.method == 'vector' or self.method == 'indexed':
self.tfidf_matrix = self.vectorizer.fit_transform(records)
retrieve 方法将查询指向适当的搜索方法:
def retrieve(self, query):
if self.method == 'keyword':
return self.keyword_search(query)
elif self.method == 'vector':
return self.vector_search(query)
elif self.method == 'indexed':
return self.indexed_search(query)
关键词搜索方法通过计算查询和文档之间的共同关键词来找到最佳匹配:
def keyword_search(self, query):
best_score = 0
best_record = None
query_keywords = set(query.lower().split())
for index, doc in enumerate(self.documents):
doc_keywords = set(doc.lower().split())
common_keywords = query_keywords.intersection(doc_keywords)
score = len(common_keywords)
if score > best_score:
best_score = score
best_record = self.documents[index]
return best_record
向量搜索方法计算查询 TF-IDF 与文档矩阵之间的相似度并返回最佳匹配:
def vector_search(self, query):
query_tfidf = self.vectorizer.transform([query])
similarities = cosine_similarity(query_tfidf, self.tfidf_matrix)
best_index = similarities.argmax()
return db_records[best_index]
基于索引的搜索方法使用预计算的 TF-IDF 矩阵快速检索最匹配的文档:
def indexed_search(self, query):
# 假设 tfidf_matrix 已预计算并存储
query_tfidf = self.vectorizer.transform([query])
similarities = cosine_similarity(query_tfidf, self.tfidf_matrix)
best_index = similarities.argmax()
return db_records[best_index]
现在我们可以激活模块化 RAG 策略。
模块化 RAG 策略
在需要时,我们可以为任何 RAG 配置调用检索组件:
# 使用示例
retrieval = RetrievalComponent(method='vector') # 从 'keyword'、'vector'、'indexed' 中选择
retrieval.fit(db_records)
best_matching_record = retrieval.retrieve(query)
print_formatted_response(best_matching_record)
在此情况下,激活了向量搜索方法。
以下单元格选择最佳记录,如“3.1 向量搜索”部分所述,增强输入,调用生成模型,并显示输出,如以下摘录所示:
Response:
---------------
Certainly! Let's break down and elaborate on the content provided: ---
**Define a RAG store:** A **RAG (Retrieval-Augmented Generation) store** is a specialized type of data storage system designed to support the retrieval and generation of information...
我们构建了一个程序,展示了如何将关键词、向量和基于索引的搜索方法有效地集成到 RAG 系统中。每种方法都有其独特的优势,并满足数据检索环境中的特定需求。方法的选择取决于数据集的大小、查询类型和性能要求,我们将在后续章节中进一步探讨。
现在是总结本章探索内容并进入下一级的时候了!
总结
用于生成式 AI 的 RAG 依赖于两个主要组件:检索器和生成器。检索器处理数据并定义一种搜索方法,例如使用关键词检索带标签的文档,而生成器(一个大型语言模型)的输入在生成序列时可以从增强信息中获益。我们介绍了 RAG 框架的三种主要配置:基础 RAG,通过关键词和其他初级搜索方法访问数据集;高级 RAG,引入嵌入和索引以改进搜索方法;模块化 RAG,可以结合基础和高级 RAG 以及其他机器学习方法。
RAG 框架依赖于可以包含动态数据的数据集。生成式 AI 模型则依赖其权重中的参数化数据。这两种方法并非互相排斥。如果 RAG 数据集变得过于庞大,微调可能会有所帮助。而当微调后的模型无法应对日常信息时,RAG 则可以派上用场。RAG 框架还高度依赖于生态系统,以提供关键功能使系统正常工作。我们讨论了 RAG 生态系统的主要组件,从检索器到生成器,并说明了训练器和评估器的重要性。最后,我们在 Python 中构建了入门级的基础、高级和模块化 RAG 程序,利用关键词匹配、向量搜索和基于索引的检索,增强了 GPT-4o 的输入。
接下来,在第 2 章《使用 Deep Lake 和 OpenAI 实现 RAG 嵌入向量存储》中,我们将把数据嵌入到向量中。我们会将这些向量存储在向量存储中,以提高 RAG 生态系统中检索功能的速度和精度。