RAG 向量检索(上):从 FLAT 到 IVF,查得快为何可能漏?

大家好,我是王不二。

大模型知道很多,但未必知道公司刚更新的报销制度、内部产品文档。这些资料可能从未出现在它的训练数据里,也可能在模型训练结束后才更新。当用户问到这些内容时,只靠模型已有的知识,就未必能给出有依据的答案。这时需要一种办法,让模型回答前先查到相关资料------这就是 RAG 技术。

RAG 的基本做法并不复杂。回答前,先从外部资料中找出可能有用的内容,再把这些内容连同问题一起交给大模型。这样,维护并更新知识库,就可以让模型在回答时用上新知识。这和查工具书很像,平时不用看,只有用到的时候再查。但这套流程有个难题:怎么能够更快、更准确地查到资料?

RAG 涉及文档处理、检索召回、答案生成等多个环节。本次我们着重探讨其中的向量召回:用户提出问题后,系统怎样从知识库中找到可能有助于回答的文本片段?本文分上下两篇,介绍四种用于向量召回的常见检索方法。上篇讲 FLAT、IVF,下篇讲 HNSW 和 DiskANN。

文本怎么计算相似度?

先想三个词:男孩、女孩、男人。假设我们只用"性别"和"年龄"两项信息表示前面的词语,把它们放在同一个二维坐标系里面。为了画图方便,暂且用 0 表示女性、1 表示男性,用靠近 0 表示年幼、靠近 1 表示成年。于是,三个词各自有了两个数,也就在图上各占了一个点。

从原点分别向这些点画箭头,就得到了三个二维向量。一种常见的比较办法叫余弦相似度,看两个向量的夹角,夹角越小,按这种规则就越相似。在这套人为设定的坐标里,"男孩"与"男人"的夹角比"男孩"与"女孩"小。当然也有其他比较方法,例如比较点与点之间的距离,点越近,按这种规则就越相似。

图 1:用性别、年龄两项信息给三个词手工安排二维坐标,演示向量夹角。

当然,真实检索中不会靠我们给每段文字手填"性别、年龄"。常用做法是,先把知识库文档切成一段段文本,再用配套的嵌入模型 把文本和问题转换成同一个可比较空间里的向量。这个过程叫嵌入(embedding)。模型自己学习用哪些数值表示文本,系统再按选定的相似度或距离规则排序。

嵌入模型有很多,例如 BGE 系列。bge-base-zh-v1.5 输出 768 维向量,bge-large-zh-v1.5 输出 1024 维向量。但真实嵌入模型的单个维度,通常不像刚才例子中的"性别"或"年龄"这么直白,而是模型学到的数学表示。具体模型怎样学到这些坐标,比较复杂,此处先不展开。如果感兴趣,可以在评论区留言;讨论多的话,我们单独写一期。

实际嵌入向量通常有很多维,无法直接画在平面上。本文为演示方便,用二维点表示文本向量,即每段文本可以看作平面上的一个点。同时,后面的 FLAT 和 IVF 动图采用点间的直线距离表示相近,方便理解。

FLAT:把所有点都比一遍

FLAT 的向量召回方式,最暴力也最简单。

第一步,入库: 先把知识库里的文本逐段嵌入,得到一批向量。FLAT 把这些向量保存起来即可。下图中,把向量依次放进一个"向量库",只是存储过程的示意。

图 2:文档向量逐个进入向量库。

第二步,问题检索: 用户的问题也经过嵌入,落到同一个向量空间里。FLAT 随后把它与库里的每一个向量比较,记下距离,再挑出最近的几个,取回它们对应的文本片段。

图 3:问题向量依次与所有已存向量比较,最后留下距离最近的三个点。

FLAT 的优点很明确:对于已经入库的向量和选定的比较规则,它能找到距离最近的向量及其对应的文本片段。

FLAT 的缺点也很明显:每次提问都要逐个比较知识库中的向量。知识库越大,要比较的向量越多,查询的过程通常也就越慢。

给个便于落地的粗略量级:以单机 CPU、768 或 1024 维文本向量为例,几千到几万条文本片段,可以优先尝试 FLAT;到了十万级,就需要认真测试响应速度了。 这里的数量级,指切分后的文本片段,也就是入库向量数。

对于规模尚能承受的知识库,或者需要一份精确近邻结果作对照时,FLAT 是一个不错的起点。

如果逐个比较已经跟不上查询要求,那么该如何提升查询效率呢?

IVF:先选桶,再查桶里的点

IVF 的想法是,不用每次比较所有的向量,先把向量分进若干个桶。提问时先找可能相关的桶,再逐个检查桶里的向量。下面以 IVF Flat 为例。"IVF"负责缩小要查的范围,"Flat"负责把选中桶内的原始向量逐个比较。

第一步,准备向量: 和 FLAT 一样,知识库文本先经过嵌入,成为向量空间里的点。

图 4:文档片段的向量陆续进入示意空间,此时还没有分桶。

第二步,学习怎么分桶: 常见做法是用 k-means 学出若干个中心,也叫质心。可以把质心理解为桶内各个点的"平均位置",它不一定是某个已有的点。每个点先去离自己最近的中心所在的桶,再根据桶里的点重新计算中心。分配、更新、再分配,重复几轮后,得到用于分桶的最终中心。

注意:k-means 会先有初始中心,然后交替进行"分配点"和"更新中心"。在实际训练中是一个反复迭代的过程。

图 5:不同颜色表示点被分配给不同中心。画面只展示一次分配,真实训练还会继续迭代。

图 6:中心根据分到的点移动;高亮菱形是训练后用于检索的质心。

上面的彩色分组展示的是训练时学习分桶规则。确定每个桶的质心后,系统会按照这套规则建立索引,把知识库向量写进相应的桶里。

第三步,问题检索: 问题也同样被嵌入为向量。系统先比较问题与各个质心,选出距离最近的质心对应的桶。

图 7:问题点先与质心比较。示例只选择一个桶,真实查询可以选择多个。

第四步,检查选中的桶: 选中桶以后,用问题向量与这个桶里的所有向量逐个比较,再挑出最近的几个,取回对应的文本片段。没选中的桶,这次暂时不查。

图 8:被选中桶里的点逐个参与比较,其他桶暂不参与。

这样一来,通常可以减少很多次向量比较。但是代价也很明显:真正最近的点可能恰好在没打开的桶里。 比如问题靠近两个桶的边界,系统先打开右边的桶,左边却有一个离问题更近的点。仅查右边时,这个本应入选的近邻就被漏掉了。

图 9:先查一个桶时漏掉近邻;多查一个桶后,这个点进入候选。

上述示例的情况,可以通过多查一个桶来缓解,但并不是"多查两个就万无一失"。只要还有桶没查,真正最近的向量就仍可能被漏掉。即使查了 5 个桶,最近邻也可能刚好位于第 6 个。所以 IVF 的结果需要在速度与近邻召回之间权衡。

IVF 中,nlist 表示建索引时分成多少个桶,nprobe 表示每次查询打开多少个桶。桶分得太少,每个桶可能仍很大;桶分得很多,又要付出训练和寻找中心的成本。nprobe 增大通常能找回更多近邻,也会增加比较量;只要还有桶没查,就不能保证找全最近邻。此外,因为 IVF 需要训练分桶、调整参数,当入库数据的分布明显变化时,也应重新检查原有分桶是否仍合适。这些都要用自己的数据反复验证和尝试。

FLAT 和 IVF,怎么选?

对比项 FLAT IVF Flat
怎么检索 与所有入库向量逐个比较 先选桶,再比较选中桶内的向量
主要优势 简单、不用训练分组,能得到精确的向量近邻 只比较部分向量,通常能缩短查询时间;可调整查桶数来权衡速度与近邻召回
主要代价 数据量和查询量增大时,全量比较可能成为瓶颈 需要训练分桶和调参;只查部分桶时,可能漏掉真正最近的向量
适合的场景 小规模知识库、低查询压力,或需要精确近邻结果作对照 数据量较大、FLAT 已不满足查询速度要求,并能通过测试确定可接受的召回损失

上篇先到这里,下篇继续讲 HNSW 和 DiskANN。看看 HNSW 怎样沿着向量之间的连接找到近邻,以及 DiskANN 怎样借助 SSD 检索超过内存容量的数据。


扩展阅读

相关推荐
iCxhust1 小时前
Matlab求解发电机的极限切除角
开发语言·算法·matlab·电力系统
中趴菜1 小时前
工具调用 3|工具拿到参数以后,程序还必须检查什么?
后端
java资料站1 小时前
七、Spring AI Alibaba · Skills
java·后端·spring
殷紫川2 小时前
Arthas 4 深度实战:从字节码原理到生产故障秒解
后端
JuiceFS2 小时前
地平线 EB 级存储工程实践
人工智能·后端·自动驾驶
垆边人似月.2 小时前
华为题:敏感词替换
算法
0+1112 小时前
算法 --哈希表
数据结构·算法·散列表
Interview Aid1123 小时前
Stripe VO 面经汇总:5 轮面试题目与考察重点
算法
EEET智选3 小时前
科技晨间速报 | AI 安全警钟、开源新模型与苹果智能家居
人工智能·科技·算法·机器学习·ai