博客园  :: 首页  :: 联系 :: 管理

大模型_3.2 RAG 高效应用指南

Posted on 2024-05-19 11:25  天戈朱  阅读(939)  评论(0编辑  收藏  举报

一、概述


 构建一个RAG 应用的概念验证过程相对简单,但要将其推广到生产环境中则会面临多方面的挑战。这主要是因为 RAG 系统涉及多个不同的组件,每个组件都需要精心设计和优化,以确保整体性能达到令人满意的水平。

在这一过程中,外部非结构化数据的清洗和处理、文本分块、Query 的预处理、是不是每次 Query 都要进行检索、上下文信息的检索和排序能力、如何评估检索生成质量、知识缓存等环节都会影响系统的性能。

同时读者也需要记住,提高 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 解析等。这里我们以图片形式的文档(如对纸质文档进行了扫描、拍照等)为例进行说明。

2.1 版面分析

   首先,我们需要对文档进行版面分析Layout Analysis,也称布局分析),用于识别和理解文档中的视觉和结构布局。这里会使用到区域检测和区域分类等技术。

  • 区域检测:用于识别文档中的不同区域,如文本块、图像、表格和图表等。
  • 区域分类:则将检测到的区域进一步分类,如文本可以进一步被细分为标题、副标题、正文文本等;图像可能被分类为图片、图表或公式等;表格需要识别为包含数据的结构化形式。 

 2.2 区域识别

然后,对这些区域分别进行识别。比如:

  • 对于表格区域,它们会被输入表格识别(TSR,Table Structure Recognition)模块进行结构化识别,包含解析表格的行和列,识别单元格边界,提取结构化数据。
  • 对于文本区域,使用 OCR 引擎将图像中的文字转为机器可读的字符。

   随着大语言模型(LLM)和多模态技术的发展,文档理解领域逐渐出现了端到端的多模态模型,它们将文档内容和文档图像进行联合学习,这样一来,模型可以学习到不同文档模板类型的局部不变性信息,当模型需要迁移到另一种模板类型时,只需要人工标注少量的样本就可以对模型进行调优。比如:

  • 微软的 LayoutLM 系列模型将视觉特征、文本和布局信息进行了联合预训练,在多种文档理解任务上取得了显著提升;
  • OpenAI 的 GPT-4V 能够分析用户输入的图像,并为有关图像的问题提供文本回应,它结合了自然语言处理和视觉理解;
  • 微软的 Table Transformer 可以从非结构化文档中提取表格;
  • 基于 Transformer 的 Donut 模型无需 OCR 就可以进行文档理解;
  • 旷世科技近期发布的 OneChart 模型可以对图表(如折线图、柱状图和饼图等)信息进行结构化提取。 github地址:https://github.com/LingyvKong/OneChart

长期来看,大模型和文档理解进行结合应该是一个趋势。但就目前而言,多模态大模型与传统的 SOTA 方案相比,还不具备很好的竞争力,尤其是在处理细粒度文本的场景下。

  • SOTA:在业内普遍被视为 “State-of-the-Art” 的缩写,它代表了特定领域或任务中,当前的最新进展和最高水准,所以也常常被作为优势亮点,来彰显自身在技术水平、产品性能、应用实践等方面的领先地位。

在这里,笔者推荐几个用于文档解析的开源项目

 1)、RAGFlow:是一款基于深度文档理解构建的开源 RAG 引擎。

  • github:https://github.com/infiniflow/ragflow
  • RAGFlow 的最大特色,就是多样化的文档智能处理,它没有采用现成的 RAG 中间件,而是完全重新研发了一套智能文档理解系统,确保数据 Garbage In Garbage Out 变为 Quality In Quality Out,并以此为依托构建 RAG 任务编排体系。
  • 对于用户上传的文档,它会自动识别文档的布局,包括标题、段落、换行等,还包含图片表格等。
  • RAGFlow 的 DeepDoc 模块提供了对多种不同格式文档的深度解析。

 2)、Unstructured:是一个灵活的Python 库,专门用于处理非结构化数据

 3)、PaddleOCR:是由百度推出的 OCR 开源项目,旨在提供全面且高效的文字识别和信息提取功能

  • github:https://github.com/Unstructured-IO/unstructured
  • PaddleOCR 提供了版面分析、表格识别和文字识别等多种功能。PaddleOCR的应用场景广泛,包括金融、教育、法律等多个行业,其高效的处理速度和准确率使其成为业界领先的 OCR 解决方案之一

 

