相似度检索TopK的问题一般的解决方案是暴力检索,循环遍历所有向量计算相似度然后得出TopK,但是当向量数量巨大时,这种方法及其耗时,Faiss的出现就很好地解决了这个问题。
Faiss的全称是Facebook AI Similarity Search是FaceBook的AI团队针对大规模相似度检索问题开发的一个工具,使用C++编写,有python接口,对10亿量级的索引可以做到毫秒级检索的性能。Faiss的工作,就是把我们自己的候选向量集封装成一个index数据库,它可以加速我们检索相似向量TopK的过程,其中有些索引还支持GPU构建,可谓是强上加强。
FAISS的使用一般包括如下三格步骤:
import numpy as np
d = 64 # 向量维度
nb = 100000 # index向量库的数据量
nq = 10000 # 待检索query的数目
np.random.seed(1234)
xb = np.random.random((nb, d)).astype('float32') # index向量库的向量
xq = np.random.random((nq, d)).astype('float32') # 待检索的query向量
import faiss
index = faiss.IndexFlatL2(d) # 创建索引时必须指定向量的维度d
print(index.is_trained) # 输出为True,代表该类index不需要训练,只需要add向量进去即可
index.add(xb) # 将向量库中的向量加入到index中
print(index.ntotal) # 输出index中包含的向量总数,为100000
k = 4 # topK的K值
D, I = index.search(xq, k)# xq为待检索向量矩阵,返回的I为每个待检索query最相似TopK的索引list,D为其对应的距离
print(I[:5])
print(D[:5])
得到如下输出,第一个矩阵是索引,第一列0,1,2,3,4表示待检索矩阵自己的索引,从第二距离矩阵也可以看出,自己和自己的距离为0
[[ 0 393 363 78]
[ 1 555 277 364]
[ 2 304 101 13]
[ 3 173 18 182]
[ 4 288 370 531]]
[[ 0. 7.17517328 7.2076292 7.25116253]
[ 0. 6.32356453 6.6845808 6.79994535]
[ 0. 5.79640865 6.39173603 7.28151226]
[ 0. 7.27790546 7.52798653 7.66284657]
[ 0. 6.76380348 7.29512024 7.36881447]]
tips 1
在实际使用时,构建索引一般都使用faiss.index_factory
方法,基本所有的index都支持这种构建索引方法,上面步骤2中构建索引可以变成如下方式:
dim, measure = 64, faiss.METRIC_L2
param = 'Flat'
index = faiss.index_factory(dim, param, measure)
dim
: 指定向量的维度
param
: 是传入index的参数,代表需要构建什么类型的索引
measure
:度量方法,目前支持两种,欧氏距离和inner product,即内积。因此,要计算余弦相似度,只需要将向量归一化后,使用内积度量即可。参数为faiss.METRIC_INNER_PRODUCT
tips 2
一些索引可以保存整型的ID,每个向量可以指定一个ID,当查询相似向量时,会返回相似向量的ID及相似度(或距离)。如果不指定,将按照添加的顺序从0开始累加。其中IndexFlatL2不支持指定ID。IndexFlatL2不支持指定id,但是可以通过IDMAP
的方式实现,如下代码
ids = [2,10, 100,...]
ids = np.array(ids)
index = faiss.index_factory(768, "IDMap, Flat")
index.add_with_ids(save_embedding, ids) # 指定id,save_embedding为向量库
下面方法与上面类似
index = faiss.IndexFlatL2(d)
ids = np.arange(100000, 200000)
index2 = faiss.IndexIDMap(index)
index2.add_with_ids(xb, ids)
dim, measure = 64, faiss.METRIC_L2
param = 'Flat'
index = faiss.index_factory(dim, param, measure)
index.is_trained # 输出为True
index.add(xb) # 向index中添加向量
dim, measure = 64, faiss.METRIC_L2
param = 'IVF100, Flat' # 代表k-means聚类中心为100,
index = faiss.index_factory(dim, param, measure)
print(index.is_trained) # 此时输出为False,因为倒排索引需要训练k-means,
index.train(xb) # 因此需要先训练index,再add向量
index.add(xb)
构建方法:
dim, measure = 64, faiss.METRIC_L2
param = 'HNSW64'
index = faiss.index_factory(dim, param, measure)
print(index.is_trained) # 此时输出为True
index.add(xb)
构建方法:
dim, measure = 64, faiss.METRIC_L2
param = 'PQ16'
index = faiss.index_factory(dim, param, measure)
print(index.is_trained) # 此时输出为False,因为倒排索引需要训练k-means,
index.train(xb) # 因此需要先训练index,再add向量
index.add(xb)
dim, measure = 64, faiss.METRIC_L2
param = 'IVF100, PQ16'
index = faiss.index_factory(dim, param, measure)
print(index.is_trained) # 此时输出为False,因为倒排索引需要训练k-means,
index.train(xb) # 因此需要先训练index,再add向量 index.add(xb)
更多参考文献[3]
参考Running on GPUs
res = faiss.StandardGpuResources() # 声明gpu资源
# 构建一个 flat (CPU) 索引
index_flat = faiss.IndexFlatL2(d)
# 将cpu索引加入到gpu中
gpu_index_flat = faiss.index_cpu_to_gpu(res, 0, index_flat)
# 接下来的操作与一般情况类似
gpu_index_flat.add(xb) # add vectors to the index
print(gpu_index_flat.ntotal)
k = 4 # we want to see 4 nearest neighbors
D, I = gpu_index_flat.search(xq, k) # actual search
print(I[:5]) # neighbors of the 5 first queries
print(I[-5:]) # neighbors of the 5 last queries
ngpus = faiss.get_num_gpus()
print("number of GPUs:", ngpus)
cpu_index = faiss.IndexFlatL2(d)
gpu_index = faiss.index_cpu_to_all_gpus(cpu_index) # build the index
gpu_index.add(xb) # add vectors to the index
print(gpu_index.ntotal)
k = 4 # we want to see 4 nearest neighbors
D, I = gpu_index.search(xq, k) # actual search
print(I[:5]) # neighbors of the 5 first queries
print(I[-5:]) # neighbors of the 5 last queries
faiss简介及示例
Faiss流程与原理分析
Faiss入门及应用经验记录