教程:使用 ELSER 进行语义搜索

编辑

教程:使用 ELSER 进行语义搜索编辑

Elastic Learned Sparse EncodeR - 或 ELSER - 是由 Elastic 训练的 NLP 模型,它允许您通过使用稀疏向量表示来执行语义搜索。语义搜索不是对搜索词进行字面匹配,而是根据搜索查询的意图和上下文含义检索结果。

本教程中的说明将向您展示如何使用 ELSER 对您的数据执行语义搜索。

使用 ELSER 进行语义搜索时,每个字段仅考虑前 512 个提取的标记。有关更多信息,请参阅 此页面

要求编辑

要使用 ELSER 执行语义搜索,您必须在集群中部署 NLP 模型。请参阅 ELSER 文档 了解如何下载和部署模型。

如果 部署自动扩展 已关闭,则在 Elasticsearch Service 中部署和使用 ELSER 模型的最小专用 ML 节点大小为 4 GB。建议打开自动扩展,因为它允许您的部署根据需求动态调整资源。通过使用更多分配或每个分配更多线程可以实现更好的性能,这需要更大的 ML 节点。自动扩展在需要时提供更大的节点。如果自动扩展已关闭,您必须自己提供大小合适的节点。

创建索引映射编辑

首先,必须创建目标索引的映射 - 包含模型根据您的文本创建的标记的索引。目标索引必须具有一个使用 sparse_vectorrank_features 字段类型来索引 ELSER 输出的字段。

ELSER 输出必须被摄取到具有 sparse_vectorrank_features 字段类型的字段中。否则,Elasticsearch 会将标记-权重对解释为文档中的大量字段。如果您遇到类似于此 "Limit of total fields [1000] has been exceeded while adding new fields" 的错误,则 ELSER 输出字段映射不正确,并且其字段类型不同于 sparse_vectorrank_features

response = client.indices.create(
  index: 'my-index',
  body: {
    mappings: {
      properties: {
        content_embedding: {
          type: 'sparse_vector'
        },
        content: {
          type: 'text'
        }
      }
    }
  }
)
puts response
PUT my-index
{
  "mappings": {
    "properties": {
      "content_embedding": { 
        "type": "sparse_vector" 
      },
      "content": { 
        "type": "text" 
      }
    }
  }
}

包含生成的标记的字段的名称。它必须在下一步的推理管道配置中引用。

包含标记的字段是一个 sparse_vector 字段。

用于创建稀疏向量表示的字段的名称。在本例中,字段的名称为 content。它必须在下一步的推理管道配置中引用。

在本例中为文本的字段类型。

要了解如何优化空间,请参阅 通过从文档源中排除 ELSER 标记来节省磁盘空间 部分。

使用推理处理器创建摄取管道编辑

使用 推理处理器 创建一个 摄取管道,以使用 ELSER 对在管道中被摄取的数据进行推理。

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
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 的配置对象。

加载数据编辑

在此步骤中,您将加载稍后在推理摄取管道中使用的数据,以从中提取标记。

使用 msmarco-passagetest2019-top1000 数据集,它是 MS MARCO Passage Ranking 数据集的子集。它包含 200 个查询,每个查询都附带一个相关文本段落的列表。所有唯一的段落及其 ID 已从该数据集中提取并编译到一个 tsv 文件 中。

重要:msmarco-passagetest2019-top1000 数据集未用于训练模型。它仅在本教程中用作易于访问的示例数据集,用于演示目的。您可以使用不同的数据集来测试工作流程并熟悉它。

下载文件并使用机器学习 UI 中的 数据可视化器 将其上传到您的集群。将名称 id 分配给第一列,将 content 分配给第二列。索引名称为 test-data。上传完成后,您将看到一个名为 test-data 的索引,其中包含 182469 个文档。

通过推理摄取管道摄取数据编辑

通过使用 ELSER 作为推理模型的推理管道重新索引数据,从文本中创建标记。

response = client.reindex(
  wait_for_completion: false,
  body: {
    source: {
      index: 'test-data',
      size: 50
    },
    dest: {
      index: 'my-index',
      pipeline: 'elser-v2-test'
    }
  }
)
puts response
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 下的“管道”选项卡以跟踪进度。

