Elasticsearch:语义搜索快速入门

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

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

创建 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

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

复制代码
from sentence_transformers import SentenceTransformer

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

初始化 Elasticsearch 客户端

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

复制代码
from elasticsearch import Elasticsearch
from getpass import getpass

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

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

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

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

启用遥测

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

我们希望你运行以下代码,以便我们收集匿名使用统计信息。有关详细信息,请参阅 telemetry.py。谢谢!

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

from telemetry import enable_telemetry

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

测试客户端

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

复制代码
print(client.info())

输出:

复制代码
{
  'name': 'instance-0000000000',
  'cluster_name': 'a72482be54904952ba46d53c3def7740',
  'cluster_uuid': 'g8BE52TtT32pGBbRzP_oKA',
  'version': {
    'number': '8.12.2',
    'build_flavor': 'default',
    'build_type': 'docker',
    'build_hash': '48a287ab9497e852de30327444b0809e55d46466',
    'build_date': '2024-02-19T10:04:32.774273190Z',
    'build_snapshot': False,
    'lucene_version': '9.9.2',
    'minimum_wire_compatibility_version': '7.17.0',
    'minimum_index_compatibility_version': '7.0.0'
  },
  'tagline': 'You Know, for Search'
}

索引测试数据

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

  • title

  • authors

  • publish_date

  • num_reviews

  • publisher

创建索引

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

复制代码
client.indices.delete(index="book_index", ignore_unavailable=True)

输出:

复制代码
ObjectApiResponse({'acknowledged': True})

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

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

复制代码
# 定义映射
mappings = {
    "properties": {
        "title_vector": {
            "type": "dense_vector",
            "dims": 384,
            "index": "true",
            "similarity": "cosine",
        }
    }
}

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

输出:

复制代码
ObjectApiResponse({'acknowledged': True, 'shards_acknowledged': True, 'index': 'book_index'})

索引测试数据

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

复制代码
import json
from urllib.request import urlopen

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

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

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

输出:

复制代码
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}}]})

附带说明:格式化 Elasticsearch 响应

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

复制代码
def pretty_response(response):
    if len(response["hits"]["hits"]) == 0:
        print("Your search returned no results.")
    else:
        for hit in response["hits"]["hits"]:
            id = hit["_id"]
            publication_date = hit["_source"]["publish_date"]
            score = hit["_score"]
            title = hit["_source"]["title"]
            summary = hit["_source"]["summary"]
            publisher = hit["_source"]["publisher"]
            num_reviews = hit["_source"]["num_reviews"]
            authors = hit["_source"]["authors"]
            pretty_output = f"\nID: {id}\nPublication date: {publication_date}\nTitle: {title}\nSummary: {summary}\nPublisher: {publisher}\nReviews: {num_reviews}\nAuthors: {authors}\nScore: {score}"
            print(pretty_output)

创建查询

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

复制代码
response = client.search(
    index="book_index",
    knn={
        "field": "title_vector",
        "query_vector": model.encode("javascript books"),
        "k": 10,
        "num_candidates": 100,
    },
)

pretty_response(response)

结果:

得分:0.8042828

标题:JavaScript:优良部分

简介:深入探索 JavaScript 中对于编写可维护代码至关重要的部分

出版社:oreilly

得分:0.6989136

标题:你不知道的 JavaScript:入门

简介:JavaScript 和整体编程的介绍

出版社:oreilly

得分:0.6796988

标题:精通 JavaScript

简介:现代编程入门

出版社:no starch press

(其余结果略)

过滤

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

复制代码
Does this timestamp fall into the range 2015 to 2016?
Is the status field set to "published"?

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

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

示例:关键词过滤

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

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

复制代码
response = client.search(
    index="book_index",
    knn={
        "field": "title_vector",
        "query_vector": model.encode("javascript books"),
        "k": 10,
        "num_candidates": 100,
        "filter": {"term": {"publisher.keyword": "addison-wesley"}},
    },
)

pretty_response(response)

结果:

得分:0.6206549

标题:The Pragmatic Programmer:Your Journey to Mastery

简介:面向软件工程师和开发者的实用编程指南

出版社:addison-wesley

得分:0.56499225

标题:设计模式:可复用面向对象软件的基础

简介:适用于任何面向对象语言的设计模式指南

出版社:addison-wesley

相关推荐
东坡肘子1 小时前
热茶还是冰咖啡 -- 肘子的 Swift 周报 #147
人工智能·swiftui·swift
Microvision维视智造1 小时前
行业洞察系列 06 | 汽车制造的视觉革命
人工智能·计算机视觉·汽车·视觉检测·制造·机器视觉
观远数据1 小时前
Excel继续用、自研BI、替换BI:产品VP拆解三条路线的隐性成本与能力边界
大数据·人工智能·excel
JJJennie7773 小时前
当AI开始学会欺骗
网络·人工智能
糖果店的幽灵3 小时前
大模型测评DeepEval快速入门-安全与通用指标详解
人工智能·安全·langgraph·大模型测评·deepeval
zhbcddxr4 小时前
GEO选择指南类内容效能测评:决策辅助与可见度提升排行
搜索引擎
江畔柳前堤9 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
阿里云大数据AI技术9 小时前
分链路差异化设计的DSP准实时数仓|钛动科技基于阿里云实时计算 Flink 版 + DLF Paimon + EMR Serverless StarRocks 的实践
人工智能·flink
陕西企来客9 小时前
2026年7月AI智能搜索曝光趋势研判
大数据·人工智能·机器学习·ai智能搜索曝光