【关于 Faiss 】 那些的你不知道的事

DayNightStudy

共 11154字,需浏览 23分钟

 ·

2021-02-18 04:11

作者:杨夕

本文链接:https://github.com/km1994/nlp_paper_study

论文名称:Billion-scale similarity search with GPUs

论文链接:https://arxiv.org/abs/1702.08734

官方源码地址:https://github.com/facebookresearch/Faiss

个人介绍:大佬们好,我叫杨夕,该项目主要是本人在研读顶会论文和复现经典论文过程中,所见、所思、所想、所闻,可能存在一些理解错误,希望大佬们多多指正。

【注:手机阅读可能图片打不开!!!】

目录

一、动机篇

1.1 传统的相似度算法所存在的问题?

  1. 传统的相似度算法(欧几里得距离、曼哈顿距离、明可夫斯基距离、余弦相似度等[1])在计算向量相似度时,效率低下;

  2. 传统的相似度算法(欧几里得距离、曼哈顿距离、明可夫斯基距离、余弦相似度等[1])在计算向量相似度时,不支持 GPU 计算;

二、介绍篇

2.1 什么是 Faiss ?

  1. Faiss是针对稠密向量进行相似性搜索和聚类的一个高效类库。

  2. 它包含可搜索任意大小的向量集的算法,这些向量集的大小甚至都不适合RAM。

  3. 它还包含用于评估和参数调整的支持代码。

  4. Faiss用C ++编写,并且有python2与python3的封装代码。

  5. 一些最有用的算法在GPU上有实现。

  6. Faiss是由Facebook AI Research开发的。

2.2 Faiss 如何使用?

Faiss 的使用方式可以分为三个步骤:

  1. 构建训练数据以矩阵的形式表示,比如我们现在经常使用的embedding,embedding出来的向量就是矩阵的一行。

  2. 为数据集选择合适的index,index是整个Faiss的核心部分,将第一步得到的训练数据add到index当中。

  3. search,或者说query,搜索到最终结果。

2.3 Faiss原理与核心算法

  • Faiss的主要功能是对向量进行相似搜索。

  • 具体介绍:就是给定一个向量,在所有已知的向量库中找出与其相似度最高的一些向量;

  • 本质:是一个KNN(K近邻)问题,比如google的以图找图功能。根据上面的描述不难看出,Faiss本质是一个向量(矢量)数据库,这个数据库在进行向量查询的时候有其独到之处,因此速度比较快,同时占用的空间也比较小。

三、Faiss 实战篇

3.1 Faiss 如何安装?

# 更新conda
conda update conda
# 先安装mkl
conda install mkl
# faiss提供gpu和cpu版,根据服务选择
# cpu版本
conda install faiss-cpu -c pytorch
# gpu版本 -- 记得根据自己安装的cuda版本安装对应的faiss版本,不然会出异常。使用命令:nvcc -V 查看
conda install faiss-gpu cudatoolkit=8.0 -c pytorch # For CUDA8
conda install faiss-gpu cudatoolkit=9.0 -c pytorch # For CUDA9
conda install faiss-gpu cudatoolkit=10.0 -c pytorch # For CUDA10
# 校验是否安装成功
python -c "import faiss"

【注:这里小编尝试过多次 window 安装,最后都失败,最后Google了一下,发现Faiss不支持 window 系统!】

3.2 Faiss 的索引Index有哪些?

  • Faiss中最重要的是索引 Index

    • 为什么要创建索引?

      • Faiss 创建索引对向量预处理,提高查询效率

    • Faiss中的稠密向量各种索引都是基于 Index实现的,主要的索引方法包括:IndexFlatL2、IndexFlatIP、IndexHNSWFlat、IndexIVFFlat、IndexLSH、IndexScalarQuantizer、IndexPQ、IndexIVFScalarQuantizer、IndexIVFPQ、IndexIVFPQR等。每个方法的具体介绍见:


3.3 Faiss 的索引Index都怎么用?

3.3.1 数据预备

  • 创建训练数据和测试数据

  • 代码:

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.

  • 解析:

    • 训练集 xb:[100000,64]

    • 查询数据集 xq:[10000,64]

3.3.2 暴力美学 IndexFlatL2

  • 介绍:暴力检索 L2 距离的索引;

  • 方式:全量搜索

  • 流程:

  1. 创建索引

import faiss # make faiss available
index = faiss.IndexFlatL2(d) # build the index
print(index.is_trained)

注:创建索引时必须指定向量的维度d。大部分索引需要训练的步骤。IndexFlatL2 跳过这一步。

当索引创建好并训练(如果需要)之后,我们就可以执行 add 方法,add方法一般添加训练时的样本;

  1. 添加 训练集

