3. 索引
3.1 索引类型全面解析
FAISS提供了多种索引类型,每种索引在精度、速度和内存使用之间有不同的权衡。
精确搜索索引
- IndexFlatL2:暴力搜索,使用L2距离
- IndexFlatIP:暴力搜索,使用内积相似度
近似搜索索引
- IVF索引(Inverted File Index):基于倒排文件的索引
- HNSW索引:基于层级导航小世界图
- PQ索引(Product Quantization):乘积量化索引
- 复合索引:结合多种技术的混合索引
3.2 索引选择指南与使用场景
IndexFlatL2/IP - 精确搜索索引
使用场景:
- 数据量较小(通常<10,000个向量)
- 需要100%准确率
- 开发调试阶段
特点:
- 结果精确
- 搜索速度慢(O(n)复杂度)
- 内存占用高
python
def flat_index_demo():
dimension = 256
n_vectors = 5000
data = np.random.random((n_vectors, dimension)).astype('float32')
# 创建L2距离索引
index_l2 = faiss.IndexFlatL2(dimension)
index_l2.add(data)
# 创建内积相似度索引
index_ip = faiss.IndexFlatIP(dimension)
index_ip.add(data)
return index_l2, index_ip
index_l2, index_ip = flat_index_demo()
IVF索引 - 倒排文件索引
使用场景:
- 中等规模数据(10万-1000万向量)
- 查询速度优先,可接受少量精度损失
- 内存相对充足
特点:
- 通过聚类加速搜索
- 需要训练阶段
- 可通过nprobe参数平衡速度与精度
python
import faiss
import numpy as np
def ivf_index_demo():
dimension = 128
n_vectors = 50000
n_clusters = 1024 # 聚类中心数量
print(f"初始化参数 - 维度: {dimension}, 向量数量: {n_vectors}, 聚类中心数量: {n_clusters}")
# 生成数据
data = np.random.random((n_vectors, dimension)).astype('float32')
print("数据生成完成,数据形状:", data.shape)
# 创建IVF索引
quantizer = faiss.IndexFlatL2(dimension) # 量化器
index = faiss.IndexIVFFlat(quantizer, dimension, n_clusters, faiss.METRIC_L2)
print("IVF索引创建完成")
print("-------------------------------------------------------------------------------------------")
# 训练索引
assert not index.is_trained
print("开始训练索引...")
index.train(data)
assert index.is_trained
print("索引训练完成")
print("-------------------------------------------------------------------------------------------")
# 添加数据
index.add(data)
print("数据添加到索引完成")
# 设置搜索时检查的聚类数量(平衡速度与精度)
index.nprobe = 16
print(f"设置搜索时检查的聚类数量为: {index.nprobe}")
# 搜索
queries = np.random.random((10, dimension)).astype('float32')
print("查询向量生成完成,形状:", queries.shape)
distances, indices = index.search(queries, 5)
print("-------------------------------------------------------------------------------------------")
print("搜索完成")
print("搜索结果 - 距离:", distances)
print("搜索结果 - 索引:", indices)
return index
ivf_index = ivf_index_demo()
HNSW索引 - 图结构索引
使用场景:
- 高维数据
- 需要高召回率和快速查询
- 实时搜索应用
特点:
- 基于图结构的近似算法
- 无需训练
- 构建时间较长,但查询速度快
HNSW索引属于图索引,它通过构建节点之间的连接关系来组织向量数据。 构建过程只是将新向量添加到图中并建立相应的连接,因此不需要训练阶段。 而在FAISS中,需要训练的索引通常是基于量化(Quantization)或聚类(Clustering)的索引, 例如 IndexIVFFlat、IndexIVFPQ、IndexFlatL2 等。这些索引在添加数据前需要先通过训练数据学习量化或聚类的参数。
python
import faiss
import numpy as np
def hnsw_index_demo():
dimension = 128
n_vectors = 1000
data = np.random.random((n_vectors, dimension)).astype('float32')
# 创建HNSW索引
index = faiss.IndexHNSWFlat(dimension, 32) # 32表示每个节点的连接数
print("创建HNSW索引")
print("-------------------------------------------------------------------------------------------")
# 设置构建参数
index.hnsw.efConstruction = 200 # 构建时考虑的邻居数量
index.hnsw.efSearch = 50 # 搜索时考虑的邻居数量
# 添加数据
index.add(data)
print(f"已向索引中添加 {n_vectors} 条数据")
print("-------------------------------------------------------------------------------------------")
# 搜索
queries = np.random.random((5, dimension)).astype('float32')
distances, indices = index.search(queries, 3)
print("搜索完成,搜索结果示例:")
print("距离:", distances)
print("索引:", indices)
return index
hnsw_index = hnsw_index_demo()
PQ索引 - 乘积量化索引
使用场景:
- 超大规模数据(百万级以上)
- 内存受限环境
- 可接受一定精度损失
特点:
- 大幅压缩向量存储
- 搜索速度快
- 需要训练,有量化误差
python
def pq_index_demo():
dimension = 128
n_vectors = 100000
data = np.random.random((n_vectors, dimension)).astype('float32')
# 创建PQ索引参数
m = 8 # 子量化器数量(必须能被dimension整除)
n_bits = 8 # 每个子量化器的位数
# 创建PQ索引
index = faiss.IndexPQ(dimension, m, n_bits)
# 训练并添加数据
index.train(data)
index.add(data)
print(f"PQ索引大小:{index.ntotal} 个向量")
return index
pq_index = pq_index_demo()
复合索引 - IVF+PQ
使用场景:
- 超大规模数据集
- 需要在速度和内存使用间取得最佳平衡
python
import faiss
import numpy as np
def ivfpq_index_demo():
dimension = 128
n_vectors = 1000000
n_clusters = 1024
data = np.random.random((n_vectors, dimension)).astype('float32')
# 创建IVFPQ索引
quantizer = faiss.IndexFlatL2(dimension)
m = 16 # 字节数(压缩后)
index = faiss.IndexIVFPQ(quantizer, dimension, n_clusters, m, 8)
# 训练并添加数据
index.train(data)
index.add(data)
index.nprobe = 32
print(f"聚类中心数量: {n_clusters}")
print(f"压缩后字节数: {m}")
return index
ivfpq_index = ivfpq_index_demo()
3.3 索引原理解析
IVF索引原理
IVF(Inverted File Index)索引的核心思想是"分而治之":
- 聚类阶段:使用k-means算法将所有向量分配到不同的聚类中心
- 倒排列表:为每个聚类中心建立包含该簇所有向量的倒排列表
- 搜索阶段:对于查询向量,只在与它最接近的nprobe个簇中进行搜索
数学原理:
- 聚类中心数量:nlist
- 搜索簇数量:nprobe
- 搜索复杂度从O(N)降低到O(nprobe × N/nlist)
HNSW索引原理
HNSW(Hierarchical Navigable Small World)基于小世界网络理论:
- 层级结构:构建多层图,上层稀疏,下层密集
- 导航机制:从上层开始搜索,逐步向下层细化
- 贪婪搜索:在每一层使用最佳优先搜索算法
优势:
- 搜索复杂度近似O(log N)
- 对高维数据友好
- 无需训练数据
PQ索引原理
乘积量化通过向量分解和标量化化实现压缩:
- 向量分割:将D维向量分割为m个D/m维子向量
- 子空间量化:对每个子空间独立进行k-means聚类
- 编码存储:用聚类中心ID代替原始子向量
压缩效果:
- 原始存储:D × 4字节(float32)
- 压缩后:m × 1字节(256个聚类中心)
- 压缩比:4D/m
3.4 性能比较
python
def benchmark_indices():
"""不同索引的性能比较"""
dimension = 128
n_vectors = 100000
n_queries = 1000
# 生成测试数据
data = np.random.random((n_vectors, dimension)).astype('float32')
queries = np.random.random((n_queries, dimension)).astype('float32')
indices = {
'FlatL2': faiss.IndexFlatL2(dimension),
'IVFFlat': faiss.IndexIVFFlat(faiss.IndexFlatL2(dimension), dimension, 1024),
'HNSW': faiss.IndexHNSWFlat(dimension, 32),
'IVFPQ': faiss.IndexIVFPQ(faiss.IndexFlatL2(dimension), dimension, 1024, 16, 8)
}
# 训练需要训练的索引
for name, index in indices.items():
if name != 'FlatL2' and name != 'HNSW':
index.train(data)
index.add(data)
if name == 'IVFFlat' or name == 'IVFPQ':
index.nprobe = 32
# 测试搜索性能
results = {}
for name, index in indices.items():
import time
start = time.time()
distances, indices_result = index.search(queries, 10)
end = time.time()
results[name] = {
'time': end - start,
'throughput': n_queries / (end - start)
}
return results
# 性能测试结果
performance_results = benchmark_indices()
for name, result in performance_results.items():
print(f"{name}: {result['throughput']:.1f} queries/second")