掘金 人工智能 07月16日 17:53
RAG 每日一技(五):大海捞针第一步,亲手构建你的向量索引!
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了在RAG系统中,如何利用向量索引解决大规模向量搜索的效率问题。文章首先指出了暴力搜索的局限性,即在处理百万级向量时,其延迟无法满足实时交互的需求。随后,介绍了向量索引的概念,它通过预处理和空间划分,实现了高效的近似最近邻搜索(ANN)。文章以FAISS为例,详细阐述了构建向量索引的流程,包括数据准备、索引类型选择、训练、向量添加和搜索。最后,文章还提到了IndexIVFFlat的nprobe参数,以及向量索引的持久化和向量数据库的重要性,为读者后续深入理解向量数据库埋下了伏笔。

💡 向量索引是一种加速相似度搜索的数据结构,它通过预处理和空间划分,避免了全局扫描,从而提高搜索效率。

📚 向量索引的核心思想类似于图书馆的图书目录,将相似的向量“分门别类”地放在一起,构建高效的查找路径,实现快速检索。

🛠️ FAISS(Facebook AI Similarity Search)是高效相似度搜索和海量向量聚类的常用工具,是构建向量索引的“王者”。

⚙️ 构建向量索引的基本流程包括:准备数据、选择索引类型、训练索引、添加向量和搜索。

🔍 IndexIVFFlat是一种常用的索引类型,通过聚类实现加速,其nprobe参数可以用来平衡搜索速度和精度。

前情回顾

上一篇文章 中,我们成功将文本转化为了包含语义信息的向量,并学会了用余弦相似度来计算它们之间的“关系远近”。我们RAG系统的核心部件已经初具雏形。

但一个尖锐的工程问题也随之而来:

假设我们的知识库有100万个文本块(Chunks),那就是100万个向量。当用户提问时,我们要把问题的向量,和这100万个向量逐一计算相似度,再排序找到最相似的几个。

我们来简单算一下:即便每次计算和比较只需要1微秒(百万分之一秒),完成一次完整的搜索也需要整整 1秒!对于一个需要实时交互的应用来说,这个延迟是绝对无法接受的。

这就是暴力搜索(Brute-force Search)的局限。我们需要一个更聪明的办法,这便是向量索引(Vector Index)

什么是向量索引?

向量索引是一种为了加速相似度搜索而对向量集合进行预处理的数据结构。

它的核心思想是:避免全局扫描,通过某种方式将向量空间划分成多个区域,搜索时只需访问可能包含查询结果的少数几个区域即可。

这个概念最好的类比就是图书馆的图书目录

向量索引就是我们向量世界的“图书目录”。它通过聚类等算法,提前将相似的向量“分门别类”地放在一起,构建起高效的查找路径。

见面实战:FAISS

要构建向量索引,我们得请出该领域的“王者”——FAISS (Facebook AI Similarity Search)。这是由Meta(原Facebook)AI团队开源的、用于高效相似度搜索和海量向量聚类的库,是目前工业界应用最广泛的工具之一。

首先,安装FAISS。我们先从CPU版本开始,它最容易安装:

pip install faiss-cpu numpy

注:如果你有支持CUDA的NVIDIA显卡,可以挑战安装 faiss-gpu 版本,速度会更快。

四步构建你的第一个向量索引

我们将通过一个完整的流程,亲手构建、训练并使用一个向量索引。

第0步:准备数据

为了模拟真实场景,我们用 numpy 来生成一个包含10万个768维向量的随机数据集(模拟上一篇中 m3e-base 模型的输出)。

import numpy as np# 设置向量维度d = 768# 设置数据集大小nb = 100000# 生成随机的向量数据集作为我们的知识库np.random.seed(1234) # 保证每次生成的数据一致xb = np.random.random((nb, d)).astype('float32')# 生成1个随机的查询向量xq = np.random.random((1, d)).astype('float32')
第1步:选择索引类型(“目录的样式”)

FAISS提供了丰富的索引类型,今天我们学习最常用的之一:IndexIVFFlat。IVF是“Inverted File”的缩写,它的原理就是我们上面提到的聚类。它会先把10万个向量分成nlist个簇(cluster),每个簇有一个中心点(centroid)。

创建它需要两个参数:

    quantizer:量化器,其实就是另一个“底层”的索引,用来对簇的中心点进行索引。我们这里用最简单的 IndexFlatL2(L2距离,即欧式距离的暴力搜索)。nlist:簇的数量。一个经验法则是设为数据集大小的平方根附近,比如sqrt(100000)约等于316,我们取一个接近的整数,比如256。
import faissnlist = 256  # 簇的数量quantizer = faiss.IndexFlatL2(d)  # 底层使用L2距离的暴力索引index = faiss.IndexIVFFlat(quantizer, d, nlist)
第2步:训练索引(“学习如何分类”)

IndexIVFFlat 需要“学习”我们数据的分布,才能有效地划分簇。这个过程就是train

print(index.is_trained)  # False,此时索引还未训练index.train(xb)print(index.is_trained)  # True,训练完成
第3步:添加向量(“将书上架”)

训练好索引后,我们就可以把全部的向量数据批量添加到索引中。

index.add(xb)print(f"索引中向量的总数: {index.ntotal}") # 100000
第4步:搜索!(“按图索骥”)

万事俱备,只欠搜索!search 方法接收两个参数:

    xq:查询向量。k:希望返回的最相似结果的数量。
k = 5  # 我们想找最相似的5个# D是距离,I是索引(在原始数据集xb中的位置)D, I = index.search(xq, k)print("查询结果的索引:", I)print("查询结果的距离:", D)

FAISS瞬间就返回了最接近查询向量的5个向量的索引位置和它们的距离!

提速的关键IndexIVFFlat 还有一个重要的参数 nprobe,它表示在搜索时,我们希望访问多少个簇(默认是1)。nprobe 越大,结果越精确,但速度越慢。这是一个速度与精度的权衡,也是近似最近邻搜索(ANN)的精髓所在。

# 增加nprobe可以提高精度,但会牺牲速度index.nprobe = 10 D, I = index.search(xq, k) print("设置nprobe=10后的索引:", I)

总结与预告

今日小结:

    面对海量向量,暴力搜索效率低下,无法满足应用需求。向量索引(如FAISS)通过预处理和划分空间,实现高效的近似最近邻搜索(ANN)。构建索引的基本流程是:准备数据 -> 选择索引类型 -> 训练索引 -> 添加向量 -> 搜索IndexIVFFlat 是一种常用的索引,它通过聚类实现加速,其 nprobe 参数可以用来平衡搜索速度和精度。

我们今天用FAISS在内存中成功构建了索引,解决了“搜得慢”的问题。但是,新的问题又来了:

    这个索引是临时的,程序一关,就没了!怎么持久化?我们只存了向量,那每个向量对应的原始文本块存在哪?怎么关联起来?每次启动都要重新加载100万个向量到内存,太麻烦了!

这些问题,正是专业的**向量数据库(Vector Database)**要解决的。

明天预告:RAG 每日一技(六):别再手动管理了!认识你的第一个向量数据库

明天,我们将上手一个轻量、好用的向量数据库(如ChromaDB),看看它是如何将向量存储、索引、元数据管理等工作“一条龙”搞定的,彻底解放我们的双手!

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

向量索引 FAISS RAG 向量数据库
相关文章