当前位置: 首页 > 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.jsqmd.com/news/978269/

相关文章:

  • 从蓝桥杯电梯赛题到真实项目:如何用状态机思想重构你的嵌入式程序
  • 终极免费方案:Wand-Enhancer解锁游戏修改器完整功能,告别时间限制!
  • 2026年国内无局放工频耐压试验装置主流品牌盘点:充气式试验变压器/变压器综合特性测试仪/变压器综合试验测试仪/选择指南 - 优质品牌商家
  • COMET框架:多尺度时序异常检测技术解析
  • mobaxterm
  • 软考网络工程师备考:用华为eNSP搞定14个必考实验(含完整命令与避坑指南)
  • svg.panzoom.js卡顿救星:手把手教你改造为高性能transform方案(保留viewBox)
  • 别再只用print了!用map、lambda和reduce优雅输出Python多个运算结果(以PTA习题为例)
  • 网络时好时坏有时候连不上
  • 原来Modbus转Profinet这么简单!耐达讯自动化NY-N801新手也能配
  • 浏览器市场与用户画像分析-数据加工2
  • TPC116S8/112S8 DAC驱动避坑指南:时序、通道选择与电压换算的实战详解
  • AD导出的STEP模型在SOLIDWORKS里总弹窗?一个设置搞定默认模板问题,附完整SW导入配置流程
  • 【MPDR SMI】失配广义夹角随输入信噪比变化趋势、输出信干噪比随输入信噪比变化趋势研究附Matlab代码
  • PyCharm设置默认运行浏览器
  • Age 1.3.1 官方版下载(夸克网盘+百度网盘,SHA256校验)
  • 山东大学等团队构建头颈癌显微高光谱病理基准数据集,突破医学组织切片智能分类难题
  • AI大模型实战:从零完成LoRA轻量化微调
  • 信息学奥赛刷题指南:从‘分数线划定’这道题,聊聊排序规则设计那些坑
  • 从《信息学奥赛一本通》到LeetCode:手把手教你用C++ STL(vector+queue)实现SPFA最短路算法
  • 性价比高的企事业单位功能性服装定制哪个靠谱
  • 别让寄生参数坑了你!从RLC震荡到防尖峰电阻,一份给电源工程师的避坑指南
  • 团队协作中的 Git Tag 最佳实践:从入门到精通
  • venv虚拟环境
  • 保姆级教程:用安信可ESP-12F模块+机智云,5步搞定你的第一个物联网设备
  • 告别野火教程:用STM32CubeMX快速搞定RT-Thread与LWIP的底层驱动适配
  • 性能测试方法详解
  • 管好供应商档案,堵住工程采购隐形亏损
  • ASTM D4169包装测试中,对于不同种类的零部件,有哪些特殊的测试要求?
  • Vue 3 Composition API 深度实践:响应式系统的底层机制与大型应用架构