简单测试下faiss 检索速度

在NLP的应用中,经常需要用到对向量的搜索,如果向量的数量级非常大,比如1千万,甚至上亿条,普通的方式就满足不了生产需要了,falcebook开源的faiss框架能够解决“海量向量搜索”的问题。faiss是为稠密向量提供高效相似度搜索和聚类的框架。由Facebook AI Research研发。 具有以下特性。

  • 1、提供多种检索方法
  • 2、速度快
  • 3、可存在内存和磁盘中
  • 4、C++实现,提供Python封装调用。
  • 5、大部分算法支持GPU实现

GitHub - xmxoxo/faiss_test: faiss packaging and faiss speed test

参考了这个项目里做一个对比实验,来看一下faiss的使用及速度。 随机生成10万个768维的向量,加载到内存中, 然后分别用普通的暴力搜索和faiss搜索两种方式去搜索,对比搜索的平时用时。

按照相同规则生成随机向量,一般句子向量维度是768

# 随机生成向量 1百万   # total = 1000000
# 向量的维度   # dim = 1024 #768
print('随机生成%d个向量,维度:%d' % (total, dim), flush=True)
#rng = np.random.RandomState(0)
#X = rng.random_sample((total, dim))
X = np.random.random((total, dim))

以下是对每一部分的详细分析:

创建 VecSearch 实例

vs = VecSearch(dim=dim, gpu=gpu)
  • VecSearch: 类的实例化,dim 是向量的维度,gpu 指定是否使用GPU。
  • 这一步会初始化FAISS索引并配置GPU资源(如果指定了)。
class VecSearch:def __init__(self):self.dicts = {}# 返回当前总共有多少个值def curr_items ():return len(self.dicts)# 添加文档def add_doc (self, key, vector):self.dicts[key] = vector# 查找向量,# 返回结果为 距离[D], 索引[I]def search(self, query, top=5):# 返回结果,结构为:[sim, key]ret = np.zeros((top,2))# 计算余弦相似度最大值for key, value in self.dicts.items():sim = CosSim_dot(query, value)#sim = CosSim(query, value)#sim = CosSim_sk(query, value)#sim = cosine(query, value)#print(sim)if sim > ret[top-1][0]:b = np.array([[sim, key]]).astype('float32')ret = np.insert(ret, 0, values=b, axis=0)# 重新排序后截取idex = np.lexsort([-1*ret[:,0]])ret = ret[idex, :]ret = ret[:top,]#print(ret)#print('-'*40)return ret[:,0], ret[:,1].astype('int')

上面search 方法: 用于查找与给定查询向量 query 最相似的文档向量,返回前 top 个最相似的结果。ret 为一个形状为 (top, 2) 的数组,用于存储相似度和对应的文档索引。

遍历存储的向量: 对字典中的每个文档向量,计算与查询向量的余弦相似度 sim。可以选择不同的相似度计算方法(如 CosSim, cosine 等),但此处使用的是 CosSim_dot

更新相似度结果: 如果当前相似度 sim 大于 ret 中最小的相似度,则将其插入到 ret 的开头。

排序和截取: 使用 np.lexsort 按相似度对 ret 进行排序,并截取前 top 个结果。

ret[:, 0], ret[:, 1].astype('int')

返回相似度和对应索引: 方法返回两个数组,分别是相似度和文档的索引(转换为整数)。

添加向量

ret = vs.add(X)
  • add(X): 将生成的向量 X 添加到 VecSearch 实例中。
  • X 是一个形状为 (N, dim) 的数组,其中 N 是向量的数量。
  • 返回值 ret 是一个元组,包含添加前后的索引范围,例如 (起始索引, 结束索引)

训练和索引

vs.reindex()
  • reindex(): 训练FAISS索引并将数据添加到索引中。
  • 此步骤是必要的,因为在向量添加后,FAISS需要训练索引以便能够有效执行后续的搜索操作。

计算创建时间

end = time.time()
total_time = end - start
print('创建用时:%4f秒' % total_time)
  • 这里通过记录结束时间 end 和开始时间 start 的差值来计算创建索引和添加向量所花费的时间。
  • 使用 print 输出创建索引的总时间,格式化为小数点后四位。

查看内存使用情况

