作者:来自 Elastic Carlos Delgado, Mike Pellegrini
semantic_text - 你知道,用于语义搜索!
你是否想开始使用语义搜索来搜索数据,但专注于模型和结果而不是技术细节?我们引入了 semantic_text 字段类型,它将处理你所需的细节和基础架构。
语义搜索(semantic search)是一种复杂的技术,旨在通过利用机器学习模型来增强搜索结果的相关性。与传统的基于关键字的搜索不同,语义搜索专注于理解单词的含义及其使用的上下文。这是通过应用机器学习模型来实现的,这些模型可以提供对文本的更深入的语义理解。
这些模型生成向量嵌入(vector embeddings),它们是捕获文本含义的数字表示。这些嵌入与你的文档数据一起存储,从而启用考虑单词含义和上下文而不是纯词汇匹配的向量搜索技术。
你需要添加什么来添加语义搜索?
要执行语义搜索,你需要执行以下步骤:
- 选择一个推理模型来创建嵌入,用于索引文档和执行查询。
- 创建索引映射以存储推理结果,以便之后可以有效地搜索它们。
- 设置索引,以便为添加到索引中的新文档计算推理结果。
- 自动处理长文本文档,以便搜索准确并覆盖整个文档。
- 查询数据以检索结果。
从头开始配置语义搜索可能很复杂。它需要设置映射、提取管道和针对你选择的推理模型量身定制的查询。每个步骤都提供了微调和优化的机会,但也需要仔细配置以确保所有组件无缝协作。
虽然这提供了很大程度的控制,但它使使用语义搜索成为一个详细而深思熟虑的过程,要求你配置彼此相关并与推理模型相关的单独部分。
semantic_text 通过专注于重要的事情来简化此过程:推理模型。一旦你选择了推理模型,semantic_text 将通过提供合理的默认值让你轻松开始使用语义搜索,这样你就可以专注于搜索,而不是如何索引、生成或查询嵌入。
让我们看一下每个步骤,以及 semantic_text 如何简化此设置。
选择一个推理模型
推理模型将为你的文档和查询生成嵌入。不同的模型在以下方面有不同的权衡:
- 结果的准确性和相关性
- 可扩展性和性能
- 语言和多语言支持
- 成本
Elasticsearch 支持内部和外部推理服务:
- 内部服务部署在 Elasticsearch 集群中。你可以使用已包含的模型(如 ELSER 和 E5),也可以使用 eland 将外部模型导入集群。
- 外部服务由模型提供商部署。Elasticsearch 支持以下内容:
- Cohere
- Hugging Face
- Mistral
- OpenAI
- Azure AI Studio
- Azure OpenAI
- Google AI Studio
选择推理模式后,为其创建推理端点。推理端点标识符将是你设置 semantic_text 所需的唯一配置详细信息。
PUT _inference/sparse_embedding/my-elser-endpoint
{
"service": "elser",
"service_settings": {
"num_allocations": 1,
"num_threads": 1
}
}
创建索引映射
Elasticsearch 需要对模型生成的嵌入进行索引,以便以后可以有效地查询它们。
在使用 semantic_text 之前,你需要了解用于存储嵌入信息的两个主要字段类型:
- sparse_vector:它对稀疏向量嵌入进行索引,例如 ELSER 生成的嵌入。每个嵌入都由标记和权重对组成。每个嵌入都会生成少量标记。
- dense_vector:它对包含嵌入信息的数字向量进行索引。模型会生成固定大小的向量,称为向量维度。
要使用的字段类型取决于你选择的模型。如果使用密集向量,你需要配置字段以包含维度计数、用于计算向量接近度的相似度函数以及存储自定义,例如量化或每个元素使用的特定数据类型。
现在,如果你使用 semantic_text,则只需为模型指定推理端点标识符即可定义 semantic_text 字段映射:
PUT test-index
{
"mappings": {
"properties": {
"infer_field": {
"type": "semantic_text",
"inference_id": "my-elser-endpoint"
}
}
}
}
就是这样。你无需定义其他映射选项,也无需了解需要使用哪种字段类型。
设置索引
一旦你的索引准备好存储嵌入,就该生成它们了。
在 semantic_text 之前,要自动在文档摄取时生成嵌入,你需要设置摄取管道。
摄取管道用于在摄取到索引中时或在摄取过程中明确指定时自动丰富或转换文档。
你需要使用 inference processor 为你的字段生成嵌入。处理器需要使用以下内容进行配置:
- 从中生成嵌入的文本字段
- 将添加生成的嵌入的输出字段
- 根据模型类型,针对文本嵌入或稀疏嵌入的特定推理配置
使用 semantic_text,你只需将文档添加到索引中。semantic_text 字段将使用指定的推理端点自动计算嵌入。
这意味着无需创建推理管道来生成嵌入。使用批量、索引或更新 API 将自动为你执行此操作:
PUT test-index/_doc/doc1
{
"infer_field": "These are not the droids you're looking for. He's free to go around"
}
semantic_text 字段中的推理请求也是批量处理的。如果你在 bulk API 请求中有 10 个文档,并且每个文档包含 2 个 semantic_text 字段,那么该请求将一次性向你的推理服务执行包含 20 个文本的单个推理请求,而不是每次发出 10 个包含 2 个文本的单独推理请求。
自动处理长文本段落
选择模型的挑战之一是模型可以生成嵌入的标记数量。模型可以处理的标记数量有限。这被称为模型的上下文窗口。
如果你需要处理的文本比模型的上下文窗口长,你可以截断文本并仅使用其中的一部分来生成嵌入。这并不理想,因为你会丢失信息;生成的嵌入将无法捕获输入文本的完整上下文。
即使你有一个长的上下文窗口,长文本也意味着大量内容将被缩减为单个嵌入,从而使其成为不准确的表示。
此外,返回长文本会让用户难以理解,因为他们必须扫描文本以检查它是否是他们要查找的内容。最好使用较小的片段。
另一种选择是使用分块将长文本分成较小的片段。这些较小的块被添加到每个文档中,以更好地表示完整的文本。然后,你可以使用嵌套查询搜索所有单个片段并检索包含得分最高的块的文档。
在 semantic_text 之前,分块不是开箱即用的 - 推理处理器不支持分块。如果你需要使用分块,则需要在提取文档之前进行分块,或者使用脚本处理器在 Elasticsearch 中执行分块。
使用 semantic_text 意味着在索引时将为你完成分块。长文档将被拆分为 250 个单词的部分,其中有 100 个单词重叠,因此每个部分与前一个部分共享 100 个单词。这种重叠可确保连续性,并防止输入文本中的重要上下文信息因硬中断而丢失。
如果模型和推理服务支持批处理,则分块输入会自动批处理为尽可能少的请求,每个请求的大小都适合推理服务。生成的块将存储在嵌套对象结构中,以便你可以检查每个块中包含的文本。
查询数据
现在文档及其嵌入已在 Elasticsearch 中编入索引,是时候进行一些查询了!
在使用 semantic_text 之前,你需要根据模型生成的嵌入类型(密集或稀疏)使用不同的查询。查询 sparse_vector 字段类型需要 sparse vector query,而搜索 density_vector 字段类型则可以使用 knn search 或 knn query。
查询过程可以进一步定制,以提高性能和相关性。例如,稀疏向量查询可以定义标记修剪(token pruning),以避免考虑不相关的标记。Knn 查询可以指定要考虑的候选数以及要从每个分片返回的前 k 个结果。
使用 semantic_text 时,你无需处理这些细节。你可以使用 single query type 来搜索文档:
GET test-index/_search
{
"query": {
"semantic": {
"field": "inference_field",
"query": "robots you're searching for"
}
}
}
只需包含字段和查询文本。无需在稀疏向量和 knn 查询之间做出选择,语义文本会为你完成此操作。
将其与使用具有所有配置参数的特定 knn 搜索进行比较:
{
"knn": {
"field": "inference-field",
"k": 10,
"num_candidates": 100,
"query_vector_builder": {
"text_embedding": {
"model_id": "my-dense-vector-embedding-model",
"model_text": "robots you're searching for"
}
}
}
}
底层原理
要了解 semantic_text 的工作原理,你可以创建一个 semantic_text 索引并检查摄取文档时会发生什么。摄取第一个文档时,推理端点会计算嵌入。索引后,你会注意到索引映射中的变化:
GET test-index
{
"test-index": {
"mappings": {
"properties": {
"infer_field": {
"type": "semantic_text",
"inference_id": "my-elser-endpoint",
"model_settings": {
"task_type": "sparse_embedding"
}
}
}
}
}
}
现在有关于模型设置的附加信息。文本嵌入模型还将包括模型的维度数或相似度函数等信息。
你可以检查文档是否已包含嵌入结果:
GET test-index/_doc/doc1
{
"_index": "test-sparse",
"_id": "doc1",
"_source": {
"infer_field": {
"text": "these are not the droids you're looking for. He's free to go around",
"inference": {
"inference_id": "my-elser-endpoint",
"model_settings": {
"task_type": "sparse_embedding"
},
"chunks": [
{
"text": "these are not the droids you're looking for. He's free to go around",
"embeddings": {
"##oid": 1.9103845,
"##oids": 1.768872,
"free": 1.693662,
"dr": 1.6103356,
"around": 1.4376559,
"these": 1.1396849
…
}
}
]
}
}
}
}
该字段不仅包含输入文本,还包含一个结构,用于存储原始文本、模型设置以及输入文本被划分成的每个块的信息。
此结构由一个具有两个元素的对象组成:
- text:包含原始输入文本
- inference:推理端点添加的推理信息,包括:
- 推理端点的 inference_id
- 包含模型属性的 model_settings
- chunks:嵌套对象,包含从输入文本创建的每个块的元素。每个块包含:
- 块的 text
- 块文本的计算 embeddings
自定义语义文本
semantic_text 通过对数据进行索引和查询做出默认决策来简化语义搜索:
- 根据推理模型类型使用 sparse_vector 或 density_vector 字段类型
- 根据推理结果自动定义维度数和相似度
- 对密集向量字段类型使用 int8_hnsw 索引类型来利用标量量化 。
- 使用查询默认值。sparse_vector 查询不应用任何标记修剪,knn 查询也不设置自定义 k 和 num_candidates。
这些都是合理的默认值,可让你快速轻松地开始使用语义搜索。随着时间的推移,你可能希望自定义查询和数据类型以优化搜索相关性、索引和查询性能以及索引存储。
查询自定义
目前还没有针对语义查询的自定义选项。如果你想针对 semantic_text 字段自定义查询,可以使用显式 knn 和稀疏向量查询执行高级 semantic_text 搜索。
我们计划添加对 semantic_text 的 retrievers 支持,并向 semantic_text 字段添加配置选项,以便在查询时不需要它们。敬请期待!
数据类型自定义
如果你需要对数据索引进行更深入的自定义,则可以使用 sparse_vector 或 density_vector 字段类型。这些字段类型让你可以完全控制嵌入的生成、索引和查询方式。
你需要创建一个带有推理处理器的摄取管道来生成嵌入。本教程将引导你完成整个过程。
下一步是什么?
我们刚刚开始使用 semantic_text!我们将继续致力于许多增强功能,包括:
- 更好的推理错误处理
- 自定义分块策略
- 默认隐藏 _source 中的嵌入,以避免使搜索响应混乱
- 内部命中支持,用于检索查询的相关信息块
- 过滤和 retrievers 支持
- Kibana 支持
试试吧!
使用此笔记本[准备好后添加链接]探索语义文本,快速了解语义文本的工作原理。
如果你已经拥有 Elasticsearch 集群,则可以在本教程中查看使用语义文本测试语义搜索的完整示例。
我们很乐意听听你使用语义文本的经验!请在论坛中告诉我们你的想法,或在 GitHub 存储库中打开问题。让我们一起让语义搜索更容易!
准备好自己尝试一下了吗?开始免费试用。
Elasticsearch 集成了 LangChain、Cohere 等工具。加入我们的高级语义搜索网络研讨会,构建你的下一个 GenAI 应用程序!
原文:Semantic search simplified with semantic_text — Elastic Search Labs