bubbleeee

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

Faiss是一个Facebook AI团队开源的库,全称为Facebook AI Similarity Search,该开源库针对高维空间中的海量数据(稠密向量),提供了高效且可靠的相似性聚类和检索方法,可支持十亿级别向量的搜索,是目前最为成熟的近似近邻搜索库。

官方资源地址https://github.com/facebookresearch/faiss

Faiss本质上是一个向量(矢量)数据库。进行搜索时,基础是原始向量数据库,基本单位是单个向量,默认输入一个向量x,返回和x最相似的k个向量。

在使用Faiss进行query向量的相似性搜索之前,需要将原始的向量集构建封装成一个索引文件(index file)并缓存在内存中,提供实时的查询计算。Index继承了一组向量库,作用是对原始向量集进行预处理和封装。所有向量在建立前需要明确向量的维度d,大多数的索引还需要训练阶段来分析向量的分布(除了IndexFlatL2)。在第一次构建索引文件的时候,需要经过TrainAdd两个过程。后续如果有新的向量需要被添加到索引文件的话还可以有一个Add操作从而实现增量build索引。当索引被建立就可以进行后续的search操作了。

为1个或者多个向量找它的k个最近邻的向量,可以认为是一个最近邻问题。可以计算x和向量集合中所有向量的欧式距离(或者cos距离,点积等),然后从小到大排序,这看上去似乎并不需要很复杂的算法,但在现实场景中往往面临海量的数据,当数据量在上亿级别的时候,这种暴力搜索的方法就并不可取了。所以在进行大规模向量检索时,通常采用的是近似最近邻算法(Approximate Nearest Neighbor,ANN)。和暴力解法不同的是,ANN算法不需要和集合中的所有向量计算距离,而是有选择地和部分向量计算距离,这样得到的答案往往不是最精确的,但是在效率上却大大提高了。所以向量检索其实是一个精度和效率的权衡问题。

关于乘积量化

矢量量化方法,即vector quantization,其具体定义为: 将向量空间的点用一个有限子集来进行编码的过程。常见的聚类算法,都是一种矢量量化方法。而在ANN(Approximate Nearest Neighbor,近似最近邻) 搜索问题中,向量量化方法又以乘积量化(PQ, Product Quantization)最为典型。

PQ有一个Pre-train的过程,一般分为两步操作,第一步「Cluster」,第二步「Assign」,这两步合起来就是对应到前文提到Faiss数据流的「Train」阶段,可以以一个128维的向量库为例:

 

PQ乘积量化的核心思想还是聚类,K-Means就是PQ乘积量化子空间数目为1的特例。

在做PQ之前,首先需要指定一个参数M,这个M就是指定向量要被切分成多少段,在上图中M=4,所以向量库的每一个向量就被切分成了4段,然后把所有向量的第一段取出来做Clustering得到256个簇心(256是一个作者拍的经验值);再把所有向量的第二段取出来做Clustering得到256个簇心,直至对所有向量的第N段做完聚类,从而最终得到了256*M个簇心。

做完Cluster,就开始对所有向量做Assign操作。这里的Assign就是把原来的N维的向量映射到M个数字,以N=128,M=4为例,首先把向量切成四段,然后对于每一段向量,都可以找到对应的最近的簇心 ID,4段向量就对应了4个簇心 ID,一个128维的向量就变成了一个由4个ID组成的向量,这样就可以完成了Assign操作的过程 -- 现在,128维向量变成了4维,每个位置都只能取0~127,这就完成了向量的压缩。

完成了PQ的Pre-train,就可以看看如何基于PQ做向量检索了。查询过程如下图所示:

 

同样是以N=128,M=4为例,对于每一个查询向量,以相同的方法把128维分成4段32维向量,「然后计算每一段向量与之前预训练好的簇心的距离」,得到一个4*256的表。然后就可以开始计算查询向量与库里面的向量的距离。此时,库的向量已经被量化成M个簇心 ID,而查询向量的M段子向量与各自的256个簇心距离已经预计算好了,所以在计算两个向量的时候只用查M次表,比如的库里的某个向量被量化成了[124, 56, 132, 222], 那么首先查表得到查询向量第一段子向量与其ID为124的簇心的距离,然后再查表得到查询向量第二段子向量与其ID为56的簇心的距离......最后就可以得到四个距离d1、d2、d3、d4,查询向量跟库里向量的距离d = d1+d2+d3+d4。

所以在提出的例子里面,使用PQ只用4×256次128/4维向量距离计算加上4xN次查表,而最原始的暴力计算则有N次128维向量距离计算,很显然随着向量个数N的增加,后者相较于前者会越来越耗时。

关于倒排索引
PQ优化了向量距离计算的过程,但是假如库里面的向量特别多,依然逃不了一个遍历整个库的过程,效率依旧还是不够高,所以这时就有了Faiss用到的另外一个关键技术——Inverted File System。倒排乘积量化(IVFPQ)是乘积量化(PQ)的更进一步加速版。其加速的本质就是在前面强调的是加速原理:暴力搜索的方式是在「全空间」进行搜索,为了加快查找的速度,几乎所有的ANN方法都是通过「对全空间分割(聚类)」,将其分割成很多小的子空间,在搜索的时候,通过某种方式,快速锁定在某一(几)子空间,然后在该(几个)子空间里做遍历。

