Faiss可以用来将向量编码为二进制数据块。这种压缩通常是有损的,也就是说,解码时只能近似还原原始向量。无论是原始向量还是编码后的二进制数据块,其大小都是固定的。
API
由于大多数Faiss索引本身就带有向量编码功能,所以编解码器(codec)API实际上就是直接使用Faiss索引作为编解码工具。你可以通过index_factory构建编解码器,并通过train方法进行训练。
Faiss编解码API专门提供了3个以sa_(standalone,独立)为前缀的函数:
sa_code_size:返回使用该编解码器对向量编码时,生成的每个编码的字节数sa_encode:将一组向量编码成二进制代码(codes)sa_decode:将一组二进制代码解码还原成向量
使用编解码器前,需要用有代表性的数据对其进行训练。训练后的编解码器可以保存到文件,也可以作为二进制数据存储。
如常见情况,API同时支持C++和Python。
示例用法
下例展示了如何将1000个20维浮点向量压缩为每个分量4位。这样每个向量最终会编码为10字节(uint8矩阵,共1000组编码)。
Python示例:数据和编码分别为float32矩阵和uint8矩阵,尺寸由Faiss自动管理。
import numpy as np
import faiss
# 生成测试数据
x = np.random.rand(1000, 20).astype('float32')
# 构建编解码器
codec = faiss.index_factory(20, "SQ4")
codec.train(x)
# 编码
code = codec.sa_encode(x)
print(x.nbytes)
# 解码
x_decoded = codec.sa_decode(code)
C++示例:数据和编码分别以浮点缓冲区和uint8_t缓冲区形式存储。
#include <vector>
#include <faiss/Index.h>
std::vector<float> x(1000 * 20);
for (int i = 0; i < 1000 * 20; i++) x[i] = drand48();
// 构建编解码器
faiss::Index *codec = faiss::index_factory(20, "SQ4");
codec->train(1000, x.data());
// 编码
std::vector<uint8_t> code(1000 * codec->sa_code_size());
codec->sa_encode(1000, x.data(), code.data());
// 解码
std::vector<float> x_decoded(1000 * codec->d);
codec->sa_decode(1000, code.data(), x_decoded.data());
你可以像操作其他Faiss索引一样使用这些编解码器,特别是通过read_index和write_index将它们保存到文件中。
如何选择合适的编解码器
选择标准
选择编解码器时主要需要权衡以下几个方面:
- 每个编码向量所占字节数:向量为何需要编码,主要目的是为了节省存储空间。
- 编解码准确度:可用还原误差(如编码和原始向量之间的L2距离)衡量;也可以根据下游任务效果衡量,例如在相似度检索任务中,经过编码的向量与未编码向量相比有多少性能损失。
- 编解码器自身大小:为了实现自包含的压缩数据,序列化(保存)后的编解码器也需要和编码结果一起传输,因此不宜太大。
- 训练 / 编码 / 解码的耗时:这通常不像索引用途那么重要,因为I/O操作才是主要瓶颈,但在部分场景下还是值得关注的。
有关编解码器在典型应用下详细对比,请参考 Vector codecs benchmarks。
编解码器的类型
常见的编解码器一般由一系列预处理步骤(预处理可以把高维向量变成更小的低维向量),再加上一个量化(quantization,向量分桶,转为整数存储)步骤构成。在factory字符串中,预处理和量化步骤以逗号分隔。
下文根据压缩强度从弱到强进行编解码器的说明。详细对比与数据集效果可查看基准测试页面。
极轻量压缩
若只需将数据减小2-4倍,标量量化(scalar quantizer)已足够。
SQfp16(压缩2倍):转为16位浮点,对精度要求不特别高的场景几乎无损SQ8(压缩4倍):每个分量压缩为8位
轻量压缩
对高维、且较“平滑”分布数据,推荐先用PCA主成分分析,再做标量量化。但需要约为输出维度10倍的样本来训练PCA,且PCA变换矩阵需与压缩模型一起存储,因此可能较大。
PCARx,SQ8(编码大小为x):先PCA和随机旋转,再用标量量化PCARx,SQ4(编码大小为x/2):前者基础上改用4位标量量化(也可用SQ6即6位)
中等强度压缩
若需要更高密度压缩,可以用乘积量化(Product Quantization,PQ)。(推荐先对数据做OPQ正交投影以提升编码效果)
OPQ64_256,PQ64(编码大小64):先降到256维再编码为64字节,OPQ的第二参数建议为4*x较合适OPQ64_256,PQ64x10(编码大小80):PQ的默认码本为8位,可通过增大为10、12等获得更平滑操作点。比如PQ8x10与PQ10x8码长相同,但准确率有差异,且编解码器更大。OPQ64_256,Residual2x14,PQ64(编码大小68):两级量化,第一级同样是量化器,通常效果优于PQ64,但训练/编码耗时更多,且压缩器较大。
二进制编码(Binary codes)
如需直接在压缩域比较二进制编码,可以使用以下编解码器:
PCAR256,LSHt:通过PCA和随机旋转后再二值化,产出256位编码;若输入维度≤256,则用RR256,LSHtITQ256,LSHt:用ITQ(Iterative Quantization)预处理数据,输出二进制编码
与普通Faiss索引的区别
Faiss索引本身可以包含已压缩的向量及额外加速检索的索引结构。理论上可以从索引中提取压缩向量,但不太方便。独立编解码器(standalone codec)专为简化此用途设计。
(实验性)高性能解码内核
Faiss针对多个IVFPQ、Residual+PQ及PQ系列编解码器,提供了实验性高性能解码内核,实现了C++模板版本。这些模板主要包含如下功能:
store():将编码还原为单个向量,输出到指定缓冲区(类似于sa_decode(1, ...))accum():将解码后向量乘以权重后累加到缓冲区,可用来高效实现解码向量的线性组合
通过这些高性能内核,解码速度可显著超过普通的sa_decode()。不过:
使用这些内核前,必须提前明确编解码器参数。同时,目前部分参数或维度组合还不被支持,仅适合实验性质的场景。
完整支持的解码器列表,请参考 SADecodeKernels.h;编程示例详见 test_cppcontrib_sa_decode.cpp。