使用 text_expansion 查询进行语义搜索编辑

要执行语义搜索,请使用 text_expansion 查询,并提供查询文本和 ELSER 模型 ID。以下示例使用查询文本“如何避免跑步后的肌肉酸痛?”,content_embedding 字段包含生成的 ELSER 输出

response = client.search(
  index: 'my-index',
  body: {
    query: {
      text_expansion: {
        content_embedding: {
          model_id: '.elser_model_2',
          model_text: 'How to avoid muscle soreness after running?'
        }
      }
    }
  }
)
puts response
GET my-index/_search
{
   "query":{
      "text_expansion":{
         "content_embedding":{
            "model_id":".elser_model_2",
            "model_text":"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."
      }
    },
    (...)
  ]
}

将语义搜索与其他查询结合使用编辑

您可以在 复合查询 中将 text_expansion 与其他查询结合使用。例如,在 布尔 中使用过滤子句,或使用与 text_expansion 查询相同(或不同)的查询文本进行全文查询。这使您能够将来自两个查询的搜索结果结合起来。

来自 text_expansion 查询的搜索命中率往往比其他 Elasticsearch 查询得分更高。可以通过使用 boost 参数增加或减少每个查询的相关性得分来规范这些得分。在存在大量不太相关的结果的尾部时,text_expansion 查询的召回率可能很高。使用 min_score 参数来修剪这些不太相关的文档。

response = client.search(
  index: 'my-index',
  body: {
    query: {
      bool: {
        should: [
          {
            text_expansion: {
              content_embedding: {
                model_text: 'How to avoid muscle soreness after running?',
                model_id: '.elser_model_2',
                boost: 1
              }
            }
          },
          {
            query_string: {
              query: 'toxins',
              boost: 4
            }
          }
        ]
      }
    },
    min_score: 10
  }
)
puts response
GET my-index/_search
{
  "query": {
    "bool": { 
      "should": [
        {
          "text_expansion": {
            "content_embedding": {
              "model_text": "How to avoid muscle soreness after running?",
              "model_id": ".elser_model_2",
              "boost": 1 
            }
          }
        },
        {
          "query_string": {
            "query": "toxins",
            "boost": 4 
          }
        }
      ]
    }
  },
  "min_score": 10 
}

text_expansionquery_string 查询都在 bool 查询的 should 子句中。

text_expansion 查询的 boost 值为 1,这是默认值。这意味着此查询结果的相关性得分不会被提升。

query_string 查询的 boost 值为 4。此查询结果的相关性得分会增加,导致它们在搜索结果中排名更高。

仅显示得分等于或高于 10 的结果。

优化性能编辑

通过从文档源中排除 ELSER 标记来节省磁盘空间编辑

由 ELSER 生成的标记必须被索引才能在 text_expansion 查询 中使用。但是,没有必要将这些术语保留在文档源中。您可以使用 源排除 映射来从文档源中删除 ELSER 术语,从而节省磁盘空间。

重新索引使用文档源来填充目标索引。 一旦 ELSER 术语从源中排除,它们就无法 通过重新索引恢复。 从源中排除标记是一种节省空间的优化,只有在您确定将来不需要重新索引时才应应用! 重要的是要仔细考虑这种权衡,并确保从源中排除 ELSER 术语符合您的特定需求和用例。 查看 禁用 _source 字段_source 中包含/排除字段 部分,以详细了解从 _source 中排除标记的可能后果。

可以通过以下 API 调用创建将 content_embedding_source 字段中排除的映射

response = client.indices.create(
  index: 'my-index',
  body: {
    mappings: {
      _source: {
        excludes: [
          'content_embedding'
        ]
      },
      properties: {
        content_embedding: {
          type: 'sparse_vector'
        },
        content: {
          type: 'text'
        }
      }
    }
  }
)
puts response
PUT my-index
{
  "mappings": {
    "_source": {
      "excludes": [
        "content_embedding"
      ]
    },
    "properties": {
      "content_embedding": {
        "type": "sparse_vector"
      },
      "content": {
        "type": "text"
      }
    }
  }
}

根据您的数据,文本扩展查询在 track_total_hits: false 时可能更快。

进一步阅读编辑

交互式示例编辑