教程:使用 ELSER 进行语义搜索
编辑教程:使用 ELSER 进行语义搜索
编辑Elastic Learned Sparse EncodeR(简称 ELSER)是由 Elastic 训练的 NLP 模型,它使您可以通过使用稀疏向量表示来执行语义搜索。语义搜索不是对搜索词进行字面匹配,而是基于搜索查询的意图和上下文含义来检索结果。
本教程中的说明将向您展示如何使用 ELSER 对您的数据执行语义搜索。
有关在 Elastic Stack 中执行语义搜索的最简单方法,请参阅 semantic_text
端到端教程。
在使用 ELSER 进行语义搜索时,仅考虑每个字段中提取的前 512 个标记。有关更多信息,请参阅 此页面。
要求
编辑要使用 ELSER 执行语义搜索,您的集群中必须部署 NLP 模型。请参阅 ELSER 文档,了解如何下载和部署模型。
如果 部署自动伸缩已关闭,则在 Elasticsearch Service 中部署和使用 ELSER 模型的最小专用 ML 节点大小为 4 GB。建议开启自动伸缩,因为它允许您的部署根据需求动态调整资源。通过使用更多分配或每个分配更多线程可以实现更好的性能,这需要更大的 ML 节点。自动伸缩会在需要时提供更大的节点。如果自动伸缩已关闭,则您必须自行提供大小合适的节点。
创建索引映射
编辑首先,必须创建目标索引的映射,即包含模型根据您的文本创建的标记的索引。目标索引必须具有带有 sparse_vector
或 rank_features
字段类型的字段,以索引 ELSER 输出。
ELSER 输出必须摄取到具有 sparse_vector
或 rank_features
字段类型的字段中。否则,Elasticsearch 会将标记-权重对解释为文档中的大量字段。如果您收到类似于此的错误:"在添加新字段时,已超过总字段 [1000] 的限制"
,则表示 ELSER 输出字段未正确映射,并且它的字段类型不同于 sparse_vector
或 rank_features
。
resp = client.indices.create( index="my-index", mappings={ "properties": { "content_embedding": { "type": "sparse_vector" }, "content": { "type": "text" } } }, ) print(resp)
response = client.indices.create( index: 'my-index', body: { mappings: { properties: { content_embedding: { type: 'sparse_vector' }, content: { type: 'text' } } } } ) puts response
const response = await client.indices.create({ index: "my-index", mappings: { properties: { content_embedding: { type: "sparse_vector", }, content: { type: "text", }, }, }, }); console.log(response);
PUT my-index { "mappings": { "properties": { "content_embedding": { "type": "sparse_vector" }, "content": { "type": "text" } } } }
包含生成的标记的字段的名称。必须在下一步的推理管道配置中引用它。 |
|
包含标记的字段是一个 |
|
从中创建稀疏向量表示的字段的名称。在此示例中,字段的名称是 |
|
在此示例中,字段类型为文本。 |
要了解如何优化空间,请参阅通过从文档源中排除 ELSER 标记来节省磁盘空间 部分。
创建带有推理处理器的摄取管道
编辑创建带有 推理处理器的摄取管道,以使用 ELSER 对正在管道中摄取的数据进行推理。
resp = client.ingest.put_pipeline( id="elser-v2-test", processors=[ { "inference": { "model_id": ".elser_model_2", "input_output": [ { "input_field": "content", "output_field": "content_embedding" } ] } } ], ) print(resp)
response = client.ingest.put_pipeline( id: 'elser-v2-test', body: { processors: [ { inference: { model_id: '.elser_model_2', input_output: [ { input_field: 'content', output_field: 'content_embedding' } ] } } ] } ) puts response
const response = await client.ingest.putPipeline({ id: "elser-v2-test", processors: [ { inference: { model_id: ".elser_model_2", input_output: [ { input_field: "content", output_field: "content_embedding", }, ], }, }, ], }); console.log(response);
PUT _ingest/pipeline/elser-v2-test { "processors": [ { "inference": { "model_id": ".elser_model_2", "input_output": [ { "input_field": "content", "output_field": "content_embedding" } ] } } ] }
加载数据
编辑在此步骤中,您将加载稍后在推理摄取管道中使用的、从中提取标记的数据。
使用 msmarco-passagetest2019-top1000
数据集,它是 MS MARCO Passage Ranking 数据集的子集。它包含 200 个查询,每个查询都附带一个相关的文本段落列表。所有唯一的段落及其 ID 都已从该数据集中提取并编译到 tsv 文件中。
msmarco-passagetest2019-top1000
数据集未用于训练模型。我们在本教程中使用此示例数据集,因为它易于访问以进行演示。您可以使用不同的数据集来测试工作流程并熟悉它。
下载该文件并使用 UI 中的 文件上传器将其上传到您的集群。分析完数据后,单击覆盖设置。在编辑字段名称下,将 id
分配给第一列,将 content
分配给第二列。单击应用,然后单击导入。将索引命名为 test-data
,然后单击导入。上传完成后,您将看到一个名为 test-data
的索引,其中包含 182,469 个文档。
通过推理摄取管道摄取数据
编辑通过使用 ELSER 作为推理模型的推理管道重新索引数据,从文本创建标记。
resp = client.reindex( wait_for_completion=False, source={ "index": "test-data", "size": 50 }, dest={ "index": "my-index", "pipeline": "elser-v2-test" }, ) print(resp)
response = client.reindex( wait_for_completion: false, body: { source: { index: 'test-data', size: 50 }, dest: { index: 'my-index', pipeline: 'elser-v2-test' } } ) puts response
const response = await client.reindex({ wait_for_completion: "false", source: { index: "test-data", size: 50, }, dest: { index: "my-index", pipeline: "elser-v2-test", }, }); console.log(response);
POST _reindex?wait_for_completion=false { "source": { "index": "test-data", "size": 50 }, "dest": { "index": "my-index", "pipeline": "elser-v2-test" } }
该调用返回一个任务 ID,用于监视进度
resp = client.tasks.get( task_id="<task_id>", ) print(resp)
const response = await client.tasks.get({ task_id: "<task_id>", }); console.log(response);
GET _tasks/<task_id>
您还可以打开“训练模型”UI,选择 ELSER 下的“管道”选项卡以跟踪进度。
重新索引大型数据集可能需要很长时间。您可以使用数据集的子集来测试此工作流程。为此,请取消重新索引过程,并且仅为已重新索引的子集生成嵌入。以下 API 请求将取消重新索引任务
resp = client.tasks.cancel( task_id="<task_id>", ) print(resp)
const response = await client.tasks.cancel({ task_id: "<task_id>", }); console.log(response);
POST _tasks/<task_id>/_cancel
使用 sparse_vector
查询进行语义搜索
编辑要执行语义搜索,请使用 sparse_vector
查询,并提供查询文本和与您的 ELSER 模型关联的推理 ID。以下示例使用查询文本“How to avoid muscle soreness after running?”,content_embedding
字段包含生成的 ELSER 输出
resp = client.search( index="my-index", query={ "sparse_vector": { "field": "content_embedding", "inference_id": "my-elser-endpoint", "query": "How to avoid muscle soreness after running?" } }, ) print(resp)
const response = await client.search({ index: "my-index", query: { sparse_vector: { field: "content_embedding", inference_id: "my-elser-endpoint", query: "How to avoid muscle soreness after running?", }, }, }); console.log(response);
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 个文档,按相关性排序。结果还包含每个相关搜索结果的提取标记及其权重。标记是捕获相关性的学习关联,它们不是同义词。要了解有关标记的更多信息,请参阅 此页面。可以从源中排除标记,请参阅此部分以了解更多信息。
"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
与 复合查询中的其他查询结合使用。例如,在 布尔查询中使用筛选子句,或者使用与 sparse_vector
查询相同(或不同)的查询文本进行全文查询。这使您可以合并来自两个查询的搜索结果。
sparse_vector
查询的搜索匹配结果的得分往往高于其他 Elasticsearch 查询。可以通过使用 boost
参数增加或减少每个查询的相关性得分来调整这些得分。sparse_vector
查询在存在大量不太相关的结果时可能会有很高的召回率。使用 min_score
参数来修剪这些不太相关的文档。
resp = client.search( index="my-index", 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, ) print(resp)
const response = await client.search({ index: "my-index", 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, }); console.log(response);
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 }
|
|
|
|
|
|
仅显示得分等于或高于 |
优化性能
编辑通过从文档源中排除 ELSER 标记来节省磁盘空间
编辑ELSER 生成的词元必须被索引,以便在sparse_vector 查询中使用。但是,没有必要将这些词项保留在文档源中。您可以使用源排除映射来删除文档源中的 ELSER 词项,从而节省磁盘空间。
重新索引使用文档源来填充目标索引。一旦从源中排除了 ELSER 词项,就无法通过重新索引恢复它们。从源中排除词元是一种节省空间的优化,只有当您确定将来不需要重新索引时才应应用!务必仔细考虑这种权衡,并确保从源中排除 ELSER 词项符合您的特定需求和用例。请仔细查看禁用 _source
字段和在 _source
中包含/排除字段部分,以了解更多关于从 _source
中排除词元可能造成的后果。
可以通过以下 API 调用创建从 _source
字段中排除 content_embedding
的映射
resp = client.indices.create( index="my-index", mappings={ "_source": { "excludes": [ "content_embedding" ] }, "properties": { "content_embedding": { "type": "sparse_vector" }, "content": { "type": "text" } } }, ) print(resp)
response = client.indices.create( index: 'my-index', body: { mappings: { _source: { excludes: [ 'content_embedding' ] }, properties: { content_embedding: { type: 'sparse_vector' }, content: { type: 'text' } } } } ) puts response
const response = await client.indices.create({ index: "my-index", mappings: { _source: { excludes: ["content_embedding"], }, properties: { content_embedding: { type: "sparse_vector", }, content: { type: "text", }, }, }, }); console.log(response);
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 驱动的语义搜索的互动示例。