zoukankan      html  css  js  c++  java
  • Faiss教程:索引(1)

    索引是faiss的关键知识,我们重点介绍下。

    索引方法汇总

    有些索引名,我就不翻译了,根据英文名去学习更准确。

    索引名 类名 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对象。

    注意:对于高维的数据,要达到较好的召回,需要的nprobes可能很大

    和LSH的关系

    最流行的cell-probe方法可能是原生的LSH方法,可参考E2LSH。然而,这个方法及其变体有两大弊端:

    • 需要大量的哈希函数(=分块数),来达到可以接受的结果
    • 哈希函数很难基于输入动态调整,实际应用中容易返回次优结果

    LSH的示例

    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的示例

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

  • 相关阅读:
    Tips(持续跟新)
    icpc 2018 徐州 网络赛 B 博弈+记忆化搜索
    2018 徐州 icpc 网络赛 A 递推or数学公式
    2018 徐州icpc网络赛 G 分块
    HDU 3092 Least common multiple(完全背包+思维)
    hdu 4747(DP?线性递推)
    Pell-方程学习小结
    C++中map的介绍用法以及Gym题目:Two Sequences
    求最长上升子序列和最长非下降子序列
    dfs+枚举,flip游戏的拓展POJ2965
  • 原文地址:https://www.cnblogs.com/houkai/p/9316155.html
Copyright © 2011-2022 走看看