简介
在前几篇文章中,我们介绍了如何将原始数据嵌入到向量中。为了重复使用嵌入的信息,我们需要存储这些嵌入信息,以便按需访问。为此,我们使用了一种特殊的数据库,称为矢量数据库。
对于使用检索增强生成(RAG)的大规模应用来说,高效的嵌入式存储和检索功能(如 CRUD 操作、元数据过滤和水平扩展)至关重要。像 ChromaDB、Pinecone 和 Weaviate 这样的矢量数据库就是这方面的专家,可以提供快速检索和相似性搜索。
集成合适的矢量数据库对于最大限度地提高 RAG 性能至关重要。考虑到使用案例的复杂性,深思熟虑的选择可确保无缝存储和检索,优化检索增强生成模型的功能。
在本文中,我们将深入探讨矢量数据库和索引方法。
矢量数据库与其他数据库
矢量数据库与其他数据库
在科技行业,一个普遍存在的误解是,矢量数据库只是近似近邻(ANN)搜索算法的包装。
矢量数据库的核心是针对非结构化数据的综合解决方案。与这种误解相反,它包含了当今结构化/半结构化数据库管理系统中的用户友好功能,包括云计算、多租户和可扩展性。它解决了独立矢量索引的局限性,解决了可扩展性挑战、集成复杂性以及缺乏实时更新和内置安全措施等问题。随着本教程的深入,这一点将变得显而易见。
另一方面,FAISS 和 ScaNN 等轻量级 ANN 库是构建矢量索引的工具。这些库旨在加速多维向量的近邻搜索。虽然这些库适用于生产系统中的小型数据集,但随着数据集的增长,可扩展性将成为一个挑战。
流行的矢量数据库
矢量数据库是如何工作的?
我们都知道,传统数据库在行和列中存储字符串、数字和其他类型的标量数据。另一方面,矢量数据库的操作对象是矢量,因此其优化和查询方式截然不同。
在传统数据库中,我们通常会查询数据库中的行,这些行的值通常与我们的查询完全匹配。在矢量数据库中,我们应用相似度量来查找与我们的查询最相似的矢量。
向量数据库使用不同算法的组合,这些算法都参与了近似近邻(ANN)搜索。这些算法通过各种索引技术优化搜索。
这些算法组合成一个流水线,可快速、准确地检索所查询向量的邻域。由于矢量数据库提供的是近似结果,我们主要考虑的是准确性和速度之间的权衡。结果越准确,查询速度就越慢。不过,一个好的系统可以提供超快的搜索速度和近乎完美的精确度。
以下是矢量数据库的常用流程:
索引技术
基于树的方法对低维数据很有效,并能提供精确的近邻搜索。然而,由于 "维度诅咒",它们在高维空间中的性能通常会下降。这些方法还需要大量内存,对大型数据集的效率较低,导致构建时间较长,延迟较高。
量化方法通过将向量压缩成紧凑的代码来节省内存,并提供快速的搜索时间。但是,这种压缩可能会导致信息丢失,从而降低搜索精度。此外,这些方法在训练阶段的计算成本较高,会增加构建时间。
散列方法速度快,内存效率相对较高,可将相似向量映射到同一个散列桶中。它们在处理高维数据和大规模数据集时表现出色,提供了很高的吞吐量;但是,由于散列碰撞导致假阳性和假阴性,搜索结果的质量可能会降低。选择合适的哈希函数数量和哈希表数量至关重要,因为它们会对性能产生重大影响。
聚类方法可将搜索空间缩小到特定聚类,从而加快搜索操作,但搜索结果的准确性会受到聚类质量的影响。聚类通常是一个批处理过程,这意味着它并不适合不断添加新向量的动态数据,从而导致频繁的重新索引。
图形方法在准确性和速度之间取得了良好的平衡。它们对高维数据很有效,并能提供高质量的搜索结果。不过,由于需要存储图结构,它们可能会耗费大量内存,而且构建图的计算成本也很高。
矢量数据库中算法的选择取决于任务的具体要求,包括数据集的大小和维度、可用的计算资源,以及在精度和效率之间可接受的权衡。值得注意的是,许多现代矢量数据库使用混合方法,将不同方法的优势结合起来,以实现高速度和高精度。了解这些算法及其权衡对于希望从矢量数据库中获得最佳性能的人来说至关重要。现在,让我们来看看这些类别中的所有流行算法。
精确匹配
平面索引(强力)
我们首先要了解的是最简单的索引--平面索引。
平面索引之所以 "平面",是因为我们不会修改输入其中的向量。
因为没有对向量进行近似或聚类,所以这些索引能产生最准确的结果。我们可以获得完美的搜索质量,但这是以大量搜索时间为代价的。
使用平面索引时,我们引入查询向量 xq,并将其与索引中的其他全尺寸向量进行比较,计算与每个向量的距离。
计算所有这些距离后,我们将返回其中最近的 k 个作为最近匹配项。k 近邻搜索(kNN)。
平衡搜索时间
平面索引的准确性很高,但速度却非常慢。在相似性搜索中,搜索速度和搜索质量(准确性)之间总是存在权衡。
我们必须做的是确定我们的最佳使用点在哪里。有了平面索引,我们就找到了:
在这里,我们有完全未优化的搜索速度,可以满足许多较小的索引用例,或者搜索时间无关紧要的情况。但是,其他用例需要在速度和质量之间取得更好的平衡。
那么,我们怎样才能提高搜索速度呢?主要有两种方法:
使用这两种方法中的任何一种,都意味着我们不再执行穷举式近邻搜索,而是近似近邻(ANN)搜索--因为我们不再搜索整个全分辨率数据集。
因此,我们所产生的是一个更加平衡的组合,既优先考虑搜索速度,也优先考虑搜索时间:
通常情况下,我们希望在搜索速度和搜索质量之间取得更平衡的组合。
近似匹配
Annoy(近似近邻)
在向量数据库中使用的基于树的算法中,有一种因其高效性和简便性而脱颖而出: Annoy,即 "近似近邻"。Annoy 是一个带有 Python 绑定的 C++ 库,由 Spotify 设计,用于搜索空间中接近给定查询点(近似匹配)的点。它创建了基于文件的大型只读数据结构,并将其映射到内存中,从而允许多个进程共享相同的数据。
Annoy 通过使用随机投影树森林来执行高效的 ANN 搜索。该算法将点投影到随机超平面上,并根据点所在超平面的哪一边来划分空间。这一过程会递归重复,最终形成一棵二进制分区树。每棵树都有不同的随机种子。当接收到一个查询点时,算法会沿森林中的每棵树向下遍历,找到该点所属的叶节点。然后,通过收集在所有树的叶节点中找到的点的列表来近似最近的邻居,并返回列表中最接近查询点的前 k 个点。
Annoy 特别适用于高维数据,在高维数据中,精确的近邻搜索可能会过于昂贵。Spotify 在音乐推荐中使用了它,它可以根据音频特征帮助找到相似的歌曲。因此,准确性会受到森林中树木数量和搜索过程中检查点数量的影响,这两个因素都可以根据任务的具体要求进行调整。
Annoy 的效率和内存效率使其成为处理高维数据和大型数据库的有力选择。但也需要考虑一些权衡因素。建立索引需要花费大量时间,尤其是对于大型数据集。由于 Annoy 使用的是随机森林分区算法,因此无法用新数据更新索引,必须从头开始重建。根据数据集的大小或数据变化的频率,重新训练索引的成本可能会非常高。
反转文件 (IVF) 索引
反转文件索引(IVF)索引包括通过聚类缩小搜索范围。这是一种非常流行的索引,因为它易于使用、搜索质量高且搜索速度合理。
它基于 Voronoi 图(也叫 Dirichlet tessellation,一个更酷的名字)的概念工作。
要理解 Voronoi 图,我们需要将高维向量放置在二维空间中。然后,我们在二维空间中放置几个额外的点,它们将成为我们的 "簇"(在我们的例子中是沃罗诺伊单元)中心点。
然后,我们从每个中心点向外延伸一个相等的半径。在某一点上,每个单元圆的圆周会与另一个单元圆相撞,从而形成我们的单元边:
现在,每个数据点都将包含在一个单元格中,并被分配给相应的中心点。
与其他索引一样,我们引入一个查询向量 xq - 这个查询向量必须位于我们的一个单元格内,此时我们将搜索范围限制在该单元格内。
但是,如果我们的查询向量位于单元格边缘附近,就会出现问题--很有可能其最接近的其他数据点就在邻近的单元格中。我们称之为边缘问题:
现在,我们可以通过增加一个名为 nprobe 值的索引参数来缓解这一问题并提高搜索质量。通过 nprobe,我们可以设置要搜索的单元格数量。
随机投影 (RP)
随机投影的基本思想是使用随机投影矩阵将高维向量投影到低维空间。我们创建一个随机数矩阵。矩阵的大小就是我们想要的低维目标值。然后,我们计算输入向量和矩阵的点积,这样得到的投影矩阵维数比原始向量少,但仍能保持相似性。
查询时,我们使用相同的投影矩阵将查询向量投影到低维空间。然后,我们将投影后的查询向量与数据库中的投影向量进行比较,找出最近的相邻向量。由于数据的维度降低了,搜索过程要比搜索整个高维空间快得多。
请记住,随机投影是一种近似方法,投影质量取决于投影矩阵的属性。一般来说,投影矩阵越随机,投影质量就越高。不过,生成真正随机的投影矩阵可能会耗费大量计算资源,尤其是对于大型数据集。
乘积量化(PQ)
另一种建立索引的方法是乘积量化(PQ),这是一种针对高维向量(如向量嵌入)的有损压缩技术。它采用原始向量,将其分解成较小的块,通过为每个块创建一个有代表性的 "代码 "来简化每个块的表示,然后将所有块重新组合在一起--而不会丢失对相似性运算至关重要的信息。PQ 的过程可分为四个步骤:分割、训练、编码和查询。
编码本中代表性向量的数量需要在表示的准确性和搜索编码本的计算成本之间进行权衡。编码本中的代表向量越多,子空间中向量的表示就越准确,但搜索编码本的计算成本就越高。相比之下,编码本中的代表性向量越少,表示的准确度就越低,但计算成本就越低。
位置敏感散列(LSH)
位置敏感散列(LSH)的工作原理是,通过散列碰撞最大化的散列函数处理每个向量,而不是像散列函数通常那样最小化散列碰撞,从而将向量分组到桶中。
这意味着什么?想象一下我们有一个 Python 字典。当我们在字典中创建一个新的键值对时,我们使用散列函数对键进行散列。键的哈希值决定了我们存储其相应值的 "桶":
Python 字典是哈希表的一个示例,它使用的是一种典型的哈希函数,能最大限度地减少哈希碰撞,即两个不同对象(键)产生相同哈希值的哈希碰撞。
在我们的字典中,我们希望避免这些碰撞,因为这意味着我们会将多个对象映射到一个键上;但对于 LSH,我们希望最大限度地减少散列碰撞。
为什么要最大限度地减少碰撞呢?对于搜索来说,我们使用 LSH 将相似的对象分组。当我们引入一个新的查询对象(或向量)时,我们的 LSH 算法可以用来找到最匹配的组:
分层导航小世界(HNSW)
HNSW 创建了一个层次分明的树状结构,树上的每个节点都代表一组向量。节点之间的边代表向量之间的相似性。该算法首先创建一组节点,每个节点都有少量的向量。这可以随机完成,也可以通过 k-means 等算法对向量进行聚类,每个聚类成为一个节点。
然后,算法会检查每个节点的向量,并在该节点与其向量最相似的节点之间画一条边。
当我们查询 HNSW 索引时,它会使用该图在树中导航,访问最有可能包含与查询向量最接近的向量的节点。
基于密度的带噪声应用空间聚类(DBSCAN)
DBSCAN 基于密度可达性和密度连通性的理念运行。它以数据集中的任意点为起点,如果从该点出发的给定半径 "eps "内至少有 "minPts",则会创建一个新的聚类。Eps 代表ε,是用户自定义的输入值,表示一个聚类中要考虑的两个点之间的最大距离,而 minPts 指的是形成一个聚类所需的最小数据点数量。然后,算法会反复将 "eps "半径范围内所有可直接到达的点添加到聚类中。这个过程一直持续到没有其他点可以添加到聚类中为止。然后,该算法继续向数据集中的下一个未访问点添加,并重复这一过程,直到所有点都被访问完毕。DBSCAN 的关键参数是 "eps "和 "minPts",它们分别定义了聚类的点数和形成聚类所需的最小点数密度。
相似性度量: 距离度量
相似度量是确定两个向量在向量空间中相似程度的数学方法。向量数据库中使用相似度量来比较数据库中存储的向量,找出与给定查询向量最相似的向量。
可以使用多种相似性度量,包括:
如何选择相似度量
一般来说,最佳做法是使用与嵌入式训练相同的相似度量来进行搜索;不过,相似度量的选择也取决于数据的具体特征和你要解决的问题的背景。以下是所讨论的每种相似性度量的一些主要应用:
欧氏距离
点积
余弦相似性
过滤
数据库中存储的每个向量还包括元数据。除了能查询相似向量外,向量数据库还能根据元数据查询过滤结果。为此,矢量数据库通常会维护两个索引:一个矢量索引和一个元数据索引。然后,它会在矢量搜索之前或之后执行元数据过滤,但无论哪种情况,都会遇到导致查询过程变慢的困难。
过滤过程可以在向量搜索之前或之后进行,但每种方法都有可能影响查询性能:
为了优化过滤过程,矢量数据库使用了各种技术,如利用先进的元数据索引方法或并行处理来加快过滤任务。要在矢量数据库中提供高效和相关的查询结果,必须在搜索性能和过滤准确性之间取得平衡。
选择矢量数据科
总之,矢量数据库没有放之四海而皆准的选择。理想的选择取决于具体的项目需求、预算限制和个人偏好。
总结
本文简明扼要地探讨了矢量数据库,重点介绍了它们在检索增强生成(RAG)应用中的关键作用。介绍了 ChromaDB、Pinecone 和 Weaviate 等流行数据库,强调了高效存储和检索对于实现最佳 RAG 性能的重要性。
深入探讨了各种索引技术和算法,深入介绍了Annoy、反转文件(IVF)索引、随机投影、乘积量化、位置敏感散列(LSH)、HNSW和DBSCAN。进一步解释了余弦相似性、欧氏距离和点积等相似性度量,以及在文档相似性、图像检索和协同过滤中的实际应用。