Rust高效近邻搜索库anndists的使用:基于近似最近邻算法的快速距离计算与相似性搜索

Rust高效近邻搜索库anndists的使用:基于近似最近邻算法的快速距离计算与相似性搜索

简介

anndists是一个Rust库,为相关crate如hnsw_rs、annembed和coreset提供距离计算功能。

核心特性

所有距离计算都实现了Distance trait:

pub trait Distance<T: Send + Sync> {
    fn eval(&self, va: &[T], vb: &[T]) -> f32;
}

功能

该库提供以下功能:

  • 常用距离度量: L1、L2、余弦、Jaccard、汉明距离(适用于标准数值类型的向量),以及u16上的Levenshtein距离
  • 概率分布之间的Hellinger距离和Jeffreys散度(f32和f64)
  • 概率分布之间的Jensen-Shannon距离(f32和f64)
  • 允许用户实现自定义距离的Trait
  • 为最常用情况提供Simd实现

实现细节

Simd支持通过simdeez crate在Intel处理器上提供,并使用std::simd进行部分实现。

构建选项

Simd支持

  • 使用simdeez_f特性启用simdeez提供的SIMD(x86_64处理器)
  • 使用stdsimd特性实验std::simd(需要nightly编译器)

示例代码

以下是使用anndists库进行距离计算的完整示例:

use anndists::prelude::*;

fn main() {
    // 创建两个向量
    let vec1 = vec![1.0, 2.0, 3.0];
    let vec2 = vec![4.0, 5.0, 6.0];
    
    // 计算L2距离(欧几里得距离)
    let l2_dist = DistL2::eval(&vec1, &vec2);
    println!("L2 Distance: {}", l2_dist);
    
    // 计算余弦距离
    let cosine_dist = DistCosine::eval(&vec1, &vec2);
    println!("Cosine Distance: {}", cosine_dist);
    
    // 计算L1距离(曼哈顿距离)
    let l1_dist = DistL1::eval(&vec1, &vec2);
    println!("L1 Distance: {}", l1_dist);
    
    // 创建自定义距离
    struct CustomDistance;
    impl Distance<f32> for CustomDistance {
        fn eval(&self, va: &[f32], vb: &[f32]) -> f32 {
            // 实现自定义距离计算逻辑
            va.iter().zip(vb.iter()).map(|(a, b)| (a - b).abs()).sum()
        }
    }
    
    let custom_dist = CustomDistance.eval(&vec1, &vec2);
    println!("Custom Distance: {}", custom_dist);
}

构建说明

要使用SIMD优化:

cargo build --release --features "simdeez_f"  # 对于x86_64处理器
# 或
cargo build --release --features "stdsimd"  # 需要nightly编译器

许可证

该库采用以下任一许可证:

  • Apache License, Version 2.0
  • MIT license

贡献者

Petter Egesund贡献了DistLevenshtein距离实现。

版本

当前版本为0.1.3,已切换到edition=2024。

完整示例demo

以下是一个更完整的示例,展示了anndists库的多种距离计算方式:

use anndists::prelude::*;

fn main() {
    // 示例1: 数值向量距离计算
    let vec_a = vec![1.0, 2.0, 3.0];
    let vec_b = vec![4.0, 5.0, 6.0];
    
    println!("数值向量距离计算:");
    println!("欧几里得距离(L2): {}", DistL2::eval(&vec_a, &vec_b));
    println!("曼哈顿距离(L1): {}", DistL1::eval(&vec_a, &vec_b));
    println!("余弦距离: {}", DistCosine::eval(&vec_a, &vec_b));
    
    // 示例2: 二进制向量汉明距离
    let bin_vec1 = vec![1, 0, 1, 0];
    let bin_vec2 = vec![0, 1, 1, 0];
    println!("\n二进制向量汉明距离: {}", DistHamming::eval(&bin_vec1, &bin_vec2));
    
    // 示例3: 概率分布距离计算
    let prob_a = vec![0.2, 0.3, 0.5];
    let prob_b = vec![0.1, 0.4, 0.5];
    println!("\n概率分布距离计算:");
    println!("Hellinger距离: {}", DistHellinger::eval(&prob_a, &prob_b));
    println!("Jensen-Shannon距离: {}", DistJensenShannon::eval(&prob_a, &prob_b));
    
    // 示例4: 字符串相似度(Levenshtein距离)
    let str1 = vec![b'h' as u16, b'e' as u16, b'l' as u16, b'l' as u16, b'o' as u16];
    let str2 = vec![b'h' as u16, b'a' as u16, b'l' as u16, b'o' as u16];
    println!("\nLevenshtein距离: {}", DistLevenshtein::eval(&str1, &str2));
    
    // 示例5: 自定义距离实现
    struct SquaredEuclidean;
    impl Distance<f32> for SquaredEuclidean {
        fn eval(&self, va: &[f32], vb: &[f32]) -> f32 {
            va.iter().zip(vb.iter())
                .map(|(a, b)| (a - b).powi(2))
                .sum()
        }
    }
    
    let sq_dist = SquaredEuclidean.eval(&vec_a, &vec_b);
    println!("\n自定义平方欧几里得距离: {}", sq_dist);
}

这个完整示例展示了:

  1. 标准数值向量的多种距离计算
  2. 二进制向量的汉明距离
  3. 概率分布之间的距离度量
  4. 字符串的Levenshtein距离计算
  5. 自定义距离度量的实现

要运行这些示例,请确保在Cargo.toml中添加anndists依赖:

[dependencies]
anndists = "0.1.3"

1 回复

Rust高效近邻搜索库anndists使用指南

简介

