登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

FAISS Index Factory 字符串怎么组合索引结构

来源:17golang原创

时间:2026-09-28 01:17:19 100浏览 收藏

FAISS 的 index_factory 可以用一条逗号分隔的字符串构造复合索引。最实用的理解不是死记所有缩写,而是把字符串从左到右拆成三层:可选的向量变换、候选筛选结构、最终存储编码。例如 OPQ16_64,IVF4096,PQ16 表示先把输入向量做 OPQ 变换并输出 64 维,再由 4096 个倒排中心缩小候选范围,最后用 16 个子量化器组成的 PQ 编码保存向量。

组合时先决定是否接受近似检索,再决定内存预算,最后才考虑 PCA、OPQ 这类预处理。Flat 是精确基线;IVF 负责粗排;PQ 和 SQ 负责压缩。需要训练的结构要先调用 train,运行时的 nprobe 则不写进 Factory 字符串,而是在搜索前单独设置。

前置条件:先把 Factory 字符串看成三层结构

官方说明把 Factory 字符串定义为组件列表。常见组合可以用下面这张表快速判断:

字符串得到的结构适合先验证什么
Flat精确穷举,原始浮点向量召回上限和距离度量
IVF1024,Flat1024 个倒排列表,列表内保留原向量粗排带来的速度与召回变化
IVF1024,PQ16倒排粗排加 16 字节 PQ 编码内存压缩后的精度
IVF1024,SQ8倒排粗排加每维 8 位标量量化较温和的压缩方案
PCA64,Flat先降到 64 维,再做精确搜索降维本身造成的损失
OPQ16_64,IVF4096,PQ16OPQ 变换、IVF 粗排、PQ 压缩完整的粗排与压缩组合

逗号不是“先后执行脚本”的含义,而是在声明一个嵌套索引。左侧组件包裹右侧组件:变换作用于输入,IVF 决定去哪些列表找候选,编码组件决定每条向量以什么形式保存在索引里。

FAISS Index Factory 的向量变换、候选筛选与存储编码三层静态关系图
图1:从左到右看三个分组。Factory 字符串同时配置向量变换、IVF 候选列表和 PQ 或 SQ 编码,但三者承担的职责不同。

初始化:从 Flat 基线确认维度和度量

我习惯先用 Flat 建一个基线索引。它不需要训练,保存的是原始浮点向量,搜索结果可以当作后续近似索引的对照。Factory 的第一个参数是输入向量维度,不能把降维后的维度误传进去;如果输入是 128 维,即使字符串写了 PCA64,第一个参数仍然是 128。

import faiss

d = 128

# 精确 L2 检索基线,不需要训练
flat_index = faiss.index_factory(d, "Flat", faiss.METRIC_L2)
flat_index.add(database_vectors)

# 返回前 10 个近邻,用于和近似索引比较
flat_distances, flat_ids = flat_index.search(query_vectors, 10)

若业务按余弦相似度检索,通常先对数据库向量和查询向量做 L2 归一化,再使用内积度量。Factory 字符串也支持 L2norm 变换,但无论采用哪种方式,都要让建库与查询保持一致。

编写代码:加入 IVF 粗排层

IVF1024,Flat 中的 1024 是聚类中心数量,也就是倒排列表数量。它属于建库结构参数,索引创建后不能像 nprobe 那样随查询自由调整。搜索时只访问部分列表,因此速度更快,但可能漏掉真实近邻。

import faiss

d = 128
factory = "IVF1024,Flat"
index = faiss.index_factory(d, factory, faiss.METRIC_L2)

# IVF 需要先用有代表性的样本训练聚类中心
if not index.is_trained:
    index.train(train_vectors)

# 训练完成后再添加数据库向量
index.add(database_vectors)

# nprobe 是运行时参数,不属于 Factory 字符串
index.nprobe = 16
distances, ids = index.search(query_vectors, 10)

nprobe 越大,搜索覆盖的倒排列表越多,通常召回更高、耗时也更长。不要只看字符串就宣布“IVF1024 一定更快”,因为最终表现还取决于向量数量、列表是否均衡、查询批量、线程与硬件。

