完全解析:使用 Faiss 进行海量特征的相似度匹配

背景

我们不妨想象下面的几个例子:

  1. 输入一张商品的图片,从商品库中匹配出相似的商品,这是以图搜图的一个例子;
  2. 输入一小段音乐,从音乐库中匹配出对应的音乐出,这是 MIR 的一个例子;
  3. 输入一张人脸,从人脸底库中匹配出对应的人,这是 1:N 人脸识别的一个例子;

像这样的例子还有很多,事实上,以神经网络对样本进行特征的提取,然后在海量的特征库里进行特征相似度的搜索/比对/匹配,已经是 AI 技术落地的一大领域。Faiss 就是 Facebook 维护的一个高效的特征相似度匹配和聚类的库。

本文将从最基本的特征比对说起,然后落脚到我们为什么需要 Faiss,以及 Faiss 上提供的在特征比对之外的功能。

一个简单的特征相似度比对的例子

设想我们使用一个在 ImageNet 上预训练的 resnet50 模型来提特征,因为只需要最后的 2048 维特征,我们在例子中把 resnet50 网络最后的 fc 层去掉。别着急,在 deepvac 项目的 examples 目录下正好有一个完全一样的例子:

https://github.com/DeepVAC/deepvac/blob/master/examples/a_resnet_project/test_emb.py

假设我们现在要在 db 里放入 7030 张图片的特征来作为我们的特征库,之后,待搜索的图片就和该特征库来做相似度匹配。为了实验这个想法,首先我们来制作特征库。当我们执行程序 test_emb.py 后,在 process 函数中,该 test_emb.py 会将目录下的 7030 张图片提取为 7030 个 2048 维的特征:

2020-09-02 11:50:44,507 1073:master INFO     feature shape: torch.Size([1, 2048])
2020-09-02 11:50:44,508 1073:master INFO     feature db shape: torch.Size([1, 2048])
2020-09-02 11:50:44,527 1073:master INFO     feature shape: torch.Size([1, 2048])
2020-09-02 11:50:44,529 1073:master INFO     feature db shape: torch.Size([2, 2048])
2020-09-02 11:50:44,544 1073:master INFO     feature shape: torch.Size([1, 2048])
2020-09-02 11:50:44,544 1073:master INFO     feature db shape: torch.Size([3, 2048])
2020-09-02 11:50:44,571 1073:master INFO     feature shape: torch.Size([1, 2048])
2020-09-02 11:50:44,571 1073:master INFO     feature db shape: torch.Size([4, 2048])
2020-09-02 11:50:44,599 1073:master INFO     feature shape: torch.Size([1, 2048])
2020-09-02 11:50:44,600 1073:master INFO     feature db shape: torch.Size([5, 2048])
......

那么最后这个特征库的 shape 就为 7030 x 2048。现在我们要检索一张图片该怎么做呢?对该图片提特征、和 db 里的特征进行 L2 距离的计算、找出距离最近的 1 个或 k 个。代码如下所示:

#deepvac关于配置文件的标准
>>> from config import config

#需要使用test_emb.py中的DeepvacEmb类
>>> from test_emb import DeepvacEmb
>>> emb = DeepvacEmb(config)

#加载上文保存的特征库
>>> emb.loadDB('resnet50_gemfield_test.emb')

#获得测试图片的2048维特征
>>> xq = emb.getPillowImgFeatureVector("/gemfield/hostpv/nsfw/test/porn/00002640.000000.jpg")
>>> xq.shape
torch.Size([1, 2048])

#从特征库中搜索最匹配的,D为距离,I为索引
>>> D,I = emb.search(xq)
>>> D
[0.0]
>>> I
[248]

#从特征库中搜索最匹配的3个
>>> D,I = emb.search(xq,k=3)
>>> D
[0.0, 14.658945083618164, 15.17888069152832]
>>> I
[248, 225, 448]

上面的代码演示了从特征库中去搜索最相似的图片。其中使用到的 Deepvac 的 search API 就是基于 PyTorch 的 torch.norm() API 进行的 L2 距离的计算。

在多个 CUDA 设备上进行特征的相似度搜索

有的时候,我们的特征库太大,以至于一个 CUDA 卡已经装载不下(比如 20G 的特征库,而一个 RTX2080ti 才 11G 显存)。这个时候该怎么办呢?一个直觉的想法就是将特征库拆分成多份,分别放在多个 CUDA 设备上。然后我们的查询向量 xq 在每个 CUDA 设备上和特征库相比对,最后再汇总排序取出距离最小的那个。

