在LangChain-Milvus中实现混合搜索:密集嵌入与稀疏嵌入的结合

2024年11月22日 由 alex 发表 16 0

简介

在这篇文章中,我们将简要介绍密集嵌入和稀疏嵌入之间的区别,以及如何利用混合搜索来结合使用这两者。我们还将提供一个代码演示,展示如何在langchain-milvus中使用这些新功能。


要使用本文章中的代码,你需要安装一些软件包:


pip install langchain_milvus==0.1.6
pip install langchain-huggingface==0.1.0
pip install "pymilvus[model]==2.4.8"


并且需要导入这些:


from langchain_huggingface import HuggingFaceEmbeddings
from langchain_milvus.utils.sparse import BM25SparseEmbedding
from langchain_milvus.vectorstores import Milvus


密集嵌入

使用向量存储的最常见方式是密集嵌入。在这里,我们使用预训练模型将数据(通常是文本,但也可以是其他媒体,如图像等)嵌入到高维向量中,并将其存储在向量数据库中。这些向量具有几百(甚至几千)个维度,每个条目都是一个浮点数。通常,向量中的所有条目都被非零值占据,因此称为“密集”。给定一个查询,我们使用相同的模型将其嵌入,然后向量存储基于向量相似性检索相似、相关的数据。使用langchain-milvus,只需几行代码即可完成。让我们看看它是如何实现的。


首先,我们使用HuggingFace的模型来定义向量存储:


dense_embedding = HuggingFaceEmbeddings(model_name=
    "sentence-transformers/all-MiniLM-L6-v2")"sentence-transformers/all-MiniLM-L6-v2")
vector_store = Milvus(
    embedding_function=dense_embedding,
    connection_args={"uri": "./milvus_dense.db"}, # Using milvus-lite for simplicity
    auto_id=True,
)


其次,我们将数据插入到向量存储中:


document = [
    "Today was very warm during the day but cold at night","Today was very warm during the day but cold at night",
    "In Israel, Hot is a TV provider that broadcasts 7 days a week",
]
vector_store.add_texts(documents)


在后台,每个文档都会使用我们提供的模型嵌入到一个向量中,并与原始文本一起存储。


最后,我们可以搜索一个查询并打印得到的结果:


query = "What is the weather? is it hot?""What is the weather? is it hot?"
dense_output = vector_store.similarity_search(query=query, k=1)
print(f"Dense embeddings results:\n{dense_output[0].page_content}\n")
# output: Dense embeddings results: 
#         Today was very warm during the day but cold at night


在这里,查询被嵌入,向量存储执行(通常是近似的)相似性搜索,并返回它找到的最接近的内容。


密集嵌入模型经过训练,可以捕获数据的语义含义并在多维空间中表示它。其优势显而易见——它实现了语义搜索,这意味着结果基于查询的含义。但有时这还不够。如果你寻找特定的关键词,甚至没有更广泛含义的单词(如人名),语义搜索会误导你,这种方法就会失败。


稀疏嵌入

在大型语言模型(LLM)出现之前,在学习模型还不那么流行的时候,搜索引擎使用传统方法,如TF-IDF或其现代增强版BM25(因其在Elastic中的使用而闻名)来搜索相关数据。在这些方法中,维度的数量等于词汇表的大小(通常为数万,远大于密集向量空间),每个条目表示一个关键词与文档的相关性,同时考虑到该词在整个文档集中的出现频率和稀有性。对于每个数据点,大多数条目都是零(对于未出现的单词),因此称为“稀疏”。尽管底层实现有所不同,但在langchain-milvus接口中,它们的使用变得非常相似。让我们来看看它是如何工作的:


sparse_embedding = BM25SparseEmbedding(corpus=documents)
vector_store = Milvus(
    embedding_function=sparse_embedding,
    connection_args={"uri": "./milvus_sparse.db"},"uri": "./milvus_sparse.db"},
    auto_id=True,
)
vector_store.add_texts(documents)
query = "Does Hot cover weather changes during weekends?"
sparse_output = vector_store.similarity_search(query=query, k=1)
print(f"Sparse embeddings results:\n{sparse_output[0].page_content}\n")
# output: Sparse embeddings results:
#         In Israel, Hot is a TV provider that broadcast 7 days a week


BM25对于精确关键词匹配非常有效,这对于缺乏明确语义含义的术语或名称来说很有用。然而,它无法捕捉查询的意图,并且在许多需要语义理解的情况下会产生较差的结果。


15


混合搜索

如果你将上述两个示例中的查询进行互换,并使用每种查询去匹配另一种的嵌入方式,两者都会产生错误的结果。这证明了每种方法都有其优势,但同时也存在不足。混合搜索结合了这两种方法,旨在取长补短。通过同时使用密集和稀疏嵌入来对数据进行索引,我们可以执行既考虑语义相关性又考虑关键词匹配的搜索,并根据自定义权重平衡结果。同样地,内部实现更为复杂,但langchain-milvus使其使用起来相当简单。让我们来看看这是如何工作的:


vector_store = Milvus(
    embedding_function=[
        sparse_embedding,
        dense_embedding,
    ],
    connection_args={"uri": "./milvus_hybrid.db"}, "uri": "./milvus_hybrid.db"}, 
    auto_id=True,
)
vector_store.add_texts(documents)


在这个设置中,同时应用了稀疏嵌入和密集嵌入。让我们来测试一下权重相等的混合搜索:


query = "Does Hot cover weather changes during weekends?""Does Hot cover weather changes during weekends?"
hybrid_output = vector_store.similarity_search(
    query=query,
    k=1,
    ranker_type="weighted",
    ranker_params={"weights": [0.49, 0.51]},  # Combine both results!
)
print(f"Hybrid search results:\n{hybrid_output[0].page_content}")
# output: Hybrid search results:
#         In Israel, Hot is a TV provider that broadcast 7 days a week


这个搜索使用每种嵌入函数来查找相似的结果,给每个分数赋予一个权重,并返回具有最佳加权分数的结果。我们可以看到,当密集嵌入的权重稍微大一些时,我们就能得到想要的结果。对于第二个查询也是如此。


如果我们给密集嵌入赋予更多的权重,我们将再次得到不相关的结果,就像只使用密集嵌入时一样。


query = "When and where is Hot active?""When and where is Hot active?"
hybrid_output = vector_store.similarity_search(
    query=query,
    k=1,
    ranker_type="weighted",
    ranker_params={"weights": [0.2, 0.8]},  # Note -> the weights changed
)
print(f"Hybrid search results:\n{hybrid_output[0].page_content}")
# output: Hybrid search results:
#         Today was very warm during the day but cold at night


在密集嵌入和稀疏嵌入之间找到正确的平衡并非易事,这可以看作是更广泛的超参数优化问题的一部分。目前,有持续的研究和工具正在尝试解决这一领域的此类问题,例如IBM的用于检索增强生成(RAG)的AutoAI。


你可以采用和使用混合搜索方法的方式还有很多。例如,如果每个文档都有一个相关的标题,你可以使用两个密集嵌入函数(可能使用不同的模型)——一个用于标题,另一个用于文档内容——并对两个索引执行混合搜索。Milvus目前支持多达10个不同的向量字段,为复杂应用提供了灵活性。此外,还有索引和重新排序方法的额外配置。


总结

现在,通过LangChain可以访问Milvus的多向量搜索功能,你可以轻松地将混合搜索集成到你的应用程序中。这为在你的应用程序中应用不同的搜索策略开辟了新的可能性,使得根据特定用例定制搜索逻辑变得容易。对我们来说,这是一个为开源项目做出贡献的好机会。

文章来源:https://medium.com/towards-data-science/dance-between-dense-and-sparse-embeddings-enabling-hybrid-search-in-langchain-milvus-7c8de54dda24
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消