import os, psutil
process = psutil.Process(os.getpid())
print('Used Memory:', process.memory_info().rss / 1024 / 1024, 'MB')
  • psutil: 用于获取系统和进程信息的库。
  • os.getpid() 获取当前进程的ID。
  • process.memory_info().rss 返回进程当前使用的物理内存(RSS: Resident Set Size)。
  • 将内存使用量从字节转换为MB,并打印出来。

获取当前进程的内存使用情况

process = psutil.Process(os.getpid())
print('Used Memory:', process.memory_info().rss / 1024 / 1024, 'MB')
  • 这段代码使用 psutil 库来获取当前运行进程的内存使用情况。
  • os.getpid() 获取当前Python程序的进程ID。
  • process.memory_info().rss 获取该进程的常驻集大小(RSS),即当前使用的物理内存量(以字节为单位)。
  • 将字节转换为MB(通过除以1024两次),并打印出来,便于查看内存占用情况。

单条查询测试的开始

print('单条查询测试'.center(40,'-'))
  • 打印一行文本,中心对齐并用 - 符号填充,便于在输出中分隔不同的测试部分。

生成查询向量

Q = np.random.random((test_times, dim))
Q[:, 0] += np.arange(test_times) / test_times
  • 生成一个形状为 (test_times, dim) 的随机数组 Q,其中 test_times 是测试的次数,dim 是向量的维度。
  • 通过 Q[:, 0] += np.arange(test_times) / test_times,对第一列进行线性调整,使得查询向量 Q 的第一维数据呈现一定的趋势。这有助于在搜索时产生更具代表性的查询结果。

执行单条查询

q = Q[0]
start = time.time()
D, I = vs.search(q, top=top_n, nprobe=10)
  • q = Q[0]: 选择生成的查询向量中的第一条作为单条查询。
  • start = time.time(): 记录查询开始的时间,用于后续计算查询所花费的时间。
  • D, I = vs.search(q, top=top_n, nprobe=10): 使用 VecSearch 实例 vs 执行搜索:
    • q 是要查询的向量。
    • top=top_n 指定返回的最相似向量的数量。
    • nprobe=10 指定在查询时要探测的聚类中心的数量,这会影响查询的准确性和速度。

测试下暴力查询 的检索速度

[root@node126 embeding]# /opt/miniconda3/envs/rag/bin/python vector_search_force.py
===========大批量向量余弦相似度计算-[暴力版]===========
随机生成100000个向量,维度:768
正在创建搜索器...
添加用时:0.034196秒
Used Memory: 682.5859375 MB
-----------------单条查询测试-----------------
搜索结果: [0.78086126 0.77952558 0.77949381 0.77775592 0.77547914] [ 541  443 1472  370  209]
显示查询结果,并验证余弦相似度...
索引号:  541, 距离:0.780861
索引号:  443, 距离:0.779526
索引号: 1472, 距离:0.779494
索引号:  370, 距离:0.777756
索引号:  209, 距离:0.775479
-----------------批量查询测试-----------------
批量测试次数:100 次,请稍候...
总用时:59 秒, 平均用时:590.072079 毫秒

分析下代码项目faiss检索代码

使用faiss 实现VecSearch

class VecSearch:def __init__(self, dim=10, nlist=100, gpu=-1):self.dim = dimself.nlist = nlist                      #聚类中心的个数#self.index = faiss.IndexFlatL2(dim)    # build the indexquantizer = faiss.IndexFlatL2(dim)      # the other index# faiss.METRIC_L2: faiss定义了两种衡量相似度的方法(metrics),# 分别为faiss.METRIC_L2 欧式距离、 faiss.METRIC_INNER_PRODUCT 向量内积# here we specify METRIC_L2, by default it performs inner-product searchself.index = faiss.IndexIVFFlat(quantizer, dim, self.nlist, faiss.METRIC_L2)try:if gpu>=0:if gpu==0:# use a single GPUres = faiss.StandardGpuResources()  gpu_index = faiss.index_cpu_to_gpu(res, 0, self.index)else:gpu_index = faiss.index_cpu_to_all_gpus(self.index)self.index = gpu_indexexcept :pass# data self.xb = None# 返回当前总共有多少个值def curr_items ():# self.index.ntotalreturn self.xb.shape[0]# 清空数据def reset (self):passself.xb = None# 添加向量,可批量添加,编号是按添加的顺序;# 参数: vector, 大小是(N, dim)# 返回结果:索引号区间, 例如 (0,8), (20,100)def add (self, vector):if not vector.dtype == 'float32':vector = vector.astype('float32')if self.xb is None:prepos = 0# vector = vector[np.newaxis, :]   self.xb = vector.copy()else:prepos = self.xb.shape[0]self.xb = np.vstack((self.xb,q))return (prepos, self.xb.shape[0]-1)# 添加后开始训练def reindex(self):self.index.train(self.xb)self.index.add(self.xb)                  # add may be a bit slower as well# 查找向量, 可以批量查找,# 参数:query (N,dim)# 返回: 距离D,索引号I  两个矩阵def search(self, query, top=5, nprobe=1):# 查找聚类中心的个数,默认为1个。self.index.nprobe = nprobe #self.nlist # 如果是单条查询,把向量处理成二维 #print(query.shape)if len(query.shape)==1:query = query[np.newaxis, :]#print(query.shape)# 查询if not query.dtype == 'float32':query = query.astype('float32')D, I = self.index.search(query, top)     # actual searchreturn D, I
  • FAISS索引:

    • 使用 faiss.IndexFlatL2 创建量化器。L2距离用于计算向量之间的距离。
    • faiss.IndexIVFFlat 创建用于高效检索的IVF索引。

