背景
一些纯语言模型,比如GPT-4,虽然也很强大,但它们的知识都是在训练时就固定下来的,无法实时更新。这就意味着,它们无法获取到训练之后出现的新信息。所以,对于一些需要最新信息,或者是一些特定场景的问题,纯语言模型可能无法给出满意的答案。
相反,检索增强生成(RAG)就像是一个高级版的“百度”或“谷歌”。它不仅可以理解你的问题,还会去网上、书本或数据库里找答案。然后,它会把这些信息结合起来给AI大模型思考,给你一个既准确又符合你问题的答案。这对于需要最新信息的问题非常有帮助。
痛点
RAG 的主流实现方法是利用向量数据库进行文本的向量化。首先,它会将文本数据转化为向量形式并存储在向量数据库中。然后,当有问题输入时,它会将问题也转化为向量,然后在向量数据库中寻找与之最匹配的文本向量。匹配成功后,就可以返回相应的文本内容作为答案。这种方法在处理大规模的信息检索任务时具有很高的效率和准确性。
对于不同类型和语言的文件,我们需要选择适当的RAG技术。例如,对于PPT和PDF这些主要包含文本和图像的文件,我们可能需要使用能够理解和生成图像描述的模型。对于CSV和Excel这些主要包含结构化数据的文件,我们可能需要使用能够理解和生成数据摘要的模型。此外,不同语言的文件可能需要不同的嵌入模型以适应不同语言的语义。
因此,当我们描述问题时,有时并不清楚我们的提问是否与提供的文本表达存在歧义,或者当我们只有模糊的记忆,只知道一两个词时,向量库的语义搜索往往无法满足我们的需求。
解决方案
因此,如果我们能够结合关键词和语义进行同时检索,就能够进一步提高我们的RAG的准确度。这种混合检索方法可以在保证搜索效率的同时,提高搜索的准确性和全面性。具体来说,当我们的问题描述不清楚,或者记忆模糊只知道一两个关键词时,混合检索方法可以通过关键词匹配找到可能的答案,然后结合语义匹配来进一步提高答案的准确性。
主流的RAG框架,如Langchain和Llamaindex,都提供了这种混合检索的方法供我们使用。然而,它们之间存在着细微的区别。
Langchain
Langchain支持使用BM25模型,以及其他嵌入模型和向量库(支持替换成其他的embeding模型,例如BGE,M3E,GTE,在一些中文语料的查询上效果更好,需要根据实际情况进行配置),结合构成一个EnsembleRetriever来检索信息。具体来说,它可以将不同的检索模型集成在一起,通过对各个模型的查询结果进行加权求和,来提高检索的准确性和全面性。
BM25 是一种基于概率的排名函数,用于信息检索系统。BM25原理是根据查询词在文档中的出现频率以及查询词在整个文本集中的出现频率,来计算查询词和文档的相似度。BM25模型的主要思想是:如果一个词在一份文档中出现的频率高,且在其他文档中出现的频率低,那么这个词对于这份文档的重要性就越高,相似度就越高。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模型,并把向量数据库换成Chroma(只是做案例,有想要看区别的可以自己学习)
# 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 的主要区别在于,它在进行混合检索之前,还会根据我们搜索的内容用大模型生成几条类似的提问,以提高我们提问的泛化能力。这种方法可以更有效地处理我们的模糊记忆和不确定的问题描述,使我们的搜索更加精确和全面。
其他的模型也是利用了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模型主要依赖于词频来判断文档和查询词的相似度,因此当我们输入的词被错误地分词,或者当我们搜索名字或者号码这类内容时,模型的效果可能会大打折扣。这是因为在这些情况下,词频可能并不能准确地反映出查询词和文档的相关性。例如,一个人的名字在一篇文档中可能只出现一次,但这并不意味着这篇文档与查询词不相关。同样,如果我们在搜索电话号码时,由于电话号码通常是一长串数字,因此它的词频很可能非常低,但这并不影响其对于查询结果的重要性。因此,尽管BM25模型在许多情况下都能提供满意的检索效果,但在处理此类问题时,它的性能可能会大大降低。
优化模型
因此,为了解决这个问题,我们可以考虑引入ElasticSearch。ElasticSearch是一个开源的、分布式的、RESTful搜索引擎,它的设计使得它非常适合执行诸如全文搜索、结构化搜索、实时聚合等复杂的搜索、分析任务。其内部使用Lucene作为索引和搜索的引擎,并做了很多优化和改进,使得ElasticSearch在处理复杂搜索时性能更高,使用更方便。
ElasticSearch的特点包括分布式搜索、支持多租户、全文搜索、异步写操作等。这些特性使得ElasticSearch非常适合用于处理大数据量的搜索和分析任务。当我们需要在大量数据中进行精准的关键词搜索时,ElasticSearch就成为了我们的得力助手。
ElasticSearch的使用也非常灵活,它提供了多种查询方式,包括全文查询、结构化查询、组合查询等,可以满足我们的各种查询需求。尤其是在处理我们的模糊记忆和不确定的问题描述时,ElasticSearch的这些功能将大大提高我们的查询精度和全面性。因此,ElasticSearch将是我们混合检索策略的重要组成部分。
首先,我们可以尝试使用ElasticSearch进行纯关键词搜索。纯关键词搜索是最简单的一种搜索方式,不需要进行任何预处理或向量化,只需要对输入的关键词进行精确匹配。这种搜索方式的优点是简单、快速,但缺点是可能会漏掉一些含义相关但没有使用相同关键词的文档。但是,对于一些简单的、明确的搜索需求,纯关键词搜索是一个非常有效的方法。
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安装并运行,我们就可以创建一个索引,并将我们的数据添加到这个索引中。一旦数据被索引,我们就可以使用Elasticsearch的搜索API来查询数据。
下面是一个使用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()
# 准备数据以便导入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优化方法适用于不同的文本内容,我们需要根据实际情况进行选择和优化。例如,对于一些具有特定术语或者专业知识的文本,我们可能需要使用特定的嵌入模型以更好地理解和表示这些特定的词汇。同样,对于不同的搜索需求,我们可能需要调整我们的RAG策略,例如调整关键词和语义的权重,或者调整我们的向量库的大小和复杂性。这需要我们在实践中不断尝试和优化,以找到最适合我们需求的模型和策略。