Faiss 向量搜索库

FreeGuideOnline 最新 2026-07-13

bash conda install -c pytorch -c conda-forge faiss-cpu


或使用 pip:

```bash
pip install faiss-cpu

GPU 版本(需已安装 CUDA 驱动与 PyTorch 环境)

conda install -c pytorch -c conda-forge faiss-gpu

安装完成后,在 Python 中验证:

import faiss
print(faiss.__version__)   # 应输出版本号,如 1.7.4

向量相似性搜索基础

在深入 Faiss 之前,先理解向量搜索的工作流:

  1. 向量化:将原始数据(文本、图片、用户行为)通过模型转换为固定长度的浮点数向量。
  2. 构建索引:将所有向量组织成一种数据结构(即索引)。
  3. 查询:给定一个查询向量,索引快速返回距离最近的一组向量及其 ID。
  4. 距离度量:常用 L2 距离(欧氏距离)内积(IP,用于余弦相似度当向量归一化时)

Faiss 将距离度量抽象为 MetricType,在创建索引时指定。


快速上手:一个极简搜索流程

从零开始,用 128 维随机向量模拟一个“10 万向量库,查询 10 条”的完整演示。

import numpy as np
import faiss

# ------------------- 1. 准备数据 -------------------
d = 128                           # 向量维度
nb = 100000                       # 数据库向量数量
nq = 10                           # 查询向量数量

np.random.seed(42)                # 复现结果
xb = np.random.random((nb, d)).astype('float32')   # 数据库向量,必须为 float32
xq = np.random.random((nq, d)).astype('float32')   # 查询向量

# ------------------- 2. 构建平面索引 -------------------
# IndexFlatL2 是暴力搜索的 L2 索引,精度最高但速度慢
index = faiss.IndexFlatL2(d)      # d 是向量维度
print("是否已训练:", index.is_trained)   # 平面索引无需训练,直接为 True
index.add(xb)                     # 将数据库向量加入索引
print("索引中向量数量:", index.ntotal)

# ------------------- 3. 执行搜索 -------------------
k = 4                             # 返回最近邻的个数
D, I = index.search(xq, k)        # D: 距离矩阵, I: 索引 ID 矩阵
print("第一个查询的最近邻 ID:", I[0])
print("第一个查询对应的距离:", D[0])

输出解读

  • I[0] 是离第一个查询向量最近的 4 个数据库向量的 ID(从 0 开始,按照距离升序)。
  • D[0] 是相应的平方欧氏距离。

注意事项:

  • Faiss 中大部分索引要求数据为 float32 类型的连续数组,务必提前转换。
  • 添加数据后,可通过 index.ntotal 查看索引中的向量总数。

索引的选择与构建

Faiss 的核心概念是索引。不同索引在搜索速度、内存占用和精度间做出权衡。以下是常用索引的分类及使用场景。

精确搜索:基准索引

IndexFlatL2 / IndexFlatIP

  • 原理:暴力计算查询向量与所有数据库向量的距离。
  • 精度:100% 准确,返回真正的最近邻。
  • 适用场景:作为其他近似索引的精度基准;适用于百万级以下的小数据集。
  • 使用faiss.IndexFlatL2(d)faiss.IndexFlatIP(d)(内积)。

IndexHNSWFlat

  • 原理:基于分层可导航小世界图(HNSW),通过快速图遍历找到最近邻。
  • 精度:通常能获得 >99% 的召回率,速度比 Flat 快数十倍。
  • 适用场景:追求较高精度且数据量适中的场景(百万到千万级)。
  • 内存占用高:因为它需要存储原始向量,不如压缩索引省内存。
# 构建 HNSW 索引,M 控制连接数,越大精度越高但内存越大
index = faiss.IndexHNSWFlat(d, 32)   # d 为维度,32 是连接数,典型值 16-64
index.add(xb)

压缩索引:牺牲精度换取内存与速度

当数据集有数千万甚至数十亿条时,存储完整向量(d * 4 字节)压力巨大。Faiss 通过**乘积量化(PQ)**等技术大幅压缩向量。

IndexIVFPQ(倒排文件 + 乘积量化)

  • 架构:先用倒排索引粗分,再用 PQ 编码残差向量。
  • 步骤
    1. 使用 K-Means 对向量进行粗聚类,划分到 nlist 个倒排链表。
    2. 用乘积量化对每个向量的残差编码为短代码。
  • 优点:内存仅需完整向量的 10%-20%,搜索极快。
  • 缺点:需训练,精度有折损。
nlist = 100          # 倒排列表数量,建议为 sqrt(n) 到 4*sqrt(n)
m = 8                # PQ 子向量个数,必须能整除维度 d
bits = 8             # 每个子向量的量化比特,通常为 8,即 1 字节

# 构建 IndexFlatL2 作为粗量化器(选择 nlist 个聚类中心)
quantizer = faiss.IndexFlatL2(d)
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, bits)

# 训练:用数据学习聚类中心和 PQ 码本
index.train(xb)      # xb 为训练向量,通常用全集或子集
index.add(xb)        # 添加数据

参数建议

  • nlist:聚类中心数,常见范围 100 ~ 65536。越大精度越高,但训练和搜索变慢。
  • m:PQ 子空间数。维度 d 必须能被 m 整除。值越大精度越高,但编码越大。
  • bits:每子空间量化位数,通常为 8。

IndexHNSWPQ

融合 HNSW 图结构 + PQ 压缩,内存占用极低且能保持较高精度。适合数亿甚至十亿级别数据。

index = faiss.IndexHNSWPQ(d, m, 32)   # m 为 PQ 子空间,32 为 HNSW 的 M

内存映射索引:突破 RAM 限制

当向量数量过大无法全载入内存时,Faiss 支持内存映射 (MMAP),直接从磁盘读取索引。

index = faiss.read_index("large_index.faiss", faiss.IO_FLAG_MMAP)

该模式下,操作系统可按需将部分索引调入内存,非常适合超大规模数据集部署。

索引工厂:一行字符串创建索引

faiss.index_factory 提供了一种极简的声明式构建方式,根据描述字符串自动生成对应索引。

dim = 128
index = faiss.index_factory(dim, "IVF100,PQ8")  
# 含义:IVF 聚类 100 个中心,PQ 编码 8 个子向量

常用工厂字符串:

  • "Flat" → IndexFlatL2 (或 IP)
  • "HNSW32" → IndexHNSWFlat, M=32
  • "IVF4096,PQ64" → IndexIVFPQ, nlist=4096, m=64
  • "PCA80,Flat" → 先 PCA 降维至 80,再 Flat 索引
  • "OPQ16_64,IVF1024,PQ16" → OPQ 预处理旋转 + IVF+PQ

工厂模式是快速原型和实验的最佳方式。


搜索技巧与参数调优

构建索引后,可通过调整搜索参数在不重建索引的前提下平衡速度与精度。

IVF 索引的搜索参数 nprobe

当使用 IVF 系列索引(如 IndexIVFFlat、IndexIVFPQ)时,查询只访问部分倒排列表,nprobe 控制访问的聚类数。

  • 默认 nprobe=1:只搜索最近的一个聚类,速度快但容易遗漏。
  • 调大 nprobe 可提高召回率,但带来更多计算。
index.nprobe = 10       # 在搜索前设置
D, I = index.search(xq, k)

经验规律:nprobe 从 1 增加到 nlist,精度持续提升,一般设到 nlist/10 即可获得较高召回。

HNSW 的搜索参数 efSearch

对于 HNSW 系列索引,efSearch 决定了动态搜索时的队列宽度。

index.hnsw.efSearch = 128     # 默认 16,增大可提高精度,但搜索变慢

批次化搜索

Faiss 本身高度优化了向量化操作,将多条查询打包成 batch 能显著提升吞吐量,尤其在 GPU 上。

batch_size = 256
for i in range(0, nq, batch_size):
    batch_queries = xq[i:i+batch_size]
    D_batch, I_batch = index.search(batch_queries, k)
    # 处理结果...

GPU 加速入门

将计算迁移到 GPU 通常仅需几行代码,却能使搜索吞吐量提升 10~100 倍。

转移索引到 GPU

res = faiss.StandardGpuResources()          # 申请 GPU 资源
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)   # 将 CPU 索引克隆到 GPU 0

# 搜索与 CPU 版本完全相同
D, I = gpu_index.search(xq, k)

直接在 GPU 上构建索引

gpu_index = faiss.index_factory(d, "IVF1024,PQ16", faiss.METRIC_L2)
# 若 faiss-gpu 安装正确,它会自动使用 GPU 加速训练和搜索
gpu_index.train(xb)
gpu_index.add(xb)

注意:GPU 版本需保证 CUDA 和 faiss-gpu 环境正确配置,且数据需提前放入 GPU 显存;超大索引可能需要多 GPU 分配。


典型实战场景

语义搜索系统

基于 BERT / Sentence-Transformers 将文本编码为 768 维或更高向量,构建 Faiss 索引实现毫秒级检索。

from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')

sentences = ["机器学习很有趣", "Faiss 是向量搜索利器", ...]
embeddings = model.encode(sentences)   # shape: [N, 384]

# 构建索引并添加
index = faiss.IndexFlatIP(embeddings.shape[1])
index.add(embeddings.astype('float32'))

# 查询
query_vec = model.encode(["如何快速检索文本?"])
D, I = index.search(query_vec.astype('float32'), k=5)

图像去重与相似推荐

使用预训练的 CNN(如 ResNet50)提取图像特征向量,通过 Faiss 查询近邻,若距离小于阈值则可判定为相似或重复。

# 伪代码:features 是 [N, 2048] 的图像特征
index = faiss.IndexHNSWFlat(2048, 64)
index.add(features)

# 对每一张图像,找到与它最相似的大量图像
D, I = index.search(features, k=10)
# 结合距离阈值用于去重

常见问题与调试建议

  1. “Index not trained” 错误
    某些索引(如 IVF、PQ 系列)需要先调用 index.train(data)add()。通过 index.is_trained 检查。

  2. 内存不足
    尝试:

    • 使用压缩索引(IVFPQ、HNSWPQ)。
    • 采用 MMAP 模式。
    • 降低精度(如减少 m 或 bits)。
  3. 搜索结果精度骤降

    • 检查 nprobeefSearch 是否设置过低。
    • 如果数据分布变化大(如在线更新),原有训练码本可能失效,需定期重新训练。
  4. 数据不是 float32
    Faiss 严格要求输入为 contiguous float32 数组。使用 np.ascontiguousarray(data.astype('float32')) 进行转换。

  5. 距离度量与归一化
    当使用内积(IP)而不是 L2 时,通常需要将向量 L2 归一化,使内积等价于余弦相似度。归一化后,IndexFlatIP 即成为余弦相似度索引。

from sklearn.preprocessing import normalize
xb = normalize(np.random.random((nb, d)).astype('float32'))
index = faiss.IndexFlatIP(d)   # 此时搜索等同于余弦相似度