向量搜索 search

def search(self, query, top=5, nprobe=1):self.index.nprobe = nprobe  # 设置要探测的聚类中心数量if len(query.shape) == 1:query = query[np.newaxis, :]  # 确保查询是二维的if not query.dtype == 'float32':query = query.astype('float32')D, I = self.index.search(query, top)  # 执行搜索return D, I
  • 功能: 执行向量搜索,返回最相似的向量。

  • 参数:

    • query: 要搜索的向量。
    • top: 返回最相似的向量数量(默认5)。
    • nprobe: 要探测的聚类中心数量(默认1)。
  • 过程:

    • 设置 self.index.nprobe 用于搜索时的聚类中心数量。
    • 检查查询向量的维度,确保其为二维(batch size, dim)。
    • 确保查询向量为 float32 类型。
    • 调用 self.index.search(query, top) 执行搜索,返回距离 D 和索引 I

代码中使用到的几个方法

1. seg_vector 函数

def seg_vector(txt, dict_vector, emb_size=768):seg_v = np.zeros(emb_size)for w in txt:if w in dict_vector.keys():v = dict_vector[w]seg_v += vreturn seg_v
  • 功能: 将输入的文本 txt 转换为一个句向量。这个句向量是通过对文本中单词的向量进行简单相加得到的。

  • 参数:

    • txt: 输入的文本,可以是一个单词的列表或字符串。
    • dict_vector: 一个字典,映射单词到其对应的向量(通常是预训练的词向量)。
    • emb_size: 向量的维度,默认为768。
  • 过程:

    • seg_v = np.zeros(emb_size): 创建一个全零的向量,长度为 emb_size,用于存储句向量。
    • for w in txt: 遍历文本中的每个单词 w
    • if w in dict_vector.keys(): 检查单词 w 是否在字典中。
    • v = dict_vector[w]: 如果在,获取该单词对应的向量 v
    • seg_v += v: 将单词向量 v 加到句向量 seg_v 上。
  • 返回: 最终返回的 seg_v 是文本 txt 的句向量。

2. CosSim 函数

def CosSim(a, b):return 1 - cosine(a, b)
  • 功能: 计算两个向量 ab 之间的余弦相似度。

  • 过程:

    • 使用 scipy.spatial.distance 中的 cosine 函数来计算余弦距离(即1减去余弦相似度)。
    • 余弦相似度的值范围在[-1, 1]之间,通常在实际应用中我们更关注0到1之间的值,所以通过 1 - cosine(a, b) 转换为相似度。
  • 返回: 返回 ab 之间的余弦相似度,值越接近1,表示相似度越高。

3. CosSim_dot 函数

def CosSim_dot(a, b):score = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))return score
  • 功能: 计算两个向量 ab 的余弦相似度,使用内积和范数。

  • 过程:

    • np.dot(a, b): 计算向量 ab 的内积。
    • np.linalg.norm(a): 计算向量 a 的范数(即长度)。
    • np.linalg.norm(b): 计算向量 b 的范数。
    • score = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)): 使用内积和向量的范数计算余弦相似度。
  • 返回: 返回计算得到的余弦相似度,值范围也是[-1, 1]。

