【RAG優(yōu)化技術】 關鍵詞&語義的混合檢索實現(xiàn)

背景

一些純語言模型,比如GPT-4,雖然也很強大,但它們的知識都是在訓練時就固定下來的,無法實時更新。這就意味著,它們無法獲取到訓練之后出現(xiàn)的新信息。所以,對于一些需要最新信息,或者是一些特定場景的問題,純語言模型可能無法給出滿意的答案。

相反,檢索增強生成(RAG)就像是一個高級版的“百度”或“谷歌”。它不僅可以理解你的問題,還會去網上、書本或數(shù)據庫里找答案。然后,它會把這些信息結合起來給AI大模型思考,給你一個既準確又符合你問題的答案。這對于需要最新信息的問題非常有幫助。

痛點

RAG 的主流實現(xiàn)方法是利用向量數(shù)據庫進行文本的向量化。首先,它會將文本數(shù)據轉化為向量形式并存儲在向量數(shù)據庫中。然后,當有問題輸入時,它會將問題也轉化為向量,然后在向量數(shù)據庫中尋找與之最匹配的文本向量。匹配成功后,就可以返回相應的文本內容作為答案。這種方法在處理大規(guī)模的信息檢索任務時具有很高的效率和準確性。

對于不同類型和語言的文件,我們需要選擇適當?shù)腞AG技術。例如,對于PPT和PDF這些主要包含文本和圖像的文件,我們可能需要使用能夠理解和生成圖像描述的模型。對于CSV和Excel這些主要包含結構化數(shù)據的文件,我們可能需要使用能夠理解和生成數(shù)據摘要的模型。此外,不同語言的文件可能需要不同的嵌入模型以適應不同語言的語義。

因此,當我們描述問題時,有時并不清楚我們的提問是否與提供的文本表達存在歧義,或者當我們只有模糊的記憶,只知道一兩個詞時,向量庫的語義搜索往往無法滿足我們的需求。

解決方案

因此,如果我們能夠結合關鍵詞和語義進行同時檢索,就能夠進一步提高我們的RAG的準確度。這種混合檢索方法可以在保證搜索效率的同時,提高搜索的準確性和全面性。具體來說,當我們的問題描述不清楚,或者記憶模糊只知道一兩個關鍵詞時,混合檢索方法可以通過關鍵詞匹配找到可能的答案,然后結合語義匹配來進一步提高答案的準確性。

主流的RAG框架,如Langchain和Llamaindex,都提供了這種混合檢索的方法供我們使用。然而,它們之間存在著細微的區(qū)別。

Langchain

Langchain支持使用BM25模型,以及其他嵌入模型和向量庫(支持替換成其他的embeding模型,例如BGE,M3E,GTE,在一些中文語料的查詢上效果更好,需要根據實際情況進行配置),結合構成一個EnsembleRetriever來檢索信息。具體來說,它可以將不同的檢索模型集成在一起,通過對各個模型的查詢結果進行加權求和,來提高檢索的準確性和全面性。

BM25 是一種基于概率的排名函數(shù),用于信息檢索系統(tǒng)。BM25原理是根據查詢詞在文檔中的出現(xiàn)頻率以及查詢詞在整個文本集中的出現(xiàn)頻率,來計算查詢詞和文檔的相似度。BM25模型的主要思想是:如果一個詞在一份文檔中出現(xiàn)的頻率高,且在其他文檔中出現(xiàn)的頻率低,那么這個詞對于這份文檔的重要性就越高,相似度就越高。BM25模型對于長文檔和短文檔有一個平衡處理,防止因文檔長度不同,而導致的詞頻偏差。

以下是使用Langchain進行混合檢索的示例代碼:

from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

doc_list_1 = [
"I like apples",
"I like oranges",
"Apples and oranges are fruits",
]

# initialize the bm25 retriever and faiss retriever
bm25_retriever = BM25Retriever.from_texts(
doc_list_1, metadatas=[{"source": 1}] * len(doc_list_1)
)
bm25_retriever.k = 2

