使用Sentence Transformers和Faiss构建语义搜索引擎

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 使用Sentence Transformers和Faiss构建语义搜索引擎

640.png


介绍

您是否曾经想过如何使用Sentence Transformers创建嵌入向量,并在诸如语义文本相似这样的下游任务中使用它们?

在本教程中,您将学习如何使用Sentence Transformers和Faiss构建一个基于向量的搜索引擎。代码地址会在本文的最后提供

为什么要构建基于向量的搜索引擎?

基于关键字的搜索引擎很容易使用,在大多数情况下工作得很好。你要求机器学习论文,他们会返回一堆包含精确匹配或接近变化的查询结果,就像机器学习一样。其中一些甚至可能返回包含查询的同义词或出现在类似上下文中的单词的结果。其他的,如Elasticsearch,可以快速、可伸缩地完成所有这些功能,甚至更多。然而,基于关键词的搜索引擎通常会遇到以下问题:

  • 复杂查询或具有双重含义的单词。
  • 长查询,如论文摘要或博客中的一段。
  • 不熟悉某个领域术语的用户或想要进行探索性搜索的用户。

基于向量(也称为语义)的搜索引擎通过使用最先进的语言模型找到文本查询的数字表示,在高维向量空间中对它们进行索引,并度量查询向量与索引文档的相似程度,从而解决了这些缺陷。

索引、矢量化和排序方法