测试下faiss 的检索速度

[root@node126 embeding]# /opt/miniconda3/envs/rag/bin/python vector_search_faiss.py
=========大批量向量余弦相似度计算-[faiss版]==========
随机生成100000个向量,维度:768
正在创建搜索器...
GPU使用情况:不使用
创建用时:5.961992秒
Used Memory: 1613.765625 MB
-----------------单条查询测试-----------------
显示查询结果,并验证余弦相似度...
索引号: 1058, 距离:114.878304, 余弦相似度:0.771127
索引号:  541, 距离:115.051338, 余弦相似度:0.780861
索引号:  370, 距离:115.715919, 余弦相似度:0.777756
索引号:  209, 距离:115.731323, 余弦相似度:0.775479
索引号: 1472, 距离:115.832909, 余弦相似度:0.779494
总用时:10毫秒
-----------------批量查询测试-----------------
正在批量测试10000次,每次返回Top 5,请稍候...
总用时:13576毫秒, 平均用时:1.357649毫秒
----------------------------------------

看到搜索速度 13576毫秒 远远优于 59 秒

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/61840.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

milvus es

ES 与 Milvus 结合实现高效文档搜索的实战指南 原文链接 目录 背景介绍场景与效果概述架构对比与问题分析Milvus 向量搜索架构ES Milvus 搜索架构详细流程解析Milvus 搜索配置详解ES 搜索策略与 DSL 配置结果合并与排序策略总结与未来优化 1. 背景介绍 随着团队和公司的发…

Flutter 设计模式全面解析:抽象工厂

设计模式作为软件开发中的经典解决方案,在 Flutter 的开发中也能为我们提供强大的架构支持。本文来介绍一下如何在 Flutter 中来实现抽象工厂设计模式,以及如何创建一系列相关或依赖对象并优雅地管理它们之间的复杂依赖关系。 日常开发中我们也能经常看…

AWS IAM 及其功能

IAM 代表身份和访问管理,可帮助控制谁可以进入云、访问 AWS 资源以及进入后可以做什么。 身份: IAM 帮助管理可以与 AWS 资源交互的身份(如用户名或服务帐户)。 访问:它决定每个身份可以在 AWS 服务上执行哪些操作&am…

SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传

总体概述 Spring Boot整合Minio后,前端的文件上传有两种方式: 1.文件上传到后端,由后端保存到Minio 这种方式好处是完全由后端集中管理,可以很好的做到、身份验证、权限控制、文件与处理等,并且可以做一些额外的业务逻…

『 Linux 』网络层 - IP协议 (二)

文章目录 路由NAT技术分片与组装分片的组装IP协议分片的短板 路由 通常情况路由器具备了一个非常重要的功能,即构建子网; 同时路由器需要实现跨网络通信,说明路由器必须存在两个或以上的IP地址,通常在路由器中可以看到几个接口,分别是一个WAN口和几个LAN口; WAN口IP被称为公网I…

深度学习实战图像缺陷修复

这里写目录标题 概述1. 图像缺陷修复的研究背景2. 传统图像缺陷修复方法的局限性(1) 基于纹理合成的方法(2) 基于偏微分方程(PDE)的方法 3. 深度学习在图像缺陷修复中的兴起(1) 深度学习的基本思路(2) 深度学习方法的优势(3) 关键技术的引入 4. 深度学习…

vue从入门到精通(七):事件处理

1,事件的基本使用 1.使用v-on:xxx或 xxx 绑定事件,其中xxx是事件名2.事件的回调需要配置在methods对象中,最终会在vm上3.methods中配置的所数,不要用箭头函数!否则this就不是vm了4.methods中配置的函数,都是被Vue所管…

如何在 .gitignore 中仅保留特定文件:以忽略文件夹中的所有文件为例

在日常的开发工作中,使用 Git 来管理项目是不可或缺的一部分。项目中的某些文件夹可能包含大量的临时文件、生成文件或不需要版本控制的文件。在这种情况下,我们通常会使用 .gitignore 文件来忽略这些文件夹。然而,有时我们可能希望在忽略整个…

【SQL实验】索引操作(菜单操作和命令操作)

【代码是自己的解答,并非标准答案,也有可能写错,文中可能会有不准确或待完善之处,恳请各位读者不吝批评指正,共同促进学习交流】 文件”成绩管理”导入【具体操作前几篇文章详细展示过来,这里跳过。还是不太…