PQ乘积量化计算距离的时候,距离虽然已经预先算好了,但是对于每个样本到查询样本的距离,还是得老老实实挨个去求和相加计算距离。但是,实际上我们感兴趣的是那些跟查询样本相近的样本(下面称之为“感兴趣区域”),也就是说老老实实挨个相加其实做了很多的无用功,如果能够通过某种手段快速「将全局遍历锁定为感兴趣区域」,则可以舍去不必要的全局计算以及排序。倒排PQ乘积量化的”倒排“,正是这样一种思想的体现,在具体实施手段上,采用的是通过聚类的方式实现感兴趣区域的快速定位,在倒排PQ乘积量化中,聚类可以说应用得淋漓尽致。

要想减少需要计算的目标向量的个数,做法就是直接对库里所有向量做KMeans Clustering,假设簇心个数为1024。那么每来一个query向量,首先计算其与1024个粗聚类簇心的距离,然后选择距离最近的top N个簇,只计算查询向量与这几个簇底下的向量的距离,计算距离的方法就是前面说的PQ。

Faiss具体实现有一个小细节,就是在计算查询向量和一个簇底下的向量的距离的时候,所有向量都会被转化成与簇心的残差,这应该就是类似于归一化的操作,使得后面用PQ计算距离更准确一点。使用了IVF过后,需要计算距离的向量个数就少了几个数量级,最终向量检索就变成一个很快的操作。

主要步骤

1.构造数据
Faiss处理固定维度d维的向量集合,通常从10维到100维均可。这些集合可以被存储在矩阵中。假设按行存储,向量i的第j维元素被存储在矩阵的第i行第j列。Faiss使用的是32位的浮点数矩阵。需要两个矩阵:

  • xb用于构建数据库,包含我们要被构建索引的,即将被检索的所有向量。它的维度是nb * d。
  • xq是我们需要寻找其最近邻的查询向量,它的维度是nq * d。如果只有单一query,nq=1。

2. 构建索引并向其中添加向量

Faiss通常构建一个index对象,它封装了数据库的向量集合,并且有选择地对它们进行一些预处理,使得检索更加高效。


所有的index在它们构建的时候都需要知道它们要操作的向量维度。然后,许多index需要一个训练(training)阶段,来分析向量的分布。对于IndexFlatL2索引来说,可以跳过这个步骤。
当索引被构建和训练之后,就可以在index上执行两个操作:add和search。
向index中添加元素称作add。我们也可以查看index的两个状态变量:is_trained,一个boolean型的变量指示是否需要训练;ntotal,表示被构建索引的向量的个数。
一些index可以给每个向量存储它们对应的int型ID(但不是IndexFlatL2)。如果没有提供ID,add只是用向量原始的作为id,比如:第1个向量的id是0,第2个向量的id是1,等等。

三种常用的索引:IndexFlatL2、IndexIVFFlat、IndexIVFPQ

1)IndexFlatL2 - 最基础的索引

indexFlatL2是最简单最常用的索引类型,对向量执行暴力的L2距离搜索,也是唯一可以保证精确结果的索引类型

2)IndexIVFFlat-更快的索引

为了加快搜索的速度,我们可以将数据集分割为几部分,每个数据向量只能落在一个cell中。查询时只需要查询query向量落在cell中的数据了,降低了距离计算次数。这就是上文说的倒排索引方法。

IndexIVFFlat需要一个训练的阶段,其与另外一个索引quantizer有关,通过quantizer来判断属于哪个cell。IndexIVFFlat在搜索的时候,引入了nlist(cell的数量)与nprobe(执行搜索的cell数)参数。增大nprobe可以得到与brute-force更为接近的结果,nprobe就是速度与精度的调节器。

3)IndexIVFPQ-内存占用更小的索引

如果一个索引在处理很大规模向量数据时都往缓存中存储完整的向量,那么对硬件的压力会特别大, 为了扩展到非常大的数据集,Faiss提供了基于乘积量化(Product Quantizer)的方法来压缩存储的向量,减小数据量。除此之外,IndexIVFPQ会应用到K-means聚类中心算法,PCA降维等算法来对向量集进行压缩、编码。但是由于向量数据是有损压缩的,所以搜索方法返回的距离也是近似值。

距离的计算,默认是L2(欧式距离)

也可以是

  • perform maximum inner product search argmax_i <x, x_i> instead of minimum Euclidean search. There is also limited support for other distances (L1, Linf, etc.).

内积/L1距离/无穷范数(切比雪夫距离)

 

先到这吧,顶不住了

 

posted on 2022-04-20 17:53  bubbleeee  阅读(1641)  评论(0编辑  收藏  举报