HNSW的工作原理:
-
层次结构:
HNSW创建了一个多层的图结构。想象一个金字塔:- 最底层包含所有数据点。
- 每往上一层,数据点变少,但每个点连接更多。
- 最顶层只有少数几个点。
-
构建过程:
- 从底层开始,每个新数据点都有机会被提升到上层。
- 提升概率随层数增加而降低,确保上层点数少。
-
搜索过程:
- 从最顶层开始搜索。
- 在每一层找到最接近的点,然后下降到下一层。
- 重复这个过程,直到到达底层。
举个例子:
假设我们在寻找与"苹果"最相似的水果。
- 顶层可能只有"水果"和"蔬菜"两个点。
- 中间层可能有"核果"、"浆果"、"柑橘"等。
- 底层包含所有具体的水果。
搜索时,我们会:
- 先选择"水果"。
- 然后可能选择"核果"。
- 最后在底层找到"梨"或"桃"等与苹果最相似的水果。
优点:
- 搜索速度快:不需要遍历所有数据点。
- 可扩展性强:可以处理大规模数据集。
- 动态性:可以轻松添加或删除数据点。
- 内存效率:不需要存储所有点之间的关系。
缺点:
- 构建复杂:初始构建图结构需要时间。
- 参数敏感:性能依赖于参数选择(如层数、每层连接数)。
- 近似结果:不保证找到绝对最近的邻居,但通常非常接近。
- 存储开销:虽然比全连接图小,但仍需额外存储空间。
代码示例:
import hnswlib
# 创建HNSW索引
dim = 128 # 向量维度
num_elements = 10000 # 数据点数量
# 初始化索引
index = hnswlib.Index(space='l2', dim=dim)
index.init_index(max_elements=num_elements, ef_construction=200, M=16)
# 添加数据点
for i in range(num_elements):
v = np.random.random(dim)
index.add_items(v)
# 搜索最近邻
query = np.random.random(dim)
labels, distances = index.knn_query(query, k=1)
在这个例子中:
- 我们创建了一个128维、最多包含10000个元素的HNSW索引。
ef_construction=200
和M=16
是影响索引质量和搜索速度的参数。- 我们添加随机生成的数据点。
- 最后,我们用一个随机查询向量搜索最近邻。
HNSW通过在速度和准确性之间取得平衡,成为了处理大规模向量搜索问题的有效解决方案。它特别适用于需要快速近似最近邻搜索的应用,如推荐系统、图像检索等
与暴力搜索的区别
不同的方法来解决一个常见的问题:如何在大量数据中快速找到最相似的项目。想象一下,你有一个巨大的图书馆,你想找到与某本特定书最相似的其他书。
- 暴力搜索方法
首先,代码展示了最简单的方法:暴力搜索。
(G_lin, G_best) = nearest_neigbor(vec_pos,query_vec)
这就像你拿着一本书,一个一个地与图书馆里的每本书比较,直到找到最相似的。这种方法准确但非常慢,特别是当图书馆很大时。
- HNSW (Hierarchical Navigable Small World) 方法
接下来,代码展示了一种更高级的方法:HNSW。
GraphArray = construct_HNSW(vec_pos,m_nearest_neighbor)
(SearchPathGraphArray, EntryGraphArray) = search_HNSW(GraphArray,G_query)
想象图书馆被组织成多个层次。顶层可能只有几个主要类别(如科幻、历史、文学等),然后每个类别下面有更细分的子类别。HNSW就是这样工作的:它首先在顶层快速找到大致方向,然后逐层下降,最终找到最相似的书。这比一本一本查找要快得多。
- Weaviate 向量数据库
最后,代码展示了如何使用专门的数据库系统来做这件事。
client = weaviate.Client(embedded_options=EmbeddedOptions())
这就像使用一个设计专门用于快速找书的高科技图书馆系统。你告诉系统你要找什么样的书,它就能快速返回结果。
例如,当你想找一本关于宇宙探索的书时:
response = (
client.query
.get("MyCollection", ["title"])
.with_near_vector({
"vector": [-0.012, 0.021, -0.23, -0.42, 0.5, 0.5]
})
.with_limit(2)
.do()
)
这段代码就像在告诉系统:"给我找两本最接近这个主题的书"。这里的向量 [-0.012, 0.021, ...]
就代表了 "宇宙探索" 这个主题。
代码中的不同之处:
- 暴力搜索:简单但慢。
- HNSW:快速但需要复杂的预处理。
- Weaviate:既快速又易用,但需要专门的系统支持。
这些方法的共同点是它们都在尝试解决同一个问题:如何在大量数据中快速找到相似项。它们的不同之处在于速度和复杂性的平衡。