一篇文章搞明白两大类型的多向量混合检索
唠点做电商搜索常踩的坑:复杂描述下的召回率偏低问题。
可能不少人都遇到过这个情况,用户发了张裙子照片,还特意备注 “北方、冬天、妈妈穿”,检索结果却匹配到一条花色、版型相似但明显为夏季穿着的沙滩裙,与需求完全不符。
医疗、法律领域做检索时,也经常出现类似问题:医生搜索 “2 型糖尿病 儿童患者 病例” ,系统却召回一堆 1 型糖尿病的资料,语义相近,但与需求牛头不对马嘴;法律场景更典型,用户明确查询 “XX 场景下的连带责任” 相关法条和案例,系统却返回大量 “XX 场景补充责任” 的文书,虽然就两字之差,实际法律含义却相去甚远。
问题看似各不相同,但本质其实都一样:**单靠一种维度的信息(也就是单向量检索),根本应付不了复杂场景的需求:**电商搜索需要的是图片 + 文字描述的多模态检索能力;医疗、法律场景则需要把语义理解和关键词精准匹配 结合起来。
**要解决这类问题,就需要引入多向量混合检索。**其核心在于整合文本语义、关键词及不同模态信息,在多维度上并行检索后对结果进行 rerank,使检索结果更贴合实际需求。
那么,多向量混合检索到底是什么,该怎么用?以下是技术解读以及手把手应用实践。
01
多向量混合检索的两种核心类型
多向量混合检索本质就是整合不同搜索方法或跨模态的embedding信息,主要分为两类:
- 稀疏 - 稠密向量搜索:稠密向量擅长捕捉语义关系,稀疏向量适合精准的关键词匹配。两者结合后,既能理解整体概念,又能匹配精确术语,弥补了单一方法的不足,让复杂查询的结果更优。
以下是稀疏 - 稠密向量混合检索的示范:
https://milvus.io/docs/hybrid_search_with_milvus.md
- 多模态向量搜索:milvus目前支持跨文本、图像、音频等多种数据类型搜索,能把不同形式的信息整合为连贯的搜索体验。比如电商搜索中,用户输入文本查询+照片,系统可以同时匹配产品的文字描述和实物图像,让结果更准、更丰富。
02
Milvus/Zilliz Cloud 的核心支持能力
要落地多向量混合检索,Milvus 和 Zilliz Cloud 提供了全方位的技术支持:
- 灵活的Schema定义:能在Collection Schema中定义多个向量字段,支持整数主键(INT64)、字符串(VARCHAR)、稠密向量(DENSE_VECTOR)、稀疏向量(SPARSE_FLOAT_VECTOR)等多种数据类型,还能开启全文搜索分析器(enable_analyzer)。
- 多样化索引与度量:提供自动索引(AUTOINDEX)、稀疏倒排索引(SPARSE_INVERTED_INDEX)等类型,支持内积(IP)、BM25 等度量方式,适配不同类型向量的搜索需求。
- 内置全文搜索:集成 BM25 算法,能自动从文本字段生成稀疏向量,不用用户手动计算,简化数据准备流程。
- 完善的重排序机制:支持 RRFRanker 等多种策略,支持对多个ANN搜索结果集进行合并与重新排序,高效输出最优结果。
- 严谨的搜索流程:明确规定了搜索请求实例创建、集合加载、参数配置等步骤,保障混合搜索的稳定性和准确性。
凭借上述能力,多向量混合检索可以落地到多种实际场景:
- 社交媒体内容检索:比如搜索同时包含特定文本描述和图像的推文,要求文本和图像都满足语义匹配。
- 电商产品搜索:用户输入文本查询后,系统同时关联产品的文字描述(语义 + 关键词)和产品图像,实现多维度精准匹配。
- 复杂信息查询:针对有多维度介绍的对象(比如多字段商品说明、文档摘要),整合多个字段的向量进行全面检索,避免遗漏关键信息。
03
示例
下面以 产品文本 + 图像的检索场景为例,详细拆解多向量混合搜索的实现步骤。
这里默认每个产品都有文字描述和图片,能支持三种搜索方式:
- 语义文本搜索:根据文本的实际含义找产品。比如搜 适合夏天穿的宽松上衣,能找到夏季透气款 oversizeT 恤这种意思相近的描述,不用严格匹配关键词。这需要把文本转换成稠密向量,常用 BERT、Transformers 这类模型,或者 OpenAI 的服务来生成。
- 全文搜索:根据关键词精准匹配。比如搜 “纯棉 白色”,能直接定位到描述里包含这两个词的产品。这需要把文本转换成稀疏向量,可以用 BM25 算法,或者 BGE-M3、SPLADE 这类专门生成稀疏向量的模型。
- 多模态图像搜索:用文字描述找对应的产品图片。比如搜 “红色带花纹的连衣裙”,能直接找到符合这个描述的裙子图片。这需要把图片转换成稠密向量,常用 CLIP 这类模型来生成。
接下来,我们会结合产品的文字描述和图片信息,一步步讲清楚怎么把这三种搜索结合起来,实现多模态混合搜索。核心是先学会怎么存储这些多类型的向量数据,再用合适的方法给搜索结果排序。
(1)创建具有多个向量字段的集合
要实现混合搜索,首先得建一个能容纳多种向量的 collection。建这个collection分三步:定义 Collection Schema、配置索引参数和创建 Collection。
**这里先重点说第一步 ——**定义Collection Schema。
因为要支持三种搜索,模板里得有对应不同向量的字段,具体包括这 5 个:
- id:每个产品的唯一编号(类似身份证号),用整数类型(INT64)存储,作为区分不同产品的主键。
- text:用于存储产品的文字描述(比如 “红色纯棉连衣裙,长度及膝”),用字符串类型(VARCHAR),最长能存 1000 字节。
enable_analyzer选项设置为True,开启文本分析功能,方便后面做全文搜索时自动提取关键词。 - text_dense:用于存储文本的稠密向量(对应语义文本搜索),用浮点向量类型(FLOAT_VECTOR),维度是 768(这个数字由生成向量的模型决定,比如 BERT 模型生成的向量通常是 768 维)。
- text_sparse:用于存储文本的稀疏向量”(对应全文搜索),用稀疏浮点向量类型(SPARSE_FLOAT_VECTOR)。
- image_dense:存产品图片的 稠密向量(对应多模态图像搜索),用浮点向量类型(FLOAT_VECTOR),维度是 512(比如 CLIP 模型生成的图像向量常为 512 维)。
另外,因为我们要用系统自带的 BM25 算法来自动生成文本的稀疏向量,所以在定义这个Schema时,还需要加上 Milvus 的内置函数支持。
from pymilvus import ( MilvusClient, DataType, Function, FunctionType ) client = MilvusClient( uri="http://localhost:19530", token="root:Milvus" ) # Init schema with auto_id disabled schema = client.create_schema(auto_id=False) # Add fields to schema schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, description="product id") schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True, description="raw text of product description") schema.add_field(field_name="text_dense", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense embedding") schema.add_field(field_name="text_sparse", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse embedding auto-generated by the built-in BM25 function") schema.add_field(field_name="image_dense", datatype=DataType.FLOAT_VECTOR, dim=512, description="image dense embedding") # Add function to schema bm25_function = Function( name="text_bm25_emb", input_field_names=["text"], output_field_names=["text_sparse"], function_type=FunctionType.BM25, ) schema.add_function(bm25_function) 定义好 Collection Schema 后,接下来要给各个向量字段建索引。简单说,索引就像给数据建了个快速查找目录,能大幅提升后续搜索的速度和效率。同时,还得明确用什么方法判断两个向量 “像不像”(也就是相似度类型)。
在这个示例里,三个向量字段的索引配置是这样的:
- text_dense_index(文本稠密向量的索引):zilliz cloud中可以用 AUTOINDEX。这种索引不用手动调参数,系统会根据数据特点自动优化,适合处理文本的语义向量。判断相似度用 内积(IP)—— 两个向量的内积值越大,说明它们代表的文本语义越接近。开源milvus里则内置了默认参数。
- text_sparse_index(文本稀疏向量的索引):用 稀疏倒排索引(SPARSE_INVERTED_INDEX)。这种索引专门为关键词类的稀疏向量设计,能快速定位包含特定关键词的文本。判断相似度用 BM25 算法 —— 这是全文搜索里常用的方法,能根据关键词出现的频率、位置等,算出文本和查询的匹配度。
- image_dense_index(图像稠密向量的索引):和文本稠密向量一样,用AUTOINDEX,搭配内积(IP) 判断相似度。因为图像的视觉特征向量和文本的语义向量类似,都是通过稠密向量表达整体特征,所以用相同的索引和相似度计算方式更合适。
# Prepare index parameters index_params = client.prepare_index_params() # Add indexes index_params.add_index( field_name="text_dense", index_name="text_dense_index", index_type="AUTOINDEX", metric_type="IP" ) index_params.add_index( field_name="text_sparse", index_name="text_sparse_index", index_type="SPARSE_INVERTED_INDEX", metric_type="BM25", params={"inverted_index_algo": "DAAT_MAXSCORE"}, # or "DAAT_WAND" or "TAAT_NAIVE" ) </include> <include target="zilliz"> index_params.add_index( field_name="text_sparse", index_name="text_sparse_index", index_type="AUTOINDEX", metric_type="BM25" ) </include> index_params.add_index( field_name="image_dense", index_name="image_dense_index", index_type="AUTOINDEX", metric_type="IP" ) 定义好 Collection Schema 、创建好向量字段索引之后,第三步就是创建 Collection此处,我们创建一个名为demo的 Collection。
client.create_collection( collection_name="my_collection", schema=schema, index_params=index_params )(2)插入数据
接下来,我们要把产品的各种信息按照前面定义的结构,存到名为 my_collection的集合里。这里有个关键点:除了系统能自动生成的内容,其他字段都得按规定格式填,不能出错。
具体来说,每个产品要填这些信息:id、text、text_dense、image_dense。
另外有两个需要注意的点:
- 生成这些稠密向量时,文本和图像可以用同一个模型,也可以用不同的模型。比如这个例子里,text_dense 是 768 维,image_dense 是 512 维,明显是用了不同的模型(比如文本用 BERT,图像用 CLIP)。这就意味着,后面做搜索时,查文本就得用生成 text_dense 的模型来生成查询向量,查图像就得用生成 image_dense 的模型,不然格式对不上,搜不准。
- 至于 text_sparse(文本的稀疏向量,用来做关键词匹配的),因为我们用了系统自带的 BM25 算法,它会自动从 text 字段里提取关键词生成稀疏向量,所以不用我们手动填。但如果不想用 BM25,就得自己提前算好稀疏向量。
import random # Generate example vectors def generate_dense_vector(dim): return [random.random() for _ in range(dim)] data=[ { "id": 0, "text": "Red cotton t-shirt with round neck", "text_dense": generate_dense_vector(768), "image_dense": generate_dense_vector(512) }, { "id": 1, "text": "Wireless noise-cancelling over-ear headphones", "text_dense": generate_dense_vector(768), "image_dense": generate_dense_vector(512) }, { "id": 2, "text": "Stainless steel water bottle, 500ml", "text_dense": generate_dense_vector(768), "image_dense": generate_dense_vector(512) } ] res = client.insert( collection_name="my_collection", data=data )(3)执行混合搜索
步骤 1:创建多个 AnnSearchRequest 实例
混合搜索的实现,需要在 hybrid_search () 函数中创建多个 AnnSearchRequest 实例 来实现—— 每个实例对应一个向量字段的基础 ANN 搜索请求。因此,执行混合搜索前,必须为每个要用到的向量字段单独创建 AnnSearchRequest。
此外,还可通过在 AnnSearchRequest 中配置 expr 参数,为混合搜索设置过滤条件(例如限定价格范围、地区等)。需要注意的是:混合搜索中,每个 AnnSearchRequest 仅支持传入一条查询数据。
为了演示不同向量字段的搜索功能,我们将基于一个示例查询构建三个 AnnSearchRequest,过程中会使用预先计算好的稠密向量。这三个请求分别对应以下向量字段:text_dense用于语义文本搜索、text_sparse用于全文搜索或关键词匹配、image_dense用于多模态文本到图像的搜索。
from pymilvus import AnnSearchRequest query_text = "white headphones, quiet and comfortable" query_dense_vector = generate_dense_vector(768) query_multimodal_vector = generate_dense_vector(512) # text semantic search (dense) search_param_1 = { "data": [query_dense_vector], "anns_field": "text_dense", "param": {"nprobe": 10}, "limit": 2 } request_1 = AnnSearchRequest(**search_param_1) # full-text search (sparse) search_param_2 = { "data": [query_text], "anns_field": "text_sparse", "param": {"drop_ratio_search": 0.2}, "limit": 2 } request_2 = AnnSearchRequest(**search_param_2) # text-to-image search (multimodal) search_param_3 = { "data": [query_multimodal_vector], "anns_field": "image_dense", "param": {"nprobe": 10}, "limit": 2 } request_3 = AnnSearchRequest(**search_param_3) reqs = [request_1, request_2, request_3]由于参数limit设置为2,每个AnnSearchRequest返回 2 个搜索结果。在这个例子中,创建了 3 个AnnSearchRequest实例,总共产生6个搜索结果。
步骤 2:配置重排序策略
为了合并和重新排序 ANN 搜索结果集,选择合适的重排序策略至关重要。提供多种重排序策略。
在这个例子中,由于没有特别强调特定的搜索查询,我们将采用 RRFRanker 策略。
ranker = Function( name="rrf", input_field_names=[], # Must be an empty list function_type=FunctionType.RERANK, params={ "reranker": "rrf", "k": 100 # Optional } ) 步骤 3:执行混合搜索在启动混合搜索之前,请确保已加载 Collection。如果 Collection 中的任何向量字段缺少索引或未加载到内存中,则在执行混合搜索方法时将发生错误。
res = client.hybrid_search( collection_name="my_collection", reqs=reqs, ranker=ranker, limit=2 ) for hits in res: print("TopK results:") for hit in hits: print(hit)(4)以下是输出内容:
["['id: 1, distance: 0.006047376897186041, entity: {}', 'id: 2, distance: 0.006422005593776703, entity: {}']"]在为混合搜索指定了limit=2参数的情况下 将对从三次搜索中获得的六个结果进行重新排序。最终,它们将只返回最相似的前两个结果。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。