写点什么

大模型 RAG:文档分块方案与 RAG 全流程

  • 2024-10-07
    吉林
  • 本文字数:3099 字

    阅读完需:约 10 分钟

大模型RAG:文档分块方案与RAG全流程

一 RAG 与文本分块

1.1 为什么要文档分块

在上一篇文章《大模型 RAG:基于 PgSql 的向量检索》中,简单介绍了 RAG 概念和简要实现。在实际的应用中,技术方案远不会这样简单。

我们知道,大模型在预训练阶段获取的知识是有限的,一般需要数据增强模块引入外部知识库,通过知识检索的方式搜索于用户提问相关的知识,这也是 RAG 相关应用架构出现的原因。但这又引申出另一个问题,外部知识文档往往比较长,可能是包含几十页甚至数百页的内容,如果直接使用会存在以下问题

  • 1.大模型处理的上下文长度有限:大模型在预训练过程都有上下文长度限制,如果超过长度限制大模型会将超出部分丢弃,从而影响回答的性能表现。(注:目前很多大模型已经支持 192K 甚至更大的超长上下文窗口+搜索增强知识库,但基于成本和性能考虑,大文档分 chunk 依然是 RAG 方案必须包含的环节)。

  • 2.语义杂揉不利于任务检索:长文档中各个片段的语义之前可能存在较大的差异,如果当成一个整体来做知识检索会存在语义的杂揉,应当将长文档切分成更多的小块,促使每个小块内部表意一致,块之间表意存在多样性,从而更充分的发挥知识检索的作用

所以我们需要根据一定策略将文本切分为小块,以便适应大模型的上下文窗口,同时提高知识检索的精度

1.2 分块的目标

