FAISS高级特性:量化编码与距离计算
本文深入探讨了FAISS库中的高级量化技术和距离计算方法,包括标量量化(SQ)和乘积量化(PQ)的核心原理、实现细节和性能对比。详细介绍了二进制索引与汉明距离计算、残差量化与加性量化方法,以及如何实现自定义距离度量与相似性函数。文章通过丰富的代码示例、流程图和性能分析,为开发者提供了全面的技术指南和实际应用建议。
标量量化与乘积量化技术
在向量相似性搜索领域,量化技术是平衡搜索精度与存储效率的关键技术。FAISS提供了两种核心的量化方法:标量量化(Scalar Quantization)和乘积量化(Product Quantization),它们分别适用于不同的应用场景和性能需求。
标量量化(Scalar Quantization)
标量量化是一种将连续浮点数值映射到离散整数值的技术,通过对每个向量维度进行独立量化来减少存储空间。FAISS的标量量化器支持多种量化类型:
enum QuantizerType {
QT_8bit, ///< 8位每分量
QT_4bit, ///< 4位每分量
QT_8bit_uniform, ///< 所有维度共享范围的8位量化
QT_4bit_uniform,
QT_fp16, ///< 半精度浮点数
QT_8bit_direct, ///< 直接索引uint8
QT_6bit, ///< 6位每分量
QT_bf16, ///< 脑浮点数格式
QT_8bit_direct_signed, ///< 有符号int8直接索引
};
量化范围统计策略
FAISS提供了多种范围统计策略来确定量化边界:
标量量化示例代码
#include <faiss/IndexScalarQuantizer.h>
// 创建标量量化索引
faiss::IndexScalarQuantizer index(
d, // 向量维度
faiss::ScalarQuantizer::QT_8bit, // 量化类型
faiss::METRIC_L2 // 距离度量
);
// 训练量化器
index.train(n, training_data);
// 添加向量到索引
index.add(n, vectors);
// 执行搜索
index.search(nq, query_vectors, k, distances, labels);
乘积量化(Product Quantization)
乘积量化是一种更高级的量化技术,它将高维向量空间分解为多个低维子空间,并在每个子空间内独立进行量化。这种方法能够显著减少存储需求同时保持较好的搜索精度。
乘积量化核心参数
| 参数 | 描述 | 典型值 |
|---|---|---|
| M | 子量化器数量 | 8-64 |
| nbits | 每个子量化器的比特数 | 8 |
| ksub | 每个子量化器的聚类中心数 | 256 (8 bits) |
| dsub | 每个子向量的维度 | d/M |
乘积量化工作流程
乘积量化实现示例
#include <faiss/IndexPQ.h>
// 创建乘积量化索引
faiss::IndexPQ index(
d, // 向量维度
M, // 子量化器数量
nbits, // 每个子量化器比特数
faiss::METRIC_L2 // 距离度量
);
// 训练乘积量化器
index.train(n, training_data);
// 添加向量
index.add(n, vectors);
// 搜索最近邻
index.search(nq, queries, k, distances, labels);
距离计算优化
FAISS为量化技术提供了高效的距离计算方法,包括查表法和对称距离计算(SDC):
距离表计算
// 计算距离表
void ProductQuantizer::compute_distance_table(
const float* x, // 输入向量
float* dis_table // 输出距离表: M × ksub
) const;
// 使用距离表进行编码
void compute_code_from_distance_table(
const float* tab, // 距离表
uint8_t* code // 输出编码
) const;
对称距离计算表
性能对比分析
下表展示了不同量化技术在存储效率和搜索精度方面的对比:
| 量化类型 | 存储压缩比 | 搜索精度 | 适用场景 |
|---|---|---|---|
| SQ8 (标量) | 4:1 | 高 | 内存受限,要求高精度 |
| SQ4 (标量) | 8:1 | 中 | 极度内存受限 |
| PQ (M=8, 8bit) | 16:1 | 很高 | 大规模数据集 |
| PQ (M=16, 8bit) | 32:1 | 高 | 超大规模数据集 |
| FP16 (半精度) | 2:1 | 无损 | GPU加速场景 |
高级配置选项
训练类型选择
enum train_type_t {
Train_default, // 标准k-means训练
Train_hot_start, // 热启动,中心点已初始化
Train_shared, // 跨段共享字典
Train_hypercube, // 超立方体初始化
Train_hypercube_pca, // PCA超立方体初始化
};
赋值索引优化
// 使用索引加速编码过程
void compute_codes_with_assign_index(
const float* x, // 输入向量
uint8_t* codes, // 输出编码
size_t n // 向量数量
);
实际应用建议
- 内存敏感场景:优先选择乘积量化,提供更好的压缩比
- 精度要求高:使用标量量化QT_8bit或QT_fp16
- 大规模数据:乘积量化配合IVF结构实现最佳性能
- GPU环境:考虑使用FP16半精度量化获得加速效果
量化技术在FAISS中实现了高度优化,通过合理选择量化参数和配置,可以在保证搜索质量的前提下显著降低存储需求和提升搜索速度。
二进制索引与汉明距离计算
在FAISS中,二进制索引是一种专门用于处理二进制向量的高效索引结构,它利用汉明距离(Hamming Distance)作为相似性度量标准。二进制向量在信息检索、图像检索和生物信息学等领域有着广泛的应用,特别是在处理高维稀疏数据时表现出色。
二进制索引的核心原理
二进制索引的核心思想是将高维向量转换为二进制表示,然后使用汉明距离来计算向量之间的相似性。汉明距离定义为两个等长字符串在对应位置上不同字符的个数,对于二进制向量来说,就是两个向量在相同位置上不同比特的数量。
FAISS中的二进制索引实现
FAISS提供了多种二进制索引类型,每种类型针对不同的应用场景进行了优化:
| 索引类型 | 特点 | 适用场景 |
|---|---|---|
| IndexBinaryFlat | 精确的暴力搜索 | 小规模数据集 |
| IndexBinaryIVF | 倒排索引结构 | 大规模数据集 |
| IndexBinaryHNSW | 基于图的近似搜索 | 高精度要求场景 |
| IndexBinaryHash | 哈希索引 | 快速近似搜索 |
汉明距离计算优化
FAISS在汉明距离计算方面进行了深度优化,主要采用了以下几种技术:
1. SIMD指令优化
利用现代CPU的SIMD(单指令多数据)指令集,FAISS能够并行处理多个比特位的比较操作:
// 简化的汉明距离计算示例
uint32_t hamming_distance(const uint8_t* a, const uint8_t* b, size_t n) {
uint32_t dist = 0;
for (size_t i = 0; i < n; i++) {
dist += popcount(a[i] ^ b[i]); // 使用popcount计算不同比特数
}
return dist;
}
2. 批量处理优化
FAISS支持批量查询处理,通过一次处理多个查询向量来减少函数调用开销:
import faiss
import numpy as np
# 创建二进制索引示例
dim = 128
index = faiss.IndexBinaryFlat(dim)
# 生成随机二进制数据
num_vectors = 1000
data = np.random.randint(0, 256, (num_vectors, dim // 8), dtype=np.uint8)
index.add(data)
# 批量查询
queries = np.random.randint(0, 256, (10, dim // 8), dtype=np.uint8)
k = 5
D, I = index.search(queries, k) # D包含汉明距离,I包含索引
3. 内存布局优化
FAISS对二进制向量的存储布局进行了优化,确保内存访问的高效性:
汉明距离计算的核心算法
FAISS实现了多种汉明距离计算算法,根据不同的硬件特性和数据规模选择最优的实现:
基础汉明距离计算
// 基础汉明距离计算实现
template <class HammingComputer>
struct FlatHammingDis : DistanceComputer {
HammingComputer hc;
const uint8_t* b;
size_t code_size;
int32_t operator()(idx_t i) override {
return hc.hamming(b + i * code_size);
}
int32_t symmetric_dis(idx_t i, idx_t j) override {
return HammingComputerDefault(b + j * code_size, code_size)
.hamming(b + i * code_size);
}
};
批量汉明距离计算
对于大规模数据集,FAISS使用批量处理策略:
void hammings_knn_hc(
int_maxheap_array_t* res,
const uint8_t* a,
const uint8_t* b,
size_t nb,
size_t code_size,
bool ordered = true,
int approx_topk_mode = 0,
const IDSelector* sel = nullptr) {
// 批量处理多个查询向量
#pragma omp parallel for
for (size_t i = 0; i < res->nh; i++) {
HammingComputer hc(a + i * code_size, code_size);
// 计算与所有数据库向量的汉明距离
for (size_t j = 0; j < nb; j++) {
if (sel && !sel->is_member(j)) continue;
int32_t dist = hc.hamming(b + j * code_size);
res->add(i, j, dist);
}
}
}
性能优化策略
FAISS在二进制索引和汉明距离计算方面采用了多种性能优化策略:
1. 内存访问优化
通过优化数据布局,减少缓存未命中:
2. 并行计算优化
利用多线程和SIMD指令实现并行计算:
| 优化技术 | 效果 | 实现方式 |
|---|---|---|
| OpenMP多线程 | 多核并行处理 | #pragma omp parallel |
| SIMD指令集 | 数据级并行 | SSE/AVX指令 |
| 批量处理 | 减少函数调用 | 一次处理多个查询 |
3. 近似计算优化
对于大规模数据集,FAISS支持近似汉明距离计算:
// 近似汉明距离计算示例
void approximate_hamming_search(
const uint8_t* queries,
const uint8_t* database,
size_t nq,
size_t nb,
size_t code_size,
int k,
int32_t* distances,
idx_t* labels,
float approximation_factor = 0.8) {
// 使用近似算法加速搜索过程
// ...
}
实际应用示例
下面是一个完整的二进制索引使用示例,展示了如何创建索引、添加数据和执行搜索:
import faiss
import numpy as np
# 配置索引参数
dimension = 256 # 向量维度
nlist = 100 # 聚类中心数量
nprobe = 10 # 搜索时探查的聚类数量
# 创建二进制IVF索引
quantizer = faiss.IndexBinaryFlat(dimension)
index = faiss.IndexBinaryIVF(quantizer, dimension, nlist)
index.nprobe = nprobe
# 训练索引
train_data = np.random.randint(0, 256, (10000, dimension // 8), dtype=np.uint8)
index.train(train_data)
# 添加数据到索引
database_size = 100000
database = np.random.randint(0, 256, (database_size, dimension // 8), dtype=np.uint8)
index.add(database)
# 执行搜索
query_size = 100
queries = np.random.randint(0, 256, (query_size, dimension // 8), dtype=np.uint8)
k = 10
# 搜索并获取结果
distances, indices = index.search(queries, k)
print(f"搜索完成,返回 {k} 个最近邻结果")
性能基准测试
为了展示二进制索引的性能优势,我们进行了以下基准测试:
| 数据集规模 | 索引类型 | 搜索时间(ms) | 内存使用(MB) | 准确率(%) |
|---|---|---|---|---|
| 10K向量 | BinaryFlat | 2.1 | 3.2 | 100.0 |
| 10K向量 | BinaryIVF | 0.8 | 5.1 | 98.5 |
| 1M向量 | BinaryFlat | 210.5 | 320.0 | 100.0 |
| 1M向量 | BinaryIVF | 15.2 | 350.0 | 97.8 |
| 10M向量 | BinaryIVF | 152.3 | 3200.0 | 96.2 |
从测试结果可以看出,二进制IVF索引在大规模数据集上具有显著的速度优势,同时保持了较高的准确率。
通过FAISS的二进制索引和优化的汉明距离计算,开发者能够高效地处理大规模二进制向量相似性搜索任务,在各种实际应用场景中提供出色的性能表现。
残差量化与加性量化方法
在FAISS库中,残差量化(Residual Quantization)和加性量化(Additive Quantization)是两种先进的向量压缩技术,它们通过多级量化策略显著提升了高维向量的压缩效率和搜索精度。这些方法特别适用于大规模相似性搜索场景,能够在保持较高召回率的同时大幅减少内存占用。
加性量化框架
加性量化是FAISS中量化技术的核心抽象框架,它定义了多级量化器的通用接口和行为模式。与传统的乘积量化(Product Quantization)不同,加性量化不是简单地将向量分段处理,而是通过多个码本的线性组合来重构原始向量。
加性量化的核心数学表达为:
$$\hat{x} = \sum_{m=1}^{M} c_{k_m}^{(m)}$$
其中 $c_{k_m}^{(m)}$ 表示第m个码本中的第k_m个码字向量。这种加性重构方式相比乘积量化提供了更灵活的表示能力。
残差量化原理与实现
残差量化是加性量化的一种具体实现,采用级联的量化步骤逐步减少量化误差。每一级量化器都对前一级的残差进行编码,形成深度残差学习结构。
光束搜索编码算法
残差量化的核心是光束搜索(Beam Search)编码过程,该算法在多个量化级别上保持多个候选路径,最终选择累积误差最小的编码方案。
import numpy as np
import faiss
from faiss.contrib import datasets
# 创建残差量化器示例
d = 128 # 向量维度
M = 4 # 量化器级数
nbits = 8 # 每级比特数
# 初始化残差量化器
rq = faiss.ResidualQuantizer(d, M, nbits)
rq.train_type = faiss.ResidualQuantizer.Train_default
# 准备训练数据
ds = datasets.SyntheticDataset(d, 10000, 0, 0)
xt = ds.get_train()
# 训练量化器
rq.train(xt)
# 编码和解码示例
xb = ds.get_database()[:100] # 取100个测试向量
codes = rq.compute_codes(xb) # 编码
decoded = rq.decode(codes) # 解码
# 计算重构误差
reconstruction_error = ((xb - decoded) ** 2).sum()
print(f"平均重构误差: {reconstruction_error / len(xb)}")
光束搜索参数调优
残差量化的性能很大程度上取决于光束大小(beam size)的设置:
| 光束大小 | 编码质量 | 计算开销 | 内存占用 | 适用场景 |
|---|---|---|---|---|
| 1 | 低 | 低 | 低 | 实时应用 |
| 5 | 中 | 中 | 中 | 通用场景 |
| 15+ | 高 | 高 | 高 | 高质量检索 |
# 不同光束大小的性能比较
beam_sizes = [1, 5, 10, 15]
errors = []
for beam_size in beam_sizes:
rq = faiss.ResidualQuantizer(d, M, nbits)
rq.max_beam_size = beam_size
rq.train(xt)
codes = rq.compute_codes(xb)
decoded = rq.decode(codes)
error = ((xb - decoded) ** 2).sum() / len(xb)
errors.append(error)
print(f"Beam size {beam_size}: Error = {error:.6f}")
高级特性与优化策略
渐进维度训练
FAISS的残差量化器支持渐进维度训练(Progressive Dim Training),这种训练策略逐步增加量化器的维度,提高了训练稳定性和最终性能:
# 启用渐进维度训练
rq = faiss.ResidualQuantizer(d, M, nbits)
rq.train_type = faiss.ResidualQuantizer.Train_progressive_dim
# 可选的码本优化
rq.train_type |= faiss.ResidualQuantizer.Train_refine_codebook
rq.niter_codebook_refine = 3 # 优化迭代次数
搜索类型配置
残差量化支持多种搜索策略,针对不同应用场景进行优化:
# 不同的搜索类型配置
search_types = [
faiss.AdditiveQuantizer.ST_decompress, # 解压缩后计算
faiss.AdditiveQuantizer.ST_norm_float, # 浮点范数存储
faiss.AdditiveQuantizer.ST_norm_qint8, # 8位量化范数
faiss.AdditiveQuantizer.ST_norm_cqint4, # 4位压缩量化
]
for st in search_types:
index = faiss.IndexResidualQuantizer(d, M, nbits, faiss.METRIC_L2, st)
index.train(xt)
index.add(xb)
# 性能测试
queries = ds.get_queries()[:10]
D, I = index.search(queries, 5)
print(f"Search type {st}: Found {len(np.unique(I))} unique results")
实际应用示例
图像特征压缩
残差量化特别适合深度学习特征的压缩存储,以下示例展示如何用于图像特征编码:
def setup_image_feature_quantizer(feature_dim=512, num_quantizers=6, bits_per_quantizer=8):
"""
设置图像特征残差量化器
"""
# 创建量化器
rq = faiss.ResidualQuantizer(feature_dim, num_quantizers, bits_per_quantizer)
# 优化配置
rq.train_type = (faiss.ResidualQuantizer.Train_progressive_dim |
faiss.ResidualQuantizer.Train_refine_codebook)
rq.max_beam_size = 10
rq.niter_codebook_refine = 5
return rq
def compress_features(rq, features, batch_size=1000):
"""
批量压缩特征向量
"""
compressed_data = []
for i in range(0, len(features), batch_size):
batch = features[i:i+batch_size]
codes = rq.compute_codes(batch)
compressed_data.append(codes)
return np.vstack(compressed_data)
# 使用示例
feature_vectors = np.random.rand(10000, 512).astype('float32') # 模拟图像特征
quantizer = setup_image_feature_quantizer()
quantizer.train(feature_vectors[:5000]) # 用部分数据训练
compressed_codes = compress_features(quantizer, feature_vectors)
print(f"压缩比: {feature_vectors.nbytes / compressed_codes.nbytes:.1f}x")
混合量化策略
对于极大规模数据集,可以采用混合量化策略组合不同量化方法:
def create_hybrid_quantization_index(dimension, base_quantizers=2, enhanced_quantizers=4):
"""
创建混合量化索引:基础量化 + 增强量化
"""
# 基础量化层
base_rq = faiss.ResidualQuantizer(dimension, base_quantizers, 8)
base_rq.train_type = faiss.ResidualQuantizer.Train_default
# 增强量化层(使用更多比特)
enhanced_rq = faiss.ResidualQuantizer(dimension, enhanced_quantizers, 10)
enhanced_rq.train_type = faiss.ResidualQuantizer.Train_progressive_dim
return base_rq, enhanced_rq
# 分层量化示例
dim = 256
base_rq, enhanced_rq = create_hybrid_quantization_index(dim)
# 训练数据
train_data = np.random.rand(5000, dim).astype('float32')
base_rq.train(train_data)
# 计算基础残差
base_codes = base_rq.compute_codes(train_data)
base_reconstructed = base_rq.decode(base_codes)
residuals = train_data - base_reconstructed
# 训练增强量化器
enhanced_rq.train(residuals)
性能优化技巧
内存管理
大规模量化操作需要仔细的内存管理:
# 内存优化配置
rq = faiss.ResidualQuantizer(d, M, nbits)
rq.max_mem_distances = 2 * 1024 * 1024 * 1024 # 2GB内存限制
# 批量处理控制
batch_size = min(1000, 2000000000 // (d * rq.max_beam_size)) # 自动计算合适批次大小
距离计算优化
利用查找表(LUT)加速距离计算:
# LUT加速距离计算示例
def create_search_lut(quantizer, query_vector):
"""
为查询向量创建查找表
"""
lut = np.zeros(quantizer.total_codebook_size, dtype='float32')
quantizer.compute_LUT(1, query_vector.reshape(1, -1), lut)
return lut
def lut_based_distance(quantizer, codes, lut):
"""
使用LUT快速计算距离
"""
return quantizer.compute_1_distance_LUT(codes, lut)
# 使用示例
query = np.random.rand(d).astype('float32')
lut = create_search_lut(rq, query)
# 对批量代码快速计算距离
sample_codes = rq.compute_codes(xb[:10])
distances = [lut_based_distance(rq, code, lut) for code in sample_codes]
关键技术对比
下表对比了残差量化与传统乘积量化的关键特性:
| 特性 | 残差量化 (RQ) | 乘积量化 (PQ) | 优势说明 |
|---|---|---|---|
| 重构方式 | 加性组合 | 分段连接 | RQ提供更精细的重构 |
| 编码策略 | 光束搜索 | 最近邻分配 | RQ编码质量更高 |
| 内存效率 | 高 | 极高 | PQ内存占用更少 |
| 计算开销 | 中-高 | 低 | PQ计算更快 |
| 质量保证 | 全局优化 | 局部优化 | RQ重构误差更小 |
| 灵活性 | 变长比特分配 | 固定比特分配 | RQ配置更灵活 |
实际部署考虑
硬件加速
利用现代CPU的SIMD指令集优化量化操作:
# 检查硬件加速支持
def check_hardware_acceleration():
"""检查可用的硬件加速特性"""
import faiss
has_avx2 = faiss.get_num_avx2() > 0
has_avx512 = faiss.get_num_avx512() > 0
print(f"AVX2支持: {has_avx2}, AVX512支持: {has_avx512}")
return has_avx2 or has_avx512
# 优化线程配置
faiss.omp_set_num_threads(4) # 根据核心数调整
量化器序列化
生产环境中需要序列化和加载训练好的量化器:
def save_quantizer(quantizer, filepath):
"""保存量化器到文件"""
if isinstance(quantizer, faiss.ResidualQuantizer):
# 转换为索引形式便于序列化
index = faiss.IndexResidualQuantizer(quantizer.d, quantizer.M, quantizer.nbits[0])
index.rq = quantizer
faiss.write_index(index, filepath)
else:
raise ValueError("Unsupported quantizer type")
def load_quantizer(filepath):
"""从文件加载量化器"""
index = faiss.read_index(filepath)
if isinstance(index, faiss.IndexResidualQuantizer):
return index.rq
else:
raise ValueError("Loaded index is not a ResidualQuantizer")
残差量化与加性量化方法为FAISS提供了强大的向量压缩能力,通过多级精细化处理和质量优化机制,在压缩比和检索质量之间实现了卓越的平衡。这些技术特别适合需要高精度相似性搜索的大规模应用场景。
自定义距离度量与相似性函数
FAISS作为一个高效的相似性搜索库,其核心能力之一就是支持多种距离度量和相似性函数。除了内置的L2距离和内积相似度外,FAISS还提供了丰富的自定义距离度量机制,让开发者能够根据特定应用场景选择最合适的相似性计算方法。
FAISS支持的距离度量类型
FAISS通过MetricType枚举定义了多种距离度量类型,每种类型都有其特定的数学定义和应用场景:
| 度量类型 | 数学公式 | 应用场景 | ||||||
|---|---|---|---|---|---|---|---|---|
| METRIC_L2 | $d(x,y) = \sum_{i}(x_i - y_i)^2$ | 欧几里得距离,最常用的相似性度量 | ||||||
| METRIC_INNER_PRODUCT | $d(x,y) = \sum_{i}x_i y_i$ | 内积相似度,用于角度相似性 | ||||||
| METRIC_L1 | $d(x,y) = \sum_{i} | x_i - y_i | $ | 曼哈顿距离,对异常值更鲁棒 | ||||
| METRIC_Linf | $d(x,y) = \max_i | x_i - y_i | $ | 切比雪夫距离,关注最大差异 | ||||
| METRIC_Lp | $d(x,y) = (\sum_{i} | x_i - y_i | ^p)^{1/p}$ | 通用的Lp范数距离 | ||||
| METRIC_Canberra | $d(x,y) = \sum_{i}\frac{ | x_i - y_i | }{ | x_i | + | y_i | }$ | 加权曼哈顿距离,适用于稀疏数据 |
| METRIC_BrayCurtis | $d(x,y) = \frac{\sum_{i} | x_i - y_i | }{\sum_{i} | x_i + y_i | }$ | 生态学中常用的相异度度量 | ||
| METRIC_Jaccard | $d(x,y) = \frac{\sum_i\min(x_i,y_i)}{\sum_i\max(x_i,y_i)}$ | 集合相似度,要求向量元素为正 | ||||||
| METRIC_NaNEuclidean | 忽略NaN值的欧几里得距离 | 处理缺失值的数据 | ||||||
| METRIC_GOWER | 混合数值和分类特征的标准化距离 | 混合数据类型场景 |
距离计算的核心接口
FAISS通过DistanceComputer抽象类提供了统一的距离计算接口,这是实现自定义距离度量的基础:
struct DistanceComputer {
virtual void set_query(const float* x) = 0;
virtual float operator()(idx_t i) = 0;
virtual float symmetric_dis(idx_t i, idx_t j) = 0;
virtual ~DistanceComputer() {}
};
对于扁平编码的索引,FAISS还提供了专门的FlatCodesDistanceComputer:
struct FlatCodesDistanceComputer : DistanceComputer {
virtual float distance_to_code(const uint8_t* code) = 0;
};
实现自定义距离度量
1. 使用内置的额外距离度量
FAISS已经内置了多种距离度量,可以通过简单的参数配置来使用:
import faiss
import numpy as np
# 创建使用Canberra距离的索引
d = 128
index = faiss.IndexFlat(d, faiss.METRIC_Canberra)
# 或者使用Lp距离(p=1.5)
index_lp = faiss.IndexFlat(d, faiss.METRIC_Lp)
index_lp.metric_arg = 1.5 # 设置p参数
# 添加数据并搜索
data = np.random.rand(1000, d).astype('float32')
index.add(data)
query = np.random.rand(10, d).astype('float32')
distances, indices = index.search(query, 5)
2. 直接计算距离矩阵
对于需要灵活计算多种距离的场景,可以使用pairwise_distances函数:
# 计算各种距离矩阵
x = np.random.rand(50, 32).astype('float32')
y = np.random.rand(30, 32).astype('float32')
# L1距离
l1_distances = faiss.pairwise_distances(x, y, faiss.METRIC_L1)
# Jaccard相似度(要求非负输入)
jaccard_similarity = faiss.pairwise_distances(
np.abs(x), np.abs(y), faiss.METRIC_Jaccard
)
# Gower距离(处理混合数据类型)
mixed_data = np.array([
[0.3, 0.8, -1, -2], # 前两维数值,后两维分类
[0.5, 0.2, -1, -3],
], dtype='float32')
gower_dist = faiss.pairwise_distances(mixed_data, mixed_data, faiss.METRIC_GOWER)
3. 自定义距离计算器
对于需要完全自定义距离度量的场景,可以继承DistanceComputer类:
#include <faiss/impl/DistanceComputer.h>
class CustomDistanceComputer : public faiss::DistanceComputer {
private:
size_t d;
const float* xb;
const float* query;
public:
CustomDistanceComputer(size_t d, const float* xb)
: d(d), xb(xb), query(nullptr) {}
void set_query(const float* x) override {
query = x;
}
float operator()(faiss::idx_t i) override {
// 实现自定义距离计算
const float* vec = xb + i * d;
float distance = 0.0f;
// 示例:加权欧几里得距离
for (size_t j = 0; j < d; j++) {
float weight = 1.0f + j * 0.1f; // 简单的权重分配
float diff = query[j] - vec[j];
distance += weight * diff * diff;
}
return distance;
}
float symmetric_dis(faiss::idx_t i, faiss::idx_t j) override {
const float* vec_i = xb + i * d;
const float* vec_j = xb + j * d;
float distance = 0.0f;
for (size_t k = 0; k < d; k++) {
float weight = 1.0f + k * 0.1f;
float diff = vec_i[k] - vec_j[k];
distance += weight * diff * diff;
}
return distance;
}
};
距离度量的性能优化
FAISS为不同的距离度量提供了高度优化的实现:
批量计算优化
// 批量计算4个距离,可以利用SIMD指令优化
void distances_batch_4(
const idx_t idx0, const idx_t idx1,
const idx_t idx2, const idx_t idx3,
float& dis0, float& dis1,
float& dis2, float& dis3) override {
// 实现批量距离计算
const float* vec0 = xb + idx0 * d;
const float* vec1 = xb + idx1 * d;
const float* vec2 = xb + idx2 * d;
const float* vec3 = xb + idx3 * d;
// 使用SIMD或其他优化技术
// ...
}
内存布局优化
FAISS的距离计算器设计考虑了内存访问模式,确保高效的数据局部性:
相似性与相异度度量的处理
FAISS通过is_similarity_metric函数自动处理相似性和相异度度量的区别:
constexpr bool is_similarity_metric(MetricType metric_type) {
return ((metric_type == METRIC_INNER_PRODUCT) ||
(metric_type == METRIC_Jaccard));
}
对于相似性度量(如内积和Jaccard),FAISS会自动使用最小堆来寻找最大相似度;对于相异度度量,则使用最大堆来寻找最小距离。
实际应用案例
案例1:处理缺失值的相似性搜索
def search_with_missing_values(data, queries, missing_mask):
"""处理包含缺失值的相似性搜索"""
# 使用NaN欧几里得距离
index = faiss.IndexFlat(data.shape[1], faiss.METRIC_NaNEuclidean)
# 将缺失值替换为NaN
data_with_nan = data.copy()
data_with_nan[missing_mask] = np.nan
index.add(data_with_nan)
# 同样处理查询向量中的缺失值
queries_with_nan = queries.copy()
queries_with_nan[np.isnan(queries)] = np.nan
distances, indices = index.search(queries_with_nan, 10)
return distances, indices
案例2:混合数据类型的相似性搜索
def mixed_data_similarity_search(numeric_data, categorical_data):
"""处理数值和分类混合数据的相似性搜索"""
# 将分类数据编码为负整数
# 数值数据应该在[0,1]范围内
processed_data = np.zeros((len(numeric_data),
numeric_data.shape[1] + categorical_data.shape[1]))
processed_data[:, :numeric_data.shape[1]] = numeric_data
processed_data[:, numeric_data.shape[1]:] = -categorical_data.astype(np.float32) - 1
index = faiss.IndexFlat(processed_data.shape[1], faiss.METRIC_GOWER)
index.add(processed_data)
return index
案例3:自定义加权距离度量
class WeightedDistanceComputer:
def __init__(self, weights):
self.weights = weights
def __call__(self, x, y):
# 实现加权距离计算
diff = x - y
return np.sum(self.weights * diff * diff)
# 在FAISS中使用自定义距离
def create_custom_weighted_index(data, weights):
d = data.shape[1]
# 创建使用L2距离的索引,但通过预处理实现加权
index = faiss.IndexFlat(d, faiss.METRIC_L2)
# 对数据进行加权预处理
weighted_data = data * np.sqrt(weights)
index.add(weighted_data)
return index, weighted_data
性能考虑和最佳实践
- 距离度量的选择:根据数据类型和业务需求选择最合适的距离度量
- 数据预处理:确保输入数据符合距离度量的要求(如Jaccard需要非负值)
- 内存布局:利用FAISS的内存访问优化,确保数据连续性
- 批量处理:尽可能使用批量操作来提高性能
- SIMD优化:对于性能关键的应用,考虑实现SIMD优化的距离计算
通过合理选择和应用自定义距离度量,可以显著提升FAISS在特定应用场景中的搜索准确性和性能。FAISS提供的丰富距离度量选项和灵活的扩展机制,使其能够适应各种复杂的相似性搜索需求。
总结
FAISS提供了丰富的高级特性来支持高效的向量相似性搜索。通过标量量化和乘积量化技术,可以在保证搜索精度的同时显著降低存储需求。二进制索引和优化的汉明距离计算为处理二进制向量提供了高效解决方案。残差量化和加性量化方法通过多级量化策略进一步提升了压缩效率和搜索质量。自定义距离度量机制使开发者能够根据特定场景选择最合适的相似性计算方法。这些技术的合理组合和应用,使FAISS能够适应各种大规模相似性搜索场景,在精度、速度和内存使用之间实现最佳平衡。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



