























一、概述
构建一个RAG 应用的概念验证过程相对简单,但要将其推广到生产环境中则会面临多方面的挑战。这主要是因为 RAG 系统涉及多个不同的组件,每个组件都需要精心设计和优化,以确保整体性能达到令人满意的水平。
在这一过程中,外部非结构化数据的清洗和处理、文本分块、Query 的预处理、是不是每次 Query 都要进行检索、上下文信息的检索和排序能力、如何评估检索生成质量、知识缓存等环节都会影响系统的性能。
同时读者也需要记住,提高 RAG 系统性能是一个持续的过程,需要不断地评估、优化和迭代。
二、RAG 简介
2020 年,Meta AI 研究人员提出了RAG的方法,用于提高 LLM 在特定任务上的性能。LLM 擅长语言理解、推理和生成等任务,但也存在一些问题:
RAG 通过将检索到的相关信息提供给 LLM,让 LLM 进行参考生成,可以较好地缓解上述问题。因此,合理使用 RAG 可以拓展 LLM 的知识边界,使其不仅能够访问专属知识库,还能动态地引入最新的数据,从而在生成响应时提供更准确、更新的信息。
如图所示,是一个 RAG 应用的基本架构,可以分为离线和在线两部分。

其中:
本系列将根据这幅架构图,对其中的重要环节进行深入探讨,提供一系列具有可操作性的方法和建议,从而提高 RAG 系统的整体性能。
二、文档智能解析
解析文档内容是 RAG 系统最重要的前置工作之一。很多时候,企业内部数据以各种各样的文件格式存在,如 PDF、Word 文档、PPT 和 Excel 表格等。如何从大量非结构化数据中提取出内容,就需要文档智能解析技术了。
文档智能解析是指利用机器学习算法,对文档内容进行自动识别、理解和处理的过程。它不仅包括文本内容的识别,还涉及到图像、图表和表格等非文本元素的解析。
一般而言,对于不同类型的文件,有不同的解析方法,如 HTML/XML 解析、PDF 解析等。这里我们以图片形式的文档(如对纸质文档进行了扫描、拍照等)为例进行说明。
2.1 版面分析
首先,我们需要对文档进行版面分析(Layout Analysis,也称布局分析),用于识别和理解文档中的视觉和结构布局。这里会使用到区域检测和区域分类等技术。

2.2 区域识别
然后,对这些区域分别进行识别。比如:
随着大语言模型(LLM)和多模态技术的发展,文档理解领域逐渐出现了端到端的多模态模型,它们将文档内容和文档图像进行联合学习,这样一来,模型可以学习到不同文档模板类型的局部不变性信息,当模型需要迁移到另一种模板类型时,只需要人工标注少量的样本就可以对模型进行调优。比如:
长期来看,大模型和文档理解进行结合应该是一个趋势。但就目前而言,多模态大模型与传统的 SOTA 方案相比,还不具备很好的竞争力,尤其是在处理细粒度文本的场景下。
在这里,笔者推荐几个用于文档解析的开源项目
1)、RAGFlow:是一款基于深度文档理解构建的开源 RAG 引擎。
2)、Unstructured:是一个灵活的Python 库,专门用于处理非结构化数据
3)、PaddleOCR:是由百度推出的 OCR 开源项目,旨在提供全面且高效的文字识别和信息提取功能
三、文本分块
文本分块(text chunking),或称为文本分割(text splitting),是指将长文本分解为较小的文本块,这些块被嵌入、索引、存储,然后用于后续的检索。通过将大型文档分解成易于管理的部分(如章节、段落,甚至是句子),文本分块可以提高搜索准确性和模型性能。
因此,文本分块是很重要的一个环节,在 RAG 的众多环节中,它也许是我们容易做到高质量的一个环节。下面我们来看看有哪些分块的策略
3.1 按大小分块
指将文本按固定字符数或单词数进行分割,这是最直接、最经济的分块方法,但也存在明显的问题,也就是语义不连贯。按大小分块通常不考虑文本的语义内容,因此有可能将相关联的信息切割开,导致分出的文本块在内容上缺乏连贯性和完整性。
例如,一个完整的句子或一段函数代码可能会被截断在两个不同的块中,使得单独的块难以理解。
下面是一个使用 Langchain 的 CharacterTextSplitter 按大小分块的示例:

可以看到,CharacterTextSplitter 设置了 3 个参数:
3.2 特定格式分块
特定格式分块是针对具有特定结构或语法特征的文本文件进行分块的一种方法,
针对特定格式的分块,langchain 提供了相应的方法,如:
说明:
3.3 递归分块
递归分块以一组分隔符为参数,以递归的方式将文本分成更小的块。如果在第一次分割时无法得到所需长度的块,它将递归地继续尝试。每次递归都会尝试更细粒度的分割符号,直到块的长度满足要求。这样可以确保即使初始块很大,最终也能得到较为合适的小块。
例如,可以先尝试按照句子结束符来分割(如句号或问号),如果这样分割出的文本块太长,就会依次尝试其他的标记,例如逗号或者空格。通过这种方式,我们可以找到比较合适的分割点,同时尽量避免破坏文本的语义结构。
下面是一个使用 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
View Code
3.4 语义分块
语义分块(semantic chunking)首先在句子之间进行分割,句子通常是一个语义单位,它包含关于一个主题的单一想法;然后使用 Embedding 表征句子;最后将相似的句子组合在一起形成块,同时保持句子的顺序。

3.4 命题分块
命题分块(propositional chunking)也是一种语义分块,它的原理是基于 LLM,逐步构建块。参考:https://arxiv.org/pdf/2312.06648
说明:
试一下:
除了上面所说的分块策略,也还有很多其他的分块策略,比如 langchain 提供了根据 OpenAI 的 token 数进行分割的 TokenTextSplitter,还有使用 NLTK 分割器的 NLTKTextSplitter 等。
文本分块并没有固定的最佳策略。选择哪种方式取决于具体的需求和场景,需要根据业务情况进行调整和优化。关键是找到适合当前应用的分块策略,而不是追求单一的完美方案。有时候,为了获得更准确的查询结果,我们甚至需要灵活地使用多种策略相结合。
另外,为了直观分析文本分割器是如何工作的,我们可以使用 ChunkViz 工具进行可视化,它会展示文本是如何被分割的,可以帮助我们调整分割参数。ChunkViz访问信息如下:
github:https://github.com/gkamradt/ChunkViz
see demo:https://chunkviz.up.railway.app/
参考资料
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。