Elasticsearch:语义搜索快速入门

45 阅读7分钟

这个交互式 Notebook 将介绍一些 Elasticsearch 基础操作,使用官方 Elasticsearch Python 客户端。你将使用 Sentence Transformers 对文本进行嵌入,并执行 语义搜索 。学习如何将传统基于文本的搜索与语义搜索结合,构建混合搜索系统。

如果你想使用本地部署,你可以参考文章 “如何在 Linux,MacOS 及 Windows 上进行安装 Elasticsearch” 来进行安装。并参考代码 github.com/liu-xiao-gu…

创建 Elastic Cloud 部署

如果你没有 Elastic Cloud 部署,可以在这里注册免费试用。

登录 Elastic Cloud 账户后,进入创建部署页面并选择创建部署(Create deployment)。保持所有设置为默认值。

安装软件包并导入模块

开始之前,我们需要使用 Python 客户端连接到我们的 Elastic 部署。由于我们使用的是 Elastic Cloud 部署,因此将使用 Cloud ID 来标识我们的部署。

首先,我们需要安装 Elasticsearch Python 客户端。

`!pip install -qU "elasticsearch<9" sentence-transformers==2.7.0` AI写代码

在此示例中,我们使用 sentence_transformers 库中的 all-MiniLM-L6-v2 模型。你可以在 Hugging Face 上阅读有关此模型的更多信息。

`

1.  from sentence_transformers import SentenceTransformer

3.  model = SentenceTransformer("all-MiniLM-L6-v2")

`AI写代码

初始化 Elasticsearch 客户端

现在,我们可以实例化 Elasticsearch Python 客户端,并提供部署中的 Cloud ID 和密码。

`

1.  from elasticsearch import Elasticsearch
2.  from getpass import getpass

4.  # https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id
5.  ELASTIC_CLOUD_ID = getpass("Elastic Cloud ID: ")

7.  # https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#creating-an-api-key
8.  ELASTIC_API_KEY = getpass("Elastic Api Key: ")

10.  # 创建客户端实例
11.  client = Elasticsearch(
12.      # 用于本地开发
13.      # hosts=["http://localhost:9200"]
14.      cloud_id=ELASTIC_CLOUD_ID,
15.      api_key=ELASTIC_API_KEY,
16.  )

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

如果你正在本地运行 Elasticsearch 或使用自托管部署,则可以改为传入 Elasticsearch 主机地址。阅读更多关于如何连接本地 Elasticsearch 的信息。

启用遥测

了解你正在使用此 Notebook,有助于我们决定将精力投入到哪些方面来改进我们的产品。

我们希望你运行以下代码,以便我们收集匿名使用统计信息。有关详细信息,请参阅 [telemetry.py](https://github.com/elastic/elasticsearch-labs/blob/main/telemetry/telemetry.py "telemetry.py")。谢谢!

`!curl -O -s https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/telemetry/telemetry.py` AI写代码
`

1.  from telemetry import enable_telemetry

3.  client = enable_telemetry(client, "00-quick-start")

`AI写代码

测试客户端

在继续之前,请通过此测试确认客户端已成功连接。

`print(client.info())` AI写代码

输出:

`