deepvac 的 syszux_feature_vector 模块就是这么干的,你可以看下该模块中的 NamesPathsClsFeatureVector 类的实现:

https://github.com/DeepVAC/deepvac/blob/master/deepvac/syszux_feature_vector.pygithub.com

这个类也是 DeepVAC 内部常用的一个工具类。现在问题来了,即使有 Deepvac 中的 search()方法,即使还有 syszux_feature_vector 模块中的 searchAllDB()方法,但在以下方面还是有点不够灵活和有力:

  1. 如何一次搜索一批的特征,而不仅仅是一个特征?
  2. 如何返回更相似度最近的一批特征,而不只是一个特征?(好吧,Deepvac 类也支持)
  3. 如何让特征库使用的内存空间更小?(你看,上面都需要把特征库拆分到多个 cuda 设备上了)
  4. 搜索速度方面如何更快?

这就是 Faiss 库存在的意义。Faiss:Facebook AI Similarity Search。

Faiss 环境准备

提前说明的福利: 你可以使用如下的 docker 环境,从而省却自己配置环境的烦恼:

#只使用cpu
docker run -it gemfield/faiss:1.6.3-devel bash

#使用GPU的话
docker run --gpus all -it gemfield/faiss:1.6.3-devel bash

如果你不想使用上述的 Docker 镜像,那么需要自行安装依赖、编译、安装,这些步骤至少有:

1,安装依赖包

安装 libopenblas-dev:

apt install libopenblas-dev

否则报错:CMake Error at /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:165 (message):
Could NOT find BLAS (missing: BLAS_LIBRARIES)

安装 swig:

apt install swig

否则报错:Could NOT find SWIG (missing: SWIG_EXECUTABLE SWIG_DIR python)。

2,编译

在 Faiss 目录里,执行如下操作:

make build
cmake ..
make VERBOSE=1
make install

整个编译的最终产物有:

  • libfaiss.a (C++ 库,也用于链接_swigfaiss.so)
  • libfaiss_python_callbacks.a (用于链接_swigfaiss.so)
  • _swigfaiss.so(用于 python)

最后一步的 make install 会将 libfaiss.a 安装到/usr/local/lib/libfaiss.a,以及将头文件安装到/usr/local/include/faiss/目录下。

3,安装 python

在 build/faiss/python 目录下:

python setup.py install

会将把_swigfaiss.so 安装在/opt/conda/lib/python3.7/site-packages/faiss-1.6.3-py3.7.egg/faiss/

Faiss 的简单使用:Flat

我们先定义两个变量 xb 和 xq。xb 用来表示特征库,xq 用来表示待查询的 2048 维向量。如果沿用上面的例子,则 xb 就是提前存储了 7030 个样本的特征的“数据库”,它的 shape 就是 7030x2048——这样的“数据库”在 Faiss 中称作 Index object。

就像数据库的种类有很多一样,Index 的种类也有很多,我们先从最简单的 IndexFlatL2 开始。IndexFlatL2 是什么类型的“数据库”呢?就是使用暴力 L2 搜索的数据库——也就是和特征库中的每个特征进行 L2 距离计算然后取出距离最近的那个。是不是看着很熟悉?没错,这和上文中提到的 DeepVAC 的 search() API 的原理是一模一样的。

好多种 Index 在被真正检索前,是需要一个“训练”操作的,类似给数据库的字段建立索引,Index object 的“训练”其实就是提前根据特征的分布进行聚类训练。而我们的 IndexFlatL2 并不在列,前面说了,它是最简单的“数据库”:

import numpy as np
import faiss

d = 2048                          # dimension
nb = 7030                     # database size
nq = 10                       # nb of queries

np.random.seed(1234)             # make reproducible
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.

xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.

index = faiss.IndexFlatL2(d)
print(index.is_trained)

index.add(xb)
print(index.ntotal)

k = 4                          # we want to see 4 nearest neighbors
D, I = index.search(xb[:5], k) # sanity check
print("I: ",I)
print("D: ",D)

上面是使用 IndexFlatL2 的一个简单例子,Gemfield 初始化了一个 7030x2048 的特征库,然后一次检索 5 个特征,也就是 xq 是 5x2048。代码输出:

