
1.什么是Faiss?
Faiss(Facebook AI Similarity Search)是由Facebook(现Meta)AI Research团队开发并开源的一个高效向量相似性搜索和聚类库。它专门用于在高维空间中对大规模向量数据进行快速的相似性检索(即近似最近邻搜索,ANN),支持十亿级向量的毫秒级检索,广泛应用于推荐系统、图像检索、文本检索、人脸识别、自然语言处理等领域。
能够对稠密向量进行高效的相似性搜索和聚类,支持多种距离度量(如欧氏距离L2、内积IP、余弦相似度等),并能灵活地在搜索速度、精度和内存占用之间做权衡。
- 主要功能:
- 向量索引:支持多种索引结构(如Flat、IVF、HNSW、PQ等),可针对不同规模和精度需求选择最优索引。
- 相似性搜索:输入一个或多个查询向量,快速返回最相似的若干向量(TopK)。
- 聚类:支持k-means等聚类算法。
- 支持CPU和GPU加速,适合大规模数据。
- 常见应用场景
- 推荐系统:根据用户兴趣向量推荐相似商品或内容。
- 图像检索:将图片转为向量,实现"以图搜图"。
- 文本检索:对文本向量化,快速查找相似文档或句子。
- 人脸识别:根据人脸特征向量快速匹配身份。
- 知识库问答(LLM相关):在知识库中检索与问题最相关的文本片段。
2.安装
根据自己的情况选择安装 cpu版本还是gpu版本
shell
# cpu版本
pip install faiss-cpu
# GPU版本
pip install faiss-gpu
3.索引
索引(Index) 是 Faiss 用于加速向量检索的核心结构。它通过预处理和压缩向量,实现以下功能:
- 快速检索:避免暴力遍历所有向量,提升查询效率。
- 内存优化:通过量化等技术减少内存占用。
- 灵活度量:支持多种距离/相似度度量(如 L2、内积、余弦相似度等)。
- 可扩展性:支持 GPU 加速和分布式部署。
3.1 索引类型
Faiss 提供多种索引类型,以适应不同数据规模、精度、速度和内存需求。主要分为精确索引和近似索引:
- Flat: 精确的暴力搜索(适合小规模数据)。
- IVF (Inverted File): 更高效的近似搜索,适合中到大规模数据。
- HNSW (Hierarchical Navigable Small World): 高效的近似搜索,适合大规模数据。
3.1.1 精确索引(Exact Search)
IndexFlatL2 / IndexFlatIP ,
- 原理:暴力检索,计算查询向量与所有向量的距离(L2 或内积)。
- 特点:结果精确,但速度随数据量线性增长,适合小规模数据。
python
import faiss
import numpy as np
# 创建一个随机的 10000 条 128 维向量
d = 128 # 向量维度
nb = 10000 # 向量数量
np.random.seed(1234)
# 生成一个形状为 (10000, 128) 的二维数组。数组中的每个元素都是一个在 [0.0, 1.0) 区间内均匀分布的随机浮点数。
# Faiss 库要求输入的向量数据必须是 float32 类型的 NumPy 数组。如果使用 Python 默认的 float64 类型,Faiss 会报错。
xb = np.random.random((nb, d)).astype('float32')
# 创建一个 Faiss 索引
index = faiss.IndexFlatL2(d) # 使用 L2 距离度量创建 Flat 索引
# 向索引中添加向量.一旦 xb 被添加到索引中,这个索引就变成了一个"向量数据库"。
index.add(xb)
print("Number of vectors in the index:", index.ntotal)
- IndexIVFFlat(倒排索引)
- 原理:先用 K-means 将向量空间划分为 nlist 个簇,查询时只在最近的 nprobe 个簇内精确检索。
- 特点:速度快于 Flat,但需训练,精度略低于精确索引。
python
# 创建一个 IVF 索引(适用于更大规模数据集)
d = 128 # 向量维度
nb = 10000 # 向量数量
nlist = 100 # 索引分桶的数量
quantizer = faiss.IndexFlatL2(d) # 使用 Flat 索引作为量化器
index_ivf = faiss.IndexIVFFlat(quantizer, d, nlist, faiss.METRIC_L2)
xb = np.random.random((nb, d)).astype('float32')
# 训练索引(需要一个大部分数据的子集来训练索引)
index_ivf.train(xb)
# 向索引中添加数据
index_ivf.add(xb)
print("Number of vectors in IVF index:", index_ivf.ntotal)
- HNSW 索引
HNSW(Hierarchical Navigable Small World,分层可导航小世界) 是一种基于图的近似最近邻搜索(ANN)算法。它通过构建多层图结构,在高维向量空间中实现对数级复杂度的快速搜索,并保持高召回率。非常适合大规模、高维向量检索场景(如 RAG、推荐、图像/文本检索等)。
python
d = 128 # 向量维度
nb = 10000 # 向量数量
# 创建一个 HNSW 索引
index_hnsw = faiss.IndexHNSWFlat(d, 32) # 32 是 HNSW 图的连接数
xb = np.random.random((nb, d)).astype('float32')
# 向索引中添加向量
index_hnsw.add(xb)
print("Number of vectors in HNSW index:", index_hnsw.ntotal)
3.1.2 近似索引(Approximate Search)
有一个查询向量,然后找出与之最相似的向量。
- 搜索最近邻向量
python
# 创建一个随机查询向量
xq = np.random.random((5, d)).astype('float32') # 5 个查询向量
# 执行最近邻搜索,返回最相似的 5 个向量
k = 5 # 查找最相似的 5 个向量
D, I = index.search(xq, k) # D 是距离,I 是索引
print("Distances of the nearest neighbors:\n", D)
print("Indices of the nearest neighbors:\n", I)
D 是距离矩阵,I 是索引矩阵,分别表示每个查询向量与最相似的 k 个向量之间的距离和对应的索引。
- 搜索时的优化(IVF 或 HNSW)
python
# 在 IVF 索引中执行搜索
index_ivf.nprobe = 10 # 搜索时考虑的桶的数量
D_ivf, I_ivf = index_ivf.search(xq, k)
print("Distances of the nearest neighbors (IVF):\n", D_ivf)
print("Indices of the nearest neighbors (IVF):\n", I_ivf)
python
# 在 HNSW 索引中执行搜索
index_hnsw.hnsw_efSearch = 32 # 增加搜索时的图搜索精度
D_hnsw, I_hnsw = index_hnsw.search(xq, k)
print("Distances of the nearest neighbors (HNSW):\n", D_hnsw)
print("Indices of the nearest neighbors (HNSW):\n", I_hnsw)
3.2 保存和加载索引
索引可以保存到磁盘上:
python
faiss.write_index(index, "index_flat.index")
从文件中加载索引:
python
index_loaded = faiss.read_index("index_flat.index")
print("Loaded index has", index_loaded.ntotal, "vectors.")
使用GPU加速(可选):
如果你有 GPU,Faiss 支持在 GPU 上加速向量搜索。首先,你需要安装 GPU 版本的 Faiss,然后将索引移动到 GPU:
python
import faiss
res = faiss.StandardGpuResources() # GPU 资源管理
gpu_index = faiss.index_cpu_to_gpu(res, 0, index) # 将索引移动到 GPU
# 搜索时使用 GPU 加速
D_gpu, I_gpu = gpu_index.search(xq, k)
3.3 小结
- 创建索引 :使用不同的索引类型(
IndexFlatL2,IndexIVFFlat,IndexHNSWFlat等)来创建 Faiss 索引。 - 插入数据 :使用
.add()方法将向量插入到索引中。 - 执行搜索 :使用
.search()方法进行最近邻搜索。 - GPU 加速:如果需要更高的性能,可以使用 Faiss 的 GPU 版本来加速搜索。
4.使用Faiss存储和检索人脸特征
shell
# GPU版本
pip install faiss-gpu
python
from insightface.app import FaceAnalysis
import os
import cv2
import pickle
import numpy as np
4.1 初始化 FaceAnalysis 模型
python
# 初始化 FaceAnalysis 模型
def init_model():
app = FaceAnalysis(name='buffalo_l',
providers=['CUDAExecutionProvider','CPUExecutionProvider'], # 优先使用GPU
allowed_modules=['detection', 'recognition'],
# 项目根目录作为模型目录,它会自动创建models文件夹
root='.',
) # 只加载检测和识别模块
app.prepare(ctx_id=0, det_thresh=0.5, det_size=(640, 640)) # ctx_id=-1 表示使用 CPU
print(f"模型初始化完成,模型文件路径 {os.path.abspath(app.model_dir)}")
return app
4.2 初始化索引
python
# 初始化 Faiss 索引
def init_faiss_index(dim=512, index_type="HNSW"):
"""
初始化 Faiss 索引
dim: 特征向量维度
index_type: 索引类型,支持 HNSW、IVF、Flat
"""
if index_type == "HNSW":
# HNSW 索引,适合高维向量,查询速度快
index = faiss.IndexHNSWFlat(dim, 32) # 32 是 HNSW 的参数
index.hnsw.efConstruction = 40 # 构建索引时的参数
index.hnsw.efSearch = 16 # 查询时的参数
elif index_type == "IVF":
# IVF 索引,适合大规模数据
nlist = 100 # 聚类中心数量
index = faiss.IndexIVFFlat(faiss.IndexFlatIP(dim), dim, nlist, faiss.METRIC_INNER_PRODUCT)
else:
# Flat 索引,暴力搜索,适合小规模数据
index = faiss.IndexFlatIP(dim) # IP 表示内积(余弦相似度)
return index
4.3 提取人脸特征并添加到Faiss索引
python
# 提取人脸特征并添加到 Faiss 索引
def extract_and_add_to_index(app, image_path, index, face_ids, prefix="face"):
"""
提取图像中所有人脸特征并添加到 Faiss 索引
app: FaceAnalysis 模型
image_path: 图像路径
index: Faiss 索引
face_ids: 人脸 ID 列表
prefix: 人脸 ID 前缀
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
print(f"无法读取图像: {image_path}")
return
# 检测人脸并提取特征
faces = app.get(img)
if len(faces) == 0:
print(f"在图像中未检测到人脸: {image_path}")
return
# 处理所有检测到的人脸
print(f"在图像中检测到 {len(faces)} 张人脸")
for i, face in enumerate(faces):
feature = face.normed_embedding
# 生成唯一的人脸 ID
face_id = f"{prefix}_{len(face_ids)}"
# 将特征添加到索引
feature_np = np.array([feature], dtype=np.float32)
index.add(feature_np)
# 添加人脸 ID 到列表
face_ids.append(face_id)
print(f"人脸 {i+1} 已添加到索引")
print(f"人脸 ID: {face_id}")
print(f"人脸位置: {face.bbox}")
4.4 保存 Faiss 索引和人脸 ID
python
def save_index_and_ids(index, face_ids, save_dir):
"""
保存 Faiss 索引和人脸 ID
index: Faiss 索引
face_ids: 人脸 ID 列表
save_dir: 保存目录
"""
# 确保保存目录存在
os.makedirs(save_dir, exist_ok=True)
# 保存 Faiss 索引
index_path = os.path.join(save_dir, "face_index.faiss")
faiss.write_index(index, index_path)
# 保存人脸 ID 列表
ids_path = os.path.join(save_dir, "face_ids.txt")
with open(ids_path, "w") as f:
for face_id in face_ids:
f.write(f"{face_id}\n")
print(f"索引已保存到: {index_path}")
print(f"人脸 ID 列表已保存到: {ids_path}")
4.5 从文件中加载Faiss索引和ID
python
def load_index_and_ids(save_dir):
"""
加载 Faiss 索引和人脸 ID
save_dir: 保存目录
返回: (Faiss 索引, 人脸 ID 列表)
"""
# 加载 Faiss 索引
index_path = os.path.join(save_dir, "face_index.faiss")
if not os.path.exists(index_path):
print(f"索引文件不存在: {index_path}")
return None, None
index = faiss.read_index(index_path)
# 加载人脸 ID 列表
ids_path = os.path.join(save_dir, "face_ids.txt")
if not os.path.exists(ids_path):
print(f"人脸 ID 文件不存在: {ids_path}")
return None, None
with open(ids_path, "r") as f:
face_ids = [line.strip() for line in f]
print(f"成功加载索引,包含 {len(face_ids)} 张人脸特征")
return index, face_ids
4.6 构建新索引主程序
python
if __name__ == "__main__":
# 初始化模型
app = init_model()
source_path = 'face_database/wangbaoqiang.png'
database_path = 'face_database/faiss'
# 创建保存特征索引目录(如果不存在)
os.makedirs(database_path, exist_ok=True)
# 先提取一个特征获取维度
img = cv2.imread(source_path)
if img is None:
print(f"无法读取图像: {source_path}")
exit(1)
faces = app.get(img)
if len(faces) == 0:
print(f"在图像中未检测到人脸: {source_path}")
exit(1)
dim = len(faces[0].normed_embedding)
# 初始化索引
index = init_faiss_index(dim)
face_ids = []
# 提取特征并添加到索引
extract_and_add_to_index(app, source_path, index, face_ids)
# 保存索引
save_index_and_ids(index, face_ids, database_path)
latex
模型初始化完成,模型文件路径 /home/qy/insightface_demo/models/buffalo_l
在图像中检测到 1 张人脸
人脸 1 已添加到索引
人脸 ID: face_0
人脸位置: [121.27753 84.08319 284.78592 294.70786]
索引已保存到: face_database/faiss/face_index.faiss
人脸 ID 列表已保存到: face_database/faiss/face_ids.txt
注意 numpy 如果是 2.0 以上版本,需要降级到 2.0以下,因为 Faiss 当前的版本 1.7.2 不支持 numpy 2.0以上版本
4.7 向现有索引添加特征
python
if __name__ == "__main__":
# 初始化模型
app = init_model()
print("向现有索引添加特征...")
# 加载现有索引
database_path = 'face_database/faiss'
source_path = 'face_database/wuqilong.png'
index, face_ids = load_index_and_ids(database_path)
if index is None or face_ids is None:
print("无法加载现有索引,正在创建新索引...")
# 初始化新索引
img = cv2.imread(source_path)
if img is None:
print(f"无法读取图像: {source_path}")
exit(1)
faces = app.get(img)
if len(faces) == 0:
print(f"在图像中未检测到人脸: {source_path}")
exit(1)
dim = len(faces[0].normed_embedding)
index = init_faiss_index(dim, "HNSW")
face_ids = []
# 提取特征并添加到索引
extract_and_add_to_index(app, source_path, index, face_ids)
# 保存更新后的索引
save_index_and_ids(index, face_ids,database_path)
latex
模型初始化完成,模型文件路径 /home/qy/insightface_demo/models/buffalo_l
向现有索引添加特征...
成功加载索引,包含 1 张人脸特征
在图像中检测到 1 张人脸
人脸 1 已添加到索引
人脸 ID: face_1
人脸位置: [339.06497 103.46757 658.17676 490.30322]
索引已保存到: face_database/faiss/face_index.faiss
人脸 ID 列表已保存到: face_database/faiss/face_ids.txt
4.8 识别图像中的人脸
python
# 识别图像中的人脸
def recognize_faces(app, image_path, index, face_ids, k=3, threshold=0.5):
"""
识别图像中的人脸
app: FaceAnalysis 模型
image_path: 图像路径
index: Faiss 索引
face_ids: 人脸 ID 列表
k: 返回前 k 个最相似的结果
threshold: 相似度阈值
"""
# 读取图像
img = cv2.imread(image_path)
if img is None:
print(f"无法读取图像: {image_path}")
return
# 检测人脸并提取特征
faces = app.get(img)
if len(faces) == 0:
print(f"在图像中未检测到人脸: {image_path}")
return
print(f"在图像中检测到 {len(faces)} 张人脸")
print("=" * 60)
# 处理所有检测到的人脸
for i, face in enumerate(faces):
feature = face.normed_embedding
# 使用 Faiss 查询最相似的人脸
query = np.array([feature], dtype=np.float32)
distances, indices = index.search(query, k)
print(f"处理人脸 {i+1}:")
print(f"人脸位置: {face.bbox}")
# 输出匹配结果
found_match = False
for j in range(k):
idx = indices[0][j]
sim = distances[0][j]
if sim > threshold:
found_match = True
print(f" 匹配 #{j+1}:")
print(f" 人脸 ID: {face_ids[idx]}")
print(f" 相似度: {sim:.4f}")
print(f" 判定结果: 匹配成功")
if not found_match:
print(f" 未找到匹配的人脸(相似度 < {threshold})")
print("=" * 60)
python
if __name__ == "__main__":
# 初始化模型
app = init_model()
# 加载现有索引
database_path = 'face_database/faiss'
source_path = 'face_database/wbq.png' # 要识别的照片
# 识别人脸
print("识别人脸...")
# 加载索引
index, face_ids = load_index_and_ids(database_path)
if index is None or face_ids is None:
print("无法加载索引,识别失败")
exit(1)
# 识别人脸
recognize_faces(app, source_path, index, face_ids)
latex
模型初始化完成,模型文件路径 /home/qy/insightface_demo/models/buffalo_l
识别人脸...
成功加载索引,包含 2 张人脸特征
在图像中检测到 1 张人脸
============================================================
处理人脸 1:
人脸位置: [317.3244 244.75237 814.38354 867.85034]
匹配 #1:
人脸 ID: face_0
相似度: 0.7359
判定结果: 匹配成功
匹配 #2:
人脸 ID: face_1
相似度: 2.0006
判定结果: 匹配成功
匹配 #3:
人脸 ID: face_1
相似度: 340282346638528859811704183484516925440.0000
判定结果: 匹配成功
============================================================