构建一个检索增强生成 (Retrieval-Augmented Generation, RAG) 应用的概念验证过程相对简单,但要将其推广到生产环境中则会面临多方面的挑战。这主要是因为 RAG 系统涉及多个不同的组件,每个组件都需要精心设计和优化,以确保整体性能达到令人满意的水平。在这一过程中,外部非结构化数据的清洗和处理、文本分块、Query 的预处理、是不是每次 Query 都要进行检索、上下文信息的检索和排序能力、如何评估检索生成质量、知识缓存等环节都会影响系统的性能。
『RAG 高效应用指南』系列将就如何提高 RAG 系统性能进行深入探讨,提供一系列具体的方法和建议。同时读者也需要记住,提高 RAG 系统性能是一个持续的过程,需要不断地评估、优化和迭代。
RAG 简介
2020 年,Meta AI 研究人员提出了检索增强生成(RAG)的方法,用于提高 LLM 在特定任务上的性能。LLM 擅长语言理解、推理和生成等任务,但也存在一些问题:
-
• 信息滞后:LLM 的知识是静态的,来源于当时训练时的数据,也就是 LLM 无法直接提供最新的信息。
-
• 模型幻觉:实践表明,当前的生成式 AI 技术存在一定的幻觉,而在一些常见的业务应用中,我们是希望保证事实性的。
-
• 私有数据匮乏:LLM 的训练数据主要来源于互联网公开的数据,而垂类领域、企业内部等有很多专属知识,这部分是 LLM 无法直接提供的。
RAG 通过将检索到的相关信息提供给 LLM,让 LLM 进行参考生成,可以较好地缓解上述问题。因此,合理使用 RAG 可以拓展 LLM 的知识边界,使其不仅能够访问专属知识库,还能动态地引入最新的数据,从而在生成响应时提供更准确、更新的信息。
如图所示,是一个 RAG 应用的基本架构,可以分为离线和在线两部分。
-
• 离线:对知识库文档进行解析、拆分、索引构建和入库。这部分可以说是 dirty work,因为需要对多种不同格式的文档数据进行大量的清洗和处理等;但同时又是非常重要的工作,因为 「Garbage in, Garbage out」,数据质量是影响 RAG 最终效果的重要因素。
-
• 在线:当用户输入问题之后,我们会对 query 进行分析,如关键词提取、意图识别等,然后再根据路由条件进行知识库的多种召回检索或者联网搜索等,接着进行重排以提取最重要最相关的上下文信息,然后将用户问题和上下文信息一起提交给 LLM,让 LLM 根据背景知识提供可靠的回答。另外,为了进一步提高系统的性能,我们也会引入后置处理的环节,如风控检测、结果缓存和 RAG 相关指标的监控上报等。
本系列将根据这幅架构图,对其中的重要环节进行深入探讨,提供一系列具有可操作性的方法和建议,从而提高 RAG 系统的整体性能。
文档智能解析
解析文档内容是 RAG 系统最重要的前置工作之一。很多时候,企业内部数据以各种各样的文件格式存在,如 PDF、Word 文档、PPT 和 Excel 表格等。如何从大量非结构化数据中提取出内容,就需要文档智能解析技术了。
文档智能解析是指利用机器学习算法,对文档内容进行自动识别、理解和处理的过程。它不仅包括文本内容的识别,还涉及到图像、图表和表格等非文本元素的解析。
一般而言,对于不同类型的文件,有不同的解析方法,如 HTML/XML 解析、PDF 解析等。这里我们以图片形式的文档(如对纸质文档进行了扫描、拍照等)为例进行说明。
首先,我们需要对文档进行版面分析(Layout Analysis,也称布局分析),用于识别和理解文档中的视觉和结构布局。这里会使用到区域检测和区域分类等技术。区域检测用于识别文档中的不同区域,如文本块、图像、表格和图表等。而区域分类则将检测到的区域进一步分类,如文本可以进一步被细分为标题、副标题、正文文本等;图像可能被分类为图片、图表或公式等;表格需要识别为包含数据的结构化形式。
然后,对这些区域分别进行识别。比如,对于表格区域,它们会被输入表格识别(TSR,Table Structure Recognition)模块进行结构化识别,包含解析表格的行和列,识别单元格边界,提取结构化数据。对于文本区域,使用 OCR 引擎将图像中的文字转为机器可读的字符。
随着大语言模型(LLM)和多模态技术的发展,文档理解领域逐渐出现了端到端的多模态模型,它们将文档内容和文档图像进行联合学习,这样一来,模型可以学习到不同文档模板类型的局部不变性信息,当模型需要迁移到另一种模板类型时,只需要人工标注少量的样本就可以对模型进行调优。
比如,微软的 LayoutLM 系列模型将视觉特征、文本和布局信息进行了联合预训练,在多种文档理解任务上取得了显著提升;OpenAI 的 GPT-4V 能够分析用户输入的图像,并为有关图像的问题提供文本回应,它结合了自然语言处理和视觉理解;微软的 Table Transformer 可以从非结构化文档中提取表格;基于 Transformer 的 Donut 模型无需 OCR 就可以进行文档理解;旷世科技近期发布的 OneChart 模型可以对图表(如折线图、柱状图和饼图等)信息进行结构化提取。
长期来看,大模型和文档理解进行结合应该是一个趋势。但就目前而言,多模态大模型与传统的 SOTA 方案相比,还不具备很好的竞争力,尤其是在处理细粒度文本的场景下。
在这里,笔者推荐几个用于文档解析的开源项目,如果读者有更好的方案推荐,也欢迎留言。
- RAGFlow
RAGFlow是一款基于深度文档理解构建的开源 RAG 引擎。RAGFlow 的最大特色,就是多样化的文档智能处理,它没有采用现成的 RAG 中间件,而是完全重新研发了一套智能文档理解系统,确保数据 Garbage In Garbage Out 变为 Quality In Quality Out,并以此为依托构建 RAG 任务编排体系。对于用户上传的文档,它会自动识别文档的布局,包括标题、段落、换行等,还包含图片和表格等。RAGFlow 的 DeepDoc 模块提供了对多种不同格式文档的深度解析。
- Unstructured
Unstructured是一个灵活的Python 库,专门用于处理非结构化数据,它可以处理各种文档格式,包括 PDF、CSV 和 PPT 等。该库被多个项目用于非结构化数据的提取,如网易有道的QAnything、Dify 等。
- PaddleOCR
PaddleOCR是由百度推出的 OCR 开源项目,旨在提供全面且高效的文字识别和信息提取功能。PaddleOCR 提供了版面分析、表格识别和文字识别等多种功能。PaddleOCR的应用场景广泛,包括金融、教育、法律等多个行业,其高效的处理速度和准确率使其成为业界领先的 OCR 解决方案之一。
文档智能解析技术的应用非常广泛,可用于法律文档的信息提取、财务报表的数据整理、医疗记录的分析等多个领域,它是搭建企业 RAG 系统不可或缺的部分。
文本分块
文本分块(text chunking), 或称为文本分割(text splitting),是指将长文本分解为较小的文本块,这些块被嵌入、索引、存储,然后用于后续的检索。通过将大型文档分解成易于管理的部分(如章节、段落,甚至是句子),文本分块可以提高搜索准确性和模型性能。
-
• 提高搜索准确性:较小的文本块允许基于关键词匹配和语义相似性进行更精确的检索。
-
• 提升模型性能:LLM 在处理过长的文本时可能会遇到性能瓶颈。通过将文本分割成较小的片段,可以使模型更有效地处理和理解每一部分,同时也有助于模型根据查询返回更准确的信息。
因此,文本分块是很重要的一个环节,在 RAG 的众多环节中,它也许是我们容易做到高质量的一个环节。
下面我们来看看有哪些分块的策略。
按大小分块
按大小分块是指将文本按固定字符数或单词数进行分割,这是最直接、最经济的分块方法,但也存在明显的问题,也就是语义不连贯。按大小分块通常不考虑文本的语义内容,因此有可能将相关联的信息切割开,导致分出的文本块在内容上缺乏连贯性和完整性。例如,一个完整的句子或一段函数代码可能会被截断在两个不同的块中,使得单独的块难以理解。
下面是一个使用 Langchain 的 CharacterTextSplitter 按大小分块的示例:
from langchain_text_splitters.character import CharacterTextSplitter
def test_character_text_splitter() -> None:
"""Test splitting by character count."""
text = "foo bar baz 123"
splitter = CharacterTextSplitter(separator=" ", chunk_size=7, chunk_overlap=3)
output = splitter.split_text(text)
expected_output = ["foo bar", "bar baz", "baz 123"]
assert output == expected_output
可以看到,CharacterTextSplitter 设置了 3 个参数:
-
• 分割符(separator):空格
-
• 文本块的最大长度(chunk_size):7
-
• 文本块之间的最大重叠长度(chunk_overlap):3,chunk_overlap 这个参数很重要,表示两个切分文本之间的重合度,设置重叠大小可以保持文本块之间的连续性。
特定格式分块
特定格式分块是针对具有特定结构或语法特征的文本文件进行分块的一种方法,如 Markdown、LaTeX、Python 代码等。这种分块方式依据各自格式的特定字符或结构标记来实现,以保证分块后的内容在结构上的完整性和逻辑上的连贯性。比如 Markdown 文本可以使用标题(#
)、列表(-
)、引用(>
)等来进行分块。
针对特定格式的分块,langchain 提供了相应的方法,如:
-
• MarkdownTextSplitter:根据 markdown 的标题、列表或引用等规则来分割文本
-
• LatexTextSplitter:根据 latex 的 chapter、section 或 subsection 等规则来分割文本
-
• HTMLHeaderTextSplitter:根据 html 特定字符串分割文本,如 h1、h2、h3 等
-
• PythonCodeTextSplitter:根据 python 特定的字符串分割文本,如 class、def 等,总共有 15 种不同的语言可供选择
递归分块
递归分块以一组分隔符为参数,以递归的方式将文本分成更小的块。如果在第一次分割时无法得到所需长度的块,它将递归地继续尝试。每次递归都会尝试更细粒度的分割符号,直到块的长度满足要求。这样可以确保即使初始块很大,最终也能得到较为合适的小块。例如,可以先尝试按照句子结束符来分割(如句号或问号),如果这样分割出的文本块太长,就会依次尝试其他的标记,例如逗号或者空格。通过这种方式,我们可以找到比较合适的分割点,同时尽量避免破坏文本的语义结构。
下面是一个使用 Langchain 的 RecursiveCharacterTextSplitter 进行递归分块的示例:
from langchain_text_splitters import RecursiveCharacterTextSplitter
def test_iterative_text_splitter() -> None:
"""Test iterative text splitter."""
text = """Hi.\n\nI'm Harrison.\n\nHow? Are? You?\nOkay then f f f f.
This is a weird text to write, but gotta test the splittingggg some how.
Bye!\n\n-H."""
# 分隔符列表是["\n\n", "\n", " ", ""]
splitter = RecursiveCharacterTextSplitter(
separators=["\n\n", "\n", " ", ""],
chunk_size=10,
chunk_overlap=1)
output = splitter.split_text(text)
expected_output = [
"Hi.",
"I'm",
"Harrison.",
"How? Are?",
"You?",
"Okay then",
"f f f f.",
"This is a",
"weird",
"text to",
"write,",
"but gotta",
"test the",
"splitting",
"gggg",
"some how.",
"Bye!",
"-H.",
]
assert output == expected_output
语义分块
语义分块(semantic chunking)首先在句子之间进行分割,句子通常是一个语义单位,它包含关于一个主题的单一想法;然后使用 Embedding 表征句子;最后将相似的句子组合在一起形成块,同时保持句子的顺序。
命题分块
命题分块(propositional chunking)也是一种语义分块,它的原理是基于 LLM,逐步构建块。
-
• 首先从基于段落的句法分块迭代开始。
-
• 对于每个段落,使用 LLM 生成独立的陈述(或者命题),比如我们可以使用简单的提示「这段文字讨论了哪些主题」。
-
• 移除冗余命题。
-
• 索引并存储生成的命题。
-
• 在查询时,从命题语料库中检索,而不是原始文档语料库。
除了上面所说的分块策略,也还有很多其他的分块策略,比如 langchain 提供了根据 OpenAI 的 token 数进行分割的 TokenTextSplitter,还有使用 NLTK 分割器的 NLTKTextSplitter 等。
文本分块并没有固定的最佳策略。选择哪种方式取决于具体的需求和场景,需要根据业务情况进行调整和优化。关键是找到适合当前应用的分块策略,而不是追求单一的完美方案。有时候,为了获得更准确的查询结果,我们甚至需要灵活地使用多种策略相结合。
另外,为了直观分析文本分割器是如何工作的,我们可以使用 ChunkViz 工具进行可视化,它会展示文本是如何被分割的,可以帮助我们调整分割参数。
拓展阅读
• https://python.langchain.com/docs/modules/data_connection/document_transformers/
• https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb
• https://arxiv.org/pdf/2312.06648
• https://chunkviz.up.railway.app/
总结
RAG 是扩展 LLM 知识边界的利器,本文对 RAG 系统前置的文档解析和文本分块两个环节进行深入了探讨。
文档智能理解从各种各样非结构化的文档中提取内容,是构建高质量 RAG 系统的基础。数据质量决定成效。
文本分块将长文本分解为较小的文本块,这些块被嵌入、索引、存储,然后用于后续的检索。文本分块并没有固定的最佳策略,每种策略各有优缺点,关键在于根据具体的需求和场景,灵活运用不同策略,提高搜索准确性与模型性能。
读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用
对于0基础小白入门:
如果你是零基础小白,想快速入门大模型是可以考虑的。
一方面是学习时间相对较短,学习内容更全面更集中。
二方面是可以根据这些资料规划好学习计划和方向。
包括:大模型学习线路汇总、学习阶段,大模型实战案例,大模型学习视频,人工智能、机器学习、大模型书籍PDF。带你从零基础系统性的学好大模型!
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费
】🆓
👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费
】🆓