Faiss的全称是Facebook AI Similarity Search。是Facebook 2017年发布的一个相似搜索开源库,针对高维空间中的海量数据,提供了高效且可靠的检索方法。它可以提供基于十亿级别的数据集构建最近邻搜索算法的实现。这个场景是基于查询的传统搜索引擎无法解决的。举个例子,假设我们做人脸检索,每个人脸图像经过神经网络后可以提取出一个2048维的特征向量,把全中国13亿人的人脸特征全部存储在一个数据库中。在查询时,任意拍摄一张人脸图像,输入神经网络,得到新的2048维特征向量,我们需要在数据库中找到与该向量最相似的向量,从而识别出现在拍摄的人脸是谁。暴力检索耗时巨大,对于一个要求实时人脸识别的应用来说是不可取的。而Faiss则为这种场景提供了一套解决方案。
给定维数为d的向量x_i的集合,Faiss用它在RAM中建立一个数据结构,该结构称之为索引(Index),在给定维度d的新向量x时,Faiss将计算新向量x与原集合中向量的欧氏距离。
对于一个检索任务,操作流程一定分为三步:训练、构建数据库、查询。
首先,原始人脸特征向量的空间占用大。假设特征向量以float类型表示,那么13亿个2048维向量占用的空间就是9.7TB,恐怕没有哪台计算机的内存可以放得下这些数据。Faiss提供了若干种方法对这些数据进行压缩,然后通过PCA算法进行K维降阶,PQ算法进行分解量化,最后计算多维数据的欧氏距离以求解最近邻数据。
其次,暴力搜索的时间复杂度是O(N),N是数据库中特征的数量,在这里就是13亿。每次查询都遍历13亿个数据,并对遍历到的每个数据计算欧氏距离,这一计算量非常可观。Faiss提供了倒排索引的算法,根据聚类结果对数据库中的所有向量建立倒排表,从而大大加速搜索时间。
传统的数据库是由包含符号信息的结构化数据表组成。比如,一个图片集可以表示为一个数据表,每行代表一个被索引的图片,包含图片标识符和描述文字之类的信息;每一行也可以与其他数据表中的实体关联起来,比如某个用户的一张图片可以与用户姓名表建立关联。
像文本嵌入(word2vec)或者卷积神经网络(CNN)描述符这样通过深度学习训练出的 AI 工具,都可以生成高维向量。这种表示远比一个固定的符号表示更加强大和灵活,正如后文将解释的那样。然而使用 SQL 查询的传统数据库并不适用这些新的表示方式。首先,海量多媒体信息的涌入产生了数十亿的向量;其次,且更重要的是,查找相似实体意味着查找相似的高维向量,如果只是使用标准查询语言这将非常低效和困难。
如下图所示,以图片搜索为例,所谓相似度搜索,便是在给定的一堆图片中,寻找出我指定的目标最像的K张图片,也简称为KNN(K近邻)问题。
为了解决KNN问题,在工程上需要实现对已有图库的存储,当用户指定检索图片后,需要知道如何从存储的图片库中找到最相似的K张图片。基于此,我们推测Faiss在应用场景中具备添加功能和搜索功能,有了添加相应的修改和删除功能也会接踵而来,从上述分析看,Faiss本质上是一个向量(矢量)数据库。
对于数据库来说,时空优化是两个永恒的主题,即在存储上如何以更少的空间来存储更多的信息,在搜索上如何以更快的速度来搜索出更准确的信息。如何减少搜索所需的时间?在数据库中很最常见的操作便是加各种索引,把各种加速搜索算法的功能或空间换时间的策略都封装成各种各样的索引,以满足各种不同的引用场景。
对于相似性搜索和分类,我们需要做下列处理:
一个额外的挑战是,要在一个超大规模比如数十亿向量上做这些运算。
欧氏距离是最常见的两点或多点之间的距离表示法,又称为欧几里德度量。
计算公式为:
将数据从40D降维到20D:
# random training data
mt = np.random.rand(1000, 40).astype('float32')
mat = faiss.PCAMatrix (40, 10)
mat.train(mt)
assert mat.is_trained
tr = mat.apply_py(mt)
# print this to show that the magnitude of tr's columns is decreasing
print (tr ** 2).sum(0)
PQ对象可用于将矢量编码或解码为代码:
d = 32 # data dimension
cs = 4 # code size (bytes)
# train set
nt = 10000
xt = np.random.rand(nt, d).astype('float32')
# dataset to encode (could be same as train)
n = 20000
x = np.random.rand(n, d).astype('float32')
pq = faiss.ProductQuantizer(d, cs, 8)
pq.train(xt)
# encode
codes = pq.compute_codes(x)
# decode
x2 = pq.decode(codes)
# compute reconstruction error
avg_relative_error = ((x - x2)**2).sum() / (x ** 2).sum()
聚类(Clustering)就是一种寻找数据之间内在结构的技术。聚类把全体数据实例组织成一些相似组,而这些相似组被称作簇。处于相同簇中的数据实例彼此相同,处于不同簇中的实例彼此不同。
聚类技术通常又被称为无监督学习,与监督学习不同的是,在簇中那些表示数据类别的分类或者分组信息是没有的。
比如数据之间的相似性是通过定义一个距离或者相似性系数来判别的,那么距离相近的数据对象被划分为一个簇。
Faiss提供了有效的k均值聚类算法实现。聚类存储在给定二维张量x中的一组向量的操作如下:
ncentroids = 1024
niter = 20
verbose = True
d = x.shape[1]
kmeans = faiss.Kmeans(d, ncentroids, niter=niter, verbose=verbose)
kmeans.train(x)
https://github.com/facebookresearch/faiss/wiki
Faiss源码解析
Faiss:Facebook 开源的相似性搜索类库