使用 ELSER 进行语义搜索
Elastic Learned Sparse EncodeR(简称 ELSER)是 Elastic 训练的一个 NLP 模型,使你能够通过使用稀疏向量表示来执行语义搜索。语义搜索不是对搜索词进行字面匹配,而是根据搜索查询的意图和上下文含义来检索结果。
本教程中的说明展示了如何使用 ELSER 对你的数据执行语义搜索。
如需了解在 Elastic Stack 中执行语义搜索的最简便方法,请参阅 semantic_text 端到端教程。
在使用 ELSER 进行语义搜索时,每个字段仅考虑前 512 个提取的 Token。有关更多信息,请参考此页面。
要使用 ELSER 执行语义搜索,你必须在集群中部署该 NLP 模型。请参考 ELSER 文档了解如何下载和部署该模型。
如果关闭了部署自动扩缩容,在 Elastic Cloud Hosted 中部署和使用 ELSER 模型的最小专用 ML 节点大小为 4 GB。建议开启自动扩缩容,因为它允许你的部署根据需求动态调整资源。通过使用更多分配或每个分配更多线程可以获得更好的性能,这需要更大的 ML 节点。自动扩缩容会在需要时提供更大的节点。如果关闭了自动扩缩容,你必须自行提供合适大小的节点。
首先,必须创建目标索引的映射——包含模型根据你的文本创建的 Token 的索引。目标索引必须具有带有 sparse_vector 或 rank_features 字段类型的字段,以便对 ELSER 输出进行索引。
ELSER 输出必须摄入到具有 sparse_vector 或 rank_features 字段类型的字段中。否则,Elasticsearch 会将 Token-权重对解释为文档中的大量字段。如果你遇到类似以下的错误:"Limit of total fields [1000] has been exceeded while adding new fields",则说明 ELSER 输出字段未正确映射,并且其字段类型不是 sparse_vector 或 rank_features。
PUT my-index
{
"mappings": {
"properties": {
"content_embedding": {
"type": "sparse_vector"
},
"content": {
"type": "text"
}
}
}
}
- 包含生成的 Token 的字段名称。必须在下一步的推理管道配置中引用它。
- 包含 Token 的字段是一个
sparse_vector字段。 - 用于创建稀疏向量表示的源字段名称。在此示例中,字段名称为
content。必须在下一步的推理管道配置中引用它。 - 字段类型,在此示例中为 text。
要了解如何优化空间,请参考通过从文档源中排除 ELSER Token 来节省磁盘空间,并配合使用推理处理器来使用 ELSER 对管道中正在摄入的数据进行推理。
PUT _ingest/pipeline/elser-v2-test
{
"processors": [
{
"inference": {
"model_id": ".elser_model_2",
"input_output": [
{
"input_field": "content",
"output_field": "content_embedding"
}
]
}
}
]
}
- 配置对象,定义推理过程的
input_field以及将包含推理结果的output_field。
在此步骤中,你加载稍后将在推理摄入管道中使用的、从中提取 Token 的数据。
使用 msmarco-passagetest2019-top1000 数据集,它是 MS MARCO 段落排序数据集的一个子集。它包含 200 个查询,每个查询都附带一个相关文本段落列表。所有不重复的段落及其 ID 都已从该数据集中提取并编译成一个 tsv 文件。
msmarco-passagetest2019-top1000 数据集未用于训练该模型。我们在本教程中使用这个示例数据集,是因为它易于获取以便进行演示。你可以使用其他数据集来测试工作流并熟悉它。
下载文件并使用 UI 中的文件上传器将其上传到你的集群。分析数据后,点击 Override settings。在 Edit field names 下,将第一列指定为 id,第二列指定为 content。点击 Apply,然后点击 Import。将索引命名为 test-data,然后点击 Import。上传完成后,你将看到一个名为 test-data 的索引,其中包含 182,469 个文档。
通过使用 ELSER 作为推理模型的推理管道对数据进行重新索引,从文本中创建 Token。
POST _reindex?wait_for_completion=false
{
"source": {
"index": "test-data",
"size": 50
},
"dest": {
"index": "my-index",
"pipeline": "elser-v2-test"
}
}
- 重新索引的默认批处理大小为 1000。将
size减小为更小的数值可以使重新索引过程的更新更迅速,从而方便您密切跟踪进度并及早发现错误。
该调用返回一个用于监控进度的任务 ID
GET _tasks/<task_id>
你还可以打开“已训练模型”UI,选择 ELSER 下的“管道”选项卡来跟踪进度。
重新索引大型数据集可能需要很长时间。你可以仅使用数据集的子集来测试此工作流。方法是取消重新索引过程,仅为已重新索引的子集生成嵌入。以下 API 请求将取消重新索引任务
POST _tasks/<task_id>/_cancel
要执行语义搜索,请使用 sparse_vector 查询,并提供查询文本以及与你的 ELSER 模型关联的推理 ID。下面的示例使用查询文本 "How to avoid muscle soreness after running?",其中 content_embedding 字段包含生成的 ELSER 输出
GET my-index/_search
{
"query":{
"sparse_vector":{
"field": "content_embedding",
"inference_id": "my-elser-endpoint",
"query": "How to avoid muscle soreness after running?"
}
}
}
结果是来自 my-index 索引的、在语义上与你的查询文本最接近且按相关性排序的前 10 个文档。结果还包含每个相关搜索结果中提取的 Token 及其权重。Token 是捕获相关性的学得关联,它们不是同义词。要了解有关 Token 是什么的更多信息,请参考此页面。可以从源中排除 Token,请参考本节了解更多信息。
"hits": {
"total": {
"value": 10000,
"relation": "gte"
},
"max_score": 26.199875,
"hits": [
{
"_index": "my-index",
"_id": "FPr9HYsBag9jXmT8lEpI",
"_score": 26.199875,
"_source": {
"content_embedding": {
"muscular": 0.2821541,
"bleeding": 0.37929374,
"foods": 1.1718726,
"delayed": 1.2112266,
"cure": 0.6848574,
"during": 0.5886185,
"fighting": 0.35022718,
"rid": 0.2752442,
"soon": 0.2967024,
"leg": 0.37649947,
"preparation": 0.32974035,
"advance": 0.09652356,
(...)
},
"id": 1713868,
"model_id": ".elser_model_2",
"content": "For example, if you go for a run, you will mostly use the muscles in your lower body. Give yourself 2 days to rest those muscles so they have a chance to heal before you exercise them again. Not giving your muscles enough time to rest can cause muscle damage, rather than muscle development."
}
},
(...)
]
}
你可以在复合查询中将 sparse_vector 与其他查询结合使用。例如,在 Boolean 查询中使用 filter 子句,或者使用与 sparse_vector 查询相同(或不同)查询文本的全文本查询。这使你能够结合两个查询的搜索结果。
来自 sparse_vector 查询的搜索命中项的得分往往比其他 Elasticsearch 查询更高。可以通过使用 boost 参数增加或减少每个查询的相关性得分来对这些得分进行调整。sparse_vector 查询的召回率可能很高,其中包含长尾的不太相关的结果。使用 min_score 参数可以剪枝这些不太相关的文档。
GET my-index/_search
{
"query": {
"bool": {
"should": [
{
"sparse_vector": {
"field": "content_embedding",
"inference_id": "my-elser-endpoint",
"query": "How to avoid muscle soreness after running?",
"boost": 1
}
},
{
"query_string": {
"query": "toxins",
"boost": 4
}
}
]
}
},
"min_score": 10
}
sparse_vector和query_string查询都位于bool查询的should子句中。sparse_vector查询的boost值为1,这是默认值。这意味着该查询结果的相关性得分不会被提升。query_string查询的boost值为4。该查询结果的相关性得分会增加,从而使它们在搜索结果中排名更高。- 仅显示得分等于或高于
10的结果。
必须对 ELSER 生成的 Token 进行索引,以便在 sparse_vector 查询中使用。但是,没有必要在文档源中保留这些词条。你可以使用源排除映射从文档源中移除 ELSER 词条,从而节省磁盘空间。
重新索引使用文档源来填充目标索引。一旦从源中排除了 ELSER 词条,就无法通过重新索引将其恢复。从源中排除 Token 是一种节省空间的优化,只有在你确信未来不需要重新索引时才应应用!仔细权衡这一利弊非常重要,要确保从源中排除 ELSER 词条符合你的具体要求和用例。请仔细阅读禁用 _source 字段和从 _source 中包含/排除字段部分,以了解从 _source 中排除 Token 可能带来的后果。
可以通过以下 API 调用来创建从 _source 字段中排除 content_embedding 的映射
PUT my-index
{
"mappings": {
"_source": {
"excludes": [
"content_embedding"
]
},
"properties": {
"content_embedding": {
"type": "sparse_vector"
},
"content": {
"type": "text"
}
}
}
}
根据你的数据,使用 track_total_hits: false 时,sparse_vector 查询可能会更快。
elasticsearch-labs仓库中有一个使用 Elasticsearch Python 客户端运行由 ELSER 驱动的语义搜索的交互式示例。