文本分块不是盲目的切分,而是必须在不影响或尽量降低对整体效果影响的前提下进行。所以文本分块(chunk最核心的目的就是把相同语义的 token 聚集在一起,不同语义的 token 互相分开,利于后续的 retrieve 和 rerank。举个例子:我们有一个 word 文档,分为多个段落,每个段落都是一个问题的问答对。那么显然把一个问答对作为一个 chunk 划分是最理想的结果。

二 常用的文本分块技术方案

常见 chunk 方案有按照字符 chunk、按 token、按段落、递归划分、语义划分、代理划分等。Langchain 作为一个 LLM 协调框架,内置了一些用于分块以及加载文档的工具,提供了很多可以开箱即用的 chunk 方法:

CharacterTextSplitter

RecursiveCharacterTextSplitter

Split by tokens

Semantic Chunking

HTMLHeaderTextSplitter

MarkdownHeaderTextSplitter

RecursiveJsonSplitter

Split Cod

由于相关的代码解说文章已经很多,这里不再赘述,只以 RecursiveCharacterTextSplitter、SentenceSplitter 和 HTMLHeaderTextSplitter 为例:

2.1 RecursiveCharacterTextSplitter

基于字符数来递归地分割文本。每个块都保持在指定的长度以下,这对于具有自然段落或句子间断的文档特别有用,确保了块的可管理性和易于处理性,而不会丢失文档的固有结构。

Langchain 中的递归字符文本分割器方法根据字符数将文本分割成块,以确保每个块低于指定的长度。这种方法有助于保持文档中段落或句子的自然断开。

from langchain_text_splitters import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter( # 这里做初始化,块大小1k以及50个跨文本字符 separators=["\n\n", "\n", " ", ""], chunk_size=1000, chunk_overlap=50, length_function=len, is_separator_regex=False,)chunks = text_splitter.split_text(pages[0].page_content)print(len(chunks))
# 打印输出for chunk in chunks: print(chunk)
复制代码

2.2 SentenceSplitter

SentenceSplitter 是在句子边界上分割文本,这种方法能够保持文本的上下文完整性。句子通常代表完整的思想,这使得这种方法非常适合那些对内容有连贯理解的场景。

from langchain.text_splitter import SentenceSplitter text = "long document text ..." # 初始化SentenceSplitter ,每个块最多5个句子splitter = SentenceSplitter(max_length=5) chunks = splitter.split_text(text) for chunk in chunks:    print(chunk)
复制代码

2.3 HTMLHeaderTextSplitter

HTMLHeaderTextSplitter 是一个网页代码分块器,它根据 HTML 元素拆分文本,并将相关元数据分配给分块内的每个标头。它可以返回单个分块或将具有相同元数据的元素组合在一起,以保持语义分组并保留文档的结构上下文。此拆分器可与分块管道中的其他文本拆分器结合使用。

from langchain_text_splitters import HTMLHeaderTextSplitter# 这里定义一段HTML网页内容代码html_string = """<!DOCTYPE html><html><body>    <div>        <h1>Foo</h1>        <p>Some intro text about Foo.</p>        <div>            <h2>Bar main section</h2>            <p>Some intro text about Bar.</p>            <h3>Bar subsection 1</h3>            <p>Some text about the first subtopic of Bar.</p>            <h3>Bar subsection 2</h3>            <p>Some text about the second subtopic of Bar.</p>        </div>        <div>            <h2>Baz</h2>            <p>Some text about Baz</p>        </div>        <br>        <p>Some concluding text about Foo</p>    </div></body></html>"""
headers_to_split_on = [ ("h1", "Header 1"), ("h2", "Header 2"), ("h3", "Header 3"),]
html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)html_header_splits = html_splitter.split_text(html_string)html_header_splits
"""[Document(page_content='Foo'), Document(page_content='Some intro text about Foo. \nBar main section Bar subsection 1 Bar subsection 2', metadata={'Header 1': 'Foo'}), Document(page_content='Some intro text about Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section'}), Document(page_content='Some text about the first subtopic of Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 1'}), Document(page_content='Some text about the second subtopic of Bar.', metadata={'Header 1': 'Foo', 'Header 2': 'Bar main section', 'Header 3': 'Bar subsection 2'}), Document(page_content='Baz', metadata={'Header 1': 'Foo'}), Document(page_content='Some text about Baz', metadata={'Header 1': 'Foo', 'Header 2': 'Baz'}), Document(page_content='Some concluding text about Foo', metadata={'Header 1': 'Foo'})]"""
复制代码

三 自实现的文本 chunk 方法

尽管 LangChain 和 llamaIndex 都已经提供了文本 Chunk 方法,但不幸的是这两者提取语义 embedding 用的都是 openAI 的接口,要收费不说,大陆地区还面临被封 API 的风险,更关键的是在很多企业内根本不允许使用。所以我们只能参考 LangChain 中各方法的实现原理,自行开发和调试文本 chunk 方法。

四 从文档角度出发的 RAG 技术方案

从文档角度出发,RAG 流程中的各个主要环节如下:


关键概念说明:

RAPTOR:RAPTOR 模型提出了一种创新的策略。它通过递归地进行文本片段的向量化、聚类和摘要生成,构建了一个树状索引结构。这种结构不仅捕捉了文档的高层次主题,还保留了低层次的细节,允许基于语义相似性而非仅仅是文本顺序对节点进行分组。这样的树状结构使得 RAPTOR 能够在不同的抽象层次上加载文档的上下文片段,从而有效地回答不同层次的问题。

GraphRAG:2024 年 4 月微软推出 GraphRAG,并于 7 月 2 日开源。GraphRAG 仍然沿袭了 RAG 的思路,即通过检索来增强模型的准确性。不过,与 RAG 不同的是,GraphRAG 还引入了“知识图谱”(Knowledge Graph)技术,以增强模型的“检索”能力,以实现对复杂信息的高效和可靠检索,从而提高 LLM 问答系统对于复杂信息的答案生成的准确性。GraphRAG 示意图如下:

发布于: 刚刚阅读数: 7
用户头像

磨炼中成长,痛苦中前行 2017-10-22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
大模型RAG:文档分块方案与RAG全流程_大模型_程序员架构进阶_InfoQ写作社区