从向量搜索到强大的 REST API,Elasticsearch 为开发人员提供了最全面的搜索工具包。探索 GitHub 上的示例笔记本,尝试新事物。您也可以立即开始免费试用或在本地运行 Elasticsearch。
Elasticsearch 最初是一个菜谱搜索引擎,旨在提供快速、强大的全文搜索功能。有鉴于此,改进文本搜索一直是我们在矢量方面持续开展工作的重要动力。在 Elasticsearch 7.0 中,我们为高维向量引入了实验性字段类型,现在 7.3 版本支持在文档评分中使用这些向量。
本篇文章主要介绍一种名为文本相似性搜索的特殊技术。在这类搜索中,用户输入一个简短的自由文本查询,然后根据文档与查询的相似度进行排序。文本相似性可用于多种用途:
- 问题解答:给定一系列常见问题,找出与用户输入的问题相似的问题。
- 文章搜索:在研究文章集中,返回标题与用户查询密切相关的文章。
- 图像搜索:在有标题的图片数据集中,查找标题与用户描述相似的图片。
相似性搜索的一种直接方法是根据文档与查询共享的单词数量进行排序。但是,即使一个文档与查询的共同点很少,它们也可能是相似的--一个更稳健的相似性概念应同时考虑其句法和语义内容。
自然语言处理(NLP)界开发了一种名为文本嵌入的技术,可将单词和句子编码为数字向量。这些向量表示旨在捕捉文本的语言内容,可用于评估查询和文档之间的相似性。
本文章将探讨如何利用文本嵌入和 Elasticsearch 的 dense_vector 类型来支持相似性搜索。我们将首先概述嵌入技术,然后使用 Elasticsearch 演示一个简单的相似性搜索原型。
注:在搜索中使用文本嵌入是一个复杂且不断发展的领域。本博客并非对特定架构或实施方案的推荐。从这里开始了解如何利用矢量搜索的强大功能提升搜索体验。
什么是文本嵌入?
让我们仔细看看不同类型的文本嵌入,以及它们与传统搜索方法的比较。
词语嵌入
单词嵌入模型将单词表示为一个密集的数字向量。这些向量旨在捕捉词语的语义属性--向量相近的词语在语义上应该是相似的。在一个好的嵌入中,向量空间中的方向与词义的不同方面相关联。例如,"加拿大" 的向量可能在一个方向上接近"法国" ,在另一个方向上接近"多伦多" 。
一段时间以来,NLP 和搜索界一直对单词的向量表示法很感兴趣。在过去的几年里,人们对单词嵌入的兴趣再次升温,许多传统的任务都在使用神经网络进行重新研究。一些成功的词嵌入算法被开发出来,包括word2vec和GloVe。这些方法利用大量文本集合,并检查每个单词出现的上下文,以确定其向量表示:
- word2vec Skip-gram 模型训练一个神经网络,以预测句子中某个单词周围的上下文单词。网络的内部权重给出了单词嵌入。
- 在 GloVe 中,单词的相似度取决于它们与其他上下文单词出现的频率。该算法根据词的共现计数训练一个简单的线性模型。
许多研究小组分发的模型都是在维基百科或 Common Crawl 等大型文本语料库上预先训练过的,方便下载并用于下游任务。虽然有时会直接使用预训练版本,但调整模型以适应特定的目标数据集和任务可能会有所帮助。这通常是通过在预训练模型上运行 "微调 "步骤来实现的。
词嵌入已被证明是相当稳健和有效的,在机器翻译和情感分类等 NLP 任务中,使用词嵌入来代替单个词块已成为一种普遍做法。
句子嵌入
最近,研究人员开始关注不仅能表示单词,还能表示较长文本部分的嵌入技术。目前的大多数方法都基于复杂的神经网络架构,有时还会在训练过程中加入标记数据,以帮助捕捉语义信息。
训练完成后,这些模型就能处理一个句子,为上下文中的每个单词生成一个向量,并为整个句子生成一个向量。与词嵌入类似,许多模型都有预训练版本,用户可以跳过昂贵的训练过程。虽然训练过程可能非常耗费资源,但调用模型却要轻便得多--句子嵌入模型的速度通常很快,足以作为实时应用的一部分。
一些常见的句子嵌入技术包括InferSent、Universal Sentence Encoder、ELMo 和BERT。改进单词和句子嵌入是一个活跃的研究领域,很可能会引入更多强大的模型。
与传统搜索方法的比较
在传统的信息检索中,将文本表示为数字向量的常见方法是为词汇表中的每个单词分配一个维度。然后,根据词汇中每个术语出现的次数来确定文本的向量。这种表示文本的方式通常被称为"bag of words," ,因为我们只计算单词出现次数,而不考虑句子结构。
文本嵌入在某些重要方面有别于传统的向量表示法:
- 编码矢量密度高,维数相对较低,通常在 100 到 1000 维之间。相比之下,词袋向量比较稀疏,可以包含 50,000 多个维度。作为语义建模的一部分,嵌入算法将文本编码到低维空间中。理想情况下,同义词和短语最终会在新的向量空间中得到相似的表示。
- 在确定向量表示时,句子嵌入可以考虑单词的顺序。例如,"tune in" 与"in tune" 可能会被映射为截然不同的向量。
- 实际上,句子嵌入通常不能很好地概括大段文本。它们通常不用于表示长度超过一小段的文本。
使用嵌入式进行相似性搜索
假设我们有一大堆问题和答案。用户可以提出一个问题,我们希望在问题集中检索出最相似的问题,以帮助他们找到答案。
我们可以使用文本嵌入来检索类似的问题:
- 在索引编制过程中,每个问题都会通过句子嵌入模型生成一个数字向量。
- 当用户输入一个查询时,它会通过相同的句子嵌入模型产生一个向量。为了对回复进行排序,我们计算每个问题与查询向量之间的向量相似度。在比较嵌入向量时,通常使用余弦相似度。
该版本库提供了一个简单的示例,说明如何在 Elasticsearch 中实现这一功能。主脚本从StackOverflow 数据集中索引约 20,000 个问题,然后允许用户针对数据集输入自由文本查询。
我们很快就会详细介绍脚本的各个部分,但首先让我们看看一些示例结果。在许多情况下,即使查询和索引问题之间没有很强的词语重叠,该方法也能捕捉到相似性:
- "压缩文件" 返回"压缩/解压缩文件夹& 文件"
- "确定某物是 IP" 返回"如何判断字符串是 IP 还是主机名"
- "将字节转换为双倍" 返回"在 Python 中将字节转换为浮点数"
实施细节
脚本首先在 TensorFlow 中下载并创建嵌入模型。我们选择了谷歌的通用句子编码器,但也可以使用许多其他嵌入方法。脚本按原样使用嵌入模型,无需额外的训练或微调。
接下来,我们创建 Elasticsearch 索引,其中包括问题标题、标签以及编码为向量的问题标题的映射:
在 dense_vector 的映射中,我们需要指定向量的维数。索引 title_vector 字段时,Elasticsearch 将检查该字段的维数是否与映射中指定的相同。
为编制文档索引,我们通过嵌入模型运行问题标题,以获得一个数字数组。该数组会添加到文档的 title_vector 字段中。
当用户输入查询时,文本会首先通过相同的嵌入模型运行,并存储在参数 query_vector 中。从 7.3 开始,Elasticsearch 在其本地脚本语言中提供了余弦相似度函数。因此,为了根据问题与用户查询的相似度对问题进行排序,我们使用了 script_score 查询:
我们确保将查询向量作为脚本参数传递,以 避免 在每次新查询时 重新编译 script()。由于 Elasticsearch 不允许负分,因此有必要在余弦相似度中加一个负分。
| 注:这篇博文最初使用了 Elasticsearch 7.3 中的另一种矢量函数语法,但在 7.6 中已被弃用。|
重要限制
script_score 查询旨在封装限制性查询,并修改其返回文档的分数。不过,我们提供了一个 match_all 查询,这意味着脚本将在索引中的所有文档上运行。这是目前 Elasticsearch 中向量相似性的一个限制--向量可用于为文档评分,但不能用于初始检索步骤。基于向量相似性的支持检索是当前工作的一个重要领域。
为了避免扫描所有文件并保持快速性能,可以用选择性更强的查询来代替 match_all 查询。用于检索的正确查询可能取决于具体的使用情况。
虽然我们在上面看到了一些令人鼓舞的例子,但重要的是要注意,结果也可能是嘈杂和不直观的。例如,"压缩文件" 也会给"部分 .csproj 分配高分文件" 和"如何避免使用 .pyc" 。而当方法返回令人惊讶的结果时,如何调试问题并不总是很清楚--每个矢量成分的含义往往是不透明的,与可解释的概念并不对应。使用基于词语重叠度的传统评分技术,通常更容易回答"为何该文档排名靠前?"
如前所述,该原型旨在举例说明如何将嵌入模型与矢量场结合使用,而不是一个可投入生产的解决方案。在制定新的搜索策略时,关键是要测试该方法在自己的数据中的表现,确保与匹配查询等强大的基线进行比较。在取得可靠结果之前,可能有必要对策略进行重大修改,包括针对目标数据集微调嵌入模型,或尝试不同的嵌入方法,如单词级查询扩展。
结论
嵌入技术是捕捉文本语言内容的有力方法。通过索引嵌入和基于向量距离的评分,我们可以使用超越词级重叠的相似性概念来比较文档。
我们期待着引入更多基于向量字段类型的功能。使用矢量进行搜索是一个细致入微、不断发展的领域--我们一如既往地希望在Github和讨论论坛上听到您的使用案例和经验!
常见问题
什么是文本相似性搜索?
文本相似性搜索是一种用户输入简短的自由文本查询,然后根据文档与查询的相似性进行排序的搜索方式。它可用于各种用途,如问题解答、文章搜索和图像搜索。




