Elasticsearch:使用 Transformers 和 Elasticsearch 进行语义搜索

news2024/11/29 4:41:50

语义/矢量搜索是一种强大的技术,可以大大提高搜索结果的准确性和相关性。 与传统的基于关键字的搜索方法不同,语义搜索使用单词的含义和上下文来理解查询背后的意图并提供更准确的结果。 Elasticsearch 是实现语义搜索最流行的工具之一,它是一种高度可扩展且功能强大的搜索引擎,可用于索引和搜索大量数据。 在本文中,我们将探讨语义搜索的基础知识以及如何使用 Elasticsearch 实现它。 到本文结束时,你将深入了解语义搜索的工作原理以及在你自己的项目中实现它的实用技能。

在进行下面的讲解之前,我需要特别指出的是:Elastic 提供了 eland 帮助我们上传在 huggingface.co 上的模型。我们在摄入文档的时候,可以试验 inference processor 来方便地进行数据字段的矢量化。eland 上传及机器学习是 Elastic 的收费项目。本文章将使用 Tensorflow 来通过代码的方式来获得矢量,并上传到 Elasticsearch。更多关于使用 eland 及机器学习上传模型的方法,请详细阅读 “Elastic:开发者上手指南” 中的 “NLP - 自然语言处理及矢量搜索” 章节。

Elasticsearch

Elasticsearch 是一个基于 Lucene 库的强大且可扩展的免费及开发的搜索引擎。 它旨在处理大量非结构化数据并提供快速准确的搜索结果。 Elasticsearch 使用分布式架构,这意味着它可以横向扩展到多个服务器以处理大量数据和流量。

Elasticsearch 建立在 RESTful API 之上,这使得它可以轻松地与各种编程语言和工具集成。 它支持复杂的搜索查询,包括全文搜索、分面搜索和地理搜索。 Elasticsearch 还提供了一个强大的聚合框架,允许你对搜索结果进行复杂的数据分析。

Transformers

Transformers 是一种机器学习模型,它彻底改变了自然语言处理 (NLP) 任务,例如语言翻译、文本摘要和情感分析。 Vaswani 等人首先介绍了 transformer。 在 2017 年的一篇论文 “Attention Is All You Need” 中,此后已成为许多 NLP 任务的最先进模型。

与循环循环神经网络 (RNN) 和卷积神经网络 (CNN) 的传统 NLP 模型不同,Transformer 使用 self-attention 机制来捕捉句子中单词之间的关系。 Self-attentiion 允许模型关注输入序列的不同部分,以确定单词之间最重要的关系。 这使得转换器比传统模型更有效地处理单词之间的远程依赖关系和上下文关系。

对于本文,我将使用 TensorFlow 的通用句子编码器对我的数据进行编码/矢量化。 你也可以选择任何其他形式的编码器。另外值得指出的是:tensorflow 在 Apple 的芯片上不能得到支持。你需要使用 x86 的机器来进行练习。

为了方便大家学习,我把代码放在地址:https://github.com/liu-xiao-guo/Semantic-Search-ElasticSearch

准备工作

Elasticsearch 及 Kibana

如果你还没有安装好自己的 Elasticsearch 及 Kibana,请参考文章:

  • 如何在 Linux,MacOS 及 Windows 上进行安装 Elasticsearch
  • Kibana:如何在 Linux,MacOS 及 Windows 上安装 Elastic 栈中的 Kibana

在我们的本次练习中,我们将使用 Elastic Stack 8.8 版本。在 Elasticsearch 首次启动的时候,它会出现如下的屏幕:

我们记下 elastic 用户的密码及 fingerprint。这些信息在一下的代码中进行使用。 

Python

你需要在自己的电脑上安装 Python:

$ python --version
Python 3.10.6

你同时需要安装如下的 Python 库:

pip3 install elasticsearch
pip3 install tensorflow_hub
pip3 install tensorflow
pip3 install pandas
pip3 install numpy

Tensorflow 模型

你需要去地址 https://tfhub.dev/google/universal-sentence-encoder/4 下载 universal-sentence-encoder 模型。下载完后,你把它置于代码根目录下的 model 子目录下:

$ pwd
/Users/liuxg/python/Semantic-Search-ElasticSearch
$ ls
README.md                        model
Semantic_Search_ElasticSearch.py sample.csv
$ tree -L 3
.
├── README.md
├── Semantic_Search_ElasticSearch.py
├── model
│   ├── assets
│   ├── saved_model.pb
│   ├── universal-sentence-encoder_4.tar.gz
│   └── variables
│       ├── variables.data-00000-of-00001
│       └── variables.index
└── sample.csv

我们在 model 子目录下,打入如下的命令来解压缩文件 universal-sentence-encoder_4.tar.gz:

tar xzf universal-sentence-encoder_4.tar.gz

样本文件

如上所示,我准备了一个叫做 sample.csv 的文件。它的内容非常之简单:

sample.csv

Text,Price,Quantity
"The latest phone model",5000,10
"The best seller phone",2000,50

也就是只有两个文档。你可以根据自己的情况修改这个文档。

代码

我先把代码贴出来:

Semantic_Search_ElasticSearch.py

from elasticsearch import Elasticsearch
import tensorflow_hub as hub
import tensorflow.compat.v1 as tf
import pandas as pd
import numpy as np

df = pd.read_csv('./sample.csv')
print(df['Text'][0])

model = hub.load("./model")

graph = tf.Graph()

with tf.Session(graph = graph) as session:
    print("Loading pre-trained embeddings")
    embed = hub.load("./model")
    text_ph = tf.placeholder(tf.string)
    embeddings = embed(text_ph)
    
    print("Creating tensorflow session…")
    session = tf.Session()
    session.run(tf.global_variables_initializer())
    session.run(tf.tables_initializer())
    
    vectors = session.run(embeddings, feed_dict={text_ph: df['Text']})

print("vectors length: ", len(vectors))
print(vectors)

vector = []
for i in vectors:
    vector.append(i)

df["Embeddings"] = vector
 
# Connect to the elastic cluster
# Password for the 'elastic' user generated by Elasticsearch
USERNAME = "elastic"
PASSWORD = "GHI8C685oSpq_kNtUJV1"
ELATICSEARCH_ENDPOINT = "https://localhost:9200"
CERT_FINGERPRINT = "abec585e4d6c383032d19f8c535369107f063ae91491e20b5e25b75afb308f13"
 
es = Elasticsearch(ELATICSEARCH_ENDPOINT, 
                   ssl_assert_fingerprint = (CERT_FINGERPRINT),
                   basic_auth=(USERNAME, PASSWORD),
                   verify_certs = True)
resp = es.info()
print(resp)

configurations = {
    "settings": {
        "index": {"number_of_replicas": 2},
        "analysis": {
            "filter": {
                "ngram_filter": {
                    "type": "edge_ngram",
                    "min_gram": 2,
                    "max_gram": 15,
                }
            },
            "analyzer": {
                "ngram_analyzer": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": ["lowercase", "ngram_filter"],
                }
            }
        }
    },
    "mappings": {
        "properties": {
          "Embeddings": {
            "type": "dense_vector",
            "dims": 512,
            "index": True,
            "similarity": "cosine" 
          },
          } 
        } 
    } 


INDEX_NAME = "vectors"

if(es.indices.exists(index=INDEX_NAME)):
    print("The index has already existed, going to remove it")
    es.options(ignore_status=404).indices.delete(index=INDEX_NAME)
    
es.indices.create(  index=INDEX_NAME,
                    settings=configurations["settings"],
                    mappings=configurations["mappings"]
                 )

actions = []
for index, row in df.iterrows():
    action = {"index": {"_index": INDEX_NAME, "_id": index}}
    doc = {
        "id": index,
        "Text": row["Text"],
        "Price": row["Price"],
        "Quantity": row["Quantity"],
        "Embeddings": row["Embeddings"]
    }
    actions.append(action)
    actions.append(doc)

es.bulk(index=INDEX_NAME, operations=actions, refresh=True)

query = "Which is the latest phone available in your shop"


def embed_text(text):
    vectors = session.run(embeddings, feed_dict={text_ph: text})
    return [vector.tolist() for vector in vectors]

query_vector = embed_text([query])[0]
print(query_vector)

query = {
    "field": "Embeddings",
    "query_vector": query_vector,
    "k": 10,
    "num_candidates": 100
  }

source_fields = ["Text", "Price", "Quantity"]

response = es.search(
    index="vectors",
    fields=source_fields,
    knn=query,
    source=False)

print(response)

这是整个代码。虽然看起来简单,但是在调试的时候还是出现了一些状况。

安装 Python 依赖项后,你将需要文本数据作为开始。 获取文本数据后,在你喜欢的 IDE 中使用 python 读取它。

from elasticsearch import Elasticsearch
import tensorflow_hub as hub
import tensorflow.compat.v1 as tf
import pandas as pd
import numpy as np

df = pd.read_csv('./sample.csv')
print(df['Text'][0])

读取文本数据后,第一个任务是将其转换为向量或嵌入。 在这里,正如我之前提到的,我使用的是 TensorFlow 的通用句子编码器,它在提供字符串后输出 “512” 维度的向量/嵌入。

这对于其他转换器/矢量化器会有所不同,你需要记住这一点以便进一步执行步骤。

model = hub.load("./model")

成功加载模型后,现在我们的下一个任务是将数据集中的文本转换为向量/嵌入,并将其存储在名为 “Embeddings” 的新字段/列中。

graph = tf.Graph()

with tf.Session(graph = graph) as session:
    print("Loading pre-trained embeddings")
    embed = hub.load("./model")
    text_ph = tf.placeholder(tf.string)
    embeddings = embed(text_ph)
    
    print("Creating tensorflow session…")
    session = tf.Session()
    session.run(tf.global_variables_initializer())
    session.run(tf.tables_initializer())
    
    vectors = session.run(embeddings, feed_dict={text_ph: df['Text']})

print("vectors length: ", len(vectors))
print(vectors)

vector = []
for i in vectors:
    vector.append(i)

df["Embeddings"] = vector

注意:在我的数据集中,我有一个名为 “Text” 的字段/列。 根据你的数据集将其更改为字段名称。

一旦嵌入完成并存储在新字段中,就可以将此数据插入我们系统中的 Elasticsearch,你应该已经在本教程开始时安装了它。

要插入数据,我们首先必须连接到 Elasticsearch,所有这一切都将使用 python 进行。

USERNAME = "elastic"
PASSWORD = "GHI8C685oSpq_kNtUJV1"
ELATICSEARCH_ENDPOINT = "https://localhost:9200"
CERT_FINGERPRINT = "abec585e4d6c383032d19f8c535369107f063ae91491e20b5e25b75afb308f13"
 
es = Elasticsearch(ELATICSEARCH_ENDPOINT, 
                   ssl_assert_fingerprint = (CERT_FINGERPRINT),
                   basic_auth=(USERNAME, PASSWORD),
                   verify_certs = True)
resp = es.info()
print(resp)

有关这个部分的描述,请详细阅读我之前的文章 “Elasticsearch:关于在 Python 中使用 Elasticsearch 你需要知道的一切 - 8.x”。

要验证连接是否已建立,你可以在首选浏览器上打开 https://localhost:9200 并检查。 你还可以通过运行 es.ping() 从你的 IDE 检查连接。 对于成功的连接,输出应该是 True。

现在我们已经建立了与 Elasticsearch 的连接,让我们继续配置 Elasticsearch 索引。

configurations = {
    "settings": {
        "index": {"number_of_replicas": 2},
        "analysis": {
            "filter": {
                "ngram_filter": {
                    "type": "edge_ngram",
                    "min_gram": 2,
                    "max_gram": 15,
                }
            },
            "analyzer": {
                "ngram_analyzer": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "filter": ["lowercase", "ngram_filter"],
                }
            }
        }
    },
    "mappings": {
        "properties": {
          "Embeddings": {
            "type": "dense_vector",
            "dims": 512,
            "index": True,
            "similarity": "cosine" 
          },
          } 
        } 
    } 


INDEX_NAME = "vectors"

if(es.indices.exists(index=INDEX_NAME)):
    print("The index has already existed, going to remove it")
    es.options(ignore_status=404).indices.delete(index=INDEX_NAME)
    
es.indices.create(  index=INDEX_NAME,
                    settings=configurations["settings"],
                    mappings=configurations["mappings"]
                 )

在上述配置的帮助下,我们能够配置插入数据的索引。 也就是说,让我们仔细看看一些重要的参数。

  • “type”:类型必须始终设置为 “dense_vector”。 这样做是为了让 ElasticSearch 知道这些是向量,并且不会自行将浮动类型分配给该字段。
  • “dims”:也即维度。 就像我之前提到的,Universal Sentence Encoder 产生和输出 512 维度,这就是我们在参数中提供 512 的原因。
  • “index”:Index 必须设置为 True,以便创建该字段并在 ElasticSearch 中具有 dense_vector 类型。
  • “similarity”:我们正在寻找余弦相似性并已经提到了它。 你也可以选择其他选项。具体可以参考链接。

配置索引后,现在让我们继续创建这个索引。在我们的应用中,我们选择 index 的名字为 vectors。

在这里,我将索引命名为 vectors。 有了这个,我们的索引已经用我们的配置创建了,最后我们准备好将我们的数据插入到 Elasticsearch 上的这个索引中。

actions = []
for index, row in df.iterrows():
    action = {"index": {"_index": INDEX_NAME, "_id": index}}
    doc = {
        "id": index,
        "Text": row["Text"],
        "Price": row["Price"],
        "Quantity": row["Quantity"],
        "Embeddings": row["Embeddings"]
    }
    actions.append(action)
    actions.append(doc)

es.bulk(index=INDEX_NAME, operations=actions, refresh=True)

在上面的代码中,我们必须注意的是 refresh 必须设置为 True,否则在下面立马进行搜索的时候,我们可能得不到任何的结果,这是因为在通常的情况下,需要 1 分钟的时间才能使得刚写入的文档变为可以搜索的。借助以上代码,你将能够将数据插入 Elasticsearch。

搜索数据

插入数据后,我们现在可以搜索此数据并提出一些相关问题。 为此,让我们从一个我们想要获得答案的问题开始。

query = "Which is the latest phone available in your shop?"

现在,由于我们需要在 Elasticsearch 上进行语义搜索,我们需要将此文本转换为嵌入/向量。

query = "Which is the latest phone available in your shop"


def embed_text(text):
    vectors = session.run(embeddings, feed_dict={text_ph: text})
    return [vector.tolist() for vector in vectors]

query_vector = embed_text([query])[0]
print(query_vector)

现在 query_vector 含有 “Which is the latest phone available in your shop” 所转换而来的向量。

将文本转换为嵌入/向量后,我们就可以根据 Elasticsearch 中的现有数据搜索此文本。 为此,我们首先必须构建一个查询以从 Elasticsearch 获取数据。

query = {
    "field": "Embeddings",
    "query_vector": query_vector,
    "k": 10,
    "num_candidates": 100
  }

source_fields = ["Text", "Price", "Quantity"]

response = es.search(
    index="vectors",
    fields=source_fields,
    knn=query,
    source=False)

print(response)

使用上面提供的代码,我们可以从 Elasticsearch 进行查询。 但在我们看下一步之前,让我们仔细看看这个查询并理解它。

  • “knn”:Elasticsearch 支持 K-Nearest Neighbors a.k.a kNN 算法并且已经在 Elasticsearch 中可用。 你不需要单独训练它。
  • “field”:你的嵌入/向量存储在 Elasticsearch 中的字段。
  • “query_vector”:你以向量/嵌入形式输入。
  • “k”:你需要的输出/搜索结果数。
  • “num_candidates”:earch API finds a num_candidates number of approximate nearest neighbor candidates on each shard.

借助上述查询,你将能够从之前存储数据的索引中获取搜索结果。

 

请记住,你只能对具有配置字段的索引执行语义搜索,该字段包含嵌入/向量作为 "type": "dense_vector" 并且向量维度必须与你的查询/问题和存储在 Elasticsearch 中的数据完全相同 . 例如,在上面的教程中,我们在 Elasticsearch 中的数据是 512 维度,在我们继续搜索操作之前,query/question 也被转换为 512 维度。 

结论

总之,语义搜索是一种强大的工具,可以通过理解单词的含义和上下文来大大提高搜索结果的准确性和相关性。 Elasticsearch 是一个高度可扩展且灵活的搜索引擎,可用于为从电子商务到医疗保健的各种应用程序实现语义搜索。 通过利用 Elasticsearch 强大的搜索和索引功能,以及查询扩展、同义词检测和实体识别等技术,你可以构建一个提供快速准确结果的语义搜索系统。 无论你是开发人员、数据科学家还是企业主,使用 Elasticsearch 掌握语义搜索都可以帮助你从数据中获得新的见解和机会。 那为什么还要等? 立即开始使用 Elasticsearch 探索语义搜索的强大功能!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/597526.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【运筹优化】元启发式算法详解:变邻域搜索算法(Variable Neighborhood Search,VNS)+ 案例讲解代码实现

文章目录 一、介绍二、基本方案三、一些扩展四、在VNS内改变配方4.1 基于变邻域的公式空间搜索4.2 变公式搜索 五、原始对偶VNS六、求解混合整数线性规划的VNS七、连续全局优化的可变邻域搜索八、可变邻域编程(VNP):自动编程的VNS九、Discovery Science十、总结十一、案例讲解&…

如何视频转语音?想知道视频转语音工具怎么用?

在教育、培训等领域中,有时候需要将讲解视频转化为文字来提供给学生反复阅读学习。那么,小伙伴们,你们知道怎样视频转语音吗?其实我们可以借助一些视频转语音的软件帮助我们实现视频转语音操作。这篇文章就给大家分享几个非常好用…

PHP学习笔记第二天

前言 作者简介:不知名白帽,网络安全学习者。 博客主页:不知名白帽的博客_CSDN博客-网络安全,CTF,内网渗透领域博主 网络安全交流社区:https://bbs.csdn.net/forums/angluoanquan 目录 PHP类型比较 和 PHP中比较0、false、null …

基于SSM的酒店管理系统代码数据库文件和LW

框架:SSM 数据库:MySQL 语言:Java 下载链接: https://download.csdn.net/download/yw1990128/87853243 B站演示链接: 基于SSM框架的酒店管理系统_哔哩哔哩_bilibili 1.1 课题研究背景及意义 随着我国改革开放的不…

hutool文件导出

hutool文件导出 需求:管理员设置会议,参加会议会根据管理员设置的会议要求,用户参加会议填写相关数据,并且生成一个动态的excel数据并导出 示例: 每场都可以自定义报名字段 根据需求与前端约定 字段名称(n…

通用读写仲裁模块(FPGA实现)

当涉及多个模块向同一个模块进行读写操作、向一个半双工模块请求读写,甚至综合一下,多个模块向一个半双工模块发起读写请求,那就要涉及读写仲裁。因为最近做的项目中涉及的读写仲裁太多了,所以就想还是要写一个通用的读写仲裁模块…

网络协议系统学习

网络为什么要分层? 因为是个复杂的程序就要分层 可以把网络包想象成一个buffer或者一块内存,是有格式的。同时,想象自己是一个处理网络包的程序,而且这个程序可以跑在电脑/服务器/路由器/交换机上,自己有很多网口&am…

抖音seo优化源码搭建/搜索排名系统,技术理论分析搭建中。

抖音seo系统源码SaaS+源码私有化部署搭建,抖音seo源码,抖音seo系统源码,抖音seo系统搭建部署,抖音已经成为了当今最为流行的短视频平台之一,拥有着庞大的用户群体和海量的视频资源。对于一些商家或者运营者…

26岁,几乎零基础,想从基础学习渗透测试该如何进行?

要成为一名渗透测试员,想从基础学习需要先掌握下面这3块(文末有相关自学资源推荐):1、学习硬件和网络 渗透测试主要涉及网络和部分涉及硬件。 2、操作系统和系统架构 操作系统和系统架构在渗透测试中起着关键作用。系统操作涉及x…

笔试强训6

作者:爱塔居 专栏:笔试强训 作者简介:大三学生,希望和大家一起进步! 1.下列关于ThreadLocal的描述中,错误的是() A.ThreadLocal采用线程隔离的方式存放数据,可以避免多线…

社区网格化管理系统

在传统的城市管理过程中存在的问题: 1、问题发现不及时,被管理对象不清楚。 2、管理部门职责不清,协调成本高。 3、城市管理整体情况缺乏数据支撑。 4、基层力量薄弱。 凡尔码搭建社区网格化管理系统依托统一的城市管理以及数字化的平台&…

Codeforces Round 875 (Div. 2)(A—D)

文章目录 A. Twin Permutations1、分析2、代码 B. Array merging1、分析2、代码 C. Copil Copac Draws Trees1、分析2、代码 D. The BOSS Can Count Pairs1、分析2、代码 A. Twin Permutations A. Twin Permutations 1、分析 作者这里的构造方法是让最终的数组满足&#xff…

linux安装jdk8

1.下载jdk8 https://www.oracle.com/java/technologies/downloads/#java8 2.上传jdk (1)将jdk源码包,上传到/usr/local (2)进入上传jar包目录 [rootiZ2ze7vthdl3oh0n0hzlu7Z ~]# cd / [rootiZ2ze7vthdl3oh0n0hzlu…

C语言之字符串,内存操作函数详解(一)

💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C语言学习分享⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习更多C语言知识   🔝🔝 字符串函数 1. 前言🚩2…

电池管理系统 (BMS)

现今的电子设备,小至TWS耳机和可穿戴设备,大至电动汽车,都离不开锂离子或聚合物电池的供电。依据电子设备所需电力的大小,电池组可能由多个电池单元(电芯)排列而成。电池组的充电和放电、输入/输出电压和电流等状态都需要精密监控…

2023年6月DAMA-CDGP数据治理专家认证,你考了吗?

DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业…

小白系统地学习it技术--python的心得体会

我对我所学习的IT技术的理解 一、it技术介绍——python二、我学习python前的准备工作三、学习时的具体操作1. 在pycharm练习python,唯手熟尔!!2. 在bilibili看python学习视频3. 报错了,CSDN是你的不二选择!4.找代码&am…

【开发日志】2023.05 ZENO----PrimitiveCurvature----曲率分析工具(几何体、图像、点云)

Screen Space Ambient Occlusion - TDA362/DIT223 - Computer Graphics Labs (chalmers.se)https://www.cse.chalmers.se/edu/course/TDA362/tutorials/ssao.html GAMES102在线课程-刘利刚 (ustc.edu.cn)http://staff.ustc.edu.cn/~lgliu/Courses/GAMES102_2020/default.html …

我给自己搭建的前端导航网站,你们都别用

欢迎关注我🥰🥰🥰 主页传送门,持续产出有思考的文档~ 💥 想法来源 前段时间在工作的时候,因为遇到了一些之前没了解过的知识,所以化身百度cv工程师,上网冲浪寻找灵感&am…

第六十一天学习记录:C语言进阶:C语言预处理1

程序的翻译环境和执行环境 在ANSI C的任何一种实现中,存在两个不同的环境。 第一种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。 详解编译链接 翻译环境 ![在这里插入图片描述](https:/…