当前位置: 首页 > news >正文

HNSW:分层可导航小世界图

NSW(Navigable Small World)基础

小世界网络理论

小世界网络两个特性:

  1. 聚类性:邻居之间互相连接,形成局部团

  2. 短路径:任意两点之间有较短的路径

普通图:A → B → C → D → E(5步) 小世界图:A → B → E(2步)

NSW 的构建

import random ​ class NSWNode: def __init__(self, id, vector): self.id = id self.vector = vector self.neighbors = [] # 邻居列表 ​ class NSW: def __init__(self, max_neighbors=6): self.nodes = {} self.max_neighbors = max_neighbors ​ def insert(self, id, vector): """插入新节点""" node = NSWNode(id, vector) self.nodes[id] = node ​ # 找到最近的几个已有节点作为邻居 candidates = list(self.nodes.values()) if len(candidates) > 1: nearest = self._find_nearest(vector, candidates, k=self.max_neighbors) node.neighbors = nearest ​ # 反向链接:让邻居也指向新节点 for neighbor_node in nearest: if len(neighbor_node.neighbors) < self.max_neighbors: neighbor_node.neighbors.append(node) ​ def _find_nearest(self, vector, candidates, k): """找到最近的 k 个候选""" distances = [] for cand in candidates: d = self._distance(vector, cand.vector) distances.append((cand, d)) ​ distances.sort(key=lambda x: x[1]) return [c for c, d in distances[:k]] ​ def _distance(self, v1, v2): return sum((a - b) ** 2 for a, b in zip(v1, v2)) ** 0.5 ​ def search(self, query, k=5): """搜索最近的 k 个向量""" # 随机选一个节点开始 start = random.choice(list(self.nodes.values())) ​ # Greedy search:沿着最临近的方向走 current = start best_dist = self._distance(query, current.vector) ​ while True: # 找当前节点邻居中比它更近的 better = None for neighbor in current.neighbors: d = self._distance(query, neighbor.vector) if d < best_dist: best_dist = d better = neighbor ​ if better is None: break # 无法再近,停止 current = better ​ # 此时 current 是局部最优 # 需要搜索更多起点找全局最优 results = [current] ​ # 从多个起点搜索,取最优 for _ in range(5): start = random.choice(list(self.nodes.values())) candidate = self._greedy_search(start, query) results.append(candidate) ​ # 排序返回最近的 k 个 results.sort(key=lambda n: self._distance(query, n.vector)) return results[:k] ​ def _greedy_search(self, start, query): """从某个节点开始的贪心搜索""" current = start best_dist = self._distance(query, current.vector) ​ while True: better = None for neighbor in current.neighbors: d = self._distance(query, neighbor.vector) if d < best_dist: best_dist = d better = neighbor ​ if better is None: break current = better ​ return current

HNSW:分层 NSW

HNSW 在 NSW 基础上增加了分层机制:

核心思想

高层:稀疏,跨度大,适合快速定位大致区域 底层:稠密,连接多,保证精度
HNSW 结构:
​ Layer 2: ●━━━━━━━━━━━━● ← 只有少量节点,跨度大 ┃ Layer 1: ●━●━━●━━●━━●━━●━● ← 中等密度 ┃ Layer 0: ●● ●● ●● ●● ●● ●● ← 最稠密,包含所有节点

搜索过程(从上层开始)

def hnsw_search(query, top_k=5): """HNSW 搜索:从顶层开始,逐层向下""" ​ # Step 1: 从顶层开始,找一个局部最优节点 # 顶层只有少量节点,快速定位大概位置 current = entry_points_at_top_layer current = greedy_search_in_layer(current, query, layer=TOP) ​ # Step 2: 下降到下一层,继续搜索 for layer in range(TOP - 1, -1, -1): # 在这一层继续从 current 出发贪心搜索 while True: better_found = False for neighbor in current.neighbors[layer]: if distance(query, neighbor) < distance(query, current): current = neighbor better_found = True if not better_found: break ​ # 当前层搜索完成后,作为下一层的起点 entry_point = current ​ # Step 3: 在底层收集更多候选 candidates = [current] # 用优先队列维护最近的候选 # 继续搜索直到无法找到更近的节点 ​ # Step 4: 返回最近的 top_k return get_top_k(candidates, query, k=top_k)

构建过程(从底层向上)

def hnsw_insert(node, layer): """ HNSW 插入: - 随机决定节点出现在哪些层(越高层概率越低) - 从顶层插入,逐层向下 """ ​ # 决定节点的最大层(随机,指数衰减概率) max_layer = get_random_layer(probability=0.5) # 概率递减 ​ # 从最高层开始插入 current = entry_point[max_layer] ​ for l in range(max_layer, -1, -1): # 在这一层找到最近邻居 neighbors = greedy_search(node.vector, layer=l) ​ # 选择前 M 个作为邻居 node.neighbors[l] = neighbors[:MAX_M] ​ # 更新邻居的反向链接 for neighbor in neighbors[:MAX_M]: if len(neighbor.neighbors[l]) < MAX_M: neighbor.neighbors[l].append(node) ​ # 下一层的起点是这一层找到的最近节点 current = neighbors[0] if neighbors else current

关键参数