1.  {
2.    'name': 'instance-0000000000',
3.    'cluster_name': 'a72482be54904952ba46d53c3def7740',
4.    'cluster_uuid': 'g8BE52TtT32pGBbRzP_oKA',
5.    'version': {
6.      'number': '8.12.2',
7.      'build_flavor': 'default',
8.      'build_type': 'docker',
9.      'build_hash': '48a287ab9497e852de30327444b0809e55d46466',
10.      'build_date': '2024-02-19T10:04:32.774273190Z',
11.      'build_snapshot': False,
12.      'lucene_version': '9.9.2',
13.      'minimum_wire_compatibility_version': '7.17.0',
14.      'minimum_index_compatibility_version': '7.0.0'
15.    },
16.    'tagline': 'You Know, for Search'
17.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

索引测试数据

我们的客户端已经设置完成,并连接到了 Elastic 部署。现在,我们需要一些数据来测试 Elasticsearch 查询基础功能。我们将使用一个包含以下字段的图书小型索引:

  • title

  • authors

  • publish_date

  • num_reviews

  • publisher

创建索引

首先确保不存在之前创建的名为 book_index 的索引。

`client.indices.delete(index="book_index", ignore_unavailable=True)` AI写代码

输出:

`ObjectApiResponse({'acknowledged': True})` AI写代码

🔐 注意:你可以随时返回此部分并运行上面的删除函数,以删除索引并从头开始。

让我们创建一个 Elasticsearch 索引,并为测试数据配置正确的映射。

`

1.  # 定义映射
2.  mappings = {
3.      "properties": {
4.          "title_vector": {
5.              "type": "dense_vector",
6.              "dims": 384,
7.              "index": "true",
8.              "similarity": "cosine",
9.          }
10.      }
11.  }

13.  # 创建索引
14.  client.indices.create(index="book_index", mappings=mappings)

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

输出:

`ObjectApiResponse({'acknowledged': True, 'shards_acknowledged': True, 'index': 'book_index'})` AI写代码

索引测试数据

运行以下命令上传一些测试数据,其中包含来自该数据集的 10 本热门编程书籍的信息。model.encode 会使用我们之前初始化的模型,将文本实时编码为向量。

`

1.  import json
2.  from urllib.request import urlopen

4.  url = "https://raw.githubusercontent.com/elastic/elasticsearch-labs/main/notebooks/search/data.json"
5.  response = urlopen(url)
6.  books = json.loads(response.read())

8.  operations = []
9.  for book in books:
10.      operations.append({"index": {"_index": "book_index"}})
11.      # 使用模型将标题转换为嵌入向量
12.      book["title_vector"] = model.encode(book["title"]).tolist()
13.      operations.append(book)

15.  client.bulk(index="book_index", operations=operations, refresh=True)

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

输出:

`ObjectApiResponse({'errors': False, 'took': 88, 'items': [{'index': {'_index': 'book_index', '_id': 'caRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 0, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'cqRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 1, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'c6RpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 2, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'dKRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 3, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'daRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 4, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'dqRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 5, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'd6RpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 6, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'eKRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 7, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'eaRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 8, '_primary_term': 1, 'status': 201}}, {'index': {'_index': 'book_index', '_id': 'eqRpvY4BKY8PuI1qPluy', '_version': 1, 'result': 'created', 'forced_refresh': True, '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 9, '_primary_term': 1, 'status': 201}}]})`AI写代码

附带说明:格式化 Elasticsearch 响应

你的 API 调用会返回难以阅读的嵌套 JSON。我们将创建一个名为 pretty_response 的小函数,用于从示例中返回清晰、易读的输出。

`

1.  def pretty_response(response):
2.      if len(response["hits"]["hits"]) == 0:
3.          print("Your search returned no results.")
4.      else:
5.          for hit in response["hits"]["hits"]:
6.              id = hit["_id"]
7.              publication_date = hit["_source"]["publish_date"]
8.              score = hit["_score"]
9.              title = hit["_source"]["title"]
10.              summary = hit["_source"]["summary"]
11.              publisher = hit["_source"]["publisher"]
12.              num_reviews = hit["_source"]["num_reviews"]
13.              authors = hit["_source"]["authors"]
14.              pretty_output = f"\nID: {id}\nPublication date: {publication_date}\nTitle: {title}\nSummary: {summary}\nPublisher: {publisher}\nReviews: {num_reviews}\nAuthors: {authors}\nScore: {score}"
15.              print(pretty_output)

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

创建查询

现在,我们已经对图书进行了索引,希望对与给定查询相似的图书执行语义搜索。我们会对查询进行嵌入,然后执行搜索。

`

1.  response = client.search(
2.      index="book_index",
3.      knn={
4.          "field": "title_vector",
5.          "query_vector": model.encode("javascript books"),
6.          "k": 10,
7.          "num_candidates": 100,
8.      },
9.  )

11.  pretty_response(response)

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

结果:

得分:0.8042828
标题:JavaScript:优良部分
简介:深入探索 JavaScript 中对于编写可维护代码至关重要的部分
出版社:oreilly

得分:0.6989136
标题:你不知道的 JavaScript:入门
简介:JavaScript 和整体编程的介绍
出版社:oreilly

得分:0.6796988
标题:精通 JavaScript
简介:现代编程入门
出版社:no starch press

(其余结果略)

过滤

过滤上下文主要用于过滤结构化数据。例如,可以使用过滤上下文回答以下问题:

`

1.  Does this timestamp fall into the range 2015 to 2016?
2.  Is the status field set to "published"?

`AI写代码

当查询子句传递给过滤参数时,例如 bool 查询中的 filter 或 must_not 参数,过滤上下文就会生效。

更多信息请参阅 Elasticsearch 文档中的过滤上下文。

示例:关键词过滤

下面示例展示如何向查询添加关键词过滤。

该示例根据标题 向量检索 与 "javascript books" 相似的热门图书,同时限制出版社为 Addison-Wesley。

`

1.  response = client.search(
2.      index="book_index",
3.      knn={
4.          "field": "title_vector",
5.          "query_vector": model.encode("javascript books"),
6.          "k": 10,
7.          "num_candidates": 100,
8.          "filter": {"term": {"publisher.keyword": "addison-wesley"}},
9.      },
10.  )

12.  pretty_response(response)

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

结果:

得分:0.6206549
标题:The Pragmatic Programmer:Your Journey to Mastery
简介:面向软件工程师和开发者的实用编程指南
出版社:addison-wesley

得分:0.56499225
标题:设计模式:可复用面向对象软件的基础
简介:适用于任何面向对象语言的设计模式指南
出版社:addison-wesley