在之前的文章中,我们系统性的介绍了 嵌入和语义检索 以及 向量相似度
本文将在语义检索领域探讨更加接近实战的话题:如何如何选择FAISS的索引类型。
Faiss 是一个用于高效相似性搜索和密集向量聚类的库。它使用 C++ 编写,并提供了完整的 Python 封装。 可以在 CPU 上运行,只是一些最有用的算法是在 GPU 上实现的。它的主要功能是:

  • k-NN 检索: 不仅返回最近的邻居,还返回第二近、第三近、……、第 k 近的邻居;
  • 批量处理: 一次搜索多个向量,而不是一次搜索一个。 对于许多索引类型,这比一个接一个地搜索向量更快;
  • 以精度换取速度: 以 10% 的不正确结果为代价,换取 10 倍的速度或使用更少的内存;
  • 范围搜索:返回查询点给定半径内的所有元素;
  • 本地存储: 将索引存储在磁盘上而不是在 RAM 中;

FAISS索引概览

Faiss 支持多种相似度度量方式,包括欧氏距离(L2距离)、内积(余弦相似度)、汉明距离等,以适应不同应用场景的需求。
Faiss 提供了众多类型的索引,大体上可以分为两大家族:二进制索引/Binary 索引/Binary Indexes(不常用) 和 实值索引(常用)。
Faiss 的核心在于其高效的索引结构和搜索算法,常用的有以下几类:

从索引结构上看


  • 平面索引(Flat,方法名:IndexFlatL2/IP):最简单的索引结构,它不修改输入的向量,将所有向量存储在一起。搜索时需遍历整个数据集,计算查询向量与每个数据向量的相似度。
  • 倒排文件索引(Inverted File Index,缩写:IVF,方法名:IndexIVF*):基于聚类的思想,先将数据集划分为多个子集。搜索时先找到最相关的几个子集(近似搜索),再在子集中精确搜索。

    为什么要倒排
    正排 :应该是 “向量→所属聚类”,即每个向量记录自己属于哪个分区(以向量为中心); 倒排(IVF 的实际做法):是 “聚类→向量列表”,即每个聚类中心记录属于该分区的所有向量,形成 “倒排表(Inverted List)”。
    在向量检索中,若用 正排 逻辑(每个向量记录自己的分区),查询时需要遍历所有向量,检查其所属分区是否匹配,效率极低(O(N))。而 倒排 逻辑通过 聚类→向量列表 的映射,能直接定位到候选分区的向量集合,只需在小范围内搜索(O(M),M 远小于 N),这正是 “倒排” 带来的核心效率提升。

  • 基于图的索引(Hierarchical Navigable Small World,缩写:HNSW,方法名:IndexHNSW*):基于图的近似最近邻搜索算法,构建多层图结构,每一层节点代表一个向量,节点间边代表相似度。搜索时通过层次跳跃快速缩小搜索范围,最终找到近似最近邻。

    HNSW(Hierarchical Navigable Small World)的本质是给向量构建一张多层级的 “导航图”,上层图是 “高速路”(少节点、跳得远),下层图是 “街道”(多节点、走得细)。即:上层快速缩小范围,下层精准定位目标

  • 乘积量化索引(Product Quantization):将高维向量拆分成多个低维子向量,分别量化(压缩)后存储,大幅降低内存占用并加速距离计算。

    单独使用较少,多与 IVF(倒排文件)结合为IndexIVFPQ(先分区再量化),平衡速度、内存与精度。

从搜索算法角度看


  • 精确索引 (Exact Index) : 遍历所有向量,保证返回最精确的近邻结果。IndexFlatIPIndexFlatL2 就属于精确索引。虽然结果精确,但当数据集非常大时,搜索速度会比较慢。
  • 近似索引 (Approximate Index) : 通过牺牲一定的搜索精度来换取更快的搜索速度和更低的内存占用。例如 IndexIVFFlatIndexHNSWFlat。它们适用于需要处理大规模数据集,且对精度要求相对宽松的场景。

下图展示了 Faiss 索引家族的概况:

graph LR A[FAISS索引层级结构\nFAISS Index Hierarchy] --> B1[实值索引\nReal-valued Index Family] B1[实值索引\nReal-valued Index Family] --> C1[IndexFlatL2/IP] B1[实值索引\nReal-valued Index Family] --> C2[IndexIVF*] B1[实值索引\nReal-valued Index Family] --> C3[IndexHNSW*] B1[实值索引\nReal-valued Index Family] --> C4[...] A[FAISS索引层级结构\nFAISS Index Hierarchy] --> B2[二进制索引\nBinary Index Family] B2[二进制索引\nBinary Index Family] --> D1[IndexBinaryFlat] B2[二进制索引\nBinary Index Family] --> D2[IndexBinaryIVF] B2[二进制索引\nBinary Index Family] --> D3[IndexBinaryHNSW] B2[二进制索引\nBinary Index Family] --> D4[...]


