3

1. HNSW索引 分层可导航小世界

1.1. 定义

HNSW(Hierarchical Navigable Small World,分层可导航小世界)的核心思想是构建一个分层图结构:

  • 分层结构:每个数据点随机分配一个层级,层级越高的数据点越少。
  • 小世界图:每层是一个“邻接图”,每个点只和部分点相连,图结构保证“短路径”导航。
  • 导航:从高层开始,逐层向下搜索,逐步接近目标点。
生活类比

想象你在一个有很多楼层的大商场里找一个你想买的商品。

  • 顶层:你可以很快地环顾整个商场的大致布局(跳跃性大,看得远)。
  • 中间层:你已经大致知道商品在哪个区域,走得更近一些。
  • 底层:你到了具体的货架,仔细找你要的商品(查得很细)。

HNSW 就像这样,从高层“远距离跳跃”,逐层到低层“精细查找”,高效地找到目标。

1.2. 分层

分层结构
  • HNSW会把所有点随机分配到不同的层,层数服从指数分布(层数越高,点越少)。
  • 层0:所有数据点都在(最底层,最全)。
  • 层1、2...:层数越高,点越少,只有部分点存在。
每层的连接方式
  • 每层的点会与同层的若干最近邻(如M个)建立连接,形成“小世界”结构。
  • 高层(比如层2、3):点少,连接稀疏,覆盖全局,便于快速跨越大范围。
  • 底层(层0):点多,连接密集,便于精细定位和局部查找。
层与层之间的关系
  • 每个点只在自己被分配到的层及以下所有层出现。
  • 比如,某点分配到层3,它就会在层0、1、2、3都存在,在更高层就不存在了。

1.3. 查找过程

假设你要查找一个查询向量q的最近邻:

步骤1:从最高层开始
  • 任选最高层的一个点(通常是最新插入的最高层点)作为起点。
  • 在这一层,遍历当前点的邻居,只要有更近的点就跳过去,直到找不到更近的邻居为止(即局部最优)。
步骤2:逐层向下导航
  • 到达当前层的局部最优点后,下降到下一层,把上层找到的点作为下层的起点。
  • 在下层重复上面的搜索过程,继续寻找更近的邻居。
  • 层层递进,直到最底层(层0)。
步骤3:底层精细搜索
  • 到达层0后,一般会用更大的候选集(比如堆/队列)做扩展搜索,以保证查找精度。
  • 通过访问邻居,维护一个候选集,不断更新,直到满足停止条件(如访问次数上限,或没有更近的点)。

1.4. Milvus 索引示例

创建索引示例:

from pymilvus import MilvusClient

index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name", # Name of the vector field to be indexed
    index_type="HNSW", # Type of the index to create
    index_name="vector_index", # Name of the index to create
    metric_type="L2", # Metric type used to measure similarity
    params={
        "M": 64, # Maximum number of neighbors each node can connect to in the graph
        "efConstruction": 100 # Number of candidate neighbors considered for connection during index construction
    } # Index building params
)

参数说明:

  • metric_type:度量方式,可选项通常有:COSINE(余弦相似度)IP(内积)L2(欧式距离)
  • M

    • 含义: 每个节点(向量)在图中可以连接的最大邻居数(通常是最底层)。
    • 作用:

      • 在构建 HNSW 图时,M 决定了每个节点可以连接的最大邻居数量。
      • 较大的 M 值会使得图的连通性更好,从而提升搜索的准确性,但同时会增加索引的存储需求和构建时间。
      • 较小的 M 值会减少索引大小和构建时间,但可能会降低搜索的准确性。
    • 推荐值: 根据经验,M 的值通常设置在 12 到 64 之间。
  • efConstruction

    • 含义: 构建索引时,每次插入一个节点时考虑的候选邻居数(每一层)。
    • 作用:

      • 在 HNSW 索引的构建过程中,每插入一个节点,算法会尝试找到最合适的邻居来连接。efConstruction 决定了在搜索候选邻居时的搜索范围。
      • 较大的 efConstruction 值会提高索引构建的质量,从而提升搜索准确性,但也会增加索引构建时间。
      • 较小的 efConstruction 值会减少构建时间,但可能会降低搜索的准确性。
    • 推荐值: 一般设置为 2 到 200 之间。efConstruction 的值通常大于或等于 M