index.add(xb) # add vectors to the index
print(index.ntotal)

  1. 寻找相似相似向量

包含向量的索引后,就可以传入搜索向量查找相似向量,search就是寻找相似相似向量了。

k = 4 # we want to see 4 nearest neighbors
D, I = index.search(xq, k) # actual search
print(I[:5]) # neighbors of the 5 first queries
print(D[-5:]) # neighbors of the 5 last queries
>>>
[[ 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]]

注:
D:numpy array对象,表示与相似向量的距离(distance),维度
I:numpy array对象,表示相似用户的ID

  • 存在问题:虽然查询速度高于 传统相似度计算方法,但是速度还是太慢

3.3.3 闪电侠 IndexIVFFlat

  • 引言:暴力美学 IndexFlatL2 查询速度太慢了

  • 介绍:IndexIVFFlat 是一种 加速索引方法,其所用的方法 为倒排法;

  • 方式:

  • 先聚类再搜索,可以加快检索速度,先将xb中的数据进行聚类(聚类的数目是超参)

    • nlist: 聚类的数目;

    • nprobe: 在多少个聚类中进行搜索,默认为1, nprobe越大,结果越精确,但是速度越慢

  • 流程:

    • 使用K-means建立聚类中心;

    • 然后通过查询最近的聚类中心;

    • 最后比较聚类中的所有向量得到相似的向量

  • 代码:

  1. 定义索引 和 聚类簇数

nlist = 100 #聚类中心的个数
k = 4
quantizer = faiss.IndexFlatL2(d) # the other index
index = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
# here we specify METRIC_L2, by default it performs inner-product search

注:创建IndexIVFFlat时需要指定一个其他的索引作为量化器(quantizer)来计算距离或相似度。
参数介绍:
faiss.METRIC_L2: faiss定义了两种衡量相似度的方法(metrics),分别为faiss.METRIC_L2、faiss.METRIC_INNER_PRODUCT。一个是欧式距离,一个是向量内积。
nlist:聚类中心的个数

  1. 添加 训练集

index.train(xb)
assert index.is_trained

index.add(xb) # add may be a bit slower as well

注:与 IndexFlatL2 对比,在 add 方法之前需要先训练

  1. 寻找相似相似向量

包含向量的索引后,就可以传入搜索向量查找相似向量,search就是寻找相似相似向量了。

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

参数介绍:
k:查找最相似的k个向量
index.nprobe:查找聚类中心的个数,默认为1个。

3.3.4 内存管家 IndexIVFPQ

  • 动机:索引IndexFlatL2和IndexIVFFlat都会全量存储所有的向量在内存中,如果数据量是海量级别的时候,怎么办呢?

  • 介绍:IndexIVFPQ 基于Product Quantizer(乘积量化)的压缩算法编码向量大小到指定的字节数的索引算法,存储的向量时压缩过的,查询的距离也是近似的。关于乘积量化的算法可自行搜索。

  • 方式:基于乘积量化(product quantizers)对存储向量进行压缩,节省存储空间

    • m:乘积量化中,将原来的向量维度平均分成多少份,d必须为m的整数倍

    • bits: 每个子向量用多少个bits表示

  • 代码:

  1. 定义索引 和 聚类簇数

nlist = 100
m = 8 # number of bytes per vector
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 a

注:之前我们定义的维度为d = 64,向量的数据类型为float32。这里压缩成了8个字节。所以压缩比率为 (64*32/8) / 8 = 32

  1. 添加 训练集

index.train(xb)
index.add(xb)

注:与 IndexFlatL2 对比,在 add 方法之前需要先训练

  1. 寻找相似相似向量

包含向量的索引后,就可以传入搜索向量查找相似向量,search就是寻找相似相似向量了。

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:])
>>>
[[ 0 608 220 228]
[ 1 1063 277 617]
[ 2 46 114 304]
[ 3 791 527 316]
[ 4 159 288 393]]

[[ 1.40704751 6.19361687 6.34912491 6.35771513]
[ 1.49901485 5.66632462 5.94188499 6.29570007]
[ 1.63260388 6.04126883 6.18447495 6.26815748]
[ 1.5356375 6.33165455 6.64519501 6.86594009]
[ 1.46203303 6.5022912 6.62621975 6.63154221]]

3.4 Faiss 然后使用 GPU?

注:并不是所有的索引都支持 GPU,所以在使用之前建议 查阅一下 Basic indexes

  • 可通过faiss.get_num_gpus()查询有多少个gpu

ngpus = faiss.get_num_gpus()
print("number of GPUs:", ngpus)

  • 单 GPU