二进制索引

二进制索引(Binary Indexes) 是 FAISS 的一个独立分支体系,它们与实值索引(Real-valued Index)共享一套 相同的上层接口,但底层存储和相似度计算方式完全不同。如下表:

家族类型 存储形式 距离度量 典型用途
实值索引(Real-valued Index) 向量为 float32(连续数值) 欧氏距离、内积、余弦相似度 语义检索、图像检索、文本嵌入
二进制索引(Binary Index) 向量为二进制码(0/1 比特) 汉明距离(Hamming Distance) 二值特征、指纹特征、压缩特征匹配

汉明距离(Hamming Distance)计算两个等长二进制字符串(或二进制向量)中,对应位置上 “0” 和 “1” 不同的位数,以此衡量两者的差异程度。
例如:

  • 字符串 A:101101
  • 字符串 B:111001
    逐位对比:第 2 位(0 vs 1)、第 4 位(1 vs 0)不同,共 2 个不同位 → 汉明距离 = 2。

二进制索引的核心思想

二进制索引 中,每个向量不是浮点数,而是一串二进制比特:

浮点向量(x): [0.1, 0.7, -0.3, -0.2, ...]
二值向量(b): 1100...

这些比特通常来自于浮点向量经过二值化(binarization)处理,例如使用下面的 Sign函数 即可将上面的 浮点向量(x) 转换为 二值向量(b):

$$ b_i = \begin{cases} 1 & \text{if } x_i \ge 0 \newline 0 & \text{otherwise} \end{cases} $$

然后通过汉明距离(Hamming distance)来比较相似度。


二进制索引的常见类型

索引类型 含义 特点
IndexBinaryFlat 精确汉明距离计算 IndexFlatL2 类似(精确搜索)
IndexBinaryIVF 倒排文件 + 汉明距离 IndexIVFFlat 类似(分区加速)
IndexBinaryHNSW 图索引 + 汉明距离 IndexHNSWFlat 类似(高效近似)

二进制索引与实值索引的关系

对比项 实值索引(Real-valued) 二进制索引(Binary)
向量类型 float32 uint8(bit-packed)
相似度度量 L2 / Inner Product / Cosine Hamming Distance
搜索精度 高(连续空间) 较低(离散空间)
存储占用 较大 极小(压缩比高)
搜索速度 较慢 极快(按位异或+位计数)
常见用途 语义检索、嵌入(Embedding) 二值哈希检索、图像指纹、局部特征

示例:两类索引的使用区别

import faiss
import numpy as np

# 浮点向量
xb = np.random.random((1000, 128)).astype('float32')
index_f = faiss.IndexFlatL2(128)
index_f.add(xb)

# 二值向量(假设已二值化)
xb_bin = np.random.randint(0, 2, (1000, 128)).astype('uint8')
index_b = faiss.IndexBinaryFlat(128)
index_b.add(xb_bin)

两者都可以 .add() / .search(), 但计算方式完全不同:

  • IndexFlatL2 使用欧氏距离
  • IndexBinaryFlat 使用汉明距离

二进制索引的优势与局限

优势 局限
极小存储(128维仅16字节) 相似度精度较低
查询极快(基于位运算) 无法直接处理浮点嵌入(Embedding)
适合硬件加速(GPU/FPGA) 不适合复杂语义向量空间

小结

二进制索引是一条并行的、针对二进制特征优化的索引体系,它的结构设计借鉴了实值索引,但使用汉明距离代替了浮点距离。
它不是“Flat/IVF/PQ”的子类,而是另一类以汉明距离为核心的索引体系,用于二值化特征的超高效相似度搜索。


实值索引

下面我们看看常用的 Faiss 实值索引。

1. IndexFlatIP (Flat Inner Product Index)

类型: 精确索引 (Exact Index)
度量: 内积 (Inner Product, IP)

工作原理: IndexFlatIP 是最基础的 Faiss 索引类型之一。它将所有向量 不做任何压缩或编码 直接存储。在搜索时,它会 暴力遍历 索引中的所有向量,计算每个向量与查询向量的内积,并返回内积值最高的 K 个向量作为最近邻。