2. IVF 倒排文件索引

2.1. 定义

IVF(Inverted File Index,倒排文件索引)的核心是通过数据分簇和倒排索引结合,减少搜索范围,从而提升检索效率。其核心步骤包括:

  • 聚类:使用k-means等算法将数据集划分为nlist个簇,每个簇的中心称为质心(Centroid)。
  • 倒排列表:对每个向量计算其与所有质心的距离,将其分配到最近的簇,建立倒排列表:{簇ID: [向量1, 向量2, ...]}
  • 近邻搜索时,仅搜索与查询向量最相关的少数簇,避免全局遍历


2.2. 检索流程

粗搜索
  • 计算查询向量与所有质心的距离,选择最近的nprobe个簇。
  • nprobe是控制搜索范围的超参数,越大召回率越高,但速度越慢。
精细搜索
  • 在选中的nprobe个簇内,遍历所有向量,计算与查询向量的距离。
  • 返回Top-K最近邻结果。

2.3. Milvus 索引示例

创建索引示例:

from pymilvus import MilvusClient

index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name", # Name of the vector field to be indexed
    index_type="IVF_FLAT", # Type of the index to create
    index_name="vector_index", # Name of the index to create
    metric_type="L2", # Metric type used to measure similarity
    params={
        "nlist": 64, # Number of clusters for the index
    } # Index building params
)

参数说明:

  • metric_type:度量方式,可选项通常有:COSINE(余弦相似度)IP(内积)L2(欧式距离)
  • nlist

    • 聚类数量:

      • nlist 决定了数据被分成多少个簇。
      • 每个簇的向量数量大致为 总数据量 / nlist
    • 搜索范围:

      • 在查询时,算法会选择一定数量的簇进行搜索(通过参数 nprobe 控制)。
      • 如果 nlist 设置得过小,每个簇可能包含太多向量,导致搜索效率和准确性降低。
      • 如果 nlist 设置得过大,索引构建时间和存储开销会增加。
    • 如果数据集较小(几千到几万条向量):

      • 设置一个较小的 nlist 值,例如 64 或 128。
      • 这样可以快速构建索引,并且查询时仍能保持较高的准确性。
    • 如果数据集较大(百万级以上):

      • 设置一个较大的 nlist 值,例如 1024 或 4096。
      • 这样可以更好地划分数据,提高查询的准确性。

3. 局部敏感哈希 LSH

3.1. 定义

局部敏感哈希(Locality Sensitive Hashing,LSH)是一种通过哈希函数对高维数据进行分区,从而实现高效相似性搜索的技术。

LSH的核心在于设计特殊的哈希函数,使得相似的数据点倾向于被分配到相同的哈希桶中,减少搜索范围。

在数学上,LSH通过概率保证以下性质:

  1. 相似的点有较高的概率映射到相同的哈希桶。
  2. 不相似的点有较低的概率映射到相同的哈希桶。

LSH通常基于特定距离度量(如欧氏距离或余弦相似度)设计哈希函数。

随机生成超平面,用于划分向量空间。通过投影结果的正负值生成二进制哈希值。

3.2. 示例

通过5个二维坐标系的示例看看。查询点Q:(2, 3),目标是找到离它最近的点。

分界线

我们选择两条分界线作为哈希函数,每条线将空间分为两个区域(0或1)。

哈希函数分界线方程规则(点在线上方=1,否则=0)
h₁y = x例如:点(1,3) → 3>1 → 哈希值1
h₂y = 2x - 1例如:点(2,2) → 2>3 → 哈希值0
用哈希函数分桶

将每个数据点通过h₁和h₂映射到哈希值,并分桶。