三、文本分块


文本分块(text chunking),或称为文本分割(text splitting),是指将长文本分解为较小的文本块,这些块被嵌入、索引、存储,然后用于后续的检索。通过将大型文档分解成易于管理的部分(如章节、段落,甚至是句子),文本分块可以提高搜索准确性和模型性能。

  • 提高搜索准确性:较小的文本块允许基于关键词匹配和语义相似性进行更精确的检索。
  • 提升模型性能:LLM 在处理过长的文本时可能会遇到性能瓶颈。通过将文本分割成较小的片段,可以使模型更有效地处理和理解每一部分,同时也有助于模型根据查询返回更准确的信息。

因此,文本分块是很重要的一个环节,在 RAG 的众多环节中,它也许是我们容易做到高质量的一个环节。下面我们来看看有哪些分块的策略

3.1 按大小分块

指将文本按固定字符数或单词数进行分割,这是最直接、最经济的分块方法,但也存在明显的问题,也就是语义不连贯。按大小分块通常不考虑文本的语义内容,因此有可能将相关联的信息切割开,导致分出的文本块在内容上缺乏连贯性和完整性。

例如,一个完整的句子或一段函数代码可能会被截断在两个不同的块中,使得单独的块难以理解。

下面是一个使用 Langchain 的 CharacterTextSplitter 按大小分块的示例:

可以看到,CharacterTextSplitter 设置了 3 个参数:

  • 分割符(separator):空格
  • 文本块的最大长度(chunk_size):7
  • 文本块之间的最大重叠长度(chunk_overlap):3,chunk_overlap 这个参数很重要,表示两个切分文本之间的重合度,设置重叠大小可以保持文本块之间的连续性。?不会重复被检索吗? 

3.2 特定格式分块

特定格式分块是针对具有特定结构或语法特征的文本文件进行分块的一种方法,

  • 如 Markdown、LaTeX、Python 代码等。这种分块方式依据各自格式的特定字符或结构标记来实现,以保证分块后的内容在结构上的完整性和逻辑上的连贯性。
  • 比如 Markdown 文本可以使用标题(#)、列表(-)、引用(>)等来进行分块。

针对特定格式的分块,langchain 提供了相应的方法,如:

  • MarkdownTextSplitter:根据 markdown 的标题、列表或引用等规则来分割文本
  • LatexTextSplitter:根据 latex 的 chapter、section 或 subsection 等规则来分割文本
  • HTMLHeaderTextSplitter:根据 html 特定字符串分割文本,如 h1、h2、h3 等
  • PythonCodeTextSplitter:根据 python 特定的字符串分割文本,如 class、def 等,总共有 15 种不同的语言可供选择

说明:

  • LaTeX(音译“拉泰赫”)是一种基于TeX的文字处理系统,用于排版和文档制作。由美国计算机学家莱斯利·兰伯特(Leslie Lamport)在20世纪80年代初期开发,它特别适合于处理包含复杂数学公式、参考文献、索引等元素的科技和学术文档。与传统的字处理软件(如Microsoft Word)不同,LaTeX采用的是“标记语言”的方式,这意味着用户通过特定的命令或“标记”来指定文档的结构和格式,而非直接在界面上调整字体大小、颜色等

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

  •  

说明:

  • 首先从基于段落的句法分块迭代开始。
  • 对于每个段落,使用 LLM 生成独立的陈述(或者命题),比如我们可以使用简单的提示「这段文字讨论了哪些主题」
  • 移除冗余命题
  • 索引并存储生成的命题
  • 在查询时,从命题语料库中检索,而不是原始文档语料库

试一下:

  •  

除了上面所说的分块策略,也还有很多其他的分块策略,比如 langchain 提供了根据 OpenAI 的 token 数进行分割的 TokenTextSplitter,还有使用 NLTK 分割器的 NLTKTextSplitter 等。 

 

文本分块并没有固定的最佳策略。选择哪种方式取决于具体的需求和场景,需要根据业务情况进行调整和优化。关键是找到适合当前应用的分块策略,而不是追求单一的完美方案。有时候,为了获得更准确的查询结果,我们甚至需要灵活地使用多种策略相结合。

另外,为了直观分析文本分割器是如何工作的,我们可以使用 ChunkViz 工具进行可视化,它会展示文本是如何被分割的,可以帮助我们调整分割参数。ChunkViz访问信息如下:

github:https://github.com/gkamradt/ChunkViz

see demo:https://chunkviz.up.railway.app/

  •  

 

参考资料