优点:

  • 精度高: 由于是针对全部向量的暴力搜索,具有完美的召回率。
  • 实现简单: 理解和实现都非常直接。

缺点:

  • 速度慢: 当数据集非常大时,搜索速度会非常慢,不适合大规模数据集的实时应用。
  • 内存占用高: 需要存储所有原始向量,内存消耗大。

适用场景:

  • 小规模数据集: 当数据集规模较小时,例如几百万甚至更小,可以接受暴力搜索的耗时。
  • 对精度要求极高: 在一些对搜索结果精度要求非常高的场景下,例如需要作为评估其他近似索引方法精度的基准。
  • 原型验证和实验: 在算法原型开发阶段,可以使用 IndexFlatIP 来快速验证算法的正确性,然后再考虑更高效的索引方法。
index = faiss.IndexFlatIP(dimension)

2. IndexFlatL2 (Flat L2 Distance Index)

类型: 精确索引 (Exact Index)
度量: L2 距离 (欧氏距离, Euclidean Distance)

工作原理: 与 IndexFlatIP 类似,IndexFlatL2 也将所有向量原始地存储,并进行暴力搜索。不同之处在于,IndexFlatL2 计算的是 L2 距离 (欧氏距离),并返回 L2 距离最小的 K 个向量作为最近邻。

优点和缺点: 与 IndexFlatIP 类似,精度高,实现简单,但速度慢,内存占用高。

适用场景:

  • 小规模数据集,且需要基于 L2 距离 进行相似性搜索的场景。
  • 同样适用于精度要求高或作为基准测试的场景。
index = faiss.IndexFlatL2(dimension)

3. IndexIVFFlat (Inverted File Flat Index)

类型: 近似索引 (Approximate Index)
度量: 通常与 L2 距离或内积结合使用

工作原理: IndexIVFFlat 引入了 倒排文件 (Inverted File) 的思想来加速搜索。它首先将向量空间划分为多个单元 ,每个单元有一个 聚类中心。在索引构建阶段,每个向量会被分配到离它最近的聚类中心所代表的单元中。在搜索阶段,首先找到与查询向量最近的若干个单元,然后在这些单元内部进行暴力搜索 (Flat 搜索)。

优点:

  • 搜索速度快: 通过只在部分单元内搜索,大大减少了搜索范围,提高了搜索速度。
  • 可控的精度和速度平衡: 可以通过调整单元的数量 (nlist) 和搜索时访问的单元数量 (nprobe) 来平衡搜索精度和速度。

在FAISS的IVF(倒排文件)索引中,nlistnprobe是两个核心参数,直接决定了索引的空间分区粒度搜索范围,进而平衡搜索的速度与精度:

nlist:聚类中心的数量(空间分区数)

  • 作用:定义IVF索引将整个向量空间划分的聚类分区数量
    训练时,IVF会对向量进行聚类,生成nlist个聚类中心,每个中心对应一个“ Voronoi 胞腔”(分区),所有向量被分配到距离最近的分区中,形成nlist个倒排表。
  • 影响
    • nlist越大,每个分区包含的向量越少,理论上局部搜索速度越快,但聚类中心的存储和计算成本越高(训练时间更长)。
    • nlist需与数据量匹配(通常设为10242048等,数据量越大,nlist可适当增大)。

nprobe:查询时访问的分区数

  • 作用:指定搜索时需要检查的候选分区数量
    查询时,先计算查询向量与nlist个聚类中心的距离,然后选取距离最近的nprobe个分区,仅在这些分区的倒排表中搜索向量。
  • 影响
    • nprobe越小,搜索范围越窄,速度越快,但可能漏掉真正的近邻(精度下降)。
    • nprobe越大,覆盖的候选分区越多,精度越高,但需要计算更多向量的距离(速度变慢)。
    • 是IVF索引中最关键的调优参数,通常通过实验在速度与精度间权衡(例如nlist=1024时,nprobe=16为常见折中值)。

缺点:

  • 近似搜索: 由于只搜索了部分单元,可能错过真正的最近邻,因此是近似搜索。
  • 需要预训练: 需要先对数据集进行聚类,得到聚类中心。

适用场景:

  • 中等规模数据集: 适用于数据集规模较大,但仍然希望在一定精度下获得较快搜索速度的场景。
  • 需要平衡精度和速度: 可以通过调整参数来适应不同的精度和速度需求。
quantizer = faiss.IndexFlatL2(dimension)  # 使用 L2 距离的量化器
index = faiss.IndexIVFFlat(quantizer, dimension, nlist=100)  # nlist 是聚类中心的数量
index.train(embeddings_array)  # 必须先训练索引
index.add(embeddings_array)  # 然后将数据添加到索引中

