基于LangChain和云原生向量数据库Milvus开发混合搜索AI程序 原创
本文将探讨基于LangChain框架和云原生向量数据库Milvus并将密集嵌入与稀疏嵌入结合起来开发混合搜索型AI程序的实战过程。
简介
最近,我们——来自IBM研究中心的团队——需要在Milvus向量存储中使用混合搜索技术。因为我们已经在使用LangChain框架,所以我们决定一鼓作气贡献出在langchain-milvus中启用这一功能所需的一切。其中包括通过langchain接口支持稀疏嵌入和多向量搜索技术。
在本文中,我们首先简要介绍密集嵌入和稀疏嵌入之间的区别,然后分析如何使用混合搜索来利用这两种嵌入技术。然后,我们还将提供关键部分的源代码分析,以演示如何在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
你还可以在链接https://gist.github.com/omriel1/3b8ea57cc14b896237c47d5417eaec8f处查看和克隆整个代码。
接下来,我们正式开始。
密集嵌入
使用向量存储的最常见方式是使用密集嵌入。在这里,我们使用预先训练的模型将数据(通常是文本,但也可以是其他媒体,如图像等)嵌入到高维向量中,并将其存储在向量数据库中。向量有几百(甚至几千)个维度,每个条目都是浮点数。通常,向量中的所有条目都对应非零值,因此称为“密集”。给定一个查询,我们使用相同的模型将其嵌入,向量存储根据向量相似性检索相似的相关数据。借助于langchain-milvus这个框架,只需几行代码即可实现这一点。让我们具体看一下这是如何完成的。
首先,我们使用来自HuggingFace的模型定义向量存储:
dense_embedding = HuggingFaceEmbeddings(model_name=
"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",
"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?"
dense_output = vector_store.similarity_search(query=query, k=1)
print(f"Dense embeddings results:\n{dense_output[0].page_content}\n")
# 输出——密集嵌入的结果如下:
# Today was very warm during the day but cold at night
在这里,查询被嵌入,向量存储执行(通常是近似的)相似性搜索并返回找到的最接近的内容。
密集嵌入模型经过训练,可以捕获数据的语义含义并将其表示在多维空间中。其优势很明显——它支持语义搜索;这意味着,结果基于查询的含义。但是,有时仅仅这些还不够。譬如,如果你寻找特定的关键字,甚至是没有更广泛含义的单词(如名称),语义搜索就会误导你,这种方法就会失败。
稀疏嵌入
在LLM成为现实之前,学习模型还没有那么流行,搜索引擎使用传统方法(如TF-IDF算法或其现代增强版的BM25算法——因其在开源分布式搜索引擎Elasticsearch中的使用而闻名)来搜索相关数据。使用这些方法,维度的数量就是词汇量(通常为数万,比密集向量空间大得多),每个条目代表关键字与文档的相关性,同时这也考虑到该术语的频率及其在文档语料库中的稀有性。对于每个数据点,大多数条目都是零(表示未出现的单词),因此称为“稀疏”。虽然底层实现不同,但使用langchain-milvus接口后,它变得非常相似。让我们看看它的实际效果:
sparse_embedding = BM25SparseEmbedding(corpus=documents)
vector_store = Milvus(
embedding_function=sparse_embedding,
connection_args={"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")
#输出:稀疏嵌入结果:
# In Israel, Hot is a TV provider that broadcast 7 days a week
BM25适用于精确关键字匹配,这对于缺乏明确语义含义的术语或名称非常有用。但是,它不会捕捉查询的意图,并且在需要语义理解的许多情况下会产生较差的结果。
【注意】“稀疏嵌入”一词也指SPLADE或Elastic Elser等高级方法。这些方法也可以与Milvus一起使用,并可以集成到混合搜索中!
作者本人提供的图片
混合搜索
如果你在上面两个示例之间交换查询,并将每个查询与另一个嵌入一起使用,则两者都会产生错误的结果。这表明每种方法都有其优点,也有其缺点。混合搜索将两者结合起来,旨在充分利用两者的优点。通过使用密集和稀疏嵌入对数据进行索引,我们可以执行同时考虑语义相关性和关键字匹配的搜索,并根据自定义权重平衡结果。同样,内部实现更复杂,但langchain-milvus库使得这一过程非常容易。让我们看看它是如何工作的:
vector_store = Milvus(
embedding_function=[
sparse_embedding,
dense_embedding,
],
connection_args={"uri": "./milvus_hybrid.db"},
auto_id=True,
)
vector_store.add_texts(documents)
在此设置中,稀疏和密集嵌入均适用。下面,让我们测试一下具有相等权重的混合搜索:
query = "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}")
#输出:混合搜索结果:
# In Israel, Hot is a TV provider that broadcast 7 days a week
这将使用每个嵌入函数搜索相似的结果,为每个分数赋予权重,并返回具有最佳加权分数的结果。我们可以看到,如果对密集嵌入赋予稍微多一点的权重,我们就会得到想要的结果。对于第二个查询也是如此。
如果我们对密集嵌入赋予更多权重,我们将再次得到不相关的结果,就像单独使用密集嵌入一样:
query = "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}")
# 输出:混合搜索结果:
# Today was very warm during the day but cold at night
在密集和稀疏之间找到适当的平衡并非易事,可以看作是更广泛的超参数优化问题的一部分。目前正在进行的研究和工具试图解决该领域的此类问题,例如IBM的AutoAI for RAG。
你可以通过更多方式调整和使用混合搜索方法。例如,如果每个文档都有一个关联的标题,则可以使用两个密集嵌入函数(可能使用不同的模型)——一个用于标题,另一个用于文档内容——并对两个索引执行混合搜索。Milvus目前支持多达10个不同的向量字段,为复杂的应用程序提供了灵活性。还有用于索引和重新排名方法的其他配置,你可以查看有关可用参数和选项的Milvus文档。
结束语
现在,可以通过LangChain访问Milvus的多向量搜索功能,你可以轻松地将混合搜索集成到自己的应用程序中。这为在你的应用程序中应用不同的搜索策略开辟了新的可能性,从而可以轻松地定制搜索逻辑以适应特定使用场景。对我们来说,这是为开源项目做贡献的好机会。我们日常使用的许多库和工具都是开源的,回馈社区是件好事。希望这对其他人有所帮助。
最后,我要大声感谢Erick Friis和Cheng Zi为langchain-milvus所做的所有努力。没有他们,这项工作就不可能完成。
译者介绍
朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。
原文标题:Dance Between Dense and Sparse Embeddings: Enabling Hybrid Search in LangChain-Milvus,作者:Omri Levy,Ohad Eytan