索引是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以及平行查询中是必需的。