True
7030
I:  [[   0  998 1309 1134]
 [   1  252 2013 2790]
 [   2 1046 1342 1204]
 [   3   56 1846  910]
 [   4  714  324   50]]
D:  [[  0.      323.07217 325.1012  325.8144 ]
 [  0.      308.83826 313.52512 317.5051 ]
 [  0.      313.24506 313.52145 316.1954 ]
 [  0.      311.7074  313.01157 313.31152]
 [  0.      316.34128 316.5642  317.65128]]

I 是 5 个待检索特征各自前 4 个特征库中最相似的 index,D 是对应的距离。既然前文说过,IndexFlatL2 只是最简单的一种“数据库”,那么其它复杂的数据库都有什么呢?你可以使用如下的方法简单 list 下就可以管中窥豹了:

>>> import faiss
>>> for s in dir(faiss):
...   if s.startswith('Index'):
...     print(s)
... 
IndexBinary
IndexBinaryFlat
IndexFlat
IndexFlat1D
IndexFlat1D_swigregister
IndexFlatIP
IndexFlatIP_swigregister
IndexFlatL2
IndexFlatL2BaseShift
IndexFlatL2BaseShift_swigregister
IndexFlatL2_swigregister
IndexFlat_swigregister
IndexHNSW
IndexHNSW2Level
IndexHNSW2Level_swigregister
IndexHNSWFlat
IndexHNSWFlat_swigregister
IndexHNSWPQ
IndexHNSWPQ_swigregister
IndexHNSWSQ
IndexHNSWSQ_swigregister
IndexHNSW_swigregister
IndexIDMap
IndexIDMap2
IndexIDMap2_swigregister
IndexIDMap_swigregister
IndexIVF
IndexIVFFlat
IndexIVFFlatDedup
IndexIVFFlatDedup_swigregister
IndexIVFFlat_swigregister
IndexIVFPQ
IndexIVFPQR
IndexIVFPQR_swigregister
IndexIVFPQStats
IndexIVFPQStats_swigregister
IndexIVFPQ_swigregister
IndexIVFScalarQuantizer
IndexIVFScalarQuantizer_swigregister
IndexIVFSpectralHash
IndexIVFSpectralHash_swigregister
IndexIVFStats
IndexIVFStats_swigregister
IndexIVF_swigregister
IndexLSH
IndexLSH_swigregister
IndexLattice
IndexLattice_swigregister
IndexPQ
IndexPQStats
IndexPQStats_swigregister
IndexPQ_swigregister
......

以及 GPU 上的 Index 类型:

>>> import faiss
>>> for s in dir(faiss):
...   if s.startswith('GpuIndex'):
...     print(s)
... 
GpuIndexFlat
GpuIndexFlatConfig
GpuIndexFlatConfig_swigregister
GpuIndexFlatIP
GpuIndexFlatIP_swigregister
GpuIndexFlatL2
GpuIndexFlatL2_swigregister
GpuIndexFlat_swigregister
GpuIndexIVF
GpuIndexIVFConfig
GpuIndexIVFConfig_swigregister
GpuIndexIVFFlat
GpuIndexIVFFlatConfig
GpuIndexIVFFlatConfig_swigregister
GpuIndexIVFFlat_swigregister
GpuIndexIVFPQ
GpuIndexIVFPQConfig
GpuIndexIVFPQConfig_swigregister
GpuIndexIVFPQ_swigregister
GpuIndexIVFScalarQuantizer
GpuIndexIVFScalarQuantizerConfig
GpuIndexIVFScalarQuantizerConfig_swigregister
GpuIndexIVFScalarQuantizer_swigregister
GpuIndexIVF_swigregister
GpuIndex_swigregister

这里已经看到了各种 CPU 的 Index 和 GPU 的 Index,那么实践中这两者谁更快呢?



  • xq 的 batch 很小,Index 很小:CPU 通常更快;
  • xq 的 batch 很小,Index 很大:GPU 通常更快;
  • xq 的 batch 很大,Index 很小:随便;
  • xq 的 batch 很大,Index 很大:GPU 通常更快;
  • GPU 通常比 CPU 快 5 到 10 倍;

让 Faiss 使用更少的内存:PQ

IndexFlatL2 的暴力 L2 距离匹配是最基本的用法。很快你就会遇到一个问题,当特征库很大的时候,一个显存就很快装载不下特征库了;即使是换成使用内存来装载特征库,也装载不下了。有没有一种压缩算法能减小特征库的大小呢?这就是 PQ(Product Quantizer)。

