跳到主要内容

预处理和后处理

预处理与后处理简介

Faiss 支持预处理(Pre-processing)和后处理(Post-processing),常用于以下场景:

  • 重新映射向量的ID
  • 对数据应用变换(如降维或维度重排)
  • 使用更精确的索引对搜索结果重新排序(re-rank)

Faiss ID 映射(ID Mapping)

默认情况下,Faiss 会为添加到索引的向量分配顺序ID(从0递增)。如果你想要使用自定义的ID,可以使用下面介绍的方法。

一些 Index(索引)类实现了 add_with_ids 方法,你可以在添加数据时,额外指定64位的向量ID。在搜索时,返回的将是你提供的ID,而不是自动分配的顺序ID。

注意

在Lua语言中,搜索函数默认会对搜索结果的ID加1(因为Lua索引是从1开始)。如果你调用 search(queries, k, false),则返回原始ID,不做加1处理。

IndexIDMap 索引

IndexIDMap 是一个包装索引,用于添加和搜索时转换ID。它内部维护了一张ID映射表。

举例:

import faiss
import numpy as np

index = faiss.IndexFlatL2(xb.shape[1])
ids = np.arange(xb.shape[0])
# 直接调用会报错,因为 IndexFlatL2 不支持 add_with_ids
index.add_with_ids(xb, ids)
index2 = faiss.IndexIDMap(index)
# 推荐做法:用IndexIDMap支持add_with_ids
index2.add_with_ids(xb, ids) # 向量存储在底层索引中

IndexIVF 的向量ID

IndexIVF(倒排文件索引)及其子类本身都可以存储自定义向量ID。因此,使用 IndexIDMap 的额外ID表在这种情况下会浪费空间。IndexIVF 类自带 add_with_ids 方法,无需再用 IndexIDMap 包装。


数据预处理:向量变换(Pre-transforming the data)

对数据进行变换,通常可提升索引的效率和准确性。变换类(Transformation classes)都继承自 VectorTransform(向量变换基类)。一个 VectorTransform 对输入向量(维度为 d_in)进行变换,输出新的向量(维度为 d_out)。

常用变换类型如下表所示:

变换类型类名说明
随机旋转RandomRotationMatrix在使用 IndexPQIndexLSH 索引前,调整向量分量的比例,使其更均衡。
维度重排列RemapDimensionsTransform调整向量的维数(增大或减小),如索引有特定的维度需求,或者对维度做随机排列。
主成分分析(PCA降维)PCAMatrix降低向量维数,提高处理效率。
OPQ旋转OPQMatrix对输入向量旋转,使其更适合 PQ(Product Quantization,乘积量化)编码。详细资料请见 Optimized product quantization, Ge等,CVPR'13

如果需要,变换可以通过带有代表性的一组向量进行训练(train方法);之后用 apply 方法将变换作用到其他向量上。

可以用 IndexPreTransform 将某个索引包装起来,这样所有数据的变换将在数据被添加进索引或查询时自动完成,同时训练过程也会集成到索引训练中。


示例:使用 PCA 降维

独立使用方式

请参见下一章节。

与 IndexPreTransform 搭配

假设输入向量是2048维,你希望减少到256维以便用更小的存储空间索引,那么可以先用 PCA 降维:

# 这里的IndexIVFPQ工作在256维空间
coarse_quantizer = faiss.IndexFlatL2(256)
sub_index = faiss.IndexIVFPQ(coarse_quantizer, 256, ncoarse, 16, 8)
# PCA: 从2048维降到256维
# 第4个参数为True表示在PCA降维后,继续做随机旋转
pca_matrix = faiss.PCAMatrix(2048, 256, 0, True)

# 用IndexPreTransform包装索引,将变换自动集成
index = faiss.IndexPreTransform(pca_matrix, sub_index)

# 训练时PCA和索引一起训练
index.train(...)
# 添加数据时先做PCA变换再存入索引
index.add(...)

示例:增加向量维度

有时需要将原始维度d增加到满足某些特定要求(例如,d是4的倍数,或者M的倍数,其中M是PQ的子空间数)。这可以通过插入零分量实现。

使用场景包括:

  • d 成为4的倍数,便于距离计算优化
  • d 成为 M 的倍数,适配PQ参数

代码示例:

# 原始向量维度为d,希望提升到d2,使其为M的倍数
d2 = int((d + M - 1) / M) * M
remapper = faiss.RemapDimensionsTransform(d, d2, True)
# 构建d2维索引
index_pq = faiss.IndexPQ(d2, M, 8)

# 包装成可自动应用变换的索引
index = faiss.IndexPreTransform(remapper, index_pq)

IndexRefineFlat:重新排序搜索结果

在检索向量时,可以先用近似(如PQ编码)快速检索,再用精确距离计算对候选结果进行重新排序(re-ranking)。

下面的代码示例先用 IndexPQ 检索,然后用 IndexRefineFlat 计算实际距离:

# 构建PQ索引
q = faiss.IndexPQ(d, M, nbits_per_index)
# 在PQ索引基础上构建RefineFlat
rq = faiss.IndexRefineFlat(q)
rq.train(xt)
rq.add(xb)
rq.k_factor = 4 # 扩搜比例
D, I = rq.search(xq, 10)

这里,search会从PQ索引中找出 4 * 10 个最近邻,然后用原始(精确)距离重新计算并选出Top 10个结果。

important

IndexRefineFlat 会保存所有原始向量,因此占用内存较大。


IndexShards:合并多个索引的检索结果

如果你的数据集合分布在多个索引中,可以用 IndexShards 实现在各索引间分发检索请求,并合并结果。

这种方式同样适用于多GPU并行检索场景。详细可参考 index_cpu_to_gpus 并将 shards 设为 True(可在 GpuClonerOptions 中设置)。


::: 上述方法可根据实际需求灵活组合,提升Faiss索引系统的性能与灵活性。 :::