MVC 模型:架构与原理

MVC 模型:架构与原理 MVC(Model-View-Controller)模型是一种广泛应用于软件工程的架构模式,主要用于分离应用程序的逻辑层,以提高其可维护性和可扩展性。MVC模型将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。本文将深入探讨MVC模型的…

[pdf,epub]162页《分析模式》漫谈合集01-35提供下载

《分析模式》漫谈合集01-35的pdf、epub文件,已上传至本号的CSDN资源。 如果CSDN资源下载有问题,可到umlchina.com/url/ap.html。 已排版成适合手机阅读,pdf的排版更好一些。 ★UMLChina为什么叒要翻译《分析模式》? ★[缝合故事…

【Linux学习】【Ubuntu入门】1-7 ubuntu下磁盘管理

1.准备一个U盘或者SD卡(插上读卡器),将U盘插入主机电脑,右键点击属性,查看U盘的文件系统确保是FAT32格式 2.右键单击ubuntu右下角图标,将U盘与虚拟机连接 参考链接 3. Ubuntu磁盘文件:/dev/s…

Leetcode152. 乘积最大子数组(HOT100)

链接 代码&#xff1a; class Solution { public:int maxProduct(vector<int>& nums) {int f nums[0],g nums[0];int res nums[0];for(int i 1;i<nums.size();i){//int i 1 not int i 0 ,因为我们已经初始化好了首元素作为子数组的最大值和最小值int a n…

移远通信推出全新5G RedCap模组RG255AA系列,以更高性价比加速5G轻量化大规模商用

11月20&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;正式推出其全新5G RedCap模组RG255AA系列。该系列模组支持5G NR独立组网&#xff08;SA&#xff09;和LTE Cat 4双模通信&#xff0c;具有高性能高集成度、低功耗、小尺寸、高性价比等优势&#…

【CVE-2024-9413】SCP-Firmware漏洞:安全通告

安全之安全(security)博客目录导读 目录 一、概述 二、修订历史 三、CVE根因分析 四、问题修复解决 一、概述 在SCP固件中发现了一个漏洞,如果利用该漏洞,可能会允许应用处理器(AP)在系统控制处理器(SCP)固件中导致缓冲区溢出。 CVE IDCVE-2024-9413受影响的产品SC…

数据集-目标检测系列- 花卉 玫瑰 检测数据集 rose >> DataBall

数据集-目标检测系列- 花卉 玫瑰 检测数据集 rose >> DataBall DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 贵在坚持&#xff01; 数据样例项目地址&#xff1a; * 相关项目 1&#xff09;数据集可视化项…

详解Qt QTimeZone 时区类

文章目录 QTimeZone 详解前言什么是 QTimeZone&#xff1f;QTimeZone 的构造函数和常用成员函数构造函数1. 默认构造函数2. 指定时区 ID 构造函数3. 根据 UTC 偏移量构造 常用成员函数1. 获取时区 IDid 2. 检查时区是否有效isValid 3. 获取 UTC 偏移量offsetFromUtc 4. 检查是否…

Linux应用编程(C语言编译过程)

目录 1. 举例 2.预处理 2.1 预处理命令 2.2 .i文件内容解读 3.编译 4.汇编 5.链接 5.1 链接方式 5.1.1 静态链接 5.1.2 动态链接 5.1.3 混合链接 1. 举例 Linux的C语言开发&#xff0c;一般选择GCC工具链进行编译&#xff0c;通过下面的例子来演示GCC如何使用&#…

GitHub 开源项目 Puter :云端互联操作系统

每天面对着各种云盘和在线应用&#xff0c;我们常常会遇到这样的困扰。 文件分散在不同平台很难统一管理&#xff0c;付费订阅的软件越来越多&#xff0c;更不用说那些烦人的存储空间限制了。 最近在 GitHub 上发现的一个开源项目 Puter 彻底改变了我的在线办公方式。 让人惊…

Python 使用 OpenCV 将 MP4 转换为 GIF图

以下是使用 Python 和 OpenCV 将 MP4 转换为 GIF 的示例代码&#xff1a; python import cv2 import imageiodef mp4_to_gif(mp4_path, gif_path, fps10, start_timeNone, end_timeNone):"""将MP4视频转换为GIF动图。:param mp4_path: 输入MP4视频的路径。:pa…