不管是 IndexFlatL2、IndexIVFFlat、或者名字中包含有 Flat 的 Index,都会在加载到内存中的特征库中保存全量的特征,以 2048 维的 float 类型为例,一个样本就需要 8192 个字节。如果类似天猫京东这样有 1 亿件商品,每件商品就按照一个样本图片来算,也需要...763G 的特征库。如果我们能将特征库进行有效压缩,那可以节省相当可观的资源。PQ 就是带着这样的目的而来的,这是一种有损压缩,所以这种 Index 进行检索的返回值只是近似的准确,而不是像 IndexFlatL2 那样可以返回绝对准确的值。那么 PQ 是如何将一个样本的 8192 字节压缩的更小的呢?假设我们有 100 万个样本,每个样本有 2048 维特征(100 万 x 2048):

  1. 我们将每个样本的 2048 维 vector 拆分为 8 个 sub-vector,每个 sub-vector 就是 256 维了。这样就会有 8 个 100 万 x256 维的矩阵;
  2. 我们在这 8 个矩阵上使用 k = 256 的 k-means 聚类算法(Gemfield:这里的 256 和上面的 256 没啥关系),这样每个矩阵上会得到 256 个 centroid(质心),一共有 8 组 256 个 centroid;聚类算法的使用,使得每个 256 维的 centroid 成为最能代表那 3906 个 256 维特征的一个向量(100w/256 = 3906);为啥选择 k=256 呢?因为 256 个 centroid 的索引可以使用一个字节装下;
  3. 我们再回到那 1 亿个商品的特征库,我们将每个商品的 2048 维特征拆分成 8 个 sub-vector,每一个 sub-vector 都被替换为距离最近的那个 centroid,并使用该 centroid 的 id 来表示(1 个字节!)。如此以来,2048 维特征就被替换成了 8 个 centroid ID,也就是 8 个字节!如此以来,商品特征库就从 763G 下降到了 0.745G!

内存的使用量确实降下来了,但是如果特征库只包含 centroid ID 的话,怎么进行向量的相似度计算呢?只有 centroid ID 的话,怎么计算 L2 距离呢???是不是需要把特征库里的 1 亿个特征(每个特征此刻为 8 个字节)中的 centroid ID 替换成 centroid 来进行 L2 距离的计算呢?这固然是一个直截了当的想法,只不过,替换回去(也就是解压缩)的话,每个特征本来用 8 个字节表示,如今又要变成 8096 个字节了,那我们这么折腾一回图啥呀!相反,我们可以这么做:

1,一个 2048 维的 xq 来了,将该 xq vector 拆分为 8 个 sub-vector;

2,xq vector 的每个 sub-vector 就和 8x256 的 centroid 中对应的那组 1x256 的 centroid 进行 L2 距离计算,得到 256 个距离;那么 8 组下来,就会得到 8 组 256 个 L2 距离;这个计算量仿佛和一个只有 256 个样本特征的特征库差不多;

3,上述的 8 组 256 个 L2 的距离,可以看成是一个表:256 行 x8 列(毕竟是一个样本的特征被拆分了 8 个 sub vector,所以不是 8x256 而是 256x8);这个表很重要啊,我暂且称之为 gemfield 表;

4,特征库现在是已经压缩的状态,也就是说每个样本特征用的是 8 个 centroid ID 来表示的。而每个特征库的样本(每个 xb 记录)和 xq 的 L2 距离,就等于每个 xb 记录的 8 个 sub-vector 和 xq 的 8 个 sub-vector 的 L2 距离之和,就约等于 xb 记录的 8 个 centroid(中的每个)和 xq 的 8 个 sub-vector(中的每个)的 L2 距离之和!而 xb 记录的 8 个 centorid 中的每个和 xq 的 8 个 sub-vector 中的每个之间的 L2 距离,直接通过上述的 gemfield 表查表获得!

5,将距离排序,取得距离最近的 k 个 xb 记录(的 index 和 distance)。

上面的 centroid(最类中心的那个 2048 维向量),在 Faiss 中我们称之为 code;上述的 8 组 256 个 centroid,在 Faiss 中我们称之为 8 个 code book;这 8 个 code book 可以表达 256^8 个值,还是很大的。

让 Faiss 进行更快的检索:IVF

IndexFlatL2 的暴力 L2 距离匹配是最基本的用法。很快你又会遇到一个问题,当检索量很大的时候,每次检索哪怕减少一点时间,对整体系统的性能提升也会有很大的帮助;换言之,可以提升边际效益。那么如何让检索更快呢?事实上,更快的检索来自于两个方面:

  1. 两两特征比对更少的计算量;PQ 顺带着做了;
  2. 只和特征库的一部分进行比对;和特征库的每一个特征进行比对,叫做穷举;只和部分特征进行比对,叫做 IVF;

问题是,为什么和特征库的一部分进行比对就能找到想要的答案呢?呃,不能。只能找到近似正确的答案。为什么和特征库的一部分进行比对就能找到近似正确的答案呢?呃,倒排索引(IVF)。IVF 用来提前过滤 dataset 从而避开对全部特征库的穷举搜索,比如 gemfield 使用 k-means 算法将数据库进行聚类(比如 100 个分区),当查询一个 xq 的时候,和能代表这 100 个分区的 100 个 centroid 进行 L2 比较,拿到最接近的 5 个;然后在这 5 个分区里再进行穷举搜索。这样,有 95% 的 xb 记录就被忽略掉了。

IVF,全称 Inverted File Index,中文翻译为倒排索引,指的是在文本搜索中(比如搜索引擎),将每个单词到该单词所属的文档之间的映射关系保存到数据库中。Gemfield 是非常不喜欢这个概念的,英文的 Inverted File Index 我都感觉词不达意,而中文的“倒排索引”更是让人摸不着头脑。以一本书为例,书前面的目录就是索引:

图片

index,索引

书最后面的 index 就是倒排索引,如下所示:

图片

IVF,倒排索引

因此,在 Faiss 中所有带有 IVF 的 index,指的都是提前将数据库使用 k-means 聚类算法划分为多个 partition,每个 partition 中包含有对应的 feature vectors(这就是 inverted file lists 指向的),同时,每个 partition 还有对应的 centroid,用来决定该 partition 是应该被穷举搜索,还是被忽略。

IVF 这个方法当然加快了检索的速度,但是也是有代价的。假如 xq 是落在 partition 的边缘地带,那这个 xq 最近的记录大概率是距离它最近(这个最近指的是和 centroid 比较)的前面好多个 partition 里的一个,这样即使我们指定很多个比较近的 partition(比如 5 个),如果 ground truth 实际上是在第 6 个 partition 里,这样返回的结果依然是不准确的。

因此,带有 IVF 的检索只能返回近似正确的值,而不能像 IndexFlatL2 那样返回绝对正确的值。但是像上面这种 ground truth 是在第 6 个 partition 里,而我们指定 5 个 partition 也是比指定 1 个 partition 要更准确些的(代价就是检索时间多了)。一旦找到最近的那个 partition 后,便就要开始穷举检索该 partition 里的每条记录了,这就是 IndexIVFFlat 的由来。在某个 partition 中进行搜索的过程还可以使用上一节的 PQ 压缩的算法,因此,在 Faiss 中,我们还经常会使用的一个 Index 叫作 IndexIVFPQ。

这个 partition 的概念,在 Faiss 中称之为 Voronoi cells;选择某个 Voronoi cells 然后进行检索的动作,称之为“probe”;而在最近的“多少个”Voronoi cells 中进行检索,这个“多少个”的数量称之为 nprobe;在 IVF Index object 的属性中,就有 nprobe 这个属性:

import numpy as np
import faiss

d = 2048                          # dimension
nb = 7030                     # database size
nq = 10                       # nb of queries

np.random.seed(1234)             # make reproducible
xb = np.random.random((nb, d)).astype('float32')
xb[:, 0] += np.arange(nb) / 1000.

xq = np.random.random((nq, d)).astype('float32')
xq[:, 0] += np.arange(nq) / 1000.

index = faiss.IndexFlatL2(d)
print(index.is_trained)

index.add(xb)
print(index.ntotal)

######
nlist = 100
m = 8
k = 4
quantizer = faiss.IndexFlatL2(d)  # this remains the same
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, 8)
                                  # 8 specifies that each sub-vector is encoded as 8 bits
index.train(xb)
index.add(xb)
#nprobe
index.nprobe = 10
D, I = index.search(xb[:5], k)
print("I: ",I)

IndexIVFPQ 的参数中,d 代表特征的维度 2048,nlist 代表 Voronoi cells 的数量,m 代表 subquantizers 的数量(给 PQ 用),8 代表使用 8bits 表示一个特征的 1 个 sub-vector。代码输出:

root@pytorch160-ai2:/home/gemfield# python test.py 
#当index.nprobe = 10
I:  [[  14   68    0  599]
 [ 297    1  546  227]
 [ 246  311    2  137]
 [ 213    3  256  248]
 [   4  919  644 1351]]

可见确实只能返回近似准确的值。

无论如何内存都不够用:Distributed index

在某些情况下,内存或者显存无论如何都不够使用。比如要加载 20 多个 GB 的特征库,但是显卡只有 11GB,又不想妥协任何的准确度。那怎么办呢?方法之一就是将特征库分散加载到多个服务器的 CUDA 设备上。

在项目仓库的 faiss/contrib/目录下,Faiss 提供了 rpc.py,一个小型的 RPC 库。基于该 RPC 库,Faiss 在 faiss/contrib/client_server.py 中实现了 run_index_server() API 和 ClientIndex 类。在不同的服务器上,使用 run_index_server() API 运行服务:

#比如四个服务器: ai{1..4}.gemfield.org
#index就是所有特征库的四分之一
run_index_server(index, port, v6=v6)

在客户端,使用 client_index=ClientIndex(host_port_array)来访问:

syszux_ports = [
    ('ai1.gemfield.org', 12010),
    ('ai2.gemfield.org', 12011),
    ('ai3.gemfield.org', 12012),
    ('ai4.gemfield.org', 12013),
]

client_index = ClientIndex(syszux_ports)
#xq是query vector
D, I = client_index.search(xq, 5)

其实 client_index 获得分段的结果,最后归并取最好结果的这个过程,和文章开头提到的 syszux_feature_vector.py 里的逻辑是一样的。

无论如何内存都不够用:On-disk storage

参考项目中的 faiss/contrib/ondisk.py。

当 xq 是 pytorch 的 tensor 时

在前文的各种例子中,你都发现,无论是 xb 还是 xq,它们都是转换为 numpy 数组才开始调用 Faiss 的 API 的。没错,在 Faiss 中,numpy 就是一等公民。既然 Faiss 是 Facebook 的项目,既然 Facebook 还有 PyTorch 这么流行的项目,那么在 Faiss 中为什么不原生支持 PyTorch 的 Tensor 呢?更何况 numpy 只能存活在 CPU 上,Tensor 可是 CPU 和 GPU 通吃啊!实在是令人莫名其妙、瞠目结舌、匪夷所思!

在前文的例子中:

https://github.com/DeepVAC/deepvac/blob/master/examples/a_resnet_project/test_emb.pygithub.com

我们的特征库可都是使用 PyTorch 的 Tensor 来存储和序列化的,查询特征 xq 也是 tensor,总不能每次都从 Tensor 转换成 numpy 吧。目前,Faiss 提供了一种临时的措施,可以直接读取 Tensor 底层的内存(Tensor 必须是 is_contiguous 的),然后使用 index 的 search_c() API 来检索。关于在 index 中检索 PyTorch Tensor 的详细用法,你可以参考 syszux_feature_vector.py 中的 NamesPathsClsFeatureVectorByFaissPytorch 类:

https://github.com/DeepVAC/deepvac/blob/master/deepvac/syszux_feature_vector.pygithub.com

最后,如何选择一种 Index 呢?

我们已经见识过的关键字有 Flat、IVF、PQ,那么如何选择一种 Index 来匹配我们的场景呢?

  1. 当需要绝对准确的结果时,使用 Flat;比如 IndexFlatL2 或者 IndexFlatIP;
  2. 如果内存完全够用富裕的不行,使用 HNSW;如果一般够用,使用 Flat;如果有点吃紧,使用 PCARx,...,SQ8;如果非常吃紧,使用 OPQx_y,...,PQx;
  3. 如果特征库小于 1M 个记录,使用"...,IVFx,...";如果在 1M 到 10M 之间,使用"...,IVF65536_HNSW32,...";如果在 10M - 100M 之间,使用"...,IVF262144_HNSW32,...";如果在 100M - 1B 之间,使用"...,IVF1048576_HNSW32,..."。

用于训练的 xb 记录越多,聚类算法的训练效果越好,但训练需要花的时间也就越多,别忘了这一点。


本文地址:https://www.6aiq.com/article/1607644126180
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出