res = faiss.StandardGpuResources() # use a single GPU, 这个命令需要安装Faiss GPU 版本
# build a flat (CPU) index
index_flat = faiss.IndexFlatL2(d)
# make it into a gpu index
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

  • 多 GPU

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 对比篇

4.1 sklearn cosine_similarity 和 Faiss 哪家强

  • 方法一:使用 sklearn 中的 cosine_similarity

# encoding utf8
import pandas as pd
import csv
import numpy as np
import sys
from sklearn.metrics.pairwise import cosine_similarity
# sys.path.append('../')
# 自定义 包
from tools.loader import loadDict,saveDict,dictCutBatchSave,dictLoadMerge
from tools.bert_tools import Bert_Class
bertModel = Bert_Class()
from tools.common_tools import timer

commonPath = "data/"
inf = commonPath+"resource/"
dataType = ""
fileName = f"medQA.valid.txt"
QueAnsFileName = f"QueAns{dataType}"
query = "睡前练瑜伽好吗睡觉之前练习40分钟的瑜伽好吗、能起到瘦身的作用吗?"
topN = 20

# 使用 sklearn cosine_similarity 方法 计算相似度
def use_sklearn_get_sim_query(query,training_vectors,topN):
# step 1:测试文本 转 bert sent
test_vecs = bertModel.get_vec_to_sent(query).astype('float32')
# step 2:计算 相似度
print("------------sklearn-------------")
ag=cosine_similarity(test_vecs,training_vectors)
# step 3:排序
fe=np.sort(ag,axis=1)
fe_index = np.argsort(ag,axis=1)
score_list = fe[0].tolist()
index_list = fe_index[0].tolist()
score_list.reverse()
index_list.reverse()
# step 4:取 Top N
return index_list[:topN],score_list[:topN]

training_vectors = np.load(f"{inf}training_vectors.npy")
id2QusAns = loadDict(inf,QueAnsFileName)
index_list,score_list = use_sklearn_get_sim_query(query,training_vectors,topN)

for index,score in zip(index_list,score_list):
print(f"index:{index} => query:{id2QusAns[index]}:{score}\n")

  • 方法二:Faiss

# encoding utf8
import pandas as pd
import csv
import numpy as np
import sys
import faiss
from faiss import normalize_L2
# sys.path.append('../')
# 自定义 包
from tools.loader import loadDict,saveDict,dictCutBatchSave,dictLoadMerge
from tools.bert_tools import Bert_Class
bertModel = Bert_Class()
from tools.common_tools import timer

commonPath = "data/"
inf = commonPath+"resource/"
dataType = ""
fileName = f"medQA.valid.txt"
QueAnsFileName = f"QueAns{dataType}"
query = "睡前练瑜伽好吗睡觉之前练习40分钟的瑜伽好吗、能起到瘦身的作用吗?"
topN = 20

# 使用 sklearn cosine_similarity 方法 计算相似度
def use_faiss_get_sim_query(query,training_vectors,topN,d=768):
# step 1:测试文本 转 bert sent
test_vecs = bertModel.get_vec_to_sent(query).astype('float32')
# step 2:计算 相似度
print("------------faiss-------------")
print('normalize_L2')
normalize_L2(training_vectors)
normalize_L2(test_vecs)
print('IndexFlatIP')
index=faiss.IndexFlatIP(d) # the other index,需要以其他index作为基础
index.train(training_vectors)
print(f"training_vectors.shape:{training_vectors.shape}")
print(index)
print('train')
print(index.is_trained)
print('add')
print(index)
index.add(training_vectors)
print('search')
print(f"test_vecs.shape:{test_vecs.shape}")
D, I =index.search(test_vecs, topN)
print(f"I:{I}") # 表示最相近的前5个的index
print(f"D:{D}") # 表示最相近的前5个的相似度的值
# step 3:排序
score_list = D.tolist()[0]
index_list = I.tolist()[0]
# step 4:取 Top N
return index_list,score_list

training_vectors = np.load(f"{inf}training_vectors.npy")
id2QusAns = loadDict(inf,QueAnsFileName)
index_list,score_list = use_faiss_get_sim_query(query,training_vectors,topN)

for index,score in zip(index_list,score_list):
print(f"index:{index} => query:{id2QusAns[index]}:{score}\n")

  • 分析:

    • 从预测结果角度看,两者结果雷同;

    • 从计算速度角度看:当 数据集 特别大时,Faiss 秒杀 sklearn cosine_similarity

参考资料

  1. 常用的相似度计算方法原理及实现

  2. Faiss从入门到实战精通

  3. Faiss 教程

  4. Faiss 用法

  5. Basic indexes


浏览 501
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报