在向量检索场景中,我们常遇到“按指定字段分组,获取每组最相似结果”的需求(如按文档ID分组,返回每个文档下与查询向量最匹配的内容)。Milvus的分组检索(group by)功能,可高效实现这一需求,无需额外二次筛选。本文将聚焦Milvus分组检索核心知识点,搭配极简前置操作(其他操作一带而过),帮你快速掌握分组检索的原理、实操及避坑要点。 1、分组检索的核心原理
很多人会疑惑:“分组检索和普通检索的区别是什么?” 用一句话概括:普通检索(无group by):返回所有与查询向量最相似的结果,不区分分组,可能出现同一docId的多个结果;分组检索(有group by):先按指定字段(如docId)对数据分组,再在每个分组内计算向量相似度,返回每个分组中最相似的1条(或多条)结果,确保分组唯一。关键注意点:分组字段(如docId)需是非向量字段(INT64、VARCHAR等),不能对向量字段进行分组;分组检索的底层的是“检索+分组筛选”一体化执行,效率远高于“先检索、后手动分组”。 2、关键参数&避坑要点(重中之重)
分组检索的核心是参数配置,以下3个参数和2个坑点,直接决定检索是否成功、结果是否符合预期:
- group_by_field(必传):指定分组的字段,必须是集合中已定义的非向量字段(如本文的docId),若传入向量字段(如vector)会直接报错。
- limit(必传):指定总返回结果数,注意:limit的数值需小于等于分组的总数量。例如本文中docId有1、2、3、4、5共5个分组,limit设为3,就会返回3个分组的最优结果;若limit大于分组数,最多返回所有分组的最优结果。
- output_fields(可选):指定需要返回的额外字段,若不设置,默认只返回主键(id)和向量相似度(distance);若需要查看分组字段(docId)或其他字段,需手动传入。
- 避坑点1:检索前必须加载集合:这是最常见的错误!如果忘记执行client.load_collection(collection_name="group_t"),会报“集合找不到”的错误,无论分组检索配置多正确都无法执行。
- 避坑点2:分组字段需提前定义:分组字段(如docId)必须在创建集合时添加到schema中,且不能是动态字段(enable_dynamic_field=True不影响,但建议提前显式定义,避免异常)。
3、分组检索的实际应用场景
理解原理和实操后,我们看几个实际场景,帮你快速落地:
- 语义检索场景:将文章拆分多个片段(chunk),每个片段关联文章ID(docId),查询时按docId分组,返回每个文章中与查询语句最相似的片段,避免同一文章返回多个片段冗余。
- 图像匹配场景:按图像类别(如“猫”“狗”)分组,查询时返回每个类别中与目标图像最相似的1张图,快速实现“类别+相似”双重筛选。
- 推荐系统场景:按用户ID分组,返回每个用户最可能感兴趣的内容(基于向量相似度),实现个性化分组推荐。
代码部分:- from pymilvus import MilvusClient, DataType, FieldSchema, CollectionSchema, Collection, connections
- # 1. 初始化 Milvus 客户端连接
- client = MilvusClient(
- uri="http://192.168.211.128:19530",
- token="root:Milvus"
- )
- # 2. 准备数据环境 - 清理历史数据
- # 如果集合 "group_t" 已存在,则删除该集合,避免重复创建导致报错
- try:
- client.drop_collection(collection_name="group_t")
- except Exception as e:
- # 捕获集合不存在的异常,不中断程序执行
- print(f"删除集合时的提示(非错误): {e}")
- # 3. 定义集合的 Schema(结构)
- # auto_id=False: 禁用自动生成ID,使用自定义的id字段作为主键
- # enable_dynamic_field=True: 启用动态字段,允许插入Schema中未定义的字段
- schema = MilvusClient.create_schema(
- auto_id=False,
- enable_dynamic_field=True,
- )
- # 4. 向Schema中添加具体字段
- # 主键字段:id,INT64类型,is_primary=True标记为主键
- schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
- # 向量字段:vector,FLOAT_VECTOR类型,dim=5表示向量维度为5
- schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=5)
- # 字符串字段:chunk,VARCHAR类型,max_length=512限制最大长度
- schema.add_field(field_name="chunk", datatype=DataType.VARCHAR, max_length=512)
- # 整数字段:docId,INT64类型,用于关联文档ID
- schema.add_field(field_name="docId", datatype=DataType.INT64)
- # 5. 创建集合
- # collection_name: 集合名称
- # schema: 上面定义的集合结构
- client.create_collection(
- collection_name="group_t",
- schema=schema
- )
- # 6. 准备待插入的测试数据
- # 每条数据包含:主键id、5维向量vector、字符串chunk、文档ID docId
- data = [
- {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "chunk": "pink_8682", "docId": 1},
- {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "chunk": "red_7025", "docId": 5},
- {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "chunk": "orange_6781", "docId": 2},
- {"id": 3, "vector": [0.3172005263489739, 0.9719044792798428, -0.36981146090600725, -0.4860894583077995, 0.95791889146345], "chunk": "pink_9298", "docId": 3},
- {"id": 4, "vector": [0.4452349528804562, -0.8757026943054742, 0.8220779437047674, 0.46406290649483184, 0.30337481143159106], "chunk": "red_4794", "docId": 3},
- {"id": 5, "vector": [0.985825131989184, -0.8144651566660419, 0.6299267002202009, 0.1206906911183383, -0.1446277761879955], "chunk": "yellow_4222", "docId": 4},
- {"id": 6, "vector": [0.8371977790571115, -0.015764369584852833, -0.31062937026679327, -0.562666951622192, -0.8984947637863987], "chunk": "red_9392", "docId": 1},
- {"id": 7, "vector": [-0.33445148015177995, -0.2567135004164067, 0.8987539745369246, 0.9402995886420709, 0.5378064918413052], "chunk": "grey_8510", "docId": 2},
- {"id": 8, "vector": [0.39524717779832685, 0.4000257286739164, -0.5890507376891594, -0.8650502298996872, -0.6140360785406336], "chunk": "white_9381", "docId": 5},
- {"id": 9, "vector": [0.5718280481994695, 0.24070317428066512, -0.3737913482606834, -0.06726932177492717, -0.6980531615588608], "chunk": "purple_4976", "docId": 3},
- ]
- # 7. 插入数据到集合中
- # collection_name: 目标集合名称
- # data: 待插入的数据集
- res = client.insert(
- collection_name="group_t",
- data=data
- )
- # 打印插入结果(包含插入的行数、成功标记等信息)
- print("数据插入结果:", res)
- # 8. 配置向量索引参数(为向量字段创建索引,提升检索效率)
- # prepare_index_params(): 创建索引参数配置对象
- index_params = MilvusClient.prepare_index_params()
- # 为vector字段添加索引配置
- # field_name: 要创建索引的字段名(向量字段)
- # metric_type: 距离计算方式(COSINE:余弦相似度)
- # index_type: 索引类型(IVF_FLAT:基于倒排文件的暴力检索,适合中小数据集)
- # index_name: 索引名称,用于标识该索引
- # params: 索引参数,nlist=128表示聚类中心数量(IVF_FLAT的核心参数)
- index_params.add_index(
- field_name="vector",
- metric_type="COSINE",
- index_type="IVF_FLAT",
- index_name="vector_index",
- params={"nlist": 128}
- )
- # 9. 创建索引
- # sync=False: 异步创建索引(不等待索引创建完成就返回,适合大数据量)
- client.create_index(
- collection_name="group_t",
- index_params=index_params,
- sync=False # True表示同步创建,等待创建完成;False异步创建
- )
- # 10. 查看集合中的索引列表,验证索引是否创建成功
- res = client.list_indexes(collection_name="group_t")
- print("集合中的索引列表:", res)
- # 11. 加载集合到内存(Milvus检索前必须加载集合到内存,否则会报集合找不到)
- client.load_collection(collection_name="group_t")
- # 12. 基于向量的相似性检索(并按docId分组)
- # 定义查询向量(5维,与数据集的向量维度一致)
- query_vectors = [
- [0.14529211512077012, 0.9147257273453546, 0.7965055218724449, 0.7009258593102812, 0.5605206522382088]
- ]
- # 执行向量检索
- # group_by_field: 按docId字段分组,每组返回最相似的结果
- # limit=3: 返回3个最相似的结果
- # output_fields: 指定要返回的字段(除了默认的距离和主键外)
- res = client.search(
- collection_name="group_t",
- data=query_vectors,
- limit=3,
- group_by_field="docId",
- output_fields=["docId"]
- )
- # 13. 解析检索结果,提取docId列表
- # res[0] 对应第一个查询向量的检索结果
- doc_ids = [result['entity']['docId'] for result in res[0]]
- # 打印提取的docId
- print("检索结果中的docId列表:", doc_ids)
复制代码 输出结果:
数据插入结果: {'insert_count': 10, 'ids': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
集合中的索引列表: ['vector_index']
检索结果中的docId列表: [5, 2, 3]
更多学习资料尽在老虎网盘资源 老虎网盘资源
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |