索引是faiss的關鍵知識,咱們重點介紹下。app
索引方法彙總
有些索引名,我就不翻譯了,根據英文名去學習更準確。dom
索引名 | 類名 | index_factory | 主要參數 | 字節數/向量 | 精準檢索 | 備註 |
---|---|---|---|---|---|---|
精準的L2搜索 | IndexFlatL2 | "Flat" | d | 4*d | yes | brute-force |
精準的內積搜索 | IndexFlatIP | "Flat" | d | 4*d | yes | 歸一化向量計算cos |
Hierarchical Navigable Small World graph exploration | IndexHNSWFlat | "HNSWx,Flat" | d, M | 4*d + 8 * M | no | - |
倒排文件 | IndexIVFFlat | "IVFx,Flat" | quantizer, d, nlists, metric | 4*d | no | 須要另外一個量化器來創建倒排 |
Locality-Sensitive Hashing (binary flat index) | IndexLSH | - | d, nbits | nbits/8 | yes | optimized by using random rotation instead of random projections |
Scalar quantizer (SQ) in flat mode | IndexScalarQuantizer | "SQ8" | d | d | yes | 每一個維度項能夠用4 bit表示,可是精度會受到必定影響 |
Product quantizer (PQ) in flat mode | IndexPQ | "PQx" | d, M, nbits | M (if nbits=8) | yes | - |
IVF and scalar quantizer | IndexIVFScalarQuantizer | "IVFx,SQ4" "IVFx,SQ8" | quantizer, d, nlists, qtype | d or d/2 | no | 有兩種編碼方式:每一個維度項4bit或8bit |
IVFADC (coarse quantizer+PQ on residuals) | IndexIVFPQ | "IVFx,PQy" | quantizer, d, nlists, M, nbits | M+4 or M+8 | no | 內存和數據id(int、long)相關,目前只支持 nbits <= 8 |
IVFADC+R (same as IVFADC with re-ranking based on codes) | IndexIVFPQR | "IVFx,PQy+z" | quantizer, d, nlists, M, nbits, M_refine, nbits_refine | M+M_refine+4 or M+M_refine+8 | no | - |
Cell-probe方法
加速查找的典型方法是對數據集進行劃分,咱們採用了基於Multi-probing(best-bin KD樹變體)的分塊方法。函數
- 特徵空間被切分爲ncells個塊
- 數據被劃分到這些塊中(k-means可根據最近歐式距離),歸屬關係存儲在ncells個節點的倒排列表中
- 搜索時,檢索離目標距離最近的nprobe個塊
- 根據倒排列表檢索nprobe個塊中的全部數據。
這即是IndexIVFFlat,它須要另外一個索引來記錄倒排列表。學習
IndexIVFKmeans 和 IndexIVFSphericalKmeans 不是對象而是方法,它們能夠返回IndexIVFFlat對象。this
注意:對於高維的數據,要達到較好的召回,須要的nprobes可能很大編碼
和LSH的關係
最流行的cell-probe方法多是原生的LSH方法,可參考E2LSH。然而,這個方法及其變體有兩大弊端:spa
- 須要大量的哈希函數(=分塊數),來達到能夠接受的結果
- 哈希函數很難基於輸入動態調整,實際應用中容易返回次優結果
LSH的示例scala
n_bits = 2 * d lsh = faiss.IndexLSH (d, n_bits) lsh.train (x_train) lsh.add (x_base) D, I = lsh.search (x_query, k)
d是輸入數據的維度,nbits是存儲向量的bits數目。翻譯
PQ的示例code
m = 16 # number of subquantizers n_bits = 8 # bits allocated per subquantizer pq = faiss.IndexPQ (d, m, n_bits) # Create the index pq.train (x_train) # Training pq.add (x_base) # Populate the index D, I = pq.search (x_query, k) # Perform a search
帶倒排的PQ:IndexIVFPQ
coarse_quantizer = faiss.IndexFlatL2 (d) index = faiss.IndexIVFPQ (coarse_quantizer, d, ncentroids, m, 8) index.nprobe = 5
複合索引
使用PQ做粗粒度量化器的Cell Probe方法
相應的文章見:The inverted multi-index, Babenko & Lempitsky, CVPR'12。在Faiss中可以使用MultiIndexQuantizer,它不須要add任何向量,所以將它應用在IndexIVF時須要設置quantizer_trains_alone。
nbits_mi = 12 # c M_mi = 2 # m coarse_quantizer_mi = faiss.MultiIndexQuantizer(d, M_mi, nbits_mi) ncentroids_mi = 2 ** (M_mi * nbits_mi) index = faiss.IndexIVFFlat(coarse_quantizer_mi, d, ncentroids_mi) index.nprobe = 2048 index.quantizer_trains_alone = True
預過濾PQ編碼,漢明距離的計算比PQ距離計算快6倍,經過對PQ中心的合理重排序,漢明距離能夠正確地替代PQ編碼距離。在搜索時設置漢明距離的閾值,能夠避免PQ比較的大量運算。
# IndexPQ index = faiss.IndexPQ (d, 16, 8) # before training index.do_polysemous_training = true index.train (...) # before searching index.search_type = faiss.IndexPQ.ST_polysemous index.polysemous_ht = 54 # the Hamming threshold index.search (...)
# IndexIVFPQ index = faiss.IndexIVFPQ (coarse_quantizer, d, 16, 8) # before training index. do_polysemous_training = true index.train (...) # before searching index.polysemous_ht = 54 # the Hamming threshold index.search (...)
閾值設定是注意兩點:
- 閾值在0到編碼bit數(16*8)之間
- 閾值越小,留下的須要計算的PQ中心數越少,推薦<1/2*bits
複合索引中也能夠創建多級PQ量化索引。
預處理和後處理
爲了得到更好的索引,能夠remap向量ids,對數據集進行變換,re-rank檢索結果等。
Faiss id mapping
默認狀況下,Faiss爲每一個向量設置id。有些Index實現了add_with_ids方法,爲向量添加64bit的ids,檢索時返回ids而不需返回原始向量。
index = faiss.IndexFlatL2(xb.shape[1]) ids = np.arange(xb.shape[0]) index.add_with_ids(xb, ids) # this will crash, because IndexFlatL2 does not support add_with_ids index2 = faiss.IndexIDMap(index) index2.add_with_ids(xb, ids) # works, the vectors are stored in the underlying index
IndexIVF原生提供了ass_with_ids方法,就不須要IndexIDMap了。
預變換
變換方法 | 類名 | 備註 |
---|---|---|
random rotation | RandomRotationMatrix | useful to re-balance components of a vector before indexing in an IndexPQ or IndexLSH |
remapping of dimensions | RemapDimensionsTransform | 爲適應索引推薦的維度,經過重排列減小或增長向量維度d |
PCA | PCAMatrix | 降維 |
OPQ rotation | OPQMatrix | OPQ經過旋轉輸入向量更利於PQ編碼,見 Optimized product quantization, Ge et al., CVPR'13 |
換行能夠經過train進行訓練,經過apply應用到數據上。這些變化能夠經過IndexPreTransform方法應用到索引上。
# the IndexIVFPQ will be in 256D not 2048 coarse_quantizer = faiss.IndexFlatL2 (256) sub_index = faiss.IndexIVFPQ (coarse_quantizer, 256, ncoarse, 16, 8) # PCA 2048->256 # also does a random rotation after the reduction (the 4th argument) pca_matrix = faiss.PCAMatrix (2048, 256, 0, True) #- the wrapping index index = faiss.IndexPreTransform (pca_matrix, sub_index) # will also train the PCA index.train(...) # PCA will be applied prior to addition index.add(...)
IndexRefineFlat
對搜索結果進行精準重排序
q = faiss.IndexPQ (d, M, nbits_per_index) rq = faiss.IndexRefineFlat (q) rq.train (xt) rq.add (xb) rq.k_factor = 4 D, I = rq:search (xq, 10)
從IndexPQ的最近4*10個鄰域中,計算真實距離,返回最好的10個結果。注意IndexRefineFlat須要積累全向量,佔用內存較高。
IndexShards
若是數據分開爲多個索引,查詢時須要合併結果集。這在多GPU以及平行查詢中是必需的。