什么語義搜索(semantic search)呢?根據(jù)搜索查詢的意圖和上下文含義(而不僅僅是關(guān)鍵字)檢索結(jié)果。語義/向量搜索是一種強(qiáng)大的技術(shù),可以大大提高搜索結(jié)果的準(zhǔn)確性和相關(guān)性。 與傳統(tǒng)的基于關(guān)鍵字的搜索方法不同,語義搜索使用單詞的含義和上下文來理解查詢背后的意圖并提供更準(zhǔn)確的結(jié)果。 Elasticsearch 是實(shí)現(xiàn)語義搜索最流行的工具之一,它是一種高度可擴(kuò)展且功能強(qiáng)大的搜索引擎,可用于索引和搜索大量數(shù)據(jù)。 在本文中,我們將探討語義搜索的基礎(chǔ)知識以及如何使用 Elasticsearch 實(shí)現(xiàn)它。 到本文結(jié)束時,你將深入了解語義搜索的工作原理以及在你自己的項目中實(shí)現(xiàn)它的實(shí)用技能。
在進(jìn)行下面的講解之前,我需要特別指出的是:Elastic 提供了 eland 幫助我們上傳在 huggingface.co 上的模型。我們在攝入文檔的時候,可以使用?inference processor 來方便地進(jìn)行數(shù)據(jù)字段的向量化。eland 上傳及機(jī)器學(xué)習(xí)是 Elastic 的收費(fèi)項目。本文章將使用 Tensorflow 來通過代碼的方式來獲得向量,并上傳到 Elasticsearch。更多關(guān)于使用 eland 及機(jī)器學(xué)習(xí)上傳模型的方法,請詳細(xì)閱讀 “Elastic:開發(fā)者上手指南” 中的 “NLP - 自然語言處理及向量搜索” 章節(jié)。
Elasticsearch
Elasticsearch 是一個基于 Lucene 庫的強(qiáng)大且可擴(kuò)展的免費(fèi)及開發(fā)的搜索引擎。 它旨在處理大量非結(jié)構(gòu)化數(shù)據(jù)并提供快速準(zhǔn)確的搜索結(jié)果。 Elasticsearch 使用分布式架構(gòu),這意味著它可以橫向擴(kuò)展到多個服務(wù)器以處理大量數(shù)據(jù)和流量。
Elasticsearch 建立在 RESTful API 之上,這使得它可以輕松地與各種編程語言和工具集成。 它支持復(fù)雜的搜索查詢,包括全文搜索、分面搜索和地理搜索。 Elasticsearch 還提供了一個強(qiáng)大的聚合框架,允許你對搜索結(jié)果進(jìn)行復(fù)雜的數(shù)據(jù)分析。
Transformers
Transformers 是一種機(jī)器學(xué)習(xí)模型,它徹底改變了自然語言處理 (NLP) 任務(wù),例如語言翻譯、文本摘要和情感分析。 Vaswani 等人首先介紹了 transformer。 在 2017 年的一篇論文 “Attention Is All You Need” 中,此后已成為許多 NLP 任務(wù)的最先進(jìn)模型。
與循環(huán)神經(jīng)網(wǎng)絡(luò) (RNN) 和卷積神經(jīng)網(wǎng)絡(luò) (CNN) 的傳統(tǒng) NLP 模型不同,Transformer 使用 self-attention 機(jī)制來捕捉句子中單詞之間的關(guān)系。 Self-attentiion 允許模型關(guān)注輸入序列的不同部分,以確定單詞之間最重要的關(guān)系。 這使得轉(zhuǎn)換器比傳統(tǒng)模型更有效地處理單詞之間的遠(yuǎn)程依賴關(guān)系和上下文關(guān)系。
對于本文,我將使用 TensorFlow 的通用句子編碼器對我的數(shù)據(jù)進(jìn)行編碼/向量化。 你也可以選擇任何其他形式的編碼器。另外值得指出的是:tensorflow 在 Apple 的芯片上不能得到支持。你需要使用 x86 的機(jī)器來進(jìn)行練習(xí)。
為了方便大家學(xué)習(xí),我把代碼放在地址:https://github.com/liu-xiao-guo/Semantic-Search-ElasticSearch
準(zhǔn)備工作
Elasticsearch 及 Kibana
如果你還沒有安裝好自己的 Elasticsearch 及 Kibana,請參考文章:
- 如何在 Linux,MacOS 及 Windows 上進(jìn)行安裝 Elasticsearch
- Kibana:如何在 Linux,MacOS 及 Windows 上安裝 Elastic 棧中的 Kibana
在我們的本次練習(xí)中,我們將使用 Elastic Stack 8.8 版本。在 Elasticsearch 首次啟動的時候,它會出現(xiàn)如下的屏幕:
我們記下 elastic 用戶的密碼及 fingerprint。這些信息在一下的代碼中進(jìn)行使用。?
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
樣本文件
如上所示,我準(zhǔn)備了一個叫做 sample.csv 的文件。它的內(nèi)容非常之簡單:
sample.csv
Text,Price,Quantity
"The latest phone model",5000,10
"The best seller phone",2000,50
也就是只有兩個文檔。你可以根據(jù)自己的情況修改這個文檔。
代碼
我先把代碼貼出來:
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)
這是整個代碼。雖然看起來簡單,但是在調(diào)試的時候還是出現(xiàn)了一些狀況。
安裝 Python 依賴項后,你將需要文本數(shù)據(jù)作為開始。 獲取文本數(shù)據(jù)后,在你喜歡的 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])
讀取文本數(shù)據(jù)后,第一個任務(wù)是將其轉(zhuǎn)換為向量或嵌入。 在這里,正如我之前提到的,我使用的是 TensorFlow 的通用句子編碼器,它在提供字符串后輸出 “512” 維度的向量/嵌入。
這對于其他轉(zhuǎn)換器/向量化器會有所不同,你需要記住這一點(diǎn)以便進(jìn)一步執(zhí)行步驟。
model = hub.load("./model")
成功加載模型后,現(xiàn)在我們的下一個任務(wù)是將數(shù)據(jù)集中的文本轉(zhuǎn)換為向量/嵌入,并將其存儲在名為 “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
注意:在我的數(shù)據(jù)集中,我有一個名為 “Text” 的字段/列。 根據(jù)你的數(shù)據(jù)集將其更改為字段名稱。
一旦嵌入完成并存儲在新字段中,就可以將此數(shù)據(jù)插入我們系統(tǒng)中的 Elasticsearch,你應(yīng)該已經(jīng)在本教程開始時安裝了它。
要插入數(shù)據(jù),我們首先必須連接到 Elasticsearch,所有這一切都將使用 python 進(jìn)行。
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)
有關(guān)這個部分的描述,請詳細(xì)閱讀我之前的文章 “Elasticsearch:關(guān)于在 Python 中使用 Elasticsearch 你需要知道的一切 - 8.x”。
要驗(yàn)證連接是否已建立,你可以在首選瀏覽器上打開 https://localhost:9200?并檢查。 你還可以通過運(yùn)行 es.ping()?從你的 IDE 檢查連接。 對于成功的連接,輸出應(yīng)該是 True。
現(xiàn)在我們已經(jīng)建立了與 Elasticsearch 的連接,讓我們繼續(xù)配置 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"]
)
在上述配置的幫助下,我們能夠配置插入數(shù)據(jù)的索引。 也就是說,讓我們仔細(xì)看看一些重要的參數(shù)。
- “type”:類型必須始終設(shè)置為 “dense_vector”。 這樣做是為了讓 ElasticSearch 知道這些是向量,并且不會自行將浮動類型分配給該字段。
- “dims”:也即維度。 就像我之前提到的,Universal Sentence Encoder 產(chǎn)生和輸出 512 維度,這就是我們在參數(shù)中提供 512 的原因。
- “index”:Index 必須設(shè)置為 True,以便創(chuàng)建該字段并在 ElasticSearch 中具有 dense_vector 類型。
- “similarity”:我們正在尋找余弦相似性并已經(jīng)提到了它。 你也可以選擇其他選項。具體可以參考鏈接。
配置索引后,現(xiàn)在讓我們繼續(xù)創(chuàng)建這個索引。在我們的應(yīng)用中,我們選擇 index 的名字為 vectors。
在這里,我將索引命名為 vectors。 有了這個,我們的索引已經(jīng)用我們的配置創(chuàng)建了,最后我們準(zhǔn)備好將我們的數(shù)據(jù)插入到 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 必須設(shè)置為 True,否則在下面立馬進(jìn)行搜索的時候,我們可能得不到任何的結(jié)果,這是因?yàn)樵谕ǔ5那闆r下,需要 1 分鐘的時間才能使得剛寫入的文檔變?yōu)榭梢运阉鞯?。借助以上代碼,你將能夠?qū)?shù)據(jù)插入 Elasticsearch。
搜索數(shù)據(jù)
插入數(shù)據(jù)后,我們現(xiàn)在可以搜索此數(shù)據(jù)并提出一些相關(guān)問題。 為此,讓我們從一個我們想要獲得答案的問題開始。
query = "Which is the latest phone available in your shop?"
現(xiàn)在,由于我們需要在 Elasticsearch 上進(jìn)行語義搜索,我們需要將此文本轉(zhuǎn)換為嵌入/向量。
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)
現(xiàn)在 query_vector 含有 “Which is the latest phone available in your shop” 所轉(zhuǎn)換而來的向量。
將文本轉(zhuǎn)換為嵌入/向量后,我們就可以根據(jù) Elasticsearch 中的現(xiàn)有數(shù)據(jù)搜索此文本。 為此,我們首先必須構(gòu)建一個查詢以從 Elasticsearch 獲取數(shù)據(jù)。
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 進(jìn)行查詢。 但在我們看下一步之前,讓我們仔細(xì)看看這個查詢并理解它。
- “knn”:Elasticsearch 支持 K-Nearest Neighbors a.k.a kNN 算法并且已經(jīng)在 Elasticsearch 中可用。 你不需要單獨(dú)訓(xùn)練它。
- “field”:你的嵌入/向量存儲在 Elasticsearch 中的字段。
- “query_vector”:你以向量/嵌入形式輸入。
- “k”:你需要的輸出/搜索結(jié)果數(shù)。
- “num_candidates”:earch API finds a?
num_candidates
?number of approximate nearest neighbor candidates on each shard.
借助上述查詢,你將能夠從之前存儲數(shù)據(jù)的索引中獲取搜索結(jié)果。
請記住,你只能對具有配置字段的索引執(zhí)行語義搜索,該字段包含嵌入/向量作為 "type": "dense_vector" 并且向量維度必須與你的查詢/問題和存儲在 Elasticsearch 中的數(shù)據(jù)完全相同 . 例如,在上面的教程中,我們在 Elasticsearch 中的數(shù)據(jù)是 512 維度,在我們繼續(xù)搜索操作之前,query/question 也被轉(zhuǎn)換為 512 維度。?文章來源:http://www.zghlxwxcb.cn/news/detail-475457.html
結(jié)論
總之,語義搜索是一種強(qiáng)大的工具,可以通過理解單詞的含義和上下文來大大提高搜索結(jié)果的準(zhǔn)確性和相關(guān)性。 Elasticsearch 是一個高度可擴(kuò)展且靈活的搜索引擎,可用于為從電子商務(wù)到醫(yī)療保健的各種應(yīng)用程序?qū)崿F(xiàn)語義搜索。 通過利用 Elasticsearch 強(qiáng)大的搜索和索引功能,以及查詢擴(kuò)展、同義詞檢測和實(shí)體識別等技術(shù),你可以構(gòu)建一個提供快速準(zhǔn)確結(jié)果的語義搜索系統(tǒng)。 無論你是開發(fā)人員、數(shù)據(jù)科學(xué)家還是企業(yè)主,使用 Elasticsearch 掌握語義搜索都可以幫助你從數(shù)據(jù)中獲得新的見解和機(jī)會。 那為什么還要等? 立即開始使用 Elasticsearch 探索語義搜索的強(qiáng)大功能!文章來源地址http://www.zghlxwxcb.cn/news/detail-475457.html
到了這里,關(guān)于Elasticsearch:使用 Transformers 和 Elasticsearch 進(jìn)行語義搜索的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!