doc_list_2 = [
"You like apples",
"You like oranges",
]
# 這里的Embeding支持替換成其他的embeding模型,例如BGE,M3E,GTE,在
embedding = OpenAIEmbeddings()
faiss_vectorstore = FAISS.from_texts(
doc_list_2, embedding, metadatas=[{"source": 2}] * len(doc_list_2)
)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})

# 替換GTE模型,并把向量數(shù)據庫換成Chroma(只是做案例,有想要看區(qū)別的可以自己學習)
# model_name = "thenlper/gte-large-zh"
# model_kwargs = {"device": "cuda"}
# encode_kwargs = {"normalize_embeddings": True}
# embeddings = HuggingFaceEmbeddings(model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs)
# chroma_vectorstore = Chroma.from_documents(data, embeddings)
# chroma_retriever = chroma_vectorstore.as_retriever(search_kwargs={"k": 5})

# initialize the ensemble retriever
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)
docs = ensemble_retriever.invoke("apples")
docs
[Document(page_content='You like apples', metadata={'source': 2}),
Document(page_content='I like apples', metadata={'source': 1}),
Document(page_content='You like oranges', metadata={'source': 2}),
Document(page_content='Apples and oranges are fruits', metadata={'source': 1})]

Llamaindex

Llamaindex 使用的方法叫做 HybridFusionRetrieverPack,與 Langchain 的主要區(qū)別在于,它在進行混合檢索之前,還會根據我們搜索的內容用大模型生成幾條類似的提問,以提高我們提問的泛化能力。這種方法可以更有效地處理我們的模糊記憶和不確定的問題描述,使我們的搜索更加精確和全面。

其他的模型也是利用了BM25還有Embeding模型,但是Llamaindex主要還是對openai的支持比較好,所以它默認是用openai 的embeding模型的,如果你想要使用其他的Embeding模型,就需要將這些模型掛載到OneAPI上,通過配置key和base_url來轉接調用

from llama_index import VectorStoreIndex, SimpleDirectoryReader
import os
from llama_index import SimpleDirectoryReader
from llama_index.node_parser import SimpleNodeParser
from llama_index.llama_pack import download_llama_pack
os.environ["OPENAI_API_KEY"] = "your_openai_key"
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
node_parser = SimpleNodeParser.from_defaults()
nodes = node_parser.get_nodes_from_documents(documents)
# download and install dependencies
HybridFusionRetrieverPack = download_llama_pack(
"HybridFusionRetrieverPack", "./hybrid_fusion_pack"
)
# create the pack
hybrid_fusion_pack = HybridFusionRetrieverPack(
nodes,
chunk_size=256,
vector_similarity_top_k=2,
bm25_similarity_top_k=2
)
hybrid_fusion_pack.run("what is the main content?")

相當于它就會根據what is the main content?來生成5條差不多意思的句子然后再進行Langchain的那個操作

BM25模型的不足

由于BM25模型主要依賴于詞頻來判斷文檔和查詢詞的相似度,因此當我們輸入的詞被錯誤地分詞,或者當我們搜索名字或者號碼這類內容時,模型的效果可能會大打折扣。這是因為在這些情況下,詞頻可能并不能準確地反映出查詢詞和文檔的相關性。例如,一個人的名字在一篇文檔中可能只出現(xiàn)一次,但這并不意味著這篇文檔與查詢詞不相關。同樣,如果我們在搜索電話號碼時,由于電話號碼通常是一長串數(shù)字,因此它的詞頻很可能非常低,但這并不影響其對于查詢結果的重要性。因此,盡管BM25模型在許多情況下都能提供滿意的檢索效果,但在處理此類問題時,它的性能可能會大大降低。

優(yōu)化模型

因此,為了解決這個問題,我們可以考慮引入ElasticSearch。ElasticSearch是一個開源的、分布式的、RESTful搜索引擎,它的設計使得它非常適合執(zhí)行諸如全文搜索、結構化搜索、實時聚合等復雜的搜索、分析任務。其內部使用Lucene作為索引和搜索的引擎,并做了很多優(yōu)化和改進,使得ElasticSearch在處理復雜搜索時性能更高,使用更方便。

ElasticSearch的特點包括分布式搜索、支持多租戶、全文搜索、異步寫操作等。這些特性使得ElasticSearch非常適合用于處理大數(shù)據量的搜索和分析任務。當我們需要在大量數(shù)據中進行精準的關鍵詞搜索時,ElasticSearch就成為了我們的得力助手。