4. IndexHNSWFlat (Hierarchical Navigable Small World graph Index)

类型: 近似索引 (Approximate Index)
度量: 通常与 L2 距离或内积结合使用

工作原理: IndexHNSWFlat 基于 分层可导航小世界图 (HNSW,Hierarchical Navigable Small World graph) 算法。它构建一个多层图结构,图中每个节点代表一个向量。在高层图中,节点之间的连接较少,主要用于快速定位到可能包含最近邻的区域;在低层图中,节点连接更密集,用于在局部区域进行更精细的搜索。搜索过程从顶层图开始,逐步向下层图深入,最终快速定位到最近邻。

优点:

  • 搜索速度非常快: HNSW 算法在搜索速度上通常比 IndexIVFFlat 更快,尤其是在高精度要求下。
  • 精度较高: 在相同的搜索速度下,HNSW 通常能提供更高的精度。
  • 无需预训练: HNSW 索引的构建过程不需要预先进行聚类。

缺点:

  • 索引构建时间较长: HNSW 索引的构建时间通常比 IndexIVFFlat 长。
  • 内存占用相对较高: 为了维护图结构,HNSW 的内存占用通常比 IndexIVFFlat 高。

适用场景:

  • 大规模数据集: 适用于需要处理非常大的数据集,并对搜索速度和精度都有较高要求的场景。
  • 对实时性要求高: 例如在线推荐系统、实时检索等。
index = faiss.IndexHNSWFlat(dimension, M=16)

5. IndexIVFPQ

类型: 近似索引 (Approximate Index)
度量: 通过 “IVF + PQ (Product Quantization)” 实现高效检索

工作原理: 倒排文件与乘积量化(Product Quantization)结合(将高维向量压缩为低维编码)。使用乘积量化来进一步压缩数据存储,优化存储空间和查询速度。

优点:对于非常大的数据集和需要压缩存储的场景,IndexIVFPQ 是一种高效的索引方式。 缺点:训练和构建索引的过程相对复杂,适合大数据集。

适用场景

  • 大规模高维向量检索:如亿级图像特征、文本嵌入向量;
  • 内存有限但需要平衡速度与精度的场景:相比精确索引 IndexFlatL2,内存可压缩 10-100 倍。
import faiss

# 1. 定义向量维度、参数
d = 128  # 向量维度
nlist = 1024  # 聚类中心数
m = 16  # PQ 子向量数
nbits = 8  # 每个子向量的量化位数

# 2. 构建量化器(用于 IVF 聚类,通常用 IndexFlatL2)
quantizer = faiss.IndexFlatL2(d)

# 3. 初始化 IndexIVFPQ
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, nbits)

# 4. 训练索引(用样本向量生成聚类中心和 PQ 码本)
index.train(train_vectors)  # train_vectors 是形状为 (N, d) 的样本向量

# 5. 添加向量到索引
index.add(database_vectors)  # database_vectors 是待索引的向量库

# 6. 设置查询参数 nprobe,搜索
index.nprobe = 32  # 访问 32 个候选分区
D, I = index.search(query_vectors, k=10)  # 返回每个查询的 top-10 近似近邻(距离 D,索引 I)

使用时需重点调优 nlist(分区数)和 nprobe(查询分区数),在速度与精度间找到平衡(通常 nprobe 越大,精度越接近精确搜索,但速度越慢)。


总结

由以上的分析可见,在选择 Faiss 索引时,需要综合考虑以下因素:

数据集规模: 数据集越大,越需要考虑近似索引,例如 IndexIVFFlat 或 IndexHNSWFlat。
精度要求: 如果对精度要求非常高,可以考虑精确索引 (IndexFlatIP, IndexFlatL2),但需要注意速度限制。如果可以接受一定的精度损失,则近似索引是更好的选择。
速度要求: 对搜索速度要求越高,越需要选择更快的近似索引,例如 IndexHNSWFlat。
内存限制: 内存资源有限时,需要考虑内存占用较低的索引方法。比如 PQ 或者 二进制索引。

在实际应用中,通常需要根据具体的数据集和需求进行实验和调优,选择最合适的索引方法和参数配置。
建议先从简单的 IndexFlatIP 或 IndexFlatL2 开始,如果速度无法满足需求,再尝试 IndexIVFFlat 或 IndexHNSWFlat 等近似索引方法。

参考:


🪐祝您好运🪐