上篇博客 Milvus python库 pymilvus 常用操作详解之Collection(上) 主要介绍了 pymilvus 库中Collection集合的相关概念以及创建过程的代码实现,现在我们要在该基础上实现对于collection中插入数据的混合检索(基于dense vector 和 sparse vector)。
首先回顾一下我们创建的名为 hybrid_demo
的集合的基本信息:
print(col)
# 可以看到集合的基本信息,该集合包含四个字段,主键id,检索文字内容text,稠密向量 dense_vector 和 稀疏向量 sparse_vector
<Collection>:
-------------
<name>: hybrid_demo
<description>:
<schema>: {'auto_id': True, 'description': '', 'fields': [{'name': 'pk', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 100}, 'is_primary': True, 'auto_id': True}, {'name': 'text', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 512}}, {'name': 'sparse_vector', 'description': '', 'type': <DataType.SPARSE_FLOAT_VECTOR: 104>}, {'name': 'dense_vector', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 1024}}], 'enable_dynamic_field': False}
因为我们想实现的检索功能是基于两个向量表达的混合检索,所以需要将名为 dense_vector
和 sparse_vector
的字段设置为针对于向量数据的 Index
索引,即 Milvus中有一个重要概念,向量索引。
向量索引
基于 Milvus 提供的官方文档内容,向量索引包含三大类索引,分别是内存索引、磁盘索引和GPU索引。在本篇博客中,我们主要聚焦在内存索引的相关概念和具体实现上。
Milvus 目前只支持一个向量场一种索引类型,当切换索引类型时,旧索引会被自动删除。
ANNS 向量索引
Milvus 支持的大多数向量索引类型都使用近似近邻检索(ANNS)算法。
何为近似近邻检索(ANNS)算法?(ChatGPT-4o回答 & Milvus 内存索引 原文内容)
近似近邻检索(Approximate Nearest Neighbor Search, ANNS)是一种用于快速寻找大规模数据集中近似最近邻的算法。传统的最近邻搜索在高维数据中计算量很大,因此ANNS方法通过引入近似来提高效率,允许在较短时间内找到与查询点距离最接近的点,虽然结果可能不是最优的,但通常足够接近并且计算速度快。
与通常非常耗时的精确检索相比,ANNS 的核心理念不再局限于返回最精确的结果,而是只搜索目标的近邻。ANNS 通过在可接受的范围内牺牲精确度来提高检索效率。
根据实现方法,ANNS 向量索引可分为四种类型:基于树、基于图、基于哈希和基于量化。
ANNS 向量索引类型 | 实际例子 | 描述 |
---|---|---|
基于树 | KD-Tree | 使用树状结构递归划分空间,适用于低维数据集的快速检索。 |
基于图 | HNSW(Hierarchical Navigable Small World) | 构建多层次小世界图,用于大规模高维数据集的高效搜索。 |
基于哈希 | LSH(Locality-Sensitive Hashing) | 通过哈希函数将相似数据点映射到相同桶中,适合高维空间的快速匹配。 |
基于量化 | PQ(Product Quantization) | 将高维向量分解并量化以减少存储和计算成本,适用于大型数据库的图像检索。 |
Milvus 支持的索引
Milvus 支持多种索引类型,这些类型按其处理的向量嵌入类型分为:浮点嵌入(也称浮点向量或密集/稠密向量)、二进制嵌入(也称二进制向量)和稀疏嵌入(也称稀疏向量)。
本篇文章中主要介绍基于稠密向量和稀疏向量的索引类型,二进制向量暂不介绍。
一、稠密向量的索引
既然我们需要通过ANNS近似近邻检索的方式去找到与某个向量表示最相关的若干个向量,那我们就得有度量向量之间距离的计算方式。而用于dense vector稠密向量的距离度量是欧氏距离(L2
)、内积(IP
)和 余弦相似度 (COSINE
)。
字段类型 | 维度范围 | 支持的度量类型 | 默认度量类型 |
---|---|---|---|
FLOAT_VECTOR | 2-32,768 | COSINE, L2, IP | COSINE |
FLOAT16_VECTOR | 2-32,768 | COSINE, L2, IP | COSINE |
BFLOAT16_VECTOR | 2-32,768 | COSINE, L2, IP | COSINE |
Milvus 支持的索引类型包括FLAT
,IVF_FLAT
,IVF_PQ
,IVF_SQ8
,HNSW
和SCANN
,用于基于 CPU 的 ANN 搜索。(若想了解每个索引类型的特点和适用场景,请参考官方文档)
在后续的代码实现中,我们将采用唯一能保证精确搜索结果的索引类型,即FLAT,并搭配内积IP度量类型实现混合检索。
对于要求完美准确性并依赖相对较小(百万量级)数据集的向量相似性搜索应用,FLAT 索引是一个不错的选择。
FLAT 不压缩向量,是唯一能保证精确搜索结果的索引。
FLAT 的结果还可以作为其他召回率低于 100% 的索引所产生结果的比较点。
FLAT 的精确度很高,因为它采用的是穷举搜索方法,这意味着每次查询都要将目标输入与数据集中的每一组向量进行比较,这使得 FLAT 成为列表中速度最慢的索引,而且不适合查询海量向量数据。
在 Milvus 中,FLAT 索引不需要任何参数,使用它也不需要数据训练。
二、稀疏向量的索引
不同于稠密向量支持三种距离度量,稀疏向量支持的距离度量只有IP
。
稀疏向量的索引类型包括SPARSE_INVERTED_INDEX
和SPARSE_WAND
:
支持的索引 | 分类 | 方案 |
---|---|---|
稀疏反转索引 | 倒置索引 | • 取决于相对较小的数据集 • 要求100%的召回率 |
稀疏反向索引 | 反向索引 | • 弱AND算法加速 • 在牺牲少量召回率的同时,速度也有明显提高 |
混合索引代码实现
一、为两个向量字段创建index
sparse_index = {"index_type": "SPARSE_INVERTED_INDEX", "metric_type": "IP"}
# 为名为"sparse_vector"的稀疏向量字段创建SPARSE_INVERTED_INDEX类型,向量度量方式为IP的索引
col.create_index("sparse_vector", sparse_index)
dense_index = {"index_type": "FLAT", "metric_type": "IP"}
# 为名为"dense_vector"的稠密向量字段创建FLAT类型,向量度量方式为IP的索引
col.create_index("dense_vector", dense_index)
# 集合调用load()函数,索引文件和所有字段的原始数据都将加载到内存中,以便快速响应搜索和查询
# 加载集合后插入的实体会自动进行索引并加载
col.load()
二、创建ANNS请求
def get_query_embeddings(query):query_embeddings_raw = model.encode([query], return_dense=True, return_sparse=True, return_colbert_vecs=False)query_embeddings = {}query_embeddings["dense"] = query_embeddings_raw["dense_vecs"]query_embeddings["sparse"] = [dict(query_embeddings_raw["lexical_weights"][0])]return query_embeddingsquery = "请问股票代码为CSCO的美股今日走势如何?"
query_embeddings = get_query_embeddings(query)
# 设置与query向量最相近的向量数量为2,即top 2
k = 2
# 为两个创建了索引的向量字段创建基于ANNS的请求
sparse_search_params = {"metric_type": "IP"}
sparse_req = AnnSearchRequest(query_embeddings["sparse"],"sparse_vector", sparse_search_params, limit=k)
dense_search_params = {"metric_type": "IP"}
dense_req = AnnSearchRequest(query_embeddings["dense"],"dense_vector", dense_search_params, limit=k)
三、进行混合检索
res = col.hybrid_search([sparse_req, dense_req], rerank=RRFRanker(),limit=k, output_fields=['text'])
代码解释(ChatGPT-4o回答):
这行代码是一个混合搜索(hybrid search)操作,结合稀疏和稠密两类请求来进行查询,并使用 RRFRanker 进行重新排序。具体解释如下:
col.hybrid_search
: 对一个集合进行混合搜索。[sparse_req, dense_req]
: 传入两个搜索请求,一个是稀疏(sparse),一个是稠密(dense),即分别基于不同类型的特征进行查询。rerank=RRFRanker()
: 使用 RRFRanker 算法进行结果的重新排序,平衡稀疏和稠密请求的结果。limit=k
: 限定返回的结果数量为k
条。output_fields=['text']
: 返回结果中的text
字段。
RRFRanker(Reciprocal Rank Fusion)是一种排名融合算法,通常用于综合多个排序结果。它通过给每个结果的排名分配一个“倒数权重”,即排名靠前的结果权重大,排名靠后的权重小。RRF的优势在于不依赖于单一排名结果,能够将多个基础排名的优势有效结合起来,从而生成一个更为均衡和多样化的最终排名。
四、查看TOP K混合检索结果
for each_res in res:print(each_res)
# 与query最相近的两条检索结果,res列表中的元素是 pymilvus.client.abstract.Hit 数据类型,有 id, distance, entity等属性
"""
id: 454256988014510347, distance: 0.032786883413791656, entity: {'text': '根据股票编号查询股票信息,包括涨跌额、股票名称、开盘价、收盘价、当前价格、最高最低价、竞买价等信息;支持沪深、香港、美国三个市场的股票数据查询,覆盖全球各地的股票信息。'}
id: 454256988014510348, distance: 0.032258063554763794, entity: {'text': '提供最新的新闻头条信息,包括国内、国际、体育、娱乐、科技等各类资讯,让用户获取及时的新闻报道;支持按照不同的新闻分类进行查询,用户可以选择感兴趣的领域,如体育、科技、娱乐等,获取相关的新闻内容。'}
"""