anndists是一个基于Rust的高效近似最近邻(ANN)搜索库,专注于快速距离计算和相似性搜索。它提供了多种近似最近邻算法实现,适用于大规模数据集的相似性搜索场景。

主要特性

  • 支持多种距离度量:欧氏距离、余弦相似度等
  • 提供多种近似最近邻算法实现
  • 内存高效,适合大规模数据集
  • 线程安全的API设计
  • 提供Rust原生接口和Python绑定

安装

在Cargo.toml中添加依赖:

[dependencies]
anndists = "0.3"

基本使用方法

1. 创建索引并添加数据

use anndists::ann::Hnsw;
use anndists::distance::Euclidean;

// 创建一个HNSW索引,维度为128,使用欧氏距离
let mut index = Hnsw::<f32, Euclidean>::new(128);

// 添加向量到索引
let data = vec![
    vec![1.0, 2.0, 3.0, /* ... */], 
    vec![4.0, 5.0, 6.0, /* ... */],
    // 更多数据...
];

for (i, vec) in data.iter().enumerate() {
    index.add(i, vec);
}

// 构建索引
index.build();

2. 最近邻搜索

let query = vec![1.5, 2.5, 3.5, /* ... */];
let k = 5; // 查找5个最近邻

// 搜索最近的k个邻居
let results = index.search(&query, k);

for (id, distance) in results {
    println!("ID: {}, Distance: {}", id, distance);
}

3. 使用不同距离度量

use anndists::distance::Cosine;

// 使用余弦相似度
let mut index = Hnsw::<f32, Cosine>::new(128);

4. 批量搜索

let queries = vec![
    vec![1.5, 2.5, 3.5, /* ... */],
    vec![4.5, 5.5, 6.5, /* ... */],
];

for result in index.batch_search(&queries, k) {
    println!("Query results: {:?}", result);
}

高级配置

调整HNSW参数

use anndists::params::HnswParams;

let params = HnswParams {
    m: 16,      // 每个节点的最大连接数
    ef_construction: 200, // 构建时的搜索范围
    ef_search: 100,       // 搜索时的搜索范围
    ..Default::default()
};

let mut index = Hnsw::<f32, Euclidean>::with_params(128, params);

持久化索引

use std::fs::File;
use std::io::{BufWriter, BufReader};

// 保存索引
let file = File::create("index.bin").unwrap();
let writer = BufWriter::new(file);
bincode::serialize_into(writer, &index).unwrap();

// 加载索引
let file = File::open("index.bin").unwrap();
let reader = BufReader::new(file);
let index: Hnsw<f32, Euclidean> = bincode::deserialize_from(reader).unwrap();

性能建议

  1. 对于大型数据集,分批添加数据并定期调用build_partial方法
  2. 根据数据特性调整HNSW参数
  3. 考虑使用并行构建索引(如果可用)
  4. 对于高维数据,可能需要增加ef_constructionef_search

应用场景

  • 推荐系统
  • 图像/视频检索
  • 自然语言处理中的语义搜索
  • 异常检测
  • 聚类分析

完整示例demo

use anndists::ann::Hnsw;
use anndists::distance::{Euclidean, Cosine};
use anndists::params::HnswParams;

fn main() {
    // 示例1: 使用欧氏距离创建索引并搜索
    let mut index_euclidean = Hnsw::<f32, Euclidean>::new(3);
    
    let data = vec![
        vec![1.0, 2.0, 3.0],
        vec![4.0, 5.0, 6.0],
        vec![7.0, 8.0, 9.0],
    ];
    
    for (i, vec) in data.iter().enumerate() {
        index_euclidean.add(i, vec);
    }
    
    index_euclidean.build();
    
    let query = vec![1.5, 2.5, 3.5];
    let results = index_euclidean.search(&query, 2);
    println!("欧氏距离搜索结果: {:?}", results);
    
    // 示例2: 使用余弦相似度
    let mut index_cosine = Hnsw::<f32, Cosine>::new(3);
    
    for (i, vec) in data.iter().enumerate() {
        index_cosine.add(i, vec);
    }
    
    index_cosine.build();
    
    let results = index_cosine.search(&query, 2);
    println!("余弦相似度搜索结果: {:?}", results);
    
    // 示例3: 高级配置
    let params = HnswParams {
        m: 8,
        ef_construction: 100,
        ef_search: 50,
        ..Default::default()
    };
    
    let mut custom_index = Hnsw::<f32, Euclidean>::with_params(3, params);
    
    for (i, vec) in data.iter().enumerate() {
        custom_index.add(i, vec);
    }
    
    custom_index.build();
    
    let batch_results = custom_index.batch_search(&[query.clone(), vec![4.5, 5.5, 6.5]], 2);
    println!("批量搜索结果: {:?}", batch_results);
    
    // 示例4: 索引持久化
    {
        use std::fs::File;
        use std::io::{BufWriter, BufReader};
        
        // 保存索引
        let file = File::create("index.bin").unwrap();
        let writer = BufWriter::new(file);
        bincode::serialize_into(writer, &custom_index).unwrap();
        
        // 加载索引
        let file = File::open("index.bin").unwrap();
        let reader = BufReader::new(file);
        let loaded_index: Hnsw<f32, Euclidean> = bincode::deserialize_from(reader).unwrap();
        
        println!("从文件加载的索引搜索结果: {:?}", loaded_index.search(&query, 2));
    }
}

这个完整示例展示了anndists库的主要功能,包括:

  1. 使用不同距离度量(欧氏距离和余弦相似度)创建索引
  2. 添加数据和构建索引
  3. 单查询和批量查询
  4. 自定义HNSW参数
  5. 索引的序列化和反序列化
回到顶部