Faiss 教程:常用操作详解
Faiss 是 Facebook AI Research 开发的高效向量相似性搜索库,专注于快速地找到向量的近似最近邻(ANN)。它适用于大规模向量检索场景,例如推荐系统、图像检索、文本检索等。以下是 Faiss 的核心功能及常用操作的详细教程。
1. 安装 Faiss
Faiss 可以通过 pip
安装。
-
CPU 版本:
pip install faiss-cpu
-
GPU 版本:
pip install faiss-gpu
-
从源码编译(高级): 如果需要完全定制的功能或优化,建议参考 Faiss 官方文档。
2. 基本概念
Faiss 的核心是基于 向量索引 的检索。其主要组成包括:
- Index 类型: 不同的索引结构适用于不同规模的数据集和性能需求(例如:暴力搜索、高效索引、近似搜索等)。
- 距离度量:
- L2 距离(欧氏距离的平方): 常用于
IndexFlatL2
。 - 内积(Inner Product): 常用于
IndexFlatIP
。 - 余弦相似度: 通过对向量归一化后用内积实现。
- L2 距离(欧氏距离的平方): 常用于
- 维度(
d
): 向量的每个维度表示一个特征,所有操作需要保证向量的维度一致。 - 批量处理: Faiss 支持对多个查询向量同时进行检索。
3. 索引的创建与操作
3.1 创建一个简单的索引
(1) IndexFlatL2
:暴力搜索
这是 Faiss 中最简单的索引类型,适合小型数据集。它通过暴力枚举计算查询向量与索引中所有向量的距离,精度最高,但速度较慢。
import faiss
import numpy as np
# 创建一个随机向量数据库
d = 64 # 向量的维度
nb = 1000 # 向量的数量
np.random.seed(1234)
data = np.random.random((nb, d)).astype('float32') # 基础向量集合
# 创建基于 L2 距离的暴力搜索索引
index = faiss.IndexFlatL2(d)
# 添加数据到索引
index.add(data) # 添加 1000 个向量到索引中
print(f"总向量数: {index.ntotal}") # 输出: 1000
# 查询操作
query = np.random.random((5, d)).astype('float32') # 创建 5 个查询向量
k = 4 # 查找每个查询向量的 4 个最近邻
distances, indices = index.search(query, k)
print("查询距离:\n", distances)
print("查询结果索引:\n", indices)
(2) IndexFlatIP
:内积搜索
基于内积的索引,通常用于向量已经归一化的情况(即所有向量的长度为 1),在这种情况下,内积等价于余弦相似度。
# 创建基于内积的索引
index_ip = faiss.IndexFlatIP(d)
index_ip.add(data) # 添加向量
query_ip = np.random.random((5, d)).astype('float32')
distances_ip, indices_ip = index_ip.search(query_ip, k)
print("基于内积的距离:\n", distances_ip)
3.2 索引保存与加载
Faiss 支持将索引保存到磁盘,并在需要时加载。
# 保存索引到文件
faiss.write_index(index, "flat_index.faiss")
# 从文件加载索引
loaded_index = faiss.read_index("flat_index.faiss")
print(f"加载的总向量数: {loaded_index.ntotal}")
3.3 批量操作
对于大规模向量,Faiss 支持批量添加和查询。
# 批量添加
new_data = np.random.random((500, d)).astype('float32')
index.add(new_data)
print(f"更新后的总向量数: {index.ntotal}") # 输出: 1500
# 批量查询
batch_query = np.random.random((20, d)).astype('float32')
distances, indices = index.search(batch_query, k)
print(f"批量查询结果: {indices.shape}") # 输出: (20, k)
4. 高效索引:大规模数据
对于百万级或更大的数据集,暴力搜索(如 IndexFlatL2
)的效率很低。Faiss 提供了更高效的索引方法,如 IndexIVFFlat
和 IndexHNSW
。
4.1 IndexIVFFlat
:基于聚类的索引
- 将向量分成多个聚类组,只在查询向量最接近的几个组中搜索。
- 适合大规模数据,支持加速但精度可能略有损失。
# 创建 IVF 索引
nlist = 100 # 聚类中心数量
quantizer = faiss.IndexFlatL2(d) # 量化器(基础索引)
index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
# 训练索引(需要数据进行聚类)
index_ivf.train(data) # 训练过程基于现有数据
index_ivf.add(data) # 添加向量
print(f"总向量数: {index_ivf.ntotal}")
# 查询
distances, indices = index_ivf.search(query, k)
print("IVF 查询结果:\n", indices)
4.2 IndexHNSW
:基于图的近似搜索
- HNSW 是基于图结构的近似最近邻搜索,速度快且精度较高,适合中小规模数据。
index_hnsw = faiss.IndexHNSWFlat(d, 32) # 32 表示图的邻居数
index_hnsw.add(data)
distances, indices = index_hnsw.search(query, k)
print("HNSW 查询结果:\n", indices)
4.3 GPU 加速
对于非常大的数据集,可以使用 GPU 加速 Faiss 索引和查询。
# 将索引转移到 GPU
res = faiss.StandardGpuResources() # 初始化 GPU 资源
gpu_index = faiss.index_cpu_to_gpu(res, 0, index) # 0 表示 GPU ID
distances, indices = gpu_index.search(query, k)
print("GPU 加速查询结果:\n", indices)
5. 自定义距离(例如余弦相似度)
Faiss 默认支持 L2 距离和内积。如果需要实现余弦相似度,可以先将所有向量归一化为单位长度,再使用内积索引。
# 归一化向量
def normalize(vectors):
norms = np.linalg.norm(vectors, axis=1, keepdims=True)
return vectors / norms
normalized_data = normalize(data)
normalized_query = normalize(query)
# 使用内积索引
index_cosine = faiss.IndexFlatIP(d)
index_cosine.add(normalized_data)
distances, indices = index_cosine.search(normalized_query, k)
print("余弦相似度查询结果:\n", indices)
6. 检查和调试
6.1 检查索引是否已训练
某些索引(如 IndexIVFFlat
)需要先调用 train()
进行训练,调用 is_trained
属性检查:
print("索引是否已训练:", index_ivf.is_trained)
6.2 查看索引信息
print(index) # 打印索引类型和参数
7. 综合示例
以下代码实现了从构建索引、保存加载到 GPU 加速的综合流程:
import faiss
import numpy as np
# 数据集
d = 128 # 向量维度
nb = 10000 # 数据库向量数量
np.random.seed(123)
data = np.random.random((nb, d)).astype('float32')
query = np.random.random((5, d)).astype('float32')
# 创建索引
index = faiss.IndexFlatL2(d)
index.add(data)
# 查询
k = 5
distances, indices = index.search(query, k)
# 保存索引
faiss.write_index(index, "example_index.faiss")
# 加载索引并用 GPU 加速
index = faiss.read_index("example_index.faiss")
res = faiss.StandardGpuResources()
gpu_index = faiss.index_cpu_to_gpu(res, 0, index)
# GPU 查询
distances_gpu, indices_gpu = gpu_index.search(query, k)
print("GPU 查询结果:", indices_gpu)
总结
Faiss 提供了从简单暴力搜索到复杂高效索引的一整套工具,适用于各种规模的向量检索任务。对于初学者,建议从 IndexFlatL2
开始,逐步学习 IndexIVFFlat
和 GPU 加速功能。掌握 Faiss 的核心操作后,可以用于多种场景,如推荐系统、图像检索和文本向量检索等。