使用Faiss来加速计算向量之间的相似度

大家阅读之后可以跟LSH的方法来对比

使用LSH来计算余弦相似度

这里我也是以1000个向量,和10万个向量为例,看一看到底加速了多少

Faiss也是通过聚类来实现距离计算的加速的,只不过Faiss用了两次聚类,一次就是普通的聚类用来寻找相近的向量(IVF),一次聚类是用来加速距离的计算(PQ)

与LSH相比,这个模型的初始化需要训练,因为聚类的中心需要计算,而LSH的聚类中心是随机的。所以与LSH相比,Faiss也不能增量去重,因为需要先初始化聚类中心嘛。好了,话不多说,具体介绍一下吧。

IVF,非常简单

训练阶段。对训练样本进行聚类,选取1024个类,那么1000个训练样本平均每个类就只有一个向量,10万个样本,每个类有(10万/1024)个向量。保存每个类的中心,还有里面的训练样本。

查询阶段,对查询样本计算1024个中心的距离,这里就计算了1024次,然后找到它所属的聚类中心,所以这里如果有1000个查询样本,就计算了1000乘1024次。那么对于库中的样本,属于其他聚类中心的,我们就不比较了。

在实际处理的时候,我们会将输入与得到的中心做一个残差,然后输入到PQ中

PQ

经过IVF一次聚类之后,我们需要比较的样本已经不多啦,如果库里只有1000个样本,那么平均只剩下一个样本啦。但是如果本身库里有10万个样本,那么还有100个样本要比较,当然实际规模可能更大,所以我们需要再进一步的聚类。

PQ的原理是拆分向量,各自聚类。假设我们输入的是一个128维的向量,我们将它拆成4组,每组就是32维的向量。

训练阶段,每组的32维向量进行聚类,得到256个中心。那么4组,一共就有1024个中心。当然,这256*4个中心,实际上将空间划分成256^4个区域,也就是2的32次方个区域。每个区域保存本区域的向量。

查询阶段

  1. 也是将查询向量的128维,分成4组,每组32维。
  2. 然后将每组与256个中心都做一次距离计算,一共计算出1024个距离。
  3. 然后遍历所有库中的向量,取出库中向量对应的聚类中心编号,再对应到这个距离上,最后相加得到与库中向量的距离。
  4. 然后比如取top5,就是最相似的5个。
  5. (如果你需要计算cos的话,你与这5个库中向量再实际计算一次就可以了)

那么查询1个向量,要进行256*4次计算,1000个向量就是1000*256*4次,再加上对库中的1000个向量进行查表操作

这边要注意几点

  1. PQ没有实际计算距离,而是将与聚类中心的距离近似为相似度距离,可能的原因是聚类空间太大,平均每一个中心中得不到一个向量(实际上经过IVF和PQ两次聚类,子空间有2的42次方,即40多千亿的空间)
  2. 与LSH相比,PQ没有少查询,而是遍历了所有查询向量的距离,只不过把距离做了简化计算,而LSH是只与聚类中的查询向量进行计算,是简化了查询次数。
  3. 如果计算cos相似度,PQ还需要在top上另行计算,才能给出准确的相似度。当然我们也可以设定一个PQ距离的阈值来计算cos相似度,但是PQ距离和cos距离还是不同的,避免不了减少召回。

那么我们可以看到,Faiss快速的原因,在于不管库中的向量有多少,计算次数是固定的,都是与聚类中心做计算,只不过库中的向量多,查表的次数就多了。而LSH则是库中的向量多,聚类中的向量也自然变多,计算次数也会变多。

那么我们算一下1000条文本去重工作,Faiss的计算开销
训练开销不计算,我们算一下查询开销。
IVF,每条文本计算1024个距离,一共1000*1024次
PQ,每条文本计算2564个距离,一共1000*256*4次(当然这里是32维计算,且4组可以并行)
一共计算了1000*(1024 + 256*4)次,对比Lsh,1000条计算了1000*176次,慢了不少。
但是当10万条文本去重时,Faiss时10万*2048次,而Lsh是10万
16000次,快上了不少。

其实Faiss本质就是,用一个聚类缩小查询范围,再用一个聚类代替距离计算。
而Lsh本质是,用一个聚类缩小查询范围,然后精确的距离计算。

你可能感兴趣的:(使用Faiss来加速计算向量之间的相似度)