0.Faiss是什么? 1.Faiss如何使用? 2.项目小结上车出发。
0. 一个神奇的相似搜索工具
Faiss:集众多算法于一身的相似搜索工具箱 Github Star:12.8K 特点:算法丰富,安全可靠,底层C++实现,提供完整Python接口;部分算法支持GPU加速 项目连接:
https://github.com/facebookresearch/faiss 使用文档: https://faiss.ai/
FaceBook在推荐新闻和用户时,使用了自家研究院开发的Faiss库。Faiss是一个高效的相似性搜索(KNN)和密集向量聚类库,包含的算法可以在任意大小的向量集合中进行搜索。官方推荐使用anaconda包和conda指令安装Faiss:
# CPU-only version $ conda install -c pytorch faiss-cpu # GPU(+CPU) version $ conda install -c pytorch faiss-gpu
测试后发现国内会有网络不通畅的问题,易安装失败。这里我推荐另一种方法,使用pip和国内镜像,约1分钟搞定。
# CPU-only version
$ pip install faiss-cpu -i https://pypi.douban.com/simple
# GPU(+CPU) version
$ pip install faiss-gpu -i https://pypi.douban.com/simple
1. Faiss使用教程
Faiss构建索引一般分为基于原始数据构建全量索引,以及加入新的数据构建增量索引。"Train"在某些算法中可以省略,“Add”是必须的。
下面,我以线性搜索和乘积量化算法为例,介绍Faiss的上手方法。
IndexFlatl2:暴力搜索
最简单的方法是基于欧式距离度量的暴力搜索法。首先,指定特征向量的维度d,通过词向量或预训练语言模型获取原始文本的语义特征。随后,构建 IndexFlatl2 索引对象,并将文本特征加入到索引。属性“ntotal”,可以查看当前索引中到数据规模。
import faiss d = 768 # embedding 维度 # 构建 IndexFlatL2 索引,它是最简单的索引类型,只执行强力L2距离搜索 faiss_l2_index = faiss.IndexFlatL2(d) print(faiss_l2_index.is_trained) # True # 在索引中添加原始特征 # faq_texts_emb 是特征向量,维度 [6417, 768] faiss_l2_index.add(faq_texts_emb) print(faiss_l2_index.ntotal) # 查看数据规模:6417
下面开始KNN搜索流程。指定需返回的最相似数据量k,假设现在用户输入了新问题q,通过编码器获得特征向量后,直接调用faiss索引,可以得到原始数据中和q最相似的k个问题,以及相应的空间距离。
# 查找 k 近邻 k = 3 q = '我咋没收到交费的通知呢?' q_emb = bc.encode([q]) # 获得特征 # dis_res:距离矩阵,由小到大排序 # idx_res:数据索引id dis_res, idx_res = faiss_l2_index.search(q_emb, k) print(idx_res.shape, dis_res.shape) # [1,3], [1,3]
为了检验结果是否正确,可以打印变量的值看看:
print(idx_res) # [[107, 111, 75]] print(dis_res) # [[18.602684 20.919521 21.184694]] # labels[i] 先映射到训练集文本对应的真实意图 label_id,再转换为 label 标签 print(list(id2name[labels[i]] for i in idx_res[0])) """['未收到催费短信', '未收到催费短信', '未收到催费短信']"""
查找的topk标准问题都是“未收到催费短信”,和真实提问“我咋没收到交费的通知呢”一致。
IndexPQ:乘积量化
Faiss调用的套路基本是类似的。再来看另一种更高效的搜索方法,乘积量化。所谓量化,就是将连续的空间离散化,目的是为了优化距离计算的速度。对原理感兴趣对小伙伴,可以阅读参考文献[1],之后小徐会单独开一篇文章介绍,欢迎关注!这里先介绍调用方法。乘积量化有一个聚类过程,所以需要显式调用索引对象的“train”方法,再添加向量文件。预测过程,和“暴力搜索”类似。
# m: 特征拆分子空间的数量 m, d = 8, 768 # 每一个子空间分配的bit大小 n_bits = 8 # 创建乘积量化索引 faiss_pq_index = faiss.IndexPQ(d, m, n_bits) # train 阶段 faiss_pq_index.train(faq_texts_emb) # add 阶段 faiss_pq_index.add(faq_texts_emb) k = 3 q = '我妹夫家的租户不交电费,这样子会影响我妹夫的信用不?' q_emb = bc.encode([q]) dis_res, idx_res = faiss_pq_index.search(q_emb, k) print(idx_res.shape, dis_res.shape) # [1,3], [1,3] print(idx_res) # [[2563 2575 2547]] print(dis_res) # [[11.660806 11.660806 11.660806]] print(list(id2name[labels[i]] for i in idx_res[0])) """['租户不交电费是否影响户主信用', '租户不交电费是否影响户主信用', '租户不交电费是否影响户主信用']"""
最终结果和预期一致。Faiss中还有非常多好用高效的相似搜索方法,例如点积计算IndexFlatIP、改进版乘积量化、加入倒排索引的量化搜索“IndexIVFPQ”等等,更多🌰可以查看Github上的tutorial文件夹。
对源码感兴趣又有C++基础的同学,可以从 AutoTone.cpp 文件开始,阅读源码。
2. Faiss 项目总结
回顾Faiss框架,这是一个用于相似检索的算法集大成者。检索目标不仅仅是文本,还包括图片、音频、视频等等多模态形式,只要能将目标转换为特征向量。除了开源项目,作者还提供了完整的文档体系[2],方便大家快速上手。
如果你的实验或项目中涉及相似匹配任务,不妨尝试一下Faiss框架,毕竟FaceBook自家产品都在用,质量还是有保障滴。如果通过调用接口,顺便弄懂了算法原理,恭喜你,在检索匹配领域你离成为巨佬又近了一步!