************* 原文 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的時間復雜度 的
。
具體解釋如下所示: