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

相关推荐
昇腾知识体系几秒前
昇腾环境安装 flash_attn:FlashAttnPrefillBackend 报错与替代算子
人工智能·pytorch·华为·知识图谱
棣廷7 分钟前
初识深度学习——数据增强与模型保存
人工智能·深度学习
找方案9 分钟前
AI+气象预报:华为盘古大模型如何让天气预报精准到街区
人工智能·算法·机器学习
敲代码的嘎仔11 分钟前
互动问答系统实战:两级评论模型、ES 搜索集成、Caffeine 多级缓存全记录
java·开发语言·数据库·elasticsearch·缓存·mybatis·高并发
IT_陈寒14 分钟前
Vite的HMR怎么突然罢工了?原来是我漏了这个配置
前端·人工智能·后端
狂师15 分钟前
最近火爆出圈的,FDE 到底是个什么岗位?
人工智能·程序员·全栈
IT毕设实战小研15 分钟前
基于大数据处理的京东商品销售态势分析与可视化设计
大数据·科技·机器学习·信息可视化·数据分析
不要生病了16 分钟前
Through Their Eyes:用简单对齐实现跨被试与跨数据集视觉脑解码
人工智能·深度学习
2601_9623042517 分钟前
AI照片上色新手好上手:怎么调出自然肤色?
人工智能
临床数据科学和人工智能兴趣组19 分钟前
399元现在超值!学R语言,订阅我们专栏就够了,包括了所有的内容,不断更新!
人工智能·数据挖掘·r语言·r语言-4.2.1·临床研究