在快速準(zhǔn)確的信息檢索至關(guān)重要的時代,開發(fā)強(qiáng)大的搜索引擎至關(guān)重要。 隨著大型語言模型和信息檢索架構(gòu)(如 RAG)的出現(xiàn),在現(xiàn)代軟件系統(tǒng)中利用文本表示(向量/嵌入)和向量數(shù)據(jù)庫已變得越來越流行。 在本文中,我們深入研究了如何使用 Elasticsearch 的 K 最近鄰 (KNN) 搜索和來自強(qiáng)大語言模型的文本嵌入,這是一個強(qiáng)大的組合,有望徹底改變我們訪問常見問題 (FAQ) 的方式。 通過對 Elasticsearch 的 KNN 功能的全面探索,我們將揭示這種集成如何使我們能夠創(chuàng)建尖端的常見問題解答搜索引擎,通過以閃電般的延遲理解查詢的語義上下文,從而增強(qiáng)用戶體驗(yàn)。
在開始設(shè)計(jì)解決方案之前,讓我們了解信息檢索系統(tǒng)中的一些基本概念。
文本表示(嵌入)
你可以通過閱讀 “Elasticsearch:什么是向量和向量存儲數(shù)據(jù)庫,我們?yōu)槭裁搓P(guān)心?” 來了解更多的關(guān)于文本嵌入的知識。
嵌入是一條信息的數(shù)字表示,例如文本、文檔、圖像、音頻等。 該表示捕獲了所嵌入內(nèi)容的語義,使其對于許多行業(yè)應(yīng)用程序來說都是穩(wěn)健的。
語義搜索
傳統(tǒng)的搜索系統(tǒng)使用詞法匹配來檢索給定查詢的文檔。 語義搜索旨在使用文本表示(嵌入)來理解查詢的上下文,以提高搜索準(zhǔn)確性。
語義搜索的類型
- 對稱語義搜索:查詢和搜索文本長度相似的搜索用例。 例如 在數(shù)據(jù)集中找到類似的問題。
- 非對稱語義搜索:查詢和搜索文本長度不同的搜索用例。 例如 查找給定查詢的相關(guān)段落。
向量搜索引擎(向量數(shù)據(jù)庫)
向量搜索引擎是專用數(shù)據(jù)庫,可用于將圖像、文本、音頻或視頻等非結(jié)構(gòu)化信息存儲為嵌入或向量。 在本文中,我們將使用 Elasticsearch 的向量搜索功能。
現(xiàn)在我們了解了搜索系統(tǒng)的構(gòu)建塊,讓我們深入了解解決方案架構(gòu)和實(shí)現(xiàn)。
- 搜索解決方案的第一步是將問題-答案對索引到 Elasticsearch 中。 我們將創(chuàng)建一個索引并將問題和答案嵌入存儲在同一索引中。 我們將根據(jù)檢索的特征使用兩個獨(dú)立的模型來嵌入問題和答案。
- 我們將使用步驟 1 中使用的相同模型來嵌入查詢,并形成搜索查詢(3 個部分,即問題、答案、詞匯搜索),將查詢嵌入映射到相應(yīng)的問題和答案嵌入。
- 我們還將為查詢的每個部分提供一個提升值,以表示它們在組合中的重要性。 返回的最終結(jié)果根據(jù)分?jǐn)?shù)總和乘以各自的提升值進(jìn)行排名。
環(huán)境設(shè)置
要使用 docker 安裝 Elasticsearch,請參閱這篇有關(guān)如何設(shè)置單節(jié)點(diǎn)集群的詳細(xì)文章。 如果你已有集群,請?zhí)^此步驟。如果你想詳細(xì)了解如何安裝 Elasticsearch,請參考文章 “如何在 Linux,MacOS 及 Windows 上進(jìn)行安裝 Elasticsearch”。在本演示中,我們將使用 Elastic Stack 8.10.4 來進(jìn)行展示。
設(shè)置你的索引。 你可以使用以下映射作為起點(diǎn)。我們在 Kibana 的 Dev Tools 中打入如下的命令:
PUT faq-index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"Question": {
"type": "text"
},
"Answer": {
"type": "text"
},
"question_emb": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "dot_product"
},
"answer_emb": {
"type": "dense_vector",
"dims": 1024,
"index": true,
"similarity": "dot_product"
}
}
}
}
模型選擇
由于我們使用相當(dāng)通用的語言處理數(shù)據(jù),因此為了進(jìn)行本實(shí)驗(yàn),我從 MTEB 排行榜的檢索(用于答案)和 STS(用于問題)部分中選擇了表現(xiàn)最好的模型。
選定型號:
- 答案:BAAI/bge-large-en-v1.5(您可以使用量化版本以加快推理速度)
- 如有問題:thenlper/gte-base
如果你有特定領(lǐng)域的常見問題解答并想要檢查哪種模型表現(xiàn)最好,你可以使用 Beir。 查看本節(jié),其中描述了如何加載自定義數(shù)據(jù)集以進(jìn)行評估。
實(shí)現(xiàn)
出于本實(shí)驗(yàn)的目的,我將使用 Kaggle 的心理健康常見問題解答數(shù)據(jù)集。
安裝所需要的模塊
pips install sentence_transformers
1. 裝載數(shù)據(jù)
import pandas as pd
data = pd.read_csv('Mental_Health_FAQ.csv')
2. 生成嵌入
Questions:
from sentence_transformers import SentenceTransformer
question_emb_model = SentenceTransformer('thenlper/gte-base')
data['question_emb'] = data['Questions'].apply(lambda x: question_emb_model.encode(x, normalize_embeddings=True))
注意:我們對嵌入進(jìn)行歸一化,以使用點(diǎn)積作為相似性度量而不是余弦相似性。 該計(jì)算速度更快,并且在 Elasticsearch 密集向量場文檔中得到推薦。
Answers:
answer_emb_model = SentenceTransformer('BAAI/bge-large-en-v1.5')
data['answer_emb'] = data['Answers'].apply(lambda x: answer_emb_model.encode(x, normalize_embeddings=True))
3. 索引文檔
我們將使用 Elasticsearch? helper 函數(shù)。 具體來說,我們將使用 streaming_bulk API 來索引我們的文檔。
首先,讓我們實(shí)例化 elasticsearch python 客戶端。
我們首先需要把安裝好的 Elasticsearch 的證書拷貝到當(dāng)前目錄中:
$ pwd
/Users/liuxg/python/faq
$ cp ~/elastic/elasticsearch-8.10.4/config/certs/http_ca.crt .
$ ls
Mental Health FAQ.ipynb archive (13).zip
Mental_Health_FAQ.csv http_ca.crt
然后我們打入如下的代碼:
from elasticsearch import Elasticsearch
from ssl import create_default_context
context = create_default_context(cafile=r"./http_ca.crt")
es = Elasticsearch('https://localhost:9200',
basic_auth=('elastic', 'YlGXk9PCN7AUlc*VMtQj'),
ssl_context=context,
)
接下來,我們需要創(chuàng)建一個可以輸入到流式 bulk?API 中的文檔生成器。
index_name="faq-index"
def generate_docs():
for index, row in data.iterrows():
doc = {
"_index": index_name,
"_source": {
"faq_id":row['Question_ID'],
"question":row['Questions'],
"answer":row['Answers'],
"question_emb": row['question_emb'],
"answer_emb": row['answer_emb']
},
}
yield doc
最后,我們可以索引文檔。
import tqdm
from elasticsearch.helpers import streaming_bulk
number_of_docs=len(data)
progress = tqdm.tqdm(unit="docs", total=number_of_docs)
successes = 0
for ok, action in streaming_bulk(client=es, index=index_name, actions=generate_docs()):
progress.update(1)
successes += ok
print("Indexed %d/%d documents" % (successes, number_of_docs))
4. 查詢文檔
def faq_search(query="", k=10, num_candidates=10):
if query is not None and len(query) == 0:
print('Query cannot be empty')
return None
else:
query_question_emb = question_emb_model.encode(query, normalize_embeddings=True)
instruction="Represent this sentence for searching relevant passages: "
query_answer_emb = answer_emb_model.encode(instruction + query, normalize_embeddings=True)
payload = {
"query": {
"match": {
"title": {
"query": query,
"boost": 0.2
}
}
},
"knn": [ {
"field": "question_emb",
"query_vector": query_question_emb,
"k": k,
"num_candidates": num_candidates,
"boost": 0.3
},
{
"field": "answer_emb",
"query_vector": query_answer_emb,
"k": k,
"num_candidates": num_candidates,
"boost": 0.5
}],
"size": 10,
"_source":["faq_id","question", "answer"]
}
response = es.search(index=index_name, body=payload)['hits']['hits']
return response
按照模型頁面上的說明,我們需要在將查詢轉(zhuǎn)換為嵌入之前將指令附加到查詢中。 此外,我們使用模型的 v1.5,因?yàn)樗哂懈玫南嗨贫确植肌?查看型號頁面上的常見問題解答以了解更多詳細(xì)信息。
評估
為了了解所提出的方法是否有效,根據(jù)傳統(tǒng)的 KNN 搜索系統(tǒng)對其進(jìn)行評估非常重要。 讓我們嘗試定義這兩個系統(tǒng)并評估所提出的系統(tǒng)。
- 系統(tǒng) 1:非對稱 KNN 搜索(查詢和答案向量)。
- 系統(tǒng)2:查詢(BM25)、非對稱KNN搜索(查詢和答案向量)和對稱KNN搜索(查詢和問題向量)的組合。
為了評估系統(tǒng),我們必須模仿用戶如何使用搜索。 簡而言之,我們需要從源問題生成與問題復(fù)雜性相似的釋義問題。 我們將使用 t5-small-finetuned-quora-for-paraphrasing 微調(diào)模型來解釋問題。
讓我們定義一個可以生成釋義問題的函數(shù)。
from transformers import AutoModelWithLMHead, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("mrm8488/t5-small-finetuned-quora-for-paraphrasing")
model = AutoModelWithLMHead.from_pretrained("mrm8488/t5-small-finetuned-quora-for-paraphrasing")
def paraphrase(question, number_of_questions=3, max_length=128):
input_ids = tokenizer.encode(question, return_tensors="pt", add_special_tokens=True)
generated_ids = model.generate(input_ids=input_ids, num_return_sequences=number_of_questions, num_beams=5, max_length=max_length, no_repeat_ngram_size=2, repetition_penalty=3.5, length_penalty=1.0, early_stopping=True)
preds = [tokenizer.decode(g, skip_special_tokens=True, clean_up_tokenization_spaces=True) for g in generated_ids]
return preds
現(xiàn)在我們已經(jīng)準(zhǔn)備好了釋義函數(shù),讓我們創(chuàng)建一個評估數(shù)據(jù)集,用于測量系統(tǒng)的準(zhǔn)確性。
temp_data = data[['Question_ID','Questions']]
eval_data = []
for index, row in temp_data.iterrows():
preds = paraphrase("paraphrase: {}".format(row['Questions']))
for pred in preds:
temp={}
temp['Question'] = pred
temp['FAQ_ID'] = row['Question_ID']
eval_data.append(temp)
eval_data = pd.DataFrame(eval_data)
#shuffle the evaluation dataset
eval_data=eval_data.sample(frac=1).reset_index(drop=True)
上面的代碼生成相應(yīng)的測試 Question,它們的結(jié)果如下:
最后,我們將修改 “faq_search” 函數(shù)以返回各個系統(tǒng)的 faq_id。
對于系統(tǒng) 1:
def get_faq_id_s1(query="", k=5, num_candidates=10):
if query is not None and len(query) == 0:
print('Query cannot be empty')
return None
else:
instruction="Represent this sentence for searching relevant passages: "
query_answer_emb = answer_emb_model.encode(instruction + query, normalize_embeddings=True)
payload = {
"knn": [
{
"field": "answer_emb",
"query_vector": query_answer_emb,
"k": k,
"num_candidates": num_candidates,
}],
"size": 1,
"_source":["faq_id"]
}
response = es.search(index=index_name, body=payload)['hits']['hits']
return response[0]['_source']['faq_id']
對于系統(tǒng) 2:
def get_faq_id_s2(query="", k=5, num_candidates=10):
if query is not None and len(query) == 0:
print('Query cannot be empty')
return None
else:
query_question_emb = question_emb_model.encode(query, normalize_embeddings=True)
instruction="Represent this sentence for searching relevant passages: "
query_answer_emb = answer_emb_model.encode(instruction + query, normalize_embeddings=True)
payload = {
"query": {
"match": {
"title": {
"query": query,
"boost": 0.2
}
}
},
"knn": [ {
"field": "question_emb",
"query_vector": query_question_emb,
"k": k,
"num_candidates": num_candidates,
"boost": 0.3
},
{
"field": "answer_emb",
"query_vector": query_answer_emb,
"k": k,
"num_candidates": num_candidates,
"boost": 0.5
}],
"size": 1,
"_source":["faq_id"]
}
response = es.search(index=index_name, body=payload)['hits']['hits']
return response[0]['_source']['faq_id']
注意:boost 值是實(shí)驗(yàn)性的。 為了這個實(shí)驗(yàn)的目的,我根據(jù)組合中各個字段的重要性進(jìn)行了劃分。 搜索中每個字段的重要性完全是主觀的,可能由業(yè)務(wù)本身定義,但如果不是,系統(tǒng)的一般經(jīng)驗(yàn)法則是 Answer 向量 > Question 向量 > 查詢。
好的! 我們一切準(zhǔn)備就緒,開始我們的評估。 我們將為兩個系統(tǒng)生成一個預(yù)測列,并將其與原始 faq_id 進(jìn)行比較。
eval_data['PRED_FAQ_ID_S1'] = eval_data['Question'].apply(get_faq_id_s1)
from sklearn.metrics import accuracy_score
ground_truth = eval_data["FAQ_ID"].values
predictions_s1 = eval_data["PRED_FAQ_ID_S1"].values
s1_accuracy = accuracy_score(ground_truth, predictions_s1)
print('System 1 Accuracy: {}'.format(s1_accuracy))
eval_data['PRED_FAQ_ID_S2'] = eval_data['Question'].apply(get_faq_id_s2)
predictions_s2 = eval_data["PRED_FAQ_ID_S2"].values
s2_accuracy = accuracy_score(ground_truth, predictions_s2)
print('System 2 Accuracy: {}'.format(s2_accuracy))
通過所提出的系統(tǒng),我們可以看到與非對稱 KNN 搜索相比,準(zhǔn)確率提高了 7-11%。
我們還可以嘗試?ramsrigouthamg/t5_paraphraser,但該模型生成的問題有點(diǎn)復(fù)雜和冗長(盡管在上下文中)。
你還可以使用 LLM 生成評估數(shù)據(jù)集并檢查系統(tǒng)的性能。
準(zhǔn)確性的提高是主觀的,取決于查詢的質(zhì)量,即 查詢的上下文有多豐富、嵌入的質(zhì)量和/或使用搜索的用戶類型。 為了更好地理解這一點(diǎn),讓我們考慮兩種最終用戶:
- 想要了解有關(guān)您的產(chǎn)品和服務(wù)的一些事實(shí)的一般用戶:在這種情況下,上述系統(tǒng)會做得很好,因?yàn)閱栴}簡單、直觀且上下文充分。
- 領(lǐng)域/產(chǎn)品特定用戶,例如 想要了解產(chǎn)品的一些復(fù)雜細(xì)節(jié)以設(shè)置系統(tǒng)或解決某些問題的工程師:在這種情況下,查詢在詞匯組成方面更具特定于領(lǐng)域,因此開箱即用的模型嵌入將無法捕獲所有上下文。 那么,我們該如何解決這個問題呢? 系統(tǒng)的架構(gòu)將保持不變,但可以通過使用特定領(lǐng)域數(shù)據(jù)(或預(yù)先訓(xùn)練的特定領(lǐng)域模型)微調(diào)這些模型來提高搜索系統(tǒng)的整體準(zhǔn)確性。
結(jié)論
在本文中,我們提出并實(shí)現(xiàn)了結(jié)合搜索類型的常見問題解答搜索。 我們研究了 Elasticsearch 如何使我們能夠結(jié)合對稱和非對稱語義搜索,從而將搜索系統(tǒng)的性能提高高達(dá) 11%。 我們還了解所提出的搜索架構(gòu)的系統(tǒng)和資源要求,這將是考慮采用這種方法時的主要決定因素。文章來源:http://www.zghlxwxcb.cn/news/detail-715699.html
你可以在我的 Github 存儲庫中找到源筆記本。文章來源地址http://www.zghlxwxcb.cn/news/detail-715699.html
到了這里,關(guān)于增強(qiáng)常見問題解答搜索引擎:在 Elasticsearch 中利用 KNN 的力量的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!