# HNSW 参数说明 ​ M = 16 # 每层每个节点的最多连接数 # M 越大,精度越高,但内存占用越大、构建越慢 # M=16:推荐默认 # M=8:内存受限场景 ​ efConstruction = 200 # 构建时搜索范围 # efConstruction 越大,构建质量越高,但越慢 # 推荐:100-200 ​ efSearch = 64 # 搜索时搜索范围 # efSearch 越大,精度越高,但越慢 # 推荐:50-200 ​ max_level = 16 # 最大层数(自动计算)

M 对精度和内存的影响

M内存占用构建速度搜索精度
8
16
32很高

HNSW vs IVF 对比

特性HNSWIVF
搜索速度极快
构建速度中等
内存占用中等
搜索精度中等
动态插入较慢(影响多层)快(只需更新局部)
适用规模1亿+1000万
参数复杂度中(M, ef)低(nlist, nprobe)

什么时候选 HNSW?

选择 HNSW 的场景: - 延迟要求极低(<10ms) - 数据量在千万级以上 - 数据相对稳定(插入不频繁) - 内存充裕 ​ 选择 IVF 的场景: - 数据量中等(百万-千万) - 内存有限 - 需要频繁插入/删除 - 需要精确召回

面试常问

Q: HNSW 的分层思想是什么?

A:

  • 高层:节点稀疏,连接跨度大,快速定位大致区域

  • 低层:节点稠密,连接多,保证最终精度

  • 搜索时从顶层开始,逐层向下精确定位

Q: HNSW 和 NSW 的区别?

A:

  • NSW:单层图,贪心搜索可能陷入局部最优

  • HNSW:多层结构,从高层快速定位,再逐层向下

Q: efConstruction 和 efSearch 是什么?如何调参?

A:

  • efConstruction:构建索引时搜索范围,越大构建质量越高但越慢

  • efSearch:搜索时搜索范围,越大搜索精度越高但越慢

  • 调参建议:efConstruction=100-200,efSearch=50-200,从大到小实验

Q: HNSW 的缺点是什么?

A:

  • 内存占用大(每条向量需要额外存储邻居指针)

  • 构建慢(需要计算多层连接)

  • 动态插入效率低(插入可能影响多层)

  • 参数调优复杂(M, ef两个参数)

http://www.gsyq.cn/news/1489995.html

相关文章:

  • 软考网络工程师备考:用华为eNSP搞定14个必考实验(含完整命令与避坑指南)
  • 别再只用print了!用map、lambda和reduce优雅输出Python多个运算结果(以PTA习题为例)
  • 原来Modbus转Profinet这么简单!耐达讯自动化NY-N801新手也能配
  • 浏览器市场与用户画像分析-数据加工2
  • 性能测试方法详解
  • 告别野火教程:用STM32CubeMX快速搞定RT-Thread与LWIP的底层驱动适配
  • 别让寄生参数坑了你!从RLC震荡到防尖峰电阻,一份给电源工程师的避坑指南
  • 管好供应商档案,堵住工程采购隐形亏损
  • ASTM D4169包装测试中,对于不同种类的零部件,有哪些特殊的测试要求?
  • 别再只把Flink当流处理了:聊聊它的‘数据管道’模式如何替代你的传统ETL作业
  • 别再让SVG拖拽卡成PPT!实战优化:从svg.panzoom卡顿到丝滑的踩坑全记录
  • 粉笔申论和行测课程怎么搭配学?国考省考备考这样安排更稳
  • webrtc neteq介绍
  • 交换机选型踩坑?PoE供电不足、端口不够用、带宽跑不满?选型前先看这5个问题
  • 避坑指南:S32K3开发中EIM与ERM的常见配置误区与SPD软件包使用详解
  • SOLIDWORKS转CAD字体终极指南:TrueType、SHX怎么选?Windows字体映射避坑全记录
  • 绝区零一条龙全自动助手:告别重复操作,解放你的双手
  • 从RS-485电平转换到CRC校验:手把手调试STM32 Modbus通信的硬件与软件全流程
  • 金属制品修理翻译:技术、术语与精准传递的专业领域
  • 从曝光到转化:手把手拆解阿里ESMM模型在PaddlePaddle上的实现与调优
  • qwen版本
  • 幼小阶段偏爱模仿言行,家长举止会成为无形榜样
  • 别再傻傻分不清了!pip list、pip freeze、pip show 查包命令的保姆级区别指南
  • 2026年防爆冲子工具评测:防爆机动套筒工具/防爆楔子工具/防爆螺丝旋工具/防爆錾子工具/防爆防跌落扣工具/内六角防爆扳手工具/选择指南 - 优质品牌商家
  • 手把手教你用MATLAB复现圆柱绕流POD分解:从Brunton的经典案例到自己的流场分析
  • 宠物经济爆发的时代,自动售货机能不能在宠物消费场景中分一杯羹?~YH
  • GetQzonehistory:专业级QQ空间数据备份与导出工具完整指南
  • 从传感器噪声到平滑点云:一份给机器人开发者的深度数据预处理避坑指南
  • 麦斯创意:面向抖音与 TikTok 电商的工业化内容生产工具
  • 别光启动服务!EMQX在Windows下的3个高级配置:ACL白名单、参数调优与生产前检查