UNPKG

@leolee9086/hnsw

Version:

JavaScript HNSW (Hierarchical Navigable Small World) 向量索引库,支持动态操作和泛型搜索

506 lines (384 loc) 12.8 kB
# @leolee9086/hnsw JavaScript HNSW (Hierarchical Navigable Small World) 向量索引库,用于快速相似性搜索。纯 JavaScript 实现,支持动态操作和泛型搜索。 ## 🚀 特性 - **动态操作**: 支持插入、搜索、删除操作,软删除设计 - **泛型支持**: 完全泛型化,支持任意数据类型和自定义距离函数 - **轻量级**: 最小化依赖,专注于核心功能 - **易用性**: 简洁的API设计,快速上手 - **可扩展**: 支持动态插入和实时搜索 - **类型安全**: 完整的TypeScript支持 ## 📦 安装 ```bash npm install @leolee9086/hnsw # 或使用 pnpm pnpm add @leolee9086/hnsw # 或使用 yarn yarn add @leolee9086/hnsw ``` ## 🚀 快速开始 ### 基本使用 ```typescript import { hnsw } from '@leolee9086/hnsw'; // 创建HNSW索引 const index = hnsw.createIndex({ M: 16, // 每个节点的最大连接数 efConstruction: 200, // 构建时的搜索参数 metricType: 'cosine' // 距离度量类型: 'cosine' | 'l2' }); // 插入向量 const vectors = [ [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], // ... 更多向量 ]; vectors.forEach(vector => { index.insertNode(vector); }); // 搜索最近邻 const queryVector = [1.5, 2.5, 3.5, 4.5]; const results = index.search(queryVector, 5); // 返回5个最近邻 console.log(results); // [ // { idx: 0, distance: 0.1 }, // { idx: 1, distance: 0.2 }, // ... // ] ``` ### 高级使用 ```typescript // 使用L2距离 const l2Index = hnsw.createIndex({ M: 32, efConstruction: 400, metricType: 'l2' }); // 自定义搜索参数 const results = index.search(queryVector, 10, 300); // 使用efSearch=300 // 删除节点 index.deleteNode(0); // 删除索引为0的节点 // 获取统计信息 const stats = index.getStats(); console.log(stats); // { // nodeCount: 100, // activeNodeCount: 99, // deletedNodeCount: 1, // entryPoint: { idx: 5, level: 3 } // } ``` ### 🎨 泛型版使用 本库提供了泛型化版本,支持任意数据类型和自定义距离函数: ```typescript import { hnsw } from '@leolee9086/hnsw'; // 1. 向量相似性搜索(使用自定义距离函数) const cosineDistance = (a: number[], b: number[]): number => { let dotProduct = 0; let normA = 0; let normB = 0; for (let i = 0; i < a.length; i++) { dotProduct += a[i]! * b[i]!; normA += a[i]! * a[i]!; normB += b[i]! * b[i]!; } normA = Math.sqrt(normA); normB = Math.sqrt(normB); if (normA === 0 || normB === 0) return 1.0; return 1.0 - dotProduct / (normA * normB); }; const vectorIndex = hnsw.createIndexGeneric({ M: 16, efConstruction: 200, distanceFunction: cosineDistance }); // 2. 字符串相似性搜索(使用编辑距离) const editDistance = (a: string, b: string): number => { const matrix: number[][] = []; for (let i = 0; i <= a.length; i++) { matrix[i] = []; matrix[i]![0] = i; } for (let j = 0; j <= b.length; j++) { matrix[0]![j] = j; } for (let i = 1; i <= a.length; i++) { for (let j = 1; j <= b.length; j++) { if (a[i - 1] === b[j - 1]) { matrix[i]![j] = matrix[i - 1]![j - 1]!; } else { matrix[i]![j] = Math.min( matrix[i - 1]![j]! + 1, // 删除 matrix[i]![j - 1]! + 1, // 插入 matrix[i - 1]![j - 1]! + 1 // 替换 ); } } } return matrix[a.length]![b.length]!; }; const stringIndex = hnsw.createIndexGeneric({ M: 16, efConstruction: 200, distanceFunction: editDistance }); // 插入字符串 const strings = ['hello', 'world', 'help', 'hell', 'hero']; strings.forEach(str => stringIndex.insertNode(str)); // 搜索相似字符串 const results = stringIndex.search('help', 3); console.log(results); // 找到编辑距离最小的字符串 // 3. 自定义对象相似性搜索 interface Point { x: number; y: number; label: string; } const euclideanDistance = (a: Point, b: Point): number => { const dx = a.x - b.x; const dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); }; const pointIndex = hnsw.createIndexGeneric({ M: 16, efConstruction: 200, distanceFunction: euclideanDistance }); // 插入点对象 const points: Point[] = [ { x: 0, y: 0, label: 'origin' }, { x: 1, y: 0, label: 'right' }, { x: 0, y: 1, label: 'up' }, { x: 1, y: 1, label: 'diagonal' } ]; points.forEach(point => pointIndex.insertNode(point)); // 搜索最近的点 const queryPoint: Point = { x: 0.1, y: 0.1, label: 'near_origin' }; const nearestPoints = pointIndex.search(queryPoint, 2); // 4. 优化距离函数(支持预计算) const optimizedCosineDistance = (a: number[], b: number[]): number => { let dotProduct = 0; let i = 0; // 循环展开优化 for (; i < a.length - 3; i += 4) { dotProduct += a[i]! * b[i]! + a[i + 1]! * b[i + 1]! + a[i + 2]! * b[i + 2]! + a[i + 3]! * b[i + 3]!; } // 处理剩余部分 for (; i < a.length; i++) { dotProduct += a[i]! * b[i]!; } // 假设向量已归一化,简化计算 return 1.0 - dotProduct; }; const optimizedIndex = hnsw.createIndexGeneric({ M: 16, efConstruction: 200, distanceFunction: optimizedCosineDistance }); ``` ## 📚 API 文档 ### 标准版 API #### `hnsw.createIndex(config)` 创建HNSW索引实例(标准版,仅支持向量)。 **参数:** - `config.M` (number): 每个节点的最大连接数,影响图的连接密度 - 推荐值: 16-64 - 值越大构建越慢但搜索越快 - `config.efConstruction` (number): 构建时的搜索参数,影响构建质量 - 推荐值: 100-400 - 值越大构建质量越高但速度越慢 - `config.metricType` ('cosine' | 'l2'): 距离度量类型 - `'cosine'`: 余弦距离,适用于归一化向量 - `'l2'`: 欧几里得距离的平方 **返回:** HNSWIndex实例 ### 🎨 泛型版 API #### `hnsw.createIndexGeneric<T>(config)` 创建泛型HNSW索引实例,支持任意数据类型。 **参数:** - `config.M` (number): 每个节点的最大连接数 - `config.efConstruction` (number): 构建时的搜索参数 - `config.distanceFunction` (a: T, b: T) => number: **必需**,自定义距离函数 - `config.distanceToQuery?` (query: T, target: T) => number: **可选**,查询专用距离函数 **返回:** HNSWIndex<T>实例 **距离函数要求:** - 必须返回非负数 - 必须满足对称性:distance(a, b) === distance(b, a) - 必须满足自反性:distance(a, a) === 0 - 建议满足三角不等式 **示例距离函数:** ```typescript // 余弦距离 const cosineDistance = (a: number[], b: number[]): number => { // 实现余弦距离计算 return 1.0 - dotProduct / (normA * normB); }; // 编辑距离 const editDistance = (a: string, b: string): number => { // 实现编辑距离计算 return matrix[a.length]![b.length]!; }; // 欧几里得距离 const euclideanDistance = (a: Point, b: Point): number => { const dx = a.x - b.x; const dy = a.y - b.y; return Math.sqrt(dx * dx + dy * dy); }; ``` ### `index.insertNode(item)` 向索引中插入新项目。 **标准版参数:** - `item` (number[]): 要插入的向量 - 必须是非空数组 - 所有向量的维度必须一致 **泛型版参数:** - `item` (T): 要插入的项目 - 类型必须与索引创建时指定的类型一致 **注意:** 插入操作是O(log n)复杂度,适合实时插入 ### `index.search(queryItem, k, efSearch?)` 搜索最近邻。 **标准版参数:** - `queryItem` (number[]): 查询向量 - 必须与索引中向量维度一致 **泛型版参数:** - `queryItem` (T): 查询项目 - 类型必须与索引创建时指定的类型一致 **通用参数:** - `k` (number): 返回的最近邻数量 - 必须大于0 - `efSearch` (number, 可选): 搜索时的参数 - 默认使用构建时的efConstruction - 推荐值: k的2-4倍 **返回:** Neighbor[] - 包含idx和distance的对象数组,按距离升序排列 ### `index.deleteNode(nodeIdx)` 删除指定节点。 **参数:** - `nodeIdx` (number): 要删除的节点索引 **返回:** boolean - 删除是否成功 **注意:** - 使用软删除机制,不影响搜索性能 - 删除的节点不会出现在搜索结果中 - 删除入口点时会自动重新选择入口点 ### `index.getStats()` 获取索引统计信息。 **返回:** ```typescript { nodeCount: number, // 节点总数(包括已删除的) activeNodeCount: number, // 活跃节点数(未删除的) deletedNodeCount: number, // 已删除节点数 entryPoint: { // 入口点信息 idx: number, // 入口点索引 level: number // 入口点层级 } } ``` ## 🎯 使用建议 ### 参数调优 | 参数 | 推荐值 | 说明 | |------|--------|------| | M | 16-64 | 连接数,影响搜索精度和速度 | | efConstruction | 100-400 | 构建质量,影响索引质量 | | efSearch | k的2-4倍 | 搜索精度,影响召回率 | ### 向量预处理 - **归一化**: 使用余弦距离时确保向量已归一化 - **维度**: 推荐100-1000维,过高维度会影响性能 - **稀疏性**: 避免使用稀疏向量,使用密集向量 ### 最佳实践 1. **批量插入**: 对于大量数据,建议批量插入而非逐个插入 2. **参数平衡**: 根据应用场景平衡精度和速度 3. **监控性能**: 使用`getStats()`监控索引状态 4. **内存管理**: 大规模应用注意内存使用 ### 🎨 泛型版最佳实践 1. **距离函数优化**: - 使用循环展开优化向量运算 - 预计算常用值(如范数) - 避免在距离函数中进行复杂计算 2. **类型安全**: - 确保距离函数返回正确的数值类型 - 验证距离函数的数学性质(对称性、自反性) 3. **性能考虑**: - 对于复杂对象,考虑使用缓存机制 - 避免在距离函数中创建大量临时对象 4. **应用场景**: - **向量搜索**: 使用标准版API - **字符串搜索**: 使用编辑距离、Jaccard距离等 - **图节点搜索**: 使用图距离函数 - **自定义对象**: 根据业务需求定义距离函数 ## ⚠️ 限制说明 ### 当前限制 #### 标准版限制 1. **向量维度**: 所有向量必须具有相同维度 2. **数据类型**: 仅支持number[]类型的向量 3. **距离度量**: 仅支持余弦距离和L2距离 4. **内存使用**: 大规模数据集需要足够内存 5. **并发安全**: 当前版本不支持并发操作 #### 泛型版限制 1. **距离函数性能**: 自定义距离函数可能影响搜索性能 2. **类型一致性**: 所有插入和搜索的项目类型必须一致 3. **距离函数要求**: 必须满足对称性和自反性 4. **内存使用**: 复杂对象可能增加内存占用 5. **并发安全**: 当前版本不支持并发操作 ### 性能限制 - **构建时间**: 大规模数据集构建时间较长 - **内存占用**: 索引会占用额外内存存储图结构 - **搜索精度**: 近似搜索,不保证100%召回率 ## 🔧 开发 ### 安装依赖 ```bash pnpm install ``` ### 运行测试 ```bash # 运行所有测试 pnpm test # 运行测试并监听变化 pnpm test:watch # 运行基准测试 pnpm bench # 生成覆盖率报告 pnpm test:coverage ``` ### 构建 ```bash # 构建项目 pnpm build # 开发模式构建 pnpm dev ``` ## 📊 基准测试 项目包含完整的基准测试套件,对比HNSWLib等主流实现: ```bash # 运行性能基准测试 pnpm bench ``` 基准测试包括: - 索引构建性能 - 搜索性能 - 召回率测试 - 内存使用分析 ## 🤝 贡献 欢迎提交Issue和Pull Request! ### 开发指南 1. Fork项目 2. 创建功能分支 3. 提交更改 4. 运行测试确保通过 5. 提交Pull Request ## 📄 许可证 MIT License - 详见 [LICENSE](LICENSE) 文件。 ## 📝 更新日志 ### 1.0.0 - 🎉 初始版本发布 - ⚡ 高性能HNSW算法实现 - 🎯 支持余弦距离和L2距离 - 🔧 支持插入、搜索、删除操作 - 📦 完整的TypeScript支持 - 🧪 全面的测试覆盖 - 📊 性能基准测试套件 ## 🔗 相关链接 - [GitHub Repository](https://github.com/leolee9086/hnsw) - [Issue Tracker](https://github.com/leolee9086/hnsw/issues) - [HNSW论文](https://arxiv.org/abs/1603.09320)