背景
混合搜索(Hybrid Search)作为 RAG 应用中 Retrieve 重要的一环,通常指的是将向量搜索与基于关键词的搜索(全文检索)相结合,并使用 RRF 算法合并、并重排两种不同检索的结果,最终来提高数据的召回率。全文检索与语义检索不是非此即彼的关系。我们需要同时兼顾语义理解和精确的关键字匹配。比如学术论文的写作中,用户不仅希望在搜索结果看到与搜索查询相关的概念,同时也希望保留查询中使用的原始信息返回搜索结果,比如基于一些特殊术语和名称。因此,许多搜索应用正在采用混合搜索方法,结合两种方法的优势,以平衡灵活的语义相关性和可预测的精确关键字匹配。
从 Milvus 2.4 版本开始,我们引入了多向量搜索和执行混合搜索(多向量搜索)的能力。混合搜索允许用户同时搜索跨多个向量列的内容。这个功能使得可以结合多模态搜索、混合稀疏和全文关键词搜索、密集向量搜索以及混合密集和全文搜索,提供多样且灵活的搜索功能,增强了我们的向量相似性搜索和数据分析。
Milvus BM25
在最新的 Milvus 2.5 里,我们带来了“全新”的全文检索能力
详情请参见 Milvus 2.5:全文检索上线,标量过滤提速,易用性再突破
Sparse-BM25 其原理类似 Elasticsearch 和其他全文搜索系统中常用的 BM25 算法,但针对稀疏向量设计,可以实现相同效果的全文搜索功能。
详情请参见 Elasticsearch vs 向量数据库:寻找最佳混合检索方案
Milvus BM25 Hybrid Search
首先,准备数据和问题,数据来自 Milvus 2.5 release notes,且通过 llama-index 的SentenceWindowNodeParser对于数据进行分块处理。
!wget https://raw.githubusercontent.com/milvus-io/milvus-docs/v2.5.x/site/en/release_notes.md -O milvus_2_5.md
documents = SimpleDirectoryReader( input_files=["./milvus_2_5.md"]).load_data()
# Create the sentence window node parser node_parser = SentenceWindowNodeParser.from_defaults( window_size=3, window_metadata_key="window", original_text_metadata_key="original_text",)
# Extract nodes from documentsnodes = node_parser.get_nodes_from_documents(documents)
# query questionquery = "What are the key features in milvus 2.5?"
复制代码
其次,创建 collection 的 schema 以及索引,其中原始文本数据存于text列,而 Sparse-BM25 数据存于sparse_bm25列,这里需要通过转换 Function 来实现
bm25_function = Function( name="bm25", function_type=FunctionType.BM25, input_field_names=["text"], output_field_names="sparse_bm25", )
复制代码
schema = MilvusClient.create_schema( auto_id=False, enable_dynamic_field=True,)
# Add fields to schemaschema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=512, enable_analyzer=True)schema.add_field(field_name="sparse_bm25", datatype=DataType.SPARSE_FLOAT_VECTOR)schema.add_field(field_name="dense", datatype=DataType.FLOAT_VECTOR, dim=dense_dim)
bm25_function = Function( name="bm25", function_type=FunctionType.BM25, input_field_names=["text"], output_field_names="sparse_bm25", )schema.add_function(bm25_function)
index_params = client.prepare_index_params()
# Add indexesindex_params.add_index( field_name="dense", index_name="dense_index", index_type="IVF_FLAT", metric_type="IP", params={"nlist": 128},)
index_params.add_index( field_name="sparse_bm25", index_name="sparse_bm25_index", index_type="SPARSE_WAND", metric_type="BM25")
# Create collectionclient.create_collection( collection_name=collection_name, schema=schema, index_params=index_params)
复制代码
然后,把数据进行 Embedding 之后,插入到 Collection 里,这里 Embedding 采用的是 OpenAI 的 text-embedding-3-large
def gen_embedding(docs): model_name = "text-embedding-3-large" openai_ef = model.dense.OpenAIEmbeddingFunction( model_name=model_name, api_key=os.environ["OPENAI_API_KEY"] ) return openai_ef.encode_documents(docs)
docs_embeddings = gen_embedding(docs)query_embeddings = gen_embedding([query])
# Assemble datadata = [ {"id": idx, "dense": docs_embeddings[idx].data, "text": doc} for idx, doc in enumerate(docs)]
# Insert datares = client.insert( collection_name=collection_name, data=data)
复制代码
最后,进行查询测试
4.1. 我们先测试下普通查询
search_params = { "metric_type": "IP", "params": {"nprobe": 10} }
res = client.search( collection_name=collection_name, data=[query_embeddings[0]], anns_field="dense", limit=5, search_params=search_params, output_fields=["text"])
复制代码
查询结果
TopK results: 00 Enhancements in cluster management, indexing, and data handling introduce new levels of flexibil...1 With this release, Milvus integrates powerful new features like term-based search, clustering co...2 Milvus 2.5 introduces a built-in Cluster Management WebUI, reducing system maintenance difficult...3 \n\nv2.5.0-beta\n\nRelease date: November 26, 2024\n\n| Milvus version | Python SDK version | No...4 \n\nRelease Notes\n\nFind out what’s new in Milvus!
复制代码
从查询结果来看,最后一条召回内容与查询问题相关度不大。
4.2. 然后进行 Hybrid Search。定义向量搜索和 Sparse-BM25 搜索
k=5 # get the top 5 docs related to the query
search_params_dense: { "metric_type": "IP", "params": {"nprobe": 10}}request_dense = AnnSearchRequest([query_embeddings[0].data], "dense", search_params_dense, limit=k)
search_params_bm25 = {"metric_type": "BM25"}request_bm25 = AnnSearchRequest([query], "sparse_bm25", search_params_bm25, limit=k)
reqs = [request_dense, request_bm25]
复制代码
这里使用RRFRanker来进行 Hybrid Search
ranker = RRFRanker(100)
res = client.hybrid_search( collection_name=collection_name, reqs=reqs, ranker=ranker, limit=5, output_fields=["text"])for hits in res: print("TopK results:") for hit in hits: print(hit)
复制代码
查询结果:
TopK results: 00 \n\nv2.5.0-beta\n\nRelease date: November 26, 2024\n\n| Milvus version | Python SDK version | No...1 Enhancements in cluster management, indexing, and data handling introduce new levels of flexibil...2 This feature is disabled by default in Milvus 2.5 and will be officially available in version 3....3 With this release, Milvus integrates powerful new features like term-based search, clustering co...4 Powered by Tantivy, Milvus 2.5 has built-in analyzers and sparse vector extraction, extending th...
复制代码
从结果来看,基于 Sparse-BM25 的 Hybrid Search 可以准确找到与查询相关的内容。相对于普通查询,召回的内容准确度更大。
总结:
本文讲述了 Milvus 2.5 中引入的 Sparse-BM25 基础原理,以及如何利用 BM25 算法实现 RAG 开发中的 Hybrid Search(混合搜索)实践。通过引入 Sparse-BM25 算法,Milvus 能够在稀疏向量上执行高效的全文检索,并与密集向量搜索相结合,提升检索的召回率和精确度。
参考文档:
代码可通过链接获取:https://pan.baidu.com/s/1eArbrvqmkYTJ-DS8eDkbJA?pwd=1234 提取码: 1234
作者介绍
Zilliz 黄金写手:臧伟
评论