坐标h₁(y=x)h₂(y=2x-1)哈希组合(h₁,h₂)
A(1,3)13 > 1 → 1(1,1)
B(2,2)02 > 3 → 0(0,0)
C(3,4)14 > 5 → 0(1,0)
D(6,5)05 > 11 → 0(0,0)
E(5,7)17 > 9 → 0(1,0)
哈希表构建
  • 哈希表1(h₁):根据h₁分桶

    • 桶1:A, C, E
    • 桶0:B, D
  • 哈希表2(h₂):根据h₂分桶

    • 桶1:A
    • 桶0:B, C, D, E
查询点Q的哈希计算

查询点Q=(2,3)的哈希值:

  • h₁(Q):3 > 2 → 1
  • h₂(Q):3 > 3(2*2-1=3) → 0(等于线时通常归为0)
  • 哈希组合:(1,0)
多哈希表检索

两个哈希表中分别查找Q所在的桶:

  1. 哈希表1(h₁=1):桶1 → 候选点A, C, E
  2. 哈希表2(h₂=0):桶0 → 候选点B, C, D, E

4. 向量数据库

4.1. Cherry Studio

不少人应该用过 Cherry Studio 的知识库,需要提前维护嵌入模型,可以往知识库中上传文件,或者粘贴网页地址。

然后就可以在知识库内检索相似语义的内容了,在和大模型的聊天中也可以基于该知识库进行RAG检索。

往知识库上传文件或粘贴网址时,就是通过ETL通道,借助嵌入模型进行向量化。当文件右侧的绿色图标亮了后,就是已经完成向量化,并将多维向量成功存储到向量数据库。

Cherry Studio 的向量数据库使用 libsql,基于 SQLite 分支开发的嵌入数据库。扩展了一些更方便向量计算的数据类型和函数等。

虽然性能上不如主流的向量数据库,但作为嵌入数据库,更适合在 Cherry Studio 这种客户端应用中使用。

具体可以查看 Cherry Studio 知识库WIKI

以mac电脑为例,向量数据库文件在 ~/Library/Application Support/CherryStudio/Data/KnowledgeBase/ 目录下。

可以 通过 SQLite 客户端打开,我是用 VS Code 的 SQLite Viewer 插件查看,表结构如下:

CREATE TABLE vectors (
  id TEXT PRIMARY KEY,
  pageContent TEXT UNIQUE,
  uniqueLoaderId TEXT NOT NULL,
  source TEXT NOT NULL,
  vector F32_BLOB (2048),
  metadata TEXT
)

4.2. Milvus

Milvus 作为主流的向量数据库,根据数据量级、检索性能、匹配精度等不同场景要求。

可以自由选择多种数据类型、度量方式、索引类型等进行组合。

各精度 数据类型

仅以稠密向量 float 存储为例,当数据量大时,可以牺牲部分精度。

类型每元素字节精度存储占用计算速度兼容性/支持度典型用途
float324极高主流检索、AI嵌入
float648最高科研/高精度运算
float162低~中很快GPU推理/特定加速
int8/uint81极低极快高(索引压缩)大规模压缩检索
各压缩级别 索引

针对稠密向量,主要支持:HNSW、IVF索引。

下面仅以 IVF 索引为例,HNSW 也有对应压缩变种。

索引类型存储占用检索精度检索速度适用数据规模典型场景
IVF_FLAT万~百万精度优先,小中规模
IVF_SQ8较小中高较快十万~千万存储有限,速度需提升
IVF_PQ很小百万~亿级海量数据,速度存储优先
IVF_RABITQ极小中高很快千万~亿级超大数据,精度和速度兼顾
选择度量方式

在创建索引时,需要指定度量方式。稠密向量默认度量方式都是余弦相似度。

from pymilvus import MilvusClient

index_params = MilvusClient.prepare_index_params()

index_params.add_index(
    field_name="your_vector_field_name", # Name of the vector field to be indexed
    index_type="HNSW", # Type of the index to create
    index_name="vector_index", # Name of the index to create
    metric_type="L2", # Metric type used to measure similarity
    params={
        "M": 64, # Maximum number of neighbors each node can connect to in the graph
        "efConstruction": 100 # Number of candidate neighbors considered for connection during index construction
    } # Index building params
)

具体使用方式就不多说了,可以看 Milvus 官网WIKI


KerryWu
679 声望171 粉丝

保持饥饿