在深入学习本教程之前,我将简要解释基于关键字和基于向量的搜索引擎如何进行以下工作的

  • 索引文档(即以一种容易检索的形式存储它们
  • 向量化文本数据
  • 衡量文档与查询的相关性

这将帮助我们突出两种系统之间的差异,并理解为什么基于矢量的搜索引擎可以为长文本查询提供更有意义的结果。

1、基于关键字搜索引擎

让我们以一个过于简化的Elasticsearch为例。Elasticsearch使用标记器将文档分割成标记(即有意义的文本单位),这些标记映射到数字序列,并用于构建反向索引。

640.png

反向索引:与检查每个文档是否包含查询词不同,反向索引使我们能够查找一个词并检索包含该词的所有文档列表。源

同时,Elasticsearch用一个高维加权向量表示每个索引文档,其中每个不同的索引项是一个维度,它们的值(或权重)是用TF-IDF计算的。

为了找到相关文档并对其进行排序,Elasticsearch将布尔模型(BM)与向量空间模型(VSM)结合在一起。BM标记包含用户查询的文档,VSM评分它们的相关性。在搜索过程中,使用相同的TF-IDF管道将查询转换为向量,文档d对查询q的VSM得分为加权查询向量V(q)和V(d)的余弦相似度。

这种度量相似度的方法非常简单,而且不可扩展。Elasticsearch背后的工作机器是Lucene,它使用了各种技巧,从增强领域到改变矢量的标准化方式,以加快搜索速度和提高其质量。

Elasticsearch在大多数情况下工作得很好,然而,我们希望创建一个系统,也关注单词的上下文。这把我们带到了基于矢量的搜索引擎。

2、基于矢量的搜索引擎

我们还需要创建考虑单词上下文的文档表示。我们还需要一种高效可靠的方法来检索存储在索引中的相关文档。

创建密集的文档向量

近年来,NLP社区在这方面取得了长足的进步,许多深度学习模型都是开源的,并通过像Huggingface 's transformer这样的软件包进行分发,这些软件包提供了最先进的、经过预先训练的模型。使用预先训练好的模型有很多优点:

  • 它们通常生成高质量的嵌入,因为它们是在大量文本数据上训练的。
  • 它们不需要您创建自定义标记器,因为转换器有自己的方法。
  • 根据您的任务对模型进行微调很简单

这些模型为文档中的每个标记生成一个固定大小的向量。我们如何获得文档级向量呢?这通常通过平均或汇集单词向量来实现。然而,这些方法产生低于平均的句子和文档嵌入,通常比平均GloVe向量差。

为了构建我们的语义搜索引擎,我们将微调基于BERT的模型,以生成语义上有意义的长文本序列嵌入。

建立一个指数并衡量相关性

检索相关文档最简单的方法是测量查询向量和数据库中每个文档向量之间的余弦相似度,然后返回得分最高的那些。不幸的是,这在实践中非常缓慢。

首选的方法是使用Faiss,一个有效的相似度搜索和聚类密集向量库。Faiss提供了大量的索引和复合索引。此外,给定一个GPU, Faiss可扩展到数十亿个向量!

用Transformers 和Faiss构建一个基于向量的搜索引擎

在这个实际的例子中,我们将使用真实的数据。通过使用Orion查询微软的学术图表,我创建了一个包含8,430篇发表于2010年至2020年的关于错误信息、虚假信息和假新闻的学术文章的数据集。

我检索了论文的摘要、标题、引用、发表年份和ID。我做了最少的数据清理,比如删除没有摘要的论文。数据是这样的:

640.png

导入Python包并从S3读取数据

让我们导入所需的包并读取数据。该文件是公开的,所以您可以在谷歌Colab上运行代码,或者通过访问GitHub repo在本地运行代码!

#UsedtoimportdatafromS3.
importpandasaspdimports3fs#Usedtocreatethedensedocumentvectors.
importtorchfromsentence_transformersimportSentenceTransformer#UsedtocreateandstoretheFaissindex.
importfaissimportnumpyasnpimportpickle#Usedtodovectorsearchesanddisplaytheresults.
fromvector_engine.utilsimportvector_search, id2details#UsepandastoreadfilesfromS3buckets!df=pd.read_csv('s3://vector-search-blog/misinformation_papers.csv')

使用Sentence Transformers对文档进行矢量化

接下来,我们对论文摘要进行编码。Sentence Transformers提供了许多预先训练过的模型,其中一些可以在这个电子表格中找到。在这里,我们将使用base-nli- stbs -mean-tokens模型,该模型在语义文本相似度任务中表现出色,而且比BERT要快得多,因为它要小得多。

我们将做如下的工作:

  • 通过将模型名作为字符串传递来实例化transformer。
  • 切换到GPU,如果它是可用的。
  • 使用' .encode() '方法对所有论文摘要进行向量化。
#Instantiatethesentence-levelDistilBERTmodel=SentenceTransformer('distilbert-base-nli-stsb-mean-tokens')
#CheckifCUDAisavailableansswitchtoGPUiftorch.cuda.is_available():
model=model.to(torch.device("cuda"))
print(model.device)
#Convertabstractstovectorsembeddings=model.encode(df.abstract.to_list(), show_progress_bar=True)

建议使用GPU对文档进行矢量转换。

用Faiss索引文档

Faiss包含的算法可以在任意大小的向量集合中搜索,甚至是那些无法放入RAM的向量。要了解更多关于Faiss的信息,你可以在arXiv阅读他们的论文。

Faiss是围绕索引对象构建的,索引对象包含可搜索向量,有时还对其进行预处理。它处理一个固定维数d的向量集合,通常是几个10到100。

Faiss只使用32位浮点矩阵。这意味着在构建索引之前,我们必须更改输入的数据类型。

在这里,我们将使用IndexFlatL2索引来执行暴力的L2距离搜索。它在我们的数据集上工作得很好,但是,对于大型数据集,它会非常慢,因为它会随着索引向量的数量线性扩展。Faiss也提供快速索引!

要用抽象向量创建索引,我们将:

将抽象向量的数据类型更改为float32。

建立一个索引,并传递它将要操作的向量的维数。

将索引传递给IndexIDMap,该对象使我们能够为索引的向量提供id的自定义列表。

将抽象向量及其ID映射添加到索引。在我们的例子中,我们将从Microsoft Academic Graph将向量映射到它们的论文id。

为了测试索引是否按预期工作,我们可以使用索引向量查询它,并检索其最相似的文档以及它们的距离。第一个结果应该是我们的查询!

#Step1: Changedatatypeembeddings=np.array([embeddingforembeddinginembeddings]).astype("float32")
#Step2: Instantiatetheindexindex=faiss.IndexFlatL2(embeddings.shape[1])
#Step3: PasstheindextoIndexIDMapindex=faiss.IndexIDMap(index)
#Step4: AddvectorsandtheirIDsindex.add_with_ids(embeddings, df.id.values)
#Retrievethe10nearestneighboursD, I=index.search(np.array([embeddings[5415]]), k=10)

640.png

由于我们使用索引向量查询Faiss,因此第一个结果必须是查询,并且距离必须等于零!

搜索用户输入的查询

让我们尝试为新搜索查询找到相关的学术文章。在此示例中,我将使用WhatsApp的第一段查询索引,这可以从揭穿事实核查的故事中受益,以减少错误信息?

640.png

要检索学术文章以进行新的查询,我们必须:

  • 使用与抽象向量相同的句子DistilBERT模型对查询进行编码。
  • 将其数据类型更改为float32
  • 使用编码的查询搜索索引

为了方便起见,我将这些步骤包装在vector_search()函数中。

importnumpyasnpdefvector_search(query, model, index, num_results=10):
"""Tranforms query to vector using a pretrained, sentence-levelDistilBERT model and finds similar vectors using FAISS.Args:query (str): User query that should be more than a sentence long.model (sentence_transformers.SentenceTransformer.SentenceTransformer)index (`numpy.ndarray`): FAISS index that needs to be deserialized.num_results (int): Number of results to return.Returns:D (:obj:`numpy.array` of `float`): Distance between results and query.I (:obj:`numpy.array` of `int`): Paper ID of the results."""vector=model.encode(list(query))
D, I=index.search(np.array(vector).astype("float32"), k=num_results)
returnD, Idefid2details(df, I, column):
"""Returns the paper titles based on the paper index."""return [list(df[df.id==idx][column]) foridxinI[0]]
#QueryingtheindexD, I=vector_search([user_query], model, index, num_results=10)

我们输入的文字讨论了错误信息,事实检查,WhatsApp和巴西和印度的选举。我们希望基于矢量的搜索引擎返回有关这些主题的结果。通过检查论文标题,大多数结果看起来与我们的查询非常相关。我们的搜索引擎可以正常工作!

640.png

结论

在本教程中,我们使用Sentence Transformers和Faiss构建了一个基于矢量的搜索引擎。我们的索引效果很好,并且相当简单。我们可以使用像SciBERT这样的领域特定的转换器来提高嵌入的质量,该转换器已在语义库的语料库上的论文中进行了预训练。我们还可以在返回结果之前删除重复项并尝试其他索引。

对于使用Elasticsearch的人员,Open Distro引入了近似的k-NN相似性搜索功能,该功能也是AWS Elasticsearch服务的一部分。

最后,您可以在GitHub上找到代码(https://github.com/kstathou/vector_engine),并通过Google Colab进行尝试(https://colab.research.google.com/github/kstathou/vector_engine/blob/master/notebooks/001_vector_search.ipynb)。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
机器学习/深度学习 自然语言处理 算法
Jieba分词的准确率提升:使用paddle模式进行分词(使用百度飞桨深度学习模型进行分词)
jieba中的paddle模式是指使用飞桨(PaddlePaddle)深度学习框架加速分词的一种模式。相对于传统的分词算法,paddle模式采用了深度学习模型,可以获得更高的分词准确度和更快的分词速度。
|
19小时前
|
索引 搜索推荐 UED
基于elasticsearch + huggingface model 实现语义检索
项目地址:https://github.com/skyterra/elastic-embedding-searcher。本项目利用Hugging Face模型生成文本的嵌入向量,并将其同步至Elasticsearch创建索引,支持通过余弦相似度进行高效的向量搜索,实现精准的内容检索与推荐功能。该项目为开发智能搜索应用提供了强大的后端支持。 (该简介有239个字符,包括标点和空格)
24 5
|
1月前
|
机器学习/深度学习 人工智能 缓存
最佳实践!使用 GraphRAG + GLM-4 对《红楼梦》全文构建中文增强检索
特别介绍`graphrag-practice-chinese`项目,这是一个针对中文优化的GraphRAG应用实例,通过改进文本切分策略、使用中文提示词及选择更适合中文的模型等手段,显著提升了处理中文内容的能力。项目不仅包括详细的搭建指南,还提供了《红楼梦》全文的索引构建与查询测试示例,非常适合个人学习和研究。
1361 1
|
2月前
|
存储 自然语言处理
LangChain-04 RAG Retrieval-Augmented Generation 检索增强生成
LangChain-04 RAG Retrieval-Augmented Generation 检索增强生成
31 3
|
存储 机器学习/深度学习 算法
语义检索系统排序模块:基于ERNIE-Gram的Pair-wise和基于RocketQA的CrossEncoder训练单塔模型
语义检索系统排序模块:基于ERNIE-Gram的Pair-wise和基于RocketQA的CrossEncoder训练单塔模型
语义检索系统排序模块:基于ERNIE-Gram的Pair-wise和基于RocketQA的CrossEncoder训练单塔模型
|
2月前
|
存储 人工智能 安全
LangChain-23 Vector stores 向量化存储 并附带一个实际案例 通过Loader加载 Embedding后持久化 LangChain ChatOpenAI ChatGLM3对话
LangChain-23 Vector stores 向量化存储 并附带一个实际案例 通过Loader加载 Embedding后持久化 LangChain ChatOpenAI ChatGLM3对话
94 0
|
4月前
|
自然语言处理
【NLP】如何实现快速加载gensim word2vec的预训练的词向量模型
本文探讨了如何提高使用gensim库加载word2vec预训练词向量模型的效率,提出了三种解决方案:保存模型以便快速重新加载、仅保存和加载所需词向量、以及使用Embedding工具库代替word2vec原训练权重。
283 2
|
4月前
|
人工智能 自然语言处理 NoSQL
LangChain 构建问题之LangChain 中生成文本的嵌入向量如何解决
LangChain 构建问题之LangChain 中生成文本的嵌入向量如何解决
54 0
|
7月前
|
人工智能 自然语言处理 搜索推荐
文本向量化模型新突破——acge_text_embedding勇夺C-MTEB榜首
在人工智能的浪潮中,大型语言模型(LLM)无疑是最引人注目的潮头。在支撑这些大型语言模型应用落地方面,文本向量化模型(Embedding Model)的重要性也不言而喻。 近期,我在浏览huggingface发现,国产自研文本向量化模型acge_text_embedding(以下简称“acge模型”)已经在业界权威的中文语义向量评测基准C-MTEB(Chinese Massive Text Embedding Benchmark)中获得了第一名。
|
7月前
|
机器学习/深度学习 人工智能 搜索推荐
【LLM】深入浅出学习模型中Embedding(嵌入)
【5月更文挑战第2天】人工智能嵌入深入浅出介绍
292 0