发布于2024-11-26 阅读(0)
扫一扫,手机访问
本文旨在介绍一种提高RAG(Retrieval-Augmented Generation)模型检索效果的高阶技巧——窗口上下文检索。我们将在以下几个方面展开:首先,回顾基础RAG的检索流程和问题;其次,解释窗口上下文检索的原理和实现方法;最后,通过一个实例来展示其效果。通过窗口上下文检索,我们可以进一步提升RAG模型的检索能力,从而更好地满足实际应用的需求。
RAG(Retrieval-Augmented Generation)是一种结合了检索和生成的AI应用方案,用于解决问题回答的需求。它的设计思路是将问题与外部知识库中的文档进行匹配,以提高回答的质量和多样性。通过利用外部知识库(如维基百科),RAG可以生成与给定问题相关且丰富的回答。 然而,RAG也存在一些问题。首先,由于知识库的更新速度不一致,RAG的回答可能不会反映最新的信息。其次,对于复杂或模糊的问题,RAG的回答可能会存在不准确或片面的情况。此外,由于知识库的限制,RAG可能无法回答涉及特定领域或专业知识的问题。 为了解决这些问题,可以采取一些措施。首先,定期更新知识库的内容,以确保回答的准确性和时效性。其次,引入更多的数据源,包括领域专家的意见和经验,以增加回答的多样性和全面性。另外,改进RAG的生成模型,提高其理解和推理能力,
图片RAG的检索流程可以分为以下几个步骤:
load:加载文档,将各种格式的文件加载后转化为文档,例如将pdf加载为文本数据,或者将表格转换为多个键值对。split:将文档拆分为适合向量存储的较小单元,以便于与向量存储,以及检索时的文档匹配,例如将“我是kxc。我喜欢唱跳,rap,和篮球。”拆分为“我是kxc。”和“我喜欢唱跳,rap,和篮。”两个数据分块(一般称之为chunk)。embedding:将文档用向量表示,例如使用BERT或TF-IDF等模型进行向量化。store: 将向量化后的数据分块,存入向量数据库。retrive:根据问题和文档的向量,计算它们之间的相似度,然后根据相似度的高低,选择最相关的文档作为检索结果,例如使用余弦相似度或点积等度量进行排序。query:将检索到的文档作为生成模型的输入,根据问题生成回答,例如使用GPT-3或T5等模型进行生成。基础RAG存在的问题图片基础RAG的检索流程虽然简单,但是也存在一些问题,主要是在split和retrive两个步骤中。这些问题会影响RAG的检索效果,从而导致生成的回答不准确或不完整。
当我们将拆分的块过大时,会导致在检索时同一块中包含了大量与问题不相关的内容,从而影响了检索的准确性。以维基百科中的一篇文章作为例子,这个文档可能涵盖了多个主题和细节,与问题的相关性较低。如果我们将这个文档作为检索结果,生成模型可能会提取出一些与问题无关或错误的信息,从而影响了回答的质量。因此,在拆分时要控制块的大小,以提高检索的准确性和回答的质量。
split拆分的块较小可以提高检索的匹配度,但在最后的query环节,由于缺少上下文信息的支撑,会导致回答不准确。举个例子,如果将一篇维基百科文章拆分成多个句子,每个句子可能只包含少量信息,与问题相关性较高。如果将这些句子作为检索结果,生成模型可能会提取出一些有用信息,但也可能忽略了重要的上下文信息,影响了回答的完整性。
解决方案-窗口上下文检索图片的一般方案是,在拆分(split)时,尽量将文本切分成最小的语义单元。在检索(retrieve)时,不直接使用匹配到的文档(doc),而是通过匹配到的文档扩展其上下文内容,并将其整合后传递给LLM模型使用。这样做既可以提高检索的准确性,又能保留上下文的完整性,从而提高生成的质量和多样性。
具体来说,这种方案的实现步骤如下:
在split拆分时,将文本切分为最小的语义单元,例如句子或段落,并给每个单元分配一个唯一的编号,作为其在文本中的位置信息。在retrive检索时,根据问题和文档的向量,计算它们之间的相似度,然后选择最相关的文档作为检索结果,同时记录下它们的编号。在query生成时,根据检索结果的编号,从文本中获取它们的上下文信息,例如前后若干个单元,然后将它们拼接成一个完整的文档,作为生成模型的输入,根据问题生成回答。窗口上下文检索实践上下文检索实现思路我们从最终要实现的目标着手,也就是在retrive时要能通过匹配到doc拓展出与这个doc内容相关的上下文。要想实现这个目标,我们就必须建立每个doc与其上下文的关联关系。这个关系的建立其实十分简单,只需要按顺序给拆分出来的每个doc进行编号,在检索时通过当前文档的编号就能匹配到相关上下文doc的编号,进而获取上下文的内容。
基于chroma向量库的代码实践想要实践以上思路,需要在split环节基于文档顺序,将文档编码写入元数据。在检索时,则通过元数据中的顺序分块编码来查找上下文。具体的代码如下:
1.split时对分块编码并写入元数据import bs4,uuidfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_community.document_loaders import WebBaseLoaderfrom langchain_community.vectorstores import Chromafrom langchain_openai importOpenAIEmbeddings
Load, chunk and index the contents of the blog.
loader = WebBaseLoader(web_paths=("https://lilianweng.github.io/posts/2023-06-23-agent/",),bs_kwargs=dict(parse_only=bs4.SoupStrainer(class_=("post-content", "post-title", "post-header"))),)doc = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)docs = text_splitter.split_documents(doc)
这里给每个docs片段的metadata里注入file_id
file_id = uuid.uuid4().hexchunk_id_counter = 0for doc in docs:doc.metadata["file_id"] = file_iddoc.metadata["chunk_id"] = f'{file_id}_{chunk_id_counter}'# 添加chunk_id到metadatachunk_id_counter += 1for key,value in doc.metadata.items():if not isinstance(value, (str, int, float, bool)):doc.metadata[key] = str(value)
vectorstore = Chroma.from_documents(documents=splits, embedding=OpenAIEmbeddings())
2.retrive时通过元数据中的顺序分块编码来查找上下文def expand_doc(group):new_cands = []group.sort(key=lambda x: int(x.metadata['chunk_id'].split('_')[-1]))id_set = set()file_id = group[0].metadata['file_id']
group_scores_map = {}# 先找出该文件所有需要搜索的chunk_idcand_chunks = []for cand_doc in group:current_chunk_id = int(cand_doc.metadata['chunk_id'].split('_')[-1])group_scores_map[current_chunk_id] = cand_doc.metadata['score']for i in range(current_chunk_id - 200, current_chunk_id + 200):need_search_id = file_id + '_' + str(i)if need_search_id not in cand_chunks:cand_chunks.append(need_search_id)where = {"chunk_id": {"$in": cand_chunks}}ids,group_relative_chunks = get(where)group_chunk_map = {int(item.metadata['chunk_id'].split('_')[-1]): item.page_content for item in group_relative_chunks}group_file_chunk_num = list(group_chunk_map.keys())for cand_doc in group:current_chunk_id = int(cand_doc.metadata['chunk_id'].split('_')[-1])doc = copy.deepcopy(cand_doc)id_set.add(current_chunk_id)docs_len = len(doc.page_content)for k in range(1, 200):break_flag = Falsefor expand_index in [current_chunk_id + k, current_chunk_id - k]:if expand_index in group_file_chunk_num:merge_content = group_chunk_map[expand_index]if docs_len + len(merge_content) > CHUNK_SIZE:break_flag = Truebreakelse:docs_len += len(merge_content)id_set.add(expand_index)if break_flag:breakid_list = sorted(list(id_set))id_lists = seperate_list(id_list)for id_seq in id_lists:for id in id_seq:if id == id_seq[0]:doc = Document(page_content=group_chunk_map[id],metadata={"score": 0, "file_id": file_id})else:doc.page_content += " " + group_chunk_map[id]doc_score = min([group_scores_map[id] for id in id_seq if id in group_scores_map])doc.metadata["score"] = doc_scorenew_cands.append(doc)return new_cands
总结在本文中,我们介绍了提高RAG模型检索效果的高阶技巧-窗口上下文检索。我们首先回顾了基础RAG的检索流程和存在的问题,然后介绍了窗口上下文检索的原理和实现方法,最后通过一个实例展示了其效果。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店