ElasticSearch的使用也非常靈活,它提供了多種查詢方式,包括全文查詢、結構化查詢、組合查詢等,可以滿足我們的各種查詢需求。尤其是在處理我們的模糊記憶和不確定的問題描述時,ElasticSearch的這些功能將大大提高我們的查詢精度和全面性。因此,ElasticSearch將是我們混合檢索策略的重要組成部分。

首先,我們可以嘗試使用ElasticSearch進行純關鍵詞搜索。純關鍵詞搜索是最簡單的一種搜索方式,不需要進行任何預處理或向量化,只需要對輸入的關鍵詞進行精確匹配。這種搜索方式的優(yōu)點是簡單、快速,但缺點是可能會漏掉一些含義相關但沒有使用相同關鍵詞的文檔。但是,對于一些簡單的、明確的搜索需求,純關鍵詞搜索是一個非常有效的方法。

from elasticsearch import Elasticsearch, helpers
import pandas as pd
def search(keyword,index_name):
query = {
"query": {
"match": {
"extract_text": keyword
}
}
}
results = es.search(index=index_name, body=query)
hits = results['hits']['hits']
# 去除重復的 _source
deduplicated_dict = {}
for hit in hits:
source = hit["_source"]
deduplicated_dict[str(source)] = hit

# 將字典的值轉換為列表
deduplicated_list = list(deduplicated_dict.values())

return deduplicated_list

es = Elasticsearch(
hosts="http://localhost:9200/",
http_auth=("elastic", "ccc"),
request_timeout=30 # 使用 request_timeout 替代 timeout
)

index_name = "order_info"

搜索一個不知道是什么的詞


keyword = "果不滿"
docs = search(keyword,index_name)
docs

結果如下,他還是能夠精準識別出來,效果很不錯

[{'_index': 'order_info',
'_id': 'RAlrHY0BKyMRV9r1rPk2',
'_score': 8.093368,
'_source': {'extract_text': '客戶對工單流水號:處理結果不滿',
'index': 93}},
{'_index': 'order_info',
'_id': 'sZ--JI0BlkI12Q0NYaAu',
'_score': 8.093368,
'_source': {'extract_text': '客戶對工單流水號:處理結果不滿',
'index': 93,
'row': 93}}]

首先,我們需要安裝并配置Elasticsearch。一旦Elasticsearch安裝并運行,我們就可以創(chuàng)建一個索引,并將我們的數(shù)據添加到這個索引中。一旦數(shù)據被索引,我們就可以使用Elasticsearch的搜索API來查詢數(shù)據。

下面是一個使用Elasticsearch進行混合識別的示例:

from elasticsearch import Elasticsearch, helpers
from sentence_transformers import SentenceTransformer
import pandas as pd
from langchain_community.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(file_path='../../mycode/order_embeding/data/info.csv')
docs = loader.load()
# 準備數(shù)據以便導入Elasticsearch
db = ElasticsearchStore.from_documents(
docs,
embeddings,
es_url="http://localhost:9200",
index_name="order_test",
es_user="elastic",
es_password="your_elasticsearch_password",
strategy=ElasticsearchStore.ApproxRetrievalStrategy(hybrid=True)
)

** 注意:有一個坑就是如果要使用ElasticSearch的這種混合檢索,需要使用它的付費版本,所以如果真的需要混合檢索可以用ElasticSearch的關鍵詞再和普通向量檢索的結果直接拼接去重融合就好了

總結

不同的Embeding模型以及不同的RAG優(yōu)化方法適用于不同的文本內容,我們需要根據實際情況進行選擇和優(yōu)化。例如,對于一些具有特定術語或者專業(yè)知識的文本,我們可能需要使用特定的嵌入模型以更好地理解和表示這些特定的詞匯。同樣,對于不同的搜索需求,我們可能需要調整我們的RAG策略,例如調整關鍵詞和語義的權重,或者調整我們的向量庫的大小和復雜性。這需要我們在實踐中不斷嘗試和優(yōu)化,以找到最適合我們需求的模型和策略。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容