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值会减少索引大小和构建时间,但可能会降低搜索的准确性。
- 在构建 HNSW 图时,
- 推荐值: 根据经验,
M的值通常设置在 12 到 64 之间。
efConstruction:
- 含义: 构建索引时,每次插入一个节点时考虑的候选邻居数(每一层)。
作用:
- 在 HNSW 索引的构建过程中,每插入一个节点,算法会尝试找到最合适的邻居来连接。
efConstruction决定了在搜索候选邻居时的搜索范围。 - 较大的
efConstruction值会提高索引构建的质量,从而提升搜索准确性,但也会增加索引构建时间。 - 较小的
efConstruction值会减少构建时间,但可能会降低搜索的准确性。
- 在 HNSW 索引的构建过程中,每插入一个节点,算法会尝试找到最合适的邻居来连接。
- 推荐值: 一般设置为 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通过概率保证以下性质:
- 相似的点有较高的概率映射到相同的哈希桶。
- 不相似的点有较低的概率映射到相同的哈希桶。
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) | 1 | 3 > 1 → 1 | (1,1) |
| B | (2,2) | 0 | 2 > 3 → 0 | (0,0) |
| C | (3,4) | 1 | 4 > 5 → 0 | (1,0) |
| D | (6,5) | 0 | 5 > 11 → 0 | (0,0) |
| E | (5,7) | 1 | 7 > 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(h₁=1):桶1 → 候选点A, C, E
- 哈希表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 存储为例,当数据量大时,可以牺牲部分精度。
| 类型 | 每元素字节 | 精度 | 存储占用 | 计算速度 | 兼容性/支持度 | 典型用途 |
|---|---|---|---|---|---|---|
| float32 | 4 | 高 | 中 | 快 | 极高 | 主流检索、AI嵌入 |
| float64 | 8 | 最高 | 高 | 慢 | 低 | 科研/高精度运算 |
| float16 | 2 | 低~中 | 低 | 很快 | 低 | GPU推理/特定加速 |
| int8/uint8 | 1 | 低 | 极低 | 极快 | 高(索引压缩) | 大规模压缩检索 |
各压缩级别 索引
针对稠密向量,主要支持: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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。