RAG 每日一技(五):大海捞针第一步,亲手构建你的向量索引!

前情回顾

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

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

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

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

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

什么是向量索引?

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

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

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

  • 没有索引(暴力搜索):为了找一本关于"人工智能"的书,你把图书馆里成千上万本书一本一本地抽出来看书名和内容。
  • 有索引(索引搜索):你直接走到计算机目录系统前,输入"人工智能",系统瞬间告诉你,这类书在"TP18"号书架上。你只需要去这一个书架寻找,效率天差地别。

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

见面实战:FAISS

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

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

bash 复制代码
pip install faiss-cpu numpy

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

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

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

第0步:准备数据

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

python 复制代码
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)。

创建它需要两个参数:

  1. quantizer:量化器,其实就是另一个"底层"的索引,用来对簇的中心点进行索引。我们这里用最简单的 IndexFlatL2(L2距离,即欧式距离的暴力搜索)。
  2. nlist:簇的数量。一个经验法则是设为数据集大小的平方根附近,比如sqrt(100000)约等于316,我们取一个接近的整数,比如256。
python 复制代码
import faiss

nlist = 256  # 簇的数量
quantizer = faiss.IndexFlatL2(d)  # 底层使用L2距离的暴力索引
index = faiss.IndexIVFFlat(quantizer, d, nlist)
第2步:训练索引("学习如何分类")

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

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

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

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

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

  1. xq:查询向量。
  2. k:希望返回的最相似结果的数量。
python 复制代码
k = 5  # 我们想找最相似的5个

# D是距离,I是索引(在原始数据集xb中的位置)
D, I = index.search(xq, k)

print("查询结果的索引:", I)
print("查询结果的距离:", D)

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

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

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

总结与预告

今日小结:

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

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

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

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

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

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

相关推荐
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530141 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908401 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒2 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境2 小时前
提升 Python 代码健壮性的方法大盘点
后端·python