向量是检索增强生成(RAG)中的一个关键组成部分,它们是帮助整个过程顺利运行的秘密武器。在本章中,我们将重新回顾前几章的代码,并重点讨论向量如何影响这些代码。简单来说,本章将讲解什么是向量,如何创建向量,然后在哪里存储它们。更技术化地讲,我们将讨论向量、向量化和向量存储。本章的核心是向量的创建以及它们为何重要。我们将重点讨论向量与RAG的关系,但我们鼓励你花更多时间深入研究向量,尽可能地理解它们。你对向量的理解越深入,就能越有效地改善你的RAG管道。
由于向量的讨论非常重要,我们将在两章中展开讨论。虽然本章重点讲解向量和向量存储,但第8章将重点讲解向量搜索,也就是向量在RAG系统中的应用。
本章将涉及以下主题:
- RAG中的向量基础
- 向量在代码中的位置
- 向量化的文本量很重要!
- 不是所有语义都是平等的!
- 常见的向量化技术
- 选择向量化选项
- 开始使用向量存储
- 向量存储
- 选择向量存储
技术要求
回顾我们在前几章讨论过的代码,本章重点讲解这一行代码:
vectorstore = Chroma.from_documents(
documents=splits,
embedding=OpenAIEmbeddings())
本章的代码在这里:GitHub链接
文件名是 CHAPTER7-1_COMMON_VECTORIZATION_TECHNIQUES.ipynb
。
第8章将重点讲解这一行代码:
retriever = vectorstore.as_retriever()
就这两行代码,涵盖两章内容?是的!这正好展示了向量在RAG系统中的重要性。为了深入理解向量,我们从基础开始,并逐步构建知识。
让我们开始吧!
RAG中的向量基础
在本节中,我们将介绍与向量和嵌入(embeddings)相关的几个重要主题,重点讨论它们在自然语言处理(NLP)和RAG中的应用。我们首先将澄清向量和嵌入之间的关系,解释嵌入是NLP中用于表示文本的特定类型的向量表示。然后,我们讨论向量的属性,例如它们的维度和大小,以及这些特性如何影响文本搜索和相似性比较的精确度和有效性。
嵌入和向量有什么区别?
向量和嵌入是NLP中的关键概念,在构建语言模型和RAG系统中起着至关重要的作用。那么它们到底是什么,如何相互关联呢?简单来说,您可以将嵌入视为一种特定类型的向量表示。当我们谈论用于RAG的超大语言模型(LLM)时,这些模型属于更广泛的NLP领域,我们使用的向量通常被称为嵌入。另一方面,向量在更广泛的领域中都有应用,不仅限于语言构造(如单词、句子、段落等)。因此,在讨论RAG时,嵌入、向量、向量嵌入和嵌入向量等术语可以互换使用!
现在我们已经澄清了这一点,让我们来讨论一下什么是向量。
什么是向量?
当你听到“向量”这个词时,你首先想到的是什么?很多人可能会说数学。这个答案是准确的;向量实际上是我们在数据中处理的文本的数学表示,它们允许我们以新的、非常有用的方式对数据进行数学运算。
“向量”这个词还可能让你联想到速度。这个理解也是正确的;使用向量,我们可以比任何以前的技术更快地进行文本搜索。
另一个经常与向量相关联的概念是精确度。通过将文本转换为具有语义表示的嵌入,我们可以显著提高搜索系统在查找目标时的精确度。
当然,如果你是《卑鄙的我》(Despicable Me)这部电影的粉丝,你可能会想到反派角色Vector,他自我介绍为:“我叫Vector。它是一个数学术语,表示一个有方向和大小的箭头。”
他虽然是个做了很多可疑事情的反派,但他的名字背后的含义是对的!从这个描述中,最重要的是要明白,向量不仅仅是一堆数字;它是一个数学对象,表示大小和方向。这就是为什么它能比单纯的数字更好地表示文本及其相似性,因为它捕捉了比简单数字更复杂的形式。
这或许能帮助你理解向量的概念,但接下来我们将讨论向量的一些重要方面,这些方面将影响你在RAG开发中的应用,首先从向量大小开始。
向量的维度和大小
《卑鄙的我》中的反派Vector说过,向量是“一种由箭头表示的量”。虽然将向量在二维或三维图形中用箭头表示可以帮助理解向量的概念,但要理解我们使用的向量通常是多维的,这点非常重要。向量的维度也称为向量大小。为了展示这一点,我们将在定义变量的代码下方添加一个新单元格。这段代码会打印出嵌入向量的一个小部分:
question = "What are the advantages of using RAG?"
question_embedding = embedding_function.embed_query(question)
first_5_numbers = question_embedding[:5]
print(f"User question embedding (first 5 dimensions): {first_5_numbers}")
在这段代码中,我们使用了前面代码示例中提到的“使用RAG的优势是什么?”这一问题,并使用OpenAI的嵌入API将其转换为向量表示。question_embedding
变量表示这个嵌入。通过切片[0:5],我们从question_embedding
中获取前五个数字,代表向量的前五个维度并打印出来。完整的向量包含1,536个浮动数字,每个数字有17到20位数,所以我们打印的只是前五个数字,以便更容易阅读。输出结果如下:
User question embedding (first 5 dim): [-0.006319054113595048, -0.0023517232115089787, 0.015498643243434815, -0.02267445873596028, 0.017820641897159206]
我们这里只打印出前五个维度,但嵌入向量远不止这些。接下来我们将展示如何实际确定嵌入的总维度数,但首先,我想引起你注意的是每个数字的长度。
这些嵌入中的所有数字都是带小数点的正负数,所以我们先讨论小数点后面的位数。第一个数字-0.006319054113595048
有18位小数,第二个数字有19位,第四个数字有17位。这些位数与OpenAI嵌入模型(OpenAIEmbeddings)所使用的浮动精度表示有关。这个模型使用的是高精度的浮点格式,提供64位数字(也称为双精度),这使得能够非常细致地区分和精确表示嵌入模型捕捉到的语义信息。
此外,回顾第1章中提到的,前面的输出看起来很像一个Python浮点数列表。实际上,这确实是一个Python列表,因为OpenAI的嵌入API返回的就是这样的格式。这是为了使其与Python编码环境更加兼容。但为了避免混淆,需要理解的是,在机器学习领域,看到类似这样的结构通常表示它是一个NumPy数组,即使在打印输出时,数字列表和NumPy数组看起来一样。
有趣的事实
如果你从事生成式AI工作,最终会听到“量化”这个概念。与嵌入类似,量化也涉及到高精度浮动点。但量化的目的是将模型参数(如权重和激活)从原始的高精度浮动点表示转换为较低精度的格式。这可以减少LLM的内存占用和计算需求,从而使其在预训练、训练和微调时更加具有成本效益。量化还可以使得进行推理时更加经济(推理就是使用LLM生成响应)。当我在这里说“经济”时,是指在较小且成本较低的硬件环境中进行这些操作。量化是一种有损压缩技术,这意味着在转换过程中会丢失一些信息,量化后的LLM可能会比原始高精度LLM的精确度低。
在使用RAG时,如果准确性和响应质量对你的RAG系统至关重要,那么在选择不同的算法将文本转换为嵌入时,务必注意嵌入值的长度,确保使用高精度的浮动点格式。
那么这些嵌入到底有多少维度呢?我们在前面的例子中只展示了五个维度,但我们当然可以打印出全部维度并逐个计数。这显然不现实,因此我们将使用len()
函数来统计总维度数。以下代码演示了这一点:
embedding_size = len(question_embedding)
print(f"Embedding size: {embedding_size}")
这段代码的输出如下:
Embedding size: 1536
这表明,这个嵌入向量有1,536个维度!当我们通常只考虑最多3维时,想象1,536维是非常困难的,但这额外的1,533维度对我们嵌入语义表示的精确度差异有着显著影响。
在大多数现代向量化算法中,向量通常有数百或数千个维度。向量的维度数等于表示嵌入的浮动点的数量,这意味着一个1,024维的向量由1,024个浮动点表示。虽然没有硬性限制规定嵌入的长度,但一些现代向量化算法通常会有预设的大小。我们使用的OpenAI的ada嵌入模型默认使用1,536维。因为它被训练为生成一定大小的嵌入,如果尝试截断该大小,会改变嵌入捕捉的上下文。
然而,这一切都在变化。现在有新的向量化工具(例如OpenAI的text-embedding-3-large模型),可以让你调整向量大小。这些嵌入模型已经被训练,能够在不同的向量维度大小之间相对保留相同的上下文。这就启用了“自适应检索”技术。
通过自适应检索,你可以生成多个不同维度的嵌入集。你首先搜索低维度的向量,快速接近最终结果,因为低维度的向量计算更加高效。如果从这些结果中找到了很好的匹配,你就可以进行更精确的搜索,选择更高维度的嵌入向量进行最终匹配。
向量的尺寸如何影响检索和计算?
向量的维度和大小对后续计算和检索性能有显著影响。在设计RAG系统时,了解如何选择合适的向量维度、如何处理大规模的嵌入空间、以及如何优化这些过程以提高性能,将直接影响系统的整体效率和用户体验。
向量在你的代码中潜藏的地方
展示向量在RAG系统中的价值的一种方式是,向你展示它们在代码中的应用场景。正如之前所讨论的那样,你从文本数据开始,在向量化过程中将其转换为向量。这发生在RAG系统的索引阶段。但是,在大多数情况下,你需要有一个地方来存储这些嵌入向量,这就引出了“向量存储”的概念。
在RAG系统的检索阶段,你以用户输入的问题开始,首先将其转换为嵌入向量,然后再开始检索。最后,检索过程使用一种相似性算法,确定问题嵌入与向量存储中所有嵌入之间的接近度。还有一个潜在的向量应用场景,那就是当你想评估RAG的响应时,但我们将在第9章讨论评估技术时涵盖这一部分。现在,让我们更深入地探讨每个概念,从向量化开始。
向量化发生的两个地方
在RAG流程的最前端,通常会有一个机制,让用户输入一个问题,并将其传递给检索器。我们在代码中看到这一处理过程,如下所示:
rag_chain_with_source = RunnableParallel(
{"context": retriever,
"question": RunnablePassthrough()}
).assign(answer=rag_chain_from_docs)
retriever
是一个 LangChain 检索器对象,负责基于用户查询进行相似性搜索并检索相关的向量。因此,当我们谈论向量化时,它实际上发生在代码中的两个地方:
- 当我们对原始数据进行向量化,这些数据将在RAG系统中使用。
- 当我们需要对用户查询进行向量化。
这两个步骤之间的关系是,它们都用于相似性搜索。在讨论搜索之前,让我们先讨论后者,即原始数据的嵌入向量存储位置——向量存储。
向量数据库/存储:存储和包含向量
向量存储通常是一个向量数据库(但并非总是如此,见下文说明),它经过优化用于存储和提供向量,并在有效的RAG系统中发挥着至关重要的作用。从技术上讲,你可以在没有向量数据库的情况下构建RAG系统,但你会错失许多已经在这些数据存储工具中构建的优化,这会不必要地影响你的内存、计算要求和搜索精度。
注释
你经常会听到“向量数据库”这个术语,用于指代优化存储向量的类似数据库的结构。然而,也有一些工具和机制,它们并不是数据库,但同样或类似地提供了向量数据库的功能。因此,我们将它们统称为向量存储(vector stores)。这是与LangChain文档一致的,LangChain文档也将所有存储和提供向量的机制统称为向量存储(vector stores)。不过,你会经常看到这两个术语互换使用,实际上“向量数据库”这个术语更为常用,用来指代所有这些机制。为了准确起见,并与LangChain文档保持一致,在本书中我们将使用“向量存储”这一术语。
在你的代码中,向量存储是存放大多数生成的向量的地方。当你对数据进行向量化时,这些嵌入会进入向量存储。当你进行相似性搜索时,表示这些数据的嵌入会从向量存储中提取。这使得向量存储成为RAG系统中的关键角色,值得我们关注。
现在我们知道了原始数据的嵌入存储的位置,让我们回到它们如何与用户查询的嵌入关联起来。
向量相似性:比较你的向量
我们有两个主要的向量化发生场景:
- 用户查询的嵌入向量
- 向量存储中代表所有数据的嵌入向量
让我们回顾一下这两者之间的关系。当我们进行关键的向量相似性搜索时,它实际上是在执行一个数学运算,计算用户查询嵌入与原始数据嵌入之间的距离。
可以使用多种数学算法来执行这个距离计算,我们将在第8章讨论这些算法。但现在,重要的是要理解,这个距离计算的目的是找出与用户查询嵌入最接近的原始数据嵌入,并按其距离顺序返回这些嵌入(从最接近到最远)。我们的代码稍微简化了一些,嵌入表示数据点(片段)之间的1:1关系。
但是,在许多应用中,比如问题回答聊天机器人,问题或答案可能非常长,并且被拆分成较小的片段,你可能会看到这些片段拥有一个外键ID,指向更大的内容。这使我们能够检索到完整的内容,而不仅仅是片段。根据你的RAG系统试图解决的问题,架构可能会有所不同,但重要的是要理解,这个检索系统的架构可以根据应用需求进行调整。
这涵盖了你在RAG系统中最常见的向量应用场景:它们出现的地方、存储的位置以及如何在RAG系统中为服务使用它们。在接下来的部分,我们将讨论数据文本的大小如何影响RAG系统的搜索。你最终会在代码中做出决定,来决定这些数据的大小。但从你已经了解的向量知识来看,你可能会开始想,如果我们对不同大小的内容进行向量化,这会如何影响我们比较它们的能力,并最终构建最有效的检索过程?你这样想是有道理的!接下来我们将讨论将内容转化为嵌入时,内容大小对检索过程的影响。
向量化的文本量很重要!
我们之前展示的向量来自于文本“使用RAG的优势是什么?”这是一段相对较短的文本,这意味着一个1,536维的向量能够很好地代表这段文本的上下文。但如果我们回到代码中,我们向量化的数据内容来自于以下部分:
loader = WebBaseLoader(
web_paths=("https://kbourne.github.io/chapter1.html",),
bs_kwargs=dict(
parse_only=bs4.SoupStrainer(
class_=("post-content", "post-title", "post-header")
)
),
)
docs = loader.load()
这段代码拉取了我们在前几章看到的网页内容,相对于问题文本来说,这段内容相对较长。为了让数据更易于处理,我们使用文本拆分器将内容分成了多个块,如下所示:
text_splitter = SemanticChunker(embedding_function)
splits = text_splitter.split_documents(docs)
如果你提取第三个块(splits[2]
),它看起来像这样:
“There are also generative models that generate images from text prompts, while others generate video from text prompts. There are other models that generate text descriptions from images. We will talk about these other types of models in Chapter 16, Going Beyond the LLM. But for most of the book, I felt it would keep things simple and let you focus on the core principles of RAG if we focus on the type of model that most RAG pipelines use, the LLM. But I did want to make sure it was clear, that while the book focuses primarily on LLMs, RAG can also be applied to other types of generative models, such as those for images and videos. Some popular examples of LLMs are the OpenAI ChatGPT models, the Meta LLaMA models, Google's PaLM and Gemini models, and Anthropic's Claude models. Foundation model\nA foundation model is the base model for most LLMs. In the case of ChatGPT, the foundation model is based on the GPT (Generative Pre-trained Transformer) architecture, and it was fine-tuned for Chat. The specific model used for ChatGPT is not publicly disclosed. The base GPT model cannot talk with you in chatbot-style like ChatGPT does. It had to get further trained to gain that skill.”
我选择了第三个块来展示,因为它是一个相对较短的块。大多数块要大得多。我们使用的Semantic Chunker文本拆分器尝试使用语义来确定如何拆分文本,利用嵌入向量来确定这些语义。理论上,这应该能给我们带来更符合上下文的块,而不仅仅是根据任意大小来拆分。
然而,在涉及嵌入向量时,有一个重要的概念需要理解,这会影响你选择的拆分器和嵌入的大小。所有这些都源于一个事实:无论你传递给向量化算法的文本有多大,它最终给你的嵌入仍然会是与其他嵌入相同大小的。在这种情况下,这意味着用户查询的嵌入将是1,536维,但存储在向量存储中的所有长文本段也将是1,536维,尽管它们在文本格式上的实际长度相差很大。这可能有点反直觉,但令人惊讶的是,这样做效果很好!
在进行用户查询与向量存储的搜索时,用户查询的嵌入与其他嵌入的数学表示方式,使得我们仍然能够检测到它们之间的语义相似性,尽管它们的大小差异很大。这是向量相似性搜索的一部分,正是这种现象让数学家们如此热爱数学。似乎完全违背逻辑:你可以将长度截然不同的文本转化为数字,并能够检测它们之间的相似性。
但是,还有另一个方面需要考虑——当你仅比较数据拆分成的块时,块的大小会影响结果。在这种情况下,被向量化的内容量越大,嵌入向量会变得越稀释。另一方面,被向量化的内容量越小,你在进行向量相似性搜索时,匹配到的上下文也会减少。对于每一个RAG实现,你需要在块大小和上下文表示之间找到一个微妙的平衡。
理解这一点有助于你在拆分数据和选择向量化算法时做出更好的决策,从而改进你的RAG系统。我们将在第11章讨论LangChain拆分器时,涵盖一些让你的拆分/分块策略更有效的其他技术。接下来,我们将讨论测试不同向量化模型的重要性。
并非所有语义都是平等的!
在RAG应用中,一个常见的错误是选择第一个实现的向量化算法,并假设它能提供最佳结果。这些算法将文本的语义意义转化为数学表示。然而,这些算法本身通常也是大型的NLP模型,它们的能力和质量可能与LLM(大语言模型)一样有所不同。就像我们人类在理解文本的复杂性和细微差别时常常遇到困难一样,这些模型也会面临同样的挑战,它们对书面语言固有复杂性的理解能力有所不同。例如,过去的模型无法区分“bark”(狗叫声)和“bark”(大多数树木的外皮),但较新的模型能够根据上下文和周围的文本来识别这种差异。这个领域正在迅速适应和发展,就像其他领域一样。
在某些情况下,可能会发现一个专门针对某一领域的向量化模型(例如在科学论文上训练的模型)在专注于科学论文的应用中比通用的向量化模型表现得更好。科学家的语言非常特定,与社交媒体上看到的语言差异很大,因此一个基于通用网络文本训练的庞大模型可能在这个特定领域的表现不佳。
趣味小知识
你经常会听到可以对LLM进行微调,以改善特定领域的结果。但你知道你也可以对嵌入模型进行微调吗?微调嵌入模型有可能改善嵌入模型对你特定领域数据的理解,因此也有可能改善相似性搜索的结果。这有可能显著提升你整个RAG系统在你领域中的表现。
总结一下关于向量的基础知识,向量的多个方面可能在你尝试构建最有效的RAG应用时帮助你,也可能对你造成困扰。当然,如果我只是告诉你向量化算法有多重要而不告诉你有哪些算法可用,那就太不礼貌了!为了解决这个问题,在下一部分中,我们将列出一些最流行的向量化技术!我们甚至会用代码来演示!
代码实验室 7.1 - 常见的向量化技术
向量化算法在过去几十年里经历了显著的演变。了解这些变化以及原因,将帮助你更好地理解如何选择最适合你需求的算法。我们将逐步介绍一些向量化算法,从最早期的算法开始,逐步到最新的更先进的选项。这并不是一个详尽无遗的列表,但这些选定的算法应该足以让你了解这一领域的起源以及它的发展方向。在开始之前,让我们安装并导入一些在向量化技术编码过程中扮演重要角色的 Python 包:
%pip install gensim --user
%pip install transformers
%pip install torch
这些代码应该放在之前代码的顶部,和其他包的安装放在同一个单元格中。
术语频率-逆文档频率(TF-IDF)
1972年可能是你在一本讲述相对全新技术(如RAG)的书中所期待的时间点,但这正是我们找到我们将要讨论的向量化技术的根源的地方。
Karen Ida Boalth Spärck Jones 是一位自学成才的程序员,也是英国的计算机科学先驱,曾撰写多篇关于自然语言处理(NLP)领域的论文。1972年,她做出了其中一项最重要的贡献,引入了逆文档频率(IDF)的概念。她在论文中阐述的基本概念是:“一个术语的特异性可以通过它出现在的文档数量的倒数来量化。”
作为一个实际示例,假设我们将文档频率(df)和逆文档频率(idf)得分应用于莎士比亚的37部戏剧中的某些词语,我们会发现“Romeo”得分最高。这是因为它频繁出现,但仅出现在一篇文档中——《罗密欧与朱丽叶》。在这种情况下,“Romeo”的df为1,因为它只出现在一篇文档中,而它的idf得分为1.57,这比任何其他词语都高,因为它在该文档中的频率较高。与此同时,莎士比亚偶尔使用“sweet”这个词,但在每一部戏剧中都会出现,因此它的得分较低。这样,“sweet”的df得分为37,idf得分为0。Karen Jones在她的论文中所说的是,当你看到像“Romeo”这样的词语只出现在少数几部戏剧中时,你可以认为这些词语出现在的戏剧非常重要,并且能够预测该戏剧的内容。相反,“sweet”则具有相反的效果,因为它对于词语的重要性和所在文档的意义而言并不具备信息量。
但说到这里就够了。让我们看看这个算法在代码中的样子!scikit-learn库提供了一个可以应用于文本的函数,用于通过TF-IDF方法将文本向量化。以下代码展示了我们如何定义 splits
变量,并使用它作为训练模型的数据:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
tfidf_documents = [split.page_content for split in splits]
tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(tfidf_documents)
vocab = tfidf_vectorizer.get_feature_names_out()
tf_values = tfidf_matrix.toarray()
idf_values = tfidf_vectorizer.idf_
word_stats = list(zip(vocab, tf_values.sum(axis=0), idf_values))
word_stats.sort(key=lambda x: x[2], reverse=True)
print("Word\t\tTF\t\tIDF")
print("----\t\t--\t\t---")
for word, tf, idf in word_stats[:10]:
print(f"{word:<12}\t{tf:.2f}\t\t{idf:.2f}")
与OpenAI的嵌入模型不同,这个模型需要你在语料库数据上进行训练,语料库数据是指你用来训练的所有文本数据。此代码主要是为了展示如何使用TF-IDF模型,而与我们当前的RAG管道检索器的比较,因此我们不会逐行讲解。但我们鼓励你自己尝试这些代码,并尝试不同的设置。
需要注意的是,这个算法生成的向量被称为稀疏向量,而我们之前在前面的代码实验室中使用的是密集向量。这是一个重要的区别,我们将在第8章详细讨论。
该模型使用语料库数据设置环境,进而计算你引入的新内容的嵌入。输出结果应该如下所示:
Word | TF | IDF |
---|---|---|
000 | 0.16 | 2.95 |
1024 | 0.04 | 2.95 |
123 | 0.02 | 2.95 |
13 | 0.04 | 2.95 |
15 | 0.01 | 2.95 |
16 | 0.07 | 2.95 |
192 | 0.06 | 2.95 |
1m | 0.08 | 2.95 |
200 | 0.08 | 2.95 |
2024 | 0.01 | 2.95 |
在这种情况下,我们看到idf的最高值至少有10个相同的词语(这里只展示了10个,实际上可能更多),它们都是基于数字的文本。这看起来并不特别有用,但这主要是因为我们的语料库数据如此之小。通过对更多来自同一作者或领域的数据进行训练,你可以构建出一个能够更好理解上下文内容的模型。
现在,回到我们一直在使用的原始问题:RAG的优势是什么?我们希望使用TF-IDF嵌入来确定最相关的文档:
tfidf_user_query = ["What are the advantages of RAG?"]
new_tfidf_matrix = tfidf_vectorizer.transform(tfidf_user_query)
tfidf_similarity_scores = cosine_similarity(new_tfidf_matrix, tfidf_matrix)
tfidf_top_doc_index = tfidf_similarity_scores.argmax()
print("TF-IDF Top Document:\n", tfidf_documents[tfidf_top_doc_index])
这模拟了我们在检索器中看到的行为,它使用相似度算法通过距离找到最接近的嵌入。在这种情况下,我们使用余弦相似度,关于这一点我们将在第8章讨论,但请记住,还有许多距离算法可以用来计算这个距离。我们从这段代码的输出结果如下:
TF-IDF Top Document:
Can you imagine what you could do with all of the benefits mentioned above, but combined with all of the data within your company, about everything your company has ever done, about your customers and all of their interactions, or about all of your products and services combined with a knowledge of what a specific customer's needs are? You do not have to imagine it, that is what RAG does…[省略内容]
如果你运行我们的原始代码,它使用原始的向量存储和检索器,你将看到如下输出:
Retrieved Document:
Can you imagine what you could do with all of the benefits mentioned above, but combined with all of the data within your company, about everything your company has ever done, about your customers and all of their interactions, or about all of your products and services combined with a knowledge of what a specific customer's needs are? You do not have to imagine it, that is what RAG does…[省略内容]
它们匹配!一个来自1972年的小算法,在我们的数据上训练只需不到一秒钟,效果与OpenAI花费数十亿美元开发的庞大算法一样好!好吧,慢一点,这显然不是这种情况!实际上,在现实场景中,你将处理远比我们更大的数据集和更复杂的用户查询,这将从使用更复杂的现代嵌入技术中受益。
多年来,TF-IDF非常有用。但是,当我们讨论有史以来最先进的生成性AI模型时,学习一个1972年的算法有必要吗?答案是BM25。这只是一个预告,你将在下一章了解这一非常流行的关键词搜索算法,它是当今最流行的算法之一。猜猜怎么着?它是基于TF-IDF的!不过,TF-IDF的一个问题是,它在捕捉上下文和语义方面不如我们接下来要讨论的一些模型。让我们讨论一下下一个重要的进步:Word2Vec及相关算法。
Word2Vec、Sentence2Vec 和 Doc2Vec
Word2Vec 以及类似模型引入了无监督学习的早期应用,是自然语言处理(NLP)领域的重要进步。不同的向量模型(如词向量、文档向量和句子向量)在训练时分别聚焦于词语、文档或句子。这些模型的不同之处在于它们的训练层次——是针对单个词、文档还是句子进行训练的。
Word2Vec 主要通过学习单个词的向量表示来捕捉其语义含义和关系。而 Doc2Vec 则是学习整个文档的向量表示,使其能够捕捉文档的整体上下文和主题。Sentence2Vec 与 Doc2Vec 类似,但它操作的是句子级别,学习每个句子的向量表示。虽然 Word2Vec 在词语相似度和类比等任务中非常有用,但 Doc2Vec 和 Sentence2Vec 更适用于文档级任务,如文档相似度、分类和检索。
由于我们正在处理的是较大的文档,而不仅仅是词语或句子,因此我们将选择 Doc2Vec 模型,而非 Word2Vec 或 Sentence2Vec,并训练该模型以查看其作为检索器的效果。与 TF-IDF 模型类似,Doc2Vec 模型可以使用我们的数据进行训练,然后我们将用户查询传递给模型,以查看是否能获得与最相似的数据块。
在 TD-IDF 代码单元后添加以下代码:
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
from sklearn.metrics.pairwise import cosine_similarity
doc2vec_documents = [split.page_content for split in splits]
doc2vec_tokenized_documents = [doc.lower().split() for doc in doc2vec_documents]
doc2vec_tagged_documents = [TaggedDocument(words=doc, tags=[str(i)]) for i, doc in enumerate(doc2vec_tokenized_documents)]
doc2vec_model = Doc2Vec(doc2vec_tagged_documents, vector_size=100, window=5, min_count=1, workers=4)
doc2vec_model.save("doc2vec_model.bin")
与 TF-IDF 模型类似,这段代码主要演示了如何使用 Doc2Vec 模型,而不是逐行讲解。但我们鼓励你自己动手试试代码,尝试不同的设置。此代码主要关注训练 Doc2Vec 模型并将其本地保存。
有趣的事实
训练语言模型如今是一个热门话题,也能带来相当丰厚的报酬。你有没有训练过语言模型?如果你的答案是没有,那你就错了。你不仅刚刚训练了一个语言模型,而且你已经训练了两个!TF-IDF 和 Doc2Vec 都是你刚刚训练的语言模型。虽然这两个模型都是相对基础的模型,但你得从某个地方开始,而你刚刚就开始了!
接下来,我们将在我们的数据上使用这个模型:
loaded_doc2vec_model = Doc2Vec.load("doc2vec_model.bin")
doc2vec_document_vectors = [loaded_doc2vec_model.dv[str(i)] for i in range(len(doc2vec_documents))]
doc2vec_user_query = ["What are the advantages of RAG?"]
doc2vec_tokenized_user_query = [content.lower().split() for content in doc2vec_user_query]
doc2vec_user_query_vector = loaded_doc2vec_model.infer_vector(doc2vec_tokenized_user_query[0])
doc2vec_similarity_scores = cosine_similarity([doc2vec_user_query_vector], doc2vec_document_vectors)
doc2vec_top_doc_index = doc2vec_similarity_scores.argmax()
print("\nDoc2Vec Top Document:\n", doc2vec_documents[doc2vec_top_doc_index])
我们将创建和保存模型的代码与使用模型的代码分开,这样你可以看到如何保存并在以后引用该模型。以下是此代码的输出:
Doc2Vec Top Document:
Once you have introduced the new knowledge, it will always have it! It is also how the model was originally created, by training with data, right? That sounds right in theory, but in practice, fine-tuning has been more reliable in teaching a model specialized tasks (like teaching a model how to converse in a certain way), and less reliable for factual recall…[TRUNCATED FOR BREVITY]
与我们之前的原始检索器结果相比,这个模型返回的结果并不相同。然而,这个模型仅使用了 100 维的向量,如下所示:
doc2vec_model = Doc2Vec(doc2vec_tagged_documents, vector_size=100, window=5, min_count=1, workers=4)
如果你在这一行中将 vector_size
改为 1,536,与 OpenAI 模型相同的向量大小会发生什么呢?
将 doc2vec_model
变量的定义修改为:
doc2vec_model = Doc2Vec(doc2vec_tagged_documents, vector_size=1536, window=5, min_count=1, workers=4)
结果将变为:
Doc2Vec Top Document:
Can you imagine what you could do with all of the benefits mentioned above, but combined with all of the data within your company, about everything your company has ever done, about your customers and all of their interactions, or about all of your products and services combined with a knowledge of what a specific customer's needs are? You do not have to imagine it, that is what RAG does…[TRUNCATED FOR BREVITY]
这与我们使用 OpenAI 嵌入模型时得到的结果相同。然而,结果并不一致。如果你在更多数据上训练该模型,结果很可能会有所改进。
理论上,这种模型相较于 TF-IDF 的优势在于它是一种基于神经网络的方法,能够考虑周围词汇,而 TF-IDF 只是一个统计度量,用来评估一个词与文档的相关性(关键字搜索)。但正如我们之前所说,尽管 Vec 模型相较于 TF-IDF 具有更强的能力,但仍然有更强大的模型能够捕捉文本的上下文和语义。接下来让我们看看下一个世代的模型——Transformer。
来自 Transformer 的双向编码器表示(BERT)
目前,我们已经完全进入了使用神经网络来更好地理解语料库的底层语义,BERT(Bidirectional Encoder Representations from Transformers)是自然语言处理(NLP)算法的又一个重大进步。BERT 也是最早应用特定类型神经网络——Transformer——的模型之一,而 Transformer 是导致我们今天熟悉的大型语言模型(LLM)发展的重要一步。OpenAI 流行的 ChatGPT 模型也基于 Transformer,但它们使用了比 BERT 更大规模的语料库,并采用了不同的训练技术。
尽管如此,BERT 仍然是一个非常强大的模型。你可以将 BERT 作为一个独立的模型导入使用,避免依赖像 OpenAI 嵌入服务这样的 API。在某些网络受限的环境中,能够在代码中使用本地模型是一大优势,而不必依赖 OpenAI 等 API 服务。
Transformer 模型的一个重要特征是使用自注意力机制来捕捉文本中词语之间的依赖关系。BERT 也包含多个 Transformer 层,使其能够学习更为复杂的表示。与我们的 Doc2Vec 模型相比,BERT 已经在大量数据上进行了预训练,例如 Wikipedia 和 BookCorpus,目的是预测下一句。
和之前的两个模型一样,我们提供了代码来让你比较使用 BERT 检索结果:
from transformers import BertTokenizer, BertModel
import torch
from sklearn.metrics.pairwise import cosine_similarity
bert_documents = [split.page_content for split in splits]
bert_tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
bert_model = BertModel.from_pretrained('bert-base-uncased')
bert_vector_size = bert_model.config.hidden_size
print(f"Vector size of BERT (base-uncased) embeddings: {bert_vector_size}\n")
bert_tokenized_documents = [bert_tokenizer(doc, return_tensors='pt', max_length=512, truncation=True) for doc in bert_documents]
bert_document_embeddings = []
with torch.no_grad():
for doc in bert_tokenized_documents:
bert_outputs = bert_model(**doc)
bert_doc_embedding = bert_outputs.last_hidden_state[0, 0, :].numpy()
bert_document_embeddings.append(bert_doc_embedding)
bert_user_query = ["What are the advantages of RAG?"]
bert_tokenized_user_query = bert_tokenizer(bert_user_query[0], return_tensors='pt', max_length=512, truncation=True)
bert_user_query_embedding = []
with torch.no_grad():
bert_outputs = bert_model(**bert_tokenized_user_query)
bert_user_query_embedding = bert_outputs.last_hidden_state[0, 0, :].numpy()
bert_similarity_scores = cosine_similarity([bert_user_query_embedding], bert_document_embeddings)
bert_top_doc_index = bert_similarity_scores.argmax()
print("BERT Top Document:\n", bert_documents[bert_top_doc_index])
与之前几个模型的使用相比,这段代码有一个非常重要的区别。在这里,我们并没有在自己的数据上对模型进行调优。这个 BERT 模型已经在大规模数据集上进行过预训练。虽然可以进一步使用我们的数据对模型进行微调,通常建议这么做,如果你想使用这个模型,微调将提升效果。由于我们没有进行微调,结果可能不如预期,但我们依然能展示其工作原理!
这段代码中,我们输出了 BERT 嵌入向量的大小,方便与其他模型进行比较。与其他模型一样,我们可以查看检索到的顶级文档。以下是输出:
Vector size of BERT (base-uncased) embeddings: 768
BERT Top Document:
Or if you are developing in a legal field, you may want it to sound more like a lawyer. Vector Store or Vector Database?
向量大小为 768,尽管不需要用指标来说明,我们也能看出,BERT 找到的顶级文档并不是最适合回答“RAG 的优势是什么?”这个问题的最佳内容。
这个模型非常强大,具有超过前两个模型的潜力,但我们需要做一些额外的工作(微调),以便在比较这些模型时,它能在我们的数据上表现得更好。虽然这并不适用于所有数据,但通常,在像这样的专门领域中,微调应该被视为嵌入模型的一个选择。如果你使用的是较小的本地模型,而不是 OpenAI 等大规模托管的 API 服务,那么微调尤其重要。
通过这三个不同的模型,我们展示了嵌入模型在过去 50 年中的发展变化。希望这次实践能让你明白,选择哪个嵌入模型是多么重要。我们将通过回到我们最初使用的嵌入模型,OpenAI 嵌入模型,再次完成对嵌入模型的讨论。接下来,我们将讨论 OpenAI 模型及其在其他云服务中的同类模型。
OpenAI 和其他类似的大规模嵌入服务
让我们再谈一谈我们刚刚使用的 BERT 模型,与 OpenAI 的嵌入模型相比。我们使用的是“bert-base-uncased”版本,这是一个相当强大的 1.1 亿参数的 Transformer 模型,特别是与我们之前使用的模型相比。自从 TD-IDF 模型以来,我们已经走了很长一段路。根据你所在的环境,这可能会考验你的计算能力。这个是我电脑能够运行的 BERT 模型中最大的一种。但如果你有更强大的环境,你可以将这两行代码中的模型更改为“bert-large-uncased”:
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased')
你可以在这里查看完整的 BERT 模型选项:huggingface.co/google-bert…
“bert-large-uncased”模型有 3.4 亿个参数,是“bert-base-uncased”的三倍多。如果你的环境无法处理如此大的模型,它会导致内核崩溃,你将不得不重新加载所有导入和相关的 notebook 单元。这只是告诉你这些模型有多大。需要明确的是,这两个 BERT 模型分别是 1.1 亿和 3.4 亿参数,是百万级的,而不是十亿级的。
我们一直在使用的 OpenAI 嵌入模型基于 GPT-3 架构,拥有 1750 亿个参数。是的,1750 亿,就是有 B 的那个。我们将在本章后面讨论 OpenAI 的新版本嵌入模型,它们基于 GPT-4 架构,参数数量达到 1 万亿(有 T 的那个!)。这些模型无疑是巨大的,远远超越了我们讨论过的任何其他模型。BERT 和 OpenAI 都是基于 Transformer 的模型,但 BERT 是在 33 亿个词汇上训练的,而 GPT-3 的完整语料库估计有 17 万亿个词汇(45 TB 的文本)。
目前,OpenAI 提供三种不同的嵌入模型。我们一直在使用较老的基于 GPT-3 的模型“text-embedding-ada-002”,它是一个非常强大的嵌入模型。另有两个基于 GPT-4 的较新模型:“text-embedding-3-small”和“text-embedding-3-large”。这两个模型都支持我们之前提到的 Matryoshka 嵌入,可以使用自适应检索方法来进行检索。
不过,OpenAI 并不是唯一提供文本嵌入 API 的云服务商。Google Cloud Platform(GCP)也提供文本嵌入 API 服务,最新版本是 2024 年 4 月 9 日发布的“text-embedding-preview-0409”。“text-embedding-preview-0409”是我目前知道的唯一一个支持 Matryoshka 嵌入的大规模云托管嵌入模型,除了 OpenAI 的新模型之外。
Amazon Web Services(AWS)也有基于 Titan 模型的嵌入模型,以及 Cohere 的嵌入模型。Titan Text Embeddings V2 预计很快发布,也有望支持 Matryoshka 嵌入。
这就是我们快速浏览嵌入生成技术 50 年发展的全过程!这些模型的选择是为了展示嵌入能力在过去 50 年中的进步,但这些只是实际生成嵌入的方式中的一小部分。现在,你对嵌入能力的了解已经拓宽了,接下来我们将转向在实际选择使用哪种模型时,你可以考虑的一些因素。
选择向量化选项的因素
选择合适的向量化选项是构建 RAG 系统时的关键决策。主要考虑因素包括嵌入的质量、相关成本、网络可用性、嵌入生成速度以及嵌入模型的兼容性。除了我们刚才提到的几种选项外,你还可以根据具体需求探索其他选择。在选择嵌入模型时,让我们一起回顾这些因素。
嵌入的质量
在考虑嵌入质量时,你不能仅仅依赖于每个模型的通用指标。例如,OpenAI 在“大规模文本嵌入基准测试(MTEB)”中测试其“text-embedding-ada-002”模型,得分为 61.0%,而“text-embedding-3-large”模型的得分为 64.6%。这些指标可能很有用,尤其是在你想要聚焦于某个特定质量的模型时,但这并不意味着该模型在你的具体应用中会提高 3.6%。甚至不能确保它一定会更好。不要完全依赖通用测试。最终重要的是嵌入在你具体应用中的表现。这也包括你用自己数据训练的嵌入模型。如果你在一个特定领域(如科学、法律或技术)工作,可能会发现或训练一个更适合你领域数据的模型。在开始项目时,尝试在 RAG 系统中使用多个嵌入模型,然后使用我们在第 9 章分享的评估方法,比较各个模型的结果,找出最适合你的应用的模型。
成本
这些嵌入服务的成本从免费到相对昂贵不等。OpenAI 最贵的嵌入模型收费为每百万个标记 0.000104,略多于 1 美分的 1%。这听起来可能不多,但对于大多数使用嵌入的应用,尤其是企业级应用来说,这些成本会迅速累积,甚至一个小项目的费用可能就会达到几千或几万美元。但其他嵌入 API 的费用较低,也许能更好地满足你的需求。当然,如果你像我在本章前面所描述的那样构建自己的模型,你只需要承担该模型的硬件或托管费用。随着时间的推移,这可能会大大降低成本,并可能满足你的需求。
网络可用性
在考虑网络可用性时,你需要考虑多种场景。几乎所有应用都会遇到一些网络不可用的情况。网络可用性不仅影响用户访问应用界面,还可能影响你从应用调用其他服务时的网络请求。在这种情况下,用户虽然能够访问应用界面,但应用却无法连接到 OpenAI 的嵌入服务来生成用户查询的嵌入。你该怎么办?如果你使用的是本地模型,这可以避免这个问题。这是可用性的一个考虑,及其对用户的影响。
请记住,你不能仅仅切换嵌入模型来应对这种情况,如果你正想在网络不可用时使用本地嵌入模型作为备用选项。使用专有的仅 API 调用的嵌入模型时,你就必须依赖该模型,RAG 系统将依赖该 API 的可用性。OpenAI 不提供可以在本地使用的嵌入模型。请参见接下来的嵌入兼容性小节!
速度
生成嵌入的速度是一个重要的考虑因素,因为它会影响你应用的响应时间和用户体验。当使用如 OpenAI 这样的托管 API 服务时,你需要进行网络调用来生成嵌入。虽然这些网络调用相对较快,但与本地生成嵌入相比,还是会有一些延迟。然而,需要注意的是,本地生成嵌入并不总是更快,因为速度还取决于所使用的具体模型。有些模型可能具有较慢的推理时间,抵消了本地处理的优势。在确定嵌入选项的速度时,需要考虑的关键因素包括网络延迟、模型推理时间、硬件资源,以及在涉及多个嵌入时批量生成嵌入的能力。
嵌入兼容性
请特别注意,这个考虑因素非常重要!在任何比较嵌入的情况下,比如在检测用户查询嵌入与存储在向量库中的嵌入之间的相似性时,它们必须由相同的嵌入模型生成。这些模型会生成独特的向量签名,只能由该模型识别。这一点即便对于同一服务提供商的模型也是成立的。例如,OpenAI 的所有三种嵌入模型之间就不能互相兼容。如果你使用任何 OpenAI 的嵌入模型来向量化存储在向量库中的嵌入,那么你必须调用 OpenAI 的 API,并使用相同的模型来向量化用户查询,以进行向量搜索。
随着应用的扩展,改变或更新嵌入模型将带来巨大的成本影响,因为这意味着你必须生成所有新的嵌入才能使用新的嵌入模型。这甚至可能促使你使用本地模型而非托管的 API 服务,因为生成新嵌入时,控制的模型通常会大大降低成本。
虽然通用基准可以提供指导,但在特定领域和应用中评估多个嵌入模型以确定最适合的模型非常重要。成本可能会有显著差异,这取决于服务提供商和所需的嵌入量。网络可用性和速度是重要因素,特别是在使用托管 API 服务时,因为它们会影响应用的响应时间和用户体验。嵌入模型之间的兼容性也至关重要,因为不同模型生成的嵌入不能直接进行比较。
随着应用的增长,更换或更新向量嵌入模型可能带来显著的成本影响。本地生成嵌入可以提供更多控制,并可能降低成本,但速度取决于具体模型和硬件资源的可用性。进行充分的测试和基准测试是必要的,以便为你的应用找到质量、成本、速度等各个因素之间的最佳平衡。现在我们已经探讨了选择向量化选项时的考虑因素,让我们深入了解它们在向量库中的存储方式。
开始使用向量存储
向量存储与其他数据存储(如数据库、数据仓库、数据湖及其他数据源)结合,是构建RAG系统引擎的核心动力。虽然这听起来很显而易见,但如果没有一个存储RAG相关数据的地方——这些数据通常包括向量的创建、管理、过滤和搜索——你将无法构建一个有效的RAG系统。你选择使用的向量存储和它的实现方式,将显著影响整个RAG系统的性能,这使得它成为一个至关重要的决策和工作任务。为开始本节内容,我们首先回顾一下数据库的基本概念。
数据源(向量以外)
在我们目前的基本RAG示例中,为了简化(目前如此),我们并没有连接额外的数据库资源。你可以将内容来源的网页视为数据库,尽管在此背景下,更准确的描述可能是将其视为一个非结构化数据源。无论如何,你的应用很可能会扩展到需要类似数据库的支持。这可能以传统的SQL数据库形式出现,或者是一个巨大的数据湖(一个包含各种类型原始数据的存储库),其中的数据经过预处理,转化为支持RAG系统的可用格式。
你的数据存储架构可能基于关系数据库管理系统(RDBMS),不同类型的NoSQL数据库,NewSQL(旨在结合前两种方法的优点),或各种版本的数据仓库和数据湖。从本书的角度来看,我们将这些系统所代表的数据源视为一个抽象的概念。但需要考虑的是,你选择使用的向量存储,将会在很大程度上受到现有数据源架构的影响。此外,你团队当前的技术能力也将扮演一个重要角色。
举个例子,如果你正在使用PostgreSQL作为RDBMS,并且团队中有一批在PostgreSQL的使用和优化方面具有丰富经验的专家,那么你可能会考虑使用PostgreSQL的pgvector扩展,它能将PostgreSQL表转化为向量存储,将你团队熟悉的许多PostgreSQL功能扩展到向量领域。像索引和为PostgreSQL编写SQL这类概念,你的团队已经很熟悉,这有助于快速适应pgvector的使用。如果你正在从零开始构建数据基础设施(在企业中这种情况较少见),那么你可能会选择针对速度、成本、准确性或这些方面的优化方案!但是对于大多数公司来说,你需要在选择向量存储时考虑与现有基础设施的兼容性。
趣味小知识——像SharePoint这样的应用程序怎么样?
SharePoint通常被视为内容管理系统(CMS),可能不严格符合我们之前提到的其他数据源的定义。然而,SharePoint及类似应用程序包含了大量非结构化数据的存储库,包括PDF、Word、Excel和PowerPoint文档,这些文档构成了公司知识库的巨大部分,尤其是在大型企业环境中。再加上生成性AI技术表现出对非结构化数据的极大偏好,这让它成为了一个不可忽视的RAG系统数据源。这类应用程序还提供了复杂的API,能够在提取文档时进行数据抽取,例如从Word文档中提取文本,并将其放入数据库中,然后再进行向量化。在许多大型公司,由于这些应用程序中的数据具有高价值,并且通过API抽取数据相对容易,这些应用程序通常是RAG系统的数据源之一。所以,是的,你完全可以把SharePoint和类似的应用程序列入潜在的数据源清单中!
我们稍后会讨论pgvector和其他向量存储选项,但重要的是要理解这些决策可能会根据具体情况有所不同,除了向量存储本身,其他因素也将在最终选择上发挥重要作用。
不管你选择哪种选项,或者从哪个选项开始,向量存储将是向RAG系统提供数据的关键组成部分。这将引领我们进入向量存储本身的讨论,我们接下来将详细介绍。
向量存储
向量存储,也称为向量数据库或向量搜索引擎,是专门设计用来高效存储、管理和检索数据的向量表示的存储系统。与传统的按行列组织数据的数据库不同,向量存储被优化用于高维向量空间的操作。它们在有效的RAG系统中起着至关重要的作用,使得快速的相似度搜索成为可能,这对于根据向量化查询识别最相关的信息至关重要。
向量存储的架构通常包括三个主要组件:
- 索引层:该层组织向量的方式,旨在加速搜索查询。它使用像基于树的分区(如KD树)或哈希(如局部敏感哈希)等索引技术,以便快速检索在向量空间中彼此接近的向量。
- 存储层:存储层高效地管理磁盘或内存中的数据存储,确保性能和可扩展性。
- 处理层(可选) :一些向量存储系统包括处理层,用于实时处理向量转换、相似度计算和其他分析操作。
虽然从技术上讲,可以在没有使用向量存储的情况下构建RAG系统,但这样做将导致性能和可扩展性不佳。向量存储专门用于解决存储和服务高维向量的独特挑战,提供的优化显著改善了内存使用、计算需求和搜索精度。
正如之前提到的,需要注意的是,尽管“向量数据库”和“向量存储”这两个术语通常可以互换使用,但并非所有的向量存储都是数据库。还有其他工具和机制可以实现与向量数据库相同或类似的功能。为了确保准确性和与LangChain文档的一致性,我们将使用“向量存储”这一术语,指代所有存储和服务向量的机制,包括向量数据库和其他非数据库解决方案。
接下来,我们将讨论向量存储的选项,帮助你更好地了解有哪些可用的选择。
常见的向量存储选项
在选择向量存储时,需要考虑以下因素:可扩展性要求、安装和维护的简便性、性能需求、预算限制以及对底层基础设施的控制和灵活性。此外,还要评估集成选项和支持的编程语言,以确保与现有技术栈的兼容性。
目前有许多向量存储选项,一些来自于已有的数据库公司和社区,许多是新兴的初创公司,甚至每天都会有新的选项出现,而且很可能一些公司在你阅读本文时已经倒闭了。这个领域非常活跃!保持警觉,并利用本章的信息了解哪些方面对你特定的RAG应用最为重要,然后查看当前市场,决定哪种选择最适合你。
我们将专注于与LangChain已建立集成的向量存储,并且在此基础上精选一些选项,以免让你感到不知所措,同时也足够让你了解有哪些可供选择的方案。请记住,这些向量存储不断添加新功能和改进。在做出选择之前,务必查看它们的最新版本!这可能会影响你的决策,帮助你做出更好的选择!
接下来的子章节中,我们将讨论一些常见的与LangChain集成的向量存储选项,并讲解在选择过程中应考虑的事项。
Chroma
Chroma 是一个开源的向量数据库,提供快速的搜索性能,并通过其Python SDK与LangChain实现简单的集成。Chroma的特点是简洁易用,具有直观的API,支持在搜索时动态过滤集合。它还内置支持文档分块和索引,便于处理大规模文本数据集。如果你优先考虑简便性,并希望使用一个开源解决方案来进行自托管,Chroma是一个不错的选择。然而,它可能不具备其他一些选项的高级功能,比如分布式搜索、支持多种索引算法以及结合向量相似度和元数据过滤的混合搜索功能。
LanceDB
LanceDB 是一个为高效的相似度搜索和检索而设计的向量数据库。它的混合搜索能力令人瞩目,结合了向量相似度搜索和传统的基于关键词的搜索。LanceDB支持多种距离度量和索引算法,包括层次可导航小世界(HNSW),用于高效的近似最近邻搜索。它与LangChain集成,提供快速的搜索性能,并支持多种索引技术。如果你需要一个专门的向量数据库,且希望有良好的性能和与LangChain的集成,LanceDB是一个不错的选择。但它可能没有一些其他选项那么庞大的社区或生态系统。
Milvus
Milvus 是一个开源向量数据库,提供可扩展的相似度搜索,并支持多种索引算法。它具有云原生架构,并支持基于Kubernetes的部署,具备可扩展性和高可用性。Milvus提供了多向量索引功能,允许你在多个向量字段中同时进行搜索,并提供插件系统以扩展其功能。它与LangChain集成良好,并支持分布式部署和水平扩展。如果你需要一个可扩展且功能丰富的开源向量存储,Milvus是一个理想的选择。然而,与托管服务相比,Milvus可能需要更多的设置和管理。
pgvector
pgvector 是一个为PostgreSQL添加支持向量相似度搜索的扩展,并作为向量存储与LangChain集成。它利用了PostgreSQL这一世界上最先进的开源关系型数据库的强大功能和可靠性,受益于PostgreSQL成熟的生态系统、丰富的文档和强大的社区支持。pgvector将向量相似度搜索与传统的关系型数据库特性无缝结合,实现了混合搜索能力。
最近的更新提高了pgvector的性能,使其与其他专用向量数据库服务相匹配。鉴于PostgreSQL是全球最受欢迎的数据库(是一项经过多次验证的成熟技术,拥有庞大的社区),而pgvector扩展提供了其他向量数据库的所有功能,这种组合为已经在使用PostgreSQL的公司提供了一个极好的解决方案。
Pinecone
Pinecone 是一个完全托管的向量数据库服务,提供高性能、可扩展性,并易于与LangChain集成。它提供完全托管和无服务器体验,抽象了基础设施管理的复杂性。Pinecone具有实时索引功能,允许你以低延迟更新和搜索向量,并支持混合搜索,结合向量相似度与元数据过滤。它还提供分布式搜索和支持多种索引算法。如果你想要一个托管解决方案,且性能良好、设置简单,Pinecone是一个不错的选择。然而,它的成本可能比自托管选项更高。
Weaviate
Weaviate 是一个开源的向量搜索引擎,支持多种向量索引和相似度搜索算法。它采用基于模式的方法,允许你为向量定义语义数据模型。Weaviate支持CRUD操作、数据验证和授权机制,并提供用于常见机器学习任务的模块,如文本分类和图像相似度搜索。它与LangChain集成,并提供模式管理、实时索引和GraphQL API等功能。如果你希望使用一个开源的向量搜索引擎,且具有更多功能和灵活性,Weaviate是一个不错的选择。然而,与托管服务相比,它可能需要更多的设置和配置。
在上述小节中,我们讨论了几种与LangChain集成的常见向量存储选项,概述了它们的特点、优势以及选择时需要考虑的因素。这强调了在选择向量存储时,评估可扩展性、易用性、性能、预算以及与现有技术栈的兼容性等因素的重要性。尽管这个列表覆盖了多个选项,但与LangChain集成并用作向量存储的可选方案依然仅是其中的一小部分。
这些向量存储涵盖了从快速相似度搜索、支持多种索引算法、分布式架构、结合向量相似度和元数据过滤的混合搜索到与其他服务和数据库的集成等多种能力。由于向量存储领域的发展非常迅速,新的选项频繁出现。将这些信息作为基础,但当你准备好构建下一个RAG系统时,我们强烈建议你访问LangChain文档中的可用向量存储,并根据当时的需求选择最适合的选项。
在下一节中,我们将更深入地讨论在选择向量存储时需要考虑的事项。
选择向量存储
为RAG系统选择合适的向量存储涉及多个因素的考虑,包括数据规模、所需的搜索性能(速度和准确性)以及向量操作的复杂性。可扩展性对于处理大数据集的应用至关重要,要求具备能够高效管理和检索来自不断增长的数据集的机制。性能考虑因素则涉及评估数据库的搜索速度以及其返回高相关结果的能力。
此外,与现有RAG模型的集成简便性和支持多种向量操作的灵活性也至关重要。开发人员应寻找提供强大API、全面文档和社区或供应商支持的向量存储。如前所述,目前有许多流行的向量存储,它们各自提供了独特的功能和优化,针对不同的使用场景和性能需求。
在选择向量存储时,必须将选择与RAG系统的整体架构和操作要求对齐。以下是一些关键考虑因素:
1. 与现有基础设施的兼容性
在评估向量存储时,必须考虑其与现有数据基础设施(如数据库、数据仓库和数据湖)的集成情况。评估向量存储与当前技术栈的兼容性,以及开发团队的技能水平。例如,如果你在某个特定数据库系统(如PostgreSQL)方面有较强的专业知识,那么像pgvector这样的向量存储扩展可能是合适的选择,因为它能够无缝集成并利用你团队的现有知识。
2. 可扩展性和性能
向量存储如何应对你数据的预期增长以及RAG系统的性能需求?评估向量存储的索引和搜索能力,确保它能够提供所需的性能和准确性。如果你预计会有大规模部署,像Milvus或带有向量插件的Elasticsearch这样的分布式向量数据库可能更为适合,因为它们设计用于处理高数据量,并提供高效的搜索吞吐量。
3. 易用性和维护性
向量存储的学习曲线如何?需要考虑现有文档、社区支持和供应商支持的可用性。了解设置、配置和持续维护向量存储所需的工作量。完全托管的服务(如Pinecone)可以简化部署和管理,减少团队的操作负担。另一方面,自托管的解决方案(如Weaviate)提供更多的控制和灵活性,允许根据特定需求进行定制和微调。
4. 数据安全和合规性
评估向量存储提供的安全功能和访问控制,确保它们符合行业合规要求。如果你处理敏感数据,评估向量存储的加密和数据保护能力。考虑向量存储是否能够满足数据隐私法规和标准(如GDPR或HIPAA),具体取决于你的需求。
5. 成本和许可证
向量存储的定价模型是什么?它是基于数据量、搜索操作,还是多种因素的组合?考虑向量存储的长期成本效益,并考虑RAG系统的可扩展性和增长预测。评估与向量存储相关的许可证费用、基础设施成本和维护费用。开源解决方案可能在前期成本较低,但需要更多的内部专业知识和资源进行维护,而托管服务可能会有更高的订阅费用,但提供简化的管理和支持。
6. 生态系统和集成
选择向量存储时,重要的是评估它支持的生态系统和集成。考虑不同编程语言的客户端库、SDK和API的可用性,这可以大大简化开发过程并实现与现有代码库的无缝集成。评估向量存储与RAG系统中常用的其他工具和框架(如NLP库或机器学习框架)的兼容性。支持的社区规模也很重要;确保它处于足够的规模,能够成长并繁荣。一个具有强大生态系统和广泛集成的向量存储可以提供更多的灵活性和扩展RAG系统功能的机会。
通过仔细评估这些因素,并将它们与你的特定需求对齐,你可以做出明智的决策,选择适合你RAG系统的向量存储。重要的是要进行充分的研究、对比不同选项,并考虑选择的长期影响,包括可扩展性、性能和可维护性。
请记住,选择向量存储并不是“一刀切”的决策,随着RAG系统的发展和需求的变化,选择可能会发生变化。定期重新评估你的向量存储选择,并根据需要调整,以确保在整体系统架构中保持最佳性能和一致性。
总结
将向量和向量存储集成到RAG系统中,是提高信息检索和生成任务效率与准确性的基础。通过仔细选择和优化向量化方法以及向量存储,你可以显著提高RAG系统的性能。向量化技术和向量存储只是向量在RAG系统中发挥作用的一部分;它们在我们的检索阶段也起着至关重要的作用。在下一章中,我们将讨论向量在检索阶段的作用,深入探讨向量相似性搜索算法和服务。
4o mini