************* 原文 https://mp.weixin.qq.com/s/5KkDjCJ_AoC0w7yh2WcOpg ***********************
faiss是facebook为稠密向量提供高效相似度计算搜索和聚类,支持十亿级别向量的搜索,为近邻搜索库
向量机大小由RAM内存决定,用c++编写
如果用暴力搜索,时间复杂度为O(mn)。加快搜索还涉及到数据集的预处理,该预处理称之为索引,我们主要关注三个指标:
1 速度
2 内存消耗
3 精确度
一 Faiss原理
首先介绍一下Faiss使用的时候的数据流
在使用faiss的时候首先需要基于原始的向量build一个索引文件,然后在对索引文件进行一个查询操作,在第一次build索引的时候需要经过train和add两个过程:
后续如果有新的向量需要被添加到索引文件的是偶,只需要一个add操作从而实现增量build索引,但是如果增量的量级与原始索引差不多的时候,整个向量空间就可能发生过一些变化,这个时候就需要重新build整个索引文件,也就是用全部的向量来走一遍train和add。
Faiss的核心原理其实就是两个部分;
1 PQ
2 IVF
二 PQ
PQ矢量量化的方法,具体定义为 将向量空间的点用一个有限子集来进行编码的过程,常见的聚类算法,都是一种矢量量化的方法,
PQ有一个pretrain的过程,一般分为两步,cluster+assign。这两个步骤简称为train的过程。
PQ乘积量化的核心思想就是聚类,KMeans的就是PQ乘积量化子空间数为1的特例。
在做PQ之前,需要首先指定一个参数M。这个M就是指定向量要被切分成多少段,在上图中M=4,所以向量库的每一个向量都被切分了4段,然后把所有向量的第一段取出做cluster得到256个中心点;再把所有的向量的第二段取出来做cluster得到256个中心点......直到所有向量的N端做完cluster,从而得到256*M个中心点
做完cluster之后,就开始对所有向量做Assign操作,这里的Assign就是把原来的N维的向量映射成M个数字,以N=128,M=4为例,首先把向量切分4段,然后对每一段向量个,都可以找到对应的最近的簇心ID,4段就对应4个簇心,一个128维的向量就变成了一个由4个ID组成的向量
三 查询过程
看看如何基于PQ做向量检索
同样是N=128 M=4为例,对于每一个查询向量,以相同的方法把128维分为4段 32维的向量,然后计算每一段向量与之训练好的簇心的距离,得到一个4*256的表,就可以开始计算查询向量与库里面的向量的距离。
此时,库的向量已经被量化为M个簇心id,而查询向量M段字向量与各自256个簇心距离以及计算好了,所以在计算两个向量的时候只查了M次表,比如库里的某个向量被量化成了[ a b c d],那么首先查表得到查询向量与第一段字向量与其a的距离为d1,一次计算第二段子向量与b簇心的距离为d2.....最后查询向量与库里面的向量的距离为d1+d2+d3+d4
所以在提出的例子里面,使用PQ只用4×256次128/4维向量距离计算加上4xN次查表,而最原始的暴力计算则有N次128维向量距离计算,很显然随着向量个数N的增加,后者相较于前者会越来越耗时。
四 IVF
PQ优化了向量距离计算的过程,但是假如库里面的向量特别多,依旧逃离不了遍历的过程。
倒排乘积量化 IVFPQ的核心思想为 为了加快查找的速度,放弃全空间搜索的方法,将其分割成很多个小的子空间,在搜索的时候,快速锁定某一个子空间,然后在该空间中做遍历。
最常见的方式就是聚类+倒排
倒排索引的核心思想就是 在利用kmeans聚类的时候,对所有的样本先进行一次粗粒度的聚类,比如聚类K个桶,每个桶里面都有一个聚类中心,此时我们应该知道每个样本Y以及他的聚类中心q(Y),但是,我们不会在样本Y上直接做PQ量化,而是对Y和q(Y)的残差向量做PQ量化。
那么检索过程就很简单了,query向量x来了,q(x)计算出属于哪个聚类中心,比如属于C1,那么计算x和C1的残差之后,应用SDC和ADC,计算出当前类别下,所有量化后的残差向量与 的距离,时间复杂度仅为
。
这样的时间复杂度为原来纯PQ的时间复杂度 的
。
具体解释如下所示: