目录

  • 前言
  • 安装
  • 向量无压缩检索
  • 暴力检索
  • 聚类检索
  • 向量压缩检索


前言

faiss是FaceBook开源的大规模向量检索库,相似度为L2距离(欧式距离)或内积,底层为C++,内置的大部分算法支持GPU加速检索,包含了C++及Python两种API,且与Numpy库无缝衔接。当数据量较少时,可以将原始向量集合全部装载进内存,当然数据量非常庞大如数十亿的级别时,可以进行数据的压缩以便内存能够装载的下,当然压缩完后在进行检索,会损失检索的精度。

安装

由于电脑上没有GPU,因此本文都是在CPU下的操作。

# cpu版本
 conda install faiss-cpu -c pytorch

向量无压缩检索

本篇文章的代码都由python编写, 大都来自faiss的官方教程。
首先设定超参数以及张量集合。

import numpy as np
d = 64                           # dimension
nb = 100000                      # database size
nq = 10000                       # 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.

暴力检索

所有的检索算法,都需要显示的传递张量的维度。

# 导入faiss库,简历L2索引
import faiss                   # make faiss available
index = faiss.IndexFlatL2(d)   # build the index
print(index.is_trained)
index.add(xb)                  # add vectors to the index
print(index.ntotal)

检索

k = 4                          # we want to see 4 nearest neighbors
D, I = index.search(xb[:5], k) # sanity check
print(I)
print(D)
D, I = index.search(xq, k)     # actual search
print(I[:5])                   # neighbors of the 5 first queries
print(I[-5:])                  # neighbors of the 5 last queries

其中search()函数接收两个参数,第一个为检索向量集合,第二个为寻找的最近邻个数,返回值K个近邻的距离以及对应的索引。

聚类检索

除了暴力的L2距离检索,对于大部分的检索算法,都需要显示的进行训练。当训练完成后,可以向向量库中新增向量以及执行检索操作。聚类检索的算法原理简单直观,首先将向量库进行聚类(nlist个),得出每个聚类的中心。当进行K近邻查询时,首先找出距离查询向量最近的聚类中心,然后找出离该中心最近的nprobe-1个聚类,最后到这个nprobe个聚类群中执行检索操作。这就有两个超参数——nlist和nprobe,当nlist一定时,nprobe越大查询越精确,当然速度越慢。其中nprobe默认为1。

faiss定义了两种衡量相似度的方法(metrics),分别为faiss.METRIC_L2、faiss.METRIC_INNER_PRODUCT。一个是欧式距离,一个是向量内积(余弦相似度,默认使用)。
该方法中,除了计算向量之间的相似度,还要计算聚类中心的距离,对应有下面代码中的quantizer。

nlist = 100
k = 4
quantizer = faiss.IndexFlatL2(d)  # the other index
index = faiss.IndexIVFFlat(quantizer, d, nlist)
# 使用L2距离计算相似度
# index = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
assert not index.is_trained
index.train(xb)
assert index.is_trained
index.add(xb)                  # add may be a bit slower as well
D, I = index.search(xq, k)     # actual search
print(I[-5:])                  # neighbors of the 5 last queries
index.nprobe = 10              # default nprobe is 1, try a few more
D, I = index.search(xq, k)
print(I[-5:])                  # neighbors of the 5 last queries

向量压缩检索

由于进行向量检索时,需要将所有的向量全部载入内存,当数据不能一次性全部载入内存时,就需要进行向量的压缩,该种情况下,向量之间的距离为近似的,检索的结果也没有上一种方法准确,压缩的方法以及力度都可以配置。
除了向量会被压缩之外,其他操作与无压缩的聚类检索相同。

# 代码示例
nlist = 100
m = 8                             # number of subquantizers
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)
D, I = index.search(xb[:5], k) # sanity check
print(I)
print(D)
index.nprobe = 10              # make comparable with experiment above
D, I = index.search(xq, k)     # search
print(I[-5:])