目录
- 前言
- 安装
- 向量无压缩检索
- 暴力检索
- 聚类检索
- 向量压缩检索
前言
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:])