编写代码:用 PQ 或 SQ 控制存储

如果 IVF1024,Flat 的内存仍然太高,可以把末尾的 Flat 换成压缩编码。PQ16 默认每个子量化器使用 8 位码,因此每条向量的 PQ 码主体是 16 字节;SQ8 则按维度做 8 位标量量化。两者都是用精度换空间,但取舍方式不同。

import faiss

d = 128
factory = "IVF1024,PQ16"
index = faiss.index_factory(d, factory, faiss.METRIC_L2)

# IVF 和 PQ 都包含需要从训练集学习的参数
index.train(train_vectors)
assert index.is_trained

index.add(database_vectors)
index.nprobe = 16
distances, ids = index.search(query_vectors, 10)

选择 PQ16 时,要检查变换后的维度是否能被子量化器数量合理切分。128 维可以均分为 16 段;如果前面写了 PCA60,再接 PQ16 就不是一个自然的均分组合。遇到维度不整除时,先调整输出维度或 PQ 的子向量数量,不要靠试错绕过约束。

扩展实验:在最前面加入 PCA 或 OPQ 变换

预处理组件总是放在最前面。PCA64,Flat 把 128 维输入压到 64 维后做穷举;OPQ16_64,IVF4096,PQ16 则先旋转并输出 64 维,让后续 PQ 更容易分配量化误差。PCA 和 OPQ 都需要训练,所以不能创建后直接 add。

import faiss

d = 128
factory = "OPQ16_64,IVF4096,PQ16"
index = faiss.index_factory(d, factory, faiss.METRIC_L2)

# 训练数据应覆盖真实数据库向量的分布
index.train(train_vectors)
index.add(database_vectors)

# 在固定结构上调节查询阶段的速度与召回
params = faiss.ParameterSpace()
params.set_index_parameters(index, "nprobe=32")
distances, ids = index.search(query_vectors, 10)

这里用 ParameterSpace 设置参数,比逐层查找嵌套对象更稳妥。官方把参数明确分成建库参数与运行时参数:Factory 字符串描述前者,nprobe、HNSW 的 efSearch 等属于后者。

运行检查:训练、添加和搜索参数要分开

Factory 字符串写对并不代表索引已经可用。实践中我会按下面四个检查点排查:

  • 维度:输入数组第二维必须等于 index_factory 的 d,而不是 PCA 或 OPQ 的输出维度。
  • 训练:只要组合中包含 IVF、PQ、SQ、PCA、OPQ 等需要学习参数的组件,就先看 index.is_trained,训练完成后再入库。
  • 数据类型:Python 侧通常准备连续的 float32 数组,避免因 dtype 或内存布局造成额外复制和调用错误。
  • 对照:用同一批查询把近似索引和 Flat 基线比较,至少记录召回、查询耗时和索引大小。
FAISS Factory 建库组件、训练数据与运行时 nprobe 参数的静态边界图
图2:结构组件、数据准备和搜索参数分为三个边界。训练集负责学习 IVF 与压缩器,数据库向量在训练后写入,nprobe 只影响查询阶段的候选覆盖。

扩展实验:常见组合怎么选

可以按目标从简单到复杂逐步加组件,而不是一次写出很长的 Factory 字符串:

  1. 先用 Flat 得到精确结果和内存基线。
  2. 数据量增大且允许近似时,试 IVF1024,Flat 或更合适的列表数,并扫描不同 nprobe。
  3. 内存成为瓶颈时,再比较 IVF...,SQ8 与 IVF...,PQ...。
  4. PQ 的精度不理想时,才考虑在最前面加入 OPQ;原始维度过高时再评估 PCA。
  5. 每增加一个组件,都保留上一版索引的召回、耗时和大小数据,避免把损失归因错位。

如果只记一个判断顺序,可以记成:Flat 定上限,IVF 定候选,PQ/SQ 定空间,PCA/OPQ 定输入表达,nprobe 定运行时取舍。这样看到复杂字符串时,先按职责拆开,再检查训练和维度约束,通常就不会把粗排、压缩与查询参数混在一起。

参考资料

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>