RAG 切块Chunk技术总结与自定义分块实现思路

news2025/1/18 8:48:04

TrustRAG项目地址🌟:https://github.com/gomate-community/TrustRAG

可配置的模块化RAG框架

切块简介

在RAG(Retrieval-Augmented Generation)任务中,Chunk切分是一个关键步骤,尤其是在处理结构复杂的PDF文档时。PDF文档可能包含图片、奇怪的排版等,增加了Chunk切分的难度。

Chunk粒度的影响

  • 句子/单词粒度:注重局部、关键信息的查询,但可能缺失上下文信息。
  • 长篇段落/文章粒度:Embedding结果反映整个文章的意思,但难以精准到个体单词。

不同场景的Chunk分块粒度

  • 微博:少字符,适合较小的Chunk粒度。
  • 知乎/小红书:中小量字符数,适合中等Chunk粒度。
  • 博客:超多字符,适合较大的Chunk粒度。
  • 专业性较强、专有名词较多的文章:需要较小的Chunk粒度以保留专业信息。
  • 综述类信息总结文章:适合较大的Chunk粒度以保留整体信息。

Chunk切分对信息的影响

  1. 上下文信息:例如,《统计学习的要素》这本书有18章,每章专注于一个主题,副标题和第二层副标题等。人们习惯于在语境中理解文章。
  2. 位置信息:文本的权重取决于它们在文档中的位置。文档开头和结尾的文字比中间的文字更重要。
  3. 连续的信息:一个故事可能以“in the beginning”开头,然后以“then”、“therefore”、“after that”继续,直到以“finally”结尾。使用分块策略,这种连接可能不再完整。
  4. 描述信息:使用分块,可能无法保证描述性信息集中在一起。

RAG任务不擅长回答的问题

  • 小范围的描述性问题:例如,哪个主体具有某些特征?
  • 关系推理:寻找从实体A到实体B的路径或识别实体集团。
  • 时间跨度很长的总结:例如,“列出所有哈利波特的战斗”或“哈利波特有多少次战斗?”

确定最佳分块策略的因素

  1. 被索引内容的性质:处理较长的文档(如文章或书籍)还是较短的内容(如微博或即时消息)?
  2. 使用的Embedding模型:例如,sentence-transformer模型在单个句子上工作得很好,但像text-embedding-ada-002这样的模型在包含256或512个tokens的块上表现得更好。
  3. 用户查询的长度和复杂性:用户输入的问题文本是简短而具体的还是冗长而复杂的?
  4. 检索结果的使用方式:用于语义搜索、问答、摘要或其他目的?底层连接的LLM的tokens限制也会影响分块的大小。

总之,没有最好的分块策略,只有适合的分块策略。为了确保查询结果更加准确,有时候需要选择性地使用几种不同的策略。

下面是Langchain/Langchain-chatchat,Langchain提供了很多文本切割的工具,其中langchain默认使用RecursiveCharacterTextSplitter,还有其他的切块方法比如:

  • RecursiveCharacterTextSplitter
  • CharacterTextSplitter
  • TokenTextSplitter
  • MarkdownHeaderTextSplitter
  • CodeTextSplitter
  • spaCy(TokenTextSplitter变形)
  • SentenceTransformersTokenTextSplitter(TokenTextSplitter变形)
  • NLTKTextSplitter(TokenTextSplitter变形)
  • GPT2TokenizerFast
  • AliTextSplitter
  • ChineseRecursiveTextSplitter
  • ChineseTextSplitter
  • zh_title_enhance

如何确定最佳块大小

确定最佳块大小通常需要通过A/B测试来进行。运行一系列查询来评估质量,并比较不同块大小的性能。这是一个反复测试的过程,针对不同的查询测试不同的块大小,直到找到最佳的块大小。

经验之谈

  • 较小的块大小:为了获得更好的结果,建议使用较小的块大小。微软的分析表明,较小的块大小有助于提高性能。

  • 分割策略:在分割文本时,可以选择不同的分割策略。最简单的方法是在单词的中间切断,也可以尝试在句子或段落的中间切断。为了得到更好的结果,可以重叠相邻的块。

Embedding模型的限制

Embedding模型在呈现多主题、多回合语料库时不如简单语料库有效。这就是为什么RAG(Retrieval-Augmented Generation)更喜欢较短的块。

  • 块大小范围:在微软的分析中,最小的块大小是512个tokens。一些企业级RAG应用程序中的块大小只有100个tokens。
  • 信息丢失:分块策略会将文本语料库分解成小块,导致信息丢失。数据块越小,丢失的信息就越多。因此,存在一个最优的块大小,过小的分块可能不太理想。

寻找最优块大小

寻找最优块大小就像超参数调优一样,必须用你的数据或者文档做实验。

文本重叠对准确率的提升

重叠的作用

重叠可以帮助将相邻的块链接在一起,并为块提供更好的上下文信息。然而,即使是非常激进的25%重叠也只能将准确率提高1.5%,从42.4%提高到43.9%。这意味着这不是优化RAG性能的最有效方法。

重叠的局限性

  • 处理代词:重叠在处理代词时很有效。例如,上一个chunk提到“徐悲鸿非常擅长画马”,下一句说“他画马的主要技法是xxxxx”,如果缺少重叠,代词“他”就会失去上下文。
  • 小块不适用:重叠分块甚至不能用于小块。

知识图谱的引入

  • 知识图谱的优势:在知识图谱的帮助下,RAG可以将这些关系存储在图数据库中,块之间的连接可以完全保留。如果关系推理对您的项目至关重要,这是一个非常可观的解决方案。
  • 挑战:从非结构化文本中建立知识图谱是非常重要的。自动提取的实体和关系可能包含大量的噪声,忽略了太多的真实信息。必须非常仔细地检查产品的质量。

支持向量搜索的关系数据库

  • pgvector:像pgvector这样的数据库允许您将复杂的信息存储为列,同时保留语义搜索功能。它比知识图谱更容易与其他企业系统集成,也更灵活。

自定义切块

下面是TrustRAG项目中,实现一个句子切块的逻辑:

1. 句子切块初始化

  • 作用: 初始化 SentenceChunker 类,设置 tokenizer 和分块的最大 token 数量。
  • 逻辑:
    • 调用父类的 __init__ 方法。
    • 设置 tokenizerrag_tokenizer,用于计算句子的 token 数量。
    • 设置 chunk_size,默认值为 512,表示每个分块的最大 token 数量。

2. 切分句子

  • 作用: 将输入的文本按照句子进行分割,支持中英文的句子分割。
  • 逻辑:
    • 使用正则表达式 re.compile(r'([。!?.!?])') 匹配句子结束的标点符号(中文:。!?;英文:.!?)。
    • 将文本按照这些标点符号进行分割,得到一个包含句子和标点符号的列表。
    • 将标点符号与前面的句子合并,形成完整的句子。
    • 处理最后一个句子(如果它没有标点符号)。
    • 去除句子前后的空白字符,并过滤掉空句子。
    • 返回一个包含所有句子的列表。

3. 处理切块

  • 作用: 对分块后的文本进行预处理,主要是规范化多余的换行符和空格。
  • 逻辑:
    • 遍历每个分块,处理其中的换行符和空格:
      • 将四个或更多连续的换行符替换为两个换行符。
      • 将四个或更多连续的空格替换为两个空格。
    • 返回处理后的分块列表。

4. 段落切块

  • 作用: 将输入的段落列表分块,确保每个分块的 token 数量不超过 chunk_size
  • 逻辑:
    • 将段落列表合并为一个完整的文本。
    • 使用 split_sentences 方法将文本分割成句子列表。
    • 如果没有分割出句子,则将段落列表作为句子列表。
    • 初始化 chunks 列表和 current_chunk 列表,用于存储当前分块的句子和 token 数量。
    • 遍历句子列表,计算每个句子的 token 数量:
      • 如果当前分块的 token 数量加上当前句子的 token 数量不超过 chunk_size,则将句子加入当前分块。
      • 否则,将当前分块加入 chunks 列表,并开始一个新的分块。
    • 处理最后一个分块(如果它包含句子)。
    • 使用 process_text_chunks 方法对分块进行预处理。
    • 返回最终的分块列表。

完整代码如下:

import re
from trustrag.modules.document import rag_tokenizer
from trustrag.modules.chunks.base import BaseChunker

class SentenceChunker(BaseChunker):
    """
    A class for splitting text into chunks based on sentences, ensuring each chunk does not exceed a specified token size.

    This class is designed to handle both Chinese and English text, splitting it into sentences using punctuation marks.
    It then groups these sentences into chunks, ensuring that the total number of tokens in each chunk does not exceed
    the specified `chunk_size`. The class also provides methods to preprocess the text chunks by normalizing excessive
    newlines and spaces.

    Attributes:
        tokenizer (callable): A tokenizer function used to count tokens in sentences.
        chunk_size (int): The maximum number of tokens allowed per chunk.

    Methods:
        split_sentences(text: str) -> list[str]:
            Splits the input text into sentences based on Chinese and English punctuation marks.

        process_text_chunks(chunks: list[str]) -> list[str]:
            Preprocesses text chunks by normalizing excessive newlines and spaces.

        get_chunks(paragraphs: list[str]) -> list[str]:
            Splits a list of paragraphs into chunks based on a specified token size.
    """

    def __init__(self, chunk_size=512):
        """
        Initializes the SentenceChunker with a tokenizer and a specified chunk size.

        Args:
            chunk_size (int, optional): The maximum number of tokens allowed per chunk. Defaults to 512.
        """
        super().__init__()
        self.tokenizer = rag_tokenizer
        self.chunk_size = chunk_size

    def split_sentences(self, text: str) -> list[str]:
        """
        Splits the input text into sentences based on Chinese and English punctuation marks.

        Args:
            text (str): The input text to be split into sentences.

        Returns:
            list[str]: A list of sentences extracted from the input text.
        """
        # Use regex to split text by sentence-ending punctuation marks
        sentence_endings = re.compile(r'([。!?.!?])')
        sentences = sentence_endings.split(text)

        # Merge punctuation marks with their preceding sentences
        result = []
        for i in range(0, len(sentences) - 1, 2):
            if sentences[i]:
                result.append(sentences[i] + sentences[i + 1])

        # Handle the last sentence if it lacks punctuation
        if sentences[-1]:
            result.append(sentences[-1])

        # Remove whitespace and filter out empty sentences
        result = [sentence.strip() for sentence in result if sentence.strip()]

        return result

    def process_text_chunks(self, chunks: list[str]) -> list[str]:
        """
        Preprocesses text chunks by normalizing excessive newlines and spaces.

        Args:
            chunks (list[str]): A list of text chunks to be processed.

        Returns:
            list[str]: A list of processed text chunks with normalized formatting.
        """
        processed_chunks = []
        for chunk in chunks:
            # Normalize four or more consecutive newlines
            while '\n\n\n\n' in chunk:
                chunk = chunk.replace('\n\n\n\n', '\n\n')

            # Normalize four or more consecutive spaces
            while '    ' in chunk:
                chunk = chunk.replace('    ', '  ')

            processed_chunks.append(chunk)

        return processed_chunks

    def get_chunks(self, paragraphs: list[str]) -> list[str]:
        """
        Splits a list of paragraphs into chunks based on a specified token size.

        Args:
            paragraphs (list[str]|str): A list of paragraphs to be chunked.

        Returns:
            list[str]: A list of text chunks, each containing sentences that fit within the token limit.
        """
        # Combine paragraphs into a single text
        text = ''.join(paragraphs)

        # Split the text into sentences
        sentences = self.split_sentences(text)

        # If no sentences are found, treat paragraphs as sentences
        if len(sentences) == 0:
            sentences = paragraphs

        chunks = []
        current_chunk = []
        current_chunk_tokens = 0

        # Iterate through sentences and build chunks based on token count
        for sentence in sentences:
            tokens = self.tokenizer.tokenize(sentence)
            if current_chunk_tokens + len(tokens) <= self.chunk_size:
                # Add sentence to the current chunk if it fits
                current_chunk.append(sentence)
                current_chunk_tokens += len(tokens)
            else:
                # Finalize the current chunk and start a new one
                chunks.append(''.join(current_chunk))
                current_chunk = [sentence]
                current_chunk_tokens = len(tokens)

        # Add the last chunk if it contains any sentences
        if current_chunk:
            chunks.append(''.join(current_chunk))

        # Preprocess the chunks to normalize formatting
        chunks = self.process_text_chunks(chunks)
        return chunks

if __name__ == '__main__':
    with open("../../../data/docs/news.txt","r",encoding="utf-8") as f:
        content=f.read()
    tc=SentenceChunker(chunk_size=128)
    chunks = tc.get_chunks([content])
    for chunk in chunks:
        print(f"Chunk Content:\n{chunk}")

输出如下:

Chunk Content:
韩国总统警卫处长辞职

#韩国总统警卫处长辞职#更新:韩国总统警卫处长朴钟俊今天(1月10日)到案接受调查前,向代总统崔相穆递交辞呈。#韩国总统警卫处长到案接受调查#今天上午,朴钟俊抵达韩国警察厅国家调查本部,接受警方调查。他在接受调查前向现场记者表示,针对被停职总统尹锡悦的逮捕令存在法理上的争议,对尹锡悦的调查程序应符合总统身份,而不是以逮捕令的形式进行。
Chunk Content:
他还说,政府机构间不能出现流血事件。韩国高级公职人员犯罪调查处(公调处)1月3日组织人员前往位于首尔市龙山区汉南洞的总统官邸进行抓捕,但遭总统警卫处抵抗,双方对峙5个多小时后,公调处宣布抓捕行动失败。韩国“共同调查本部”以涉嫌妨碍执行特殊公务为由对朴钟俊立案,要求其到案接受调查。朴钟俊曾两次拒绝到案接受警方调查。(总台记者 张昀

参考资料

  • 派神 -:让Langchain与你的数据对话(一):数据加载与分割
  • LangChain 按Token拆分文本内容
  • Chunking Strategies for LLM Applications | Pinecone
  • RAG行业交流中发现的一些问题和改进方法
  • 最详细的文本分块(Chunking)方法,直接影响LLM应用效果
  • RAG 分块Chunk技术优劣、技巧、方法汇总(五)

添加微信1185918903,关注公众号ChallengeHub获取更所咨询

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2278392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java基础——概念和常识(语言特点、JVM、JDK、JRE、AOT/JIT等介绍)

我是一个计算机专业研0的学生卡蒙Camel&#x1f42b;&#x1f42b;&#x1f42b;&#xff08;刚保研&#xff09; 记录每天学习过程&#xff08;主要学习Java、python、人工智能&#xff09;&#xff0c;总结知识点&#xff08;内容来自&#xff1a;自我总结网上借鉴&#xff0…

Low-Level 大一统:如何使用Diffusion Models完成视频超分、去雨、去雾、降噪等所有Low-Level 任务?

Diffusion Models专栏文章汇总&#xff1a;入门与实战 前言&#xff1a;视频在传输过程中常常因为各种因素&#xff08;如恶劣天气、噪声、压缩和传感器分辨率限制&#xff09;而出现质量下降&#xff0c;这会严重影响计算机视觉任务&#xff08;如目标检测和视频监控&#xff…

矩阵碰一碰发视频源码技术开发全解析,支持OEM

在当今数字化内容传播迅速发展的时代&#xff0c;矩阵碰一碰发视频功能以其便捷、高效的特点&#xff0c;为用户分享视频提供了全新的体验。本文将深入探讨矩阵碰一碰发视频源码的技术开发过程&#xff0c;从原理到实现&#xff0c;为开发者提供全面的技术指引。 一、技术原理 …

测试工程师的linux 命令学习(持续更新中)

1.ls """1.ls""" ls -l 除文件名称外&#xff0c;亦将文件型态、权限、拥有者、文件大小等资讯详细列出 ls -l等同于 ll第一列共10位&#xff0c;第1位表示文档类型&#xff0c;d表示目录&#xff0c;-表示普通文件&#xff0c;l表示链接文件。…

HJ34 图片整理(Java版)

一、试题地址 图片整理_牛客题霸_牛客网 二、试题描述 描述 对于给定的由大小写字母和数字组成的字符串&#xff0c;请按照 ASCIIASCII 码值将其从小到大排序。 如果您需要了解更多关于 ASCIIASCII 码的知识&#xff0c;请参考下表。 输入描述&#xff1a; 在一行上输入一…

web开发工具之:三、JWT的理论知识,java的支持,封装的工具类可以直接使用

文章目录 前言一、JWT的理论知识1. 什么是 JWT&#xff08;JSON Web Token&#xff09;&#xff1f;2. **JWT 的组成**3. **JWT 的特点**4. **JWT 的使用场景**5. **JWT 的生命周期**6. **JWT 的优点**7. **JWT 的注意事项**5. **JWT 示例**总结 二、java的springboot支持1. po…

电路笔记(信号):Python 滤波器设计分析工具pyfda

目录 滤波器设置(3步实现滤波器设计)数据分析与使用pyfda功能界面数字滤波器数学表示线性相位线性相位的定义线性相位的特性 冲击响应quartus数据加载 CG 滤波器设置(3步实现滤波器设计) pip install pyfda #安装python依赖&#xff0c;详见https://pyfda.readthedocs.io/en/la…

【15】Word:互联网发展状况❗

目录 题目​ NO2 NO3 NO4 NO5 NO6 NO7.8.9 NO7 NO8 NO9 NO10 题目 NO2 布局→页面设置→纸张&#xff1a;A4→页边距&#xff1a;上下左右→版式&#xff1a;页眉/页脚页码范围&#xff1a;多页&#xff1a;对称页边距→内侧/外侧→装订线 NO3 首先为文档应用内置…

Web前端开发技术之HTMLCSS知识点总结

学习路线 一、新闻网界面1. 代码示例2. 效果展示3. 知识点总结3.1 HTML标签和字符实体3.2 超链接、颜色描述与标题元素3.3 关于图片和视频标签&#xff1a;3.4 CSS引入方式3.5 CSS选择器优先级 二、flex布局1. 代码示例2. 效果展示3. 知识点总结3.1 span标签和flex容器的区别3.…

BUUCTF Web

[极客大挑战 2019]LoveSQL union注入 是sql注入类型 输入1 发现不是数字型注入&#xff0c;那就是字符型注入。判断字段数&#xff0c;输入order by 4 #发现错误&#xff0c;就存在三个字段数 判断回显点&#xff1a;1 union select 1,2,3 # 判断回显点为2,3 判断数据库名 …

Kinova仿生机械臂Gen3搭载BOTA 力矩传感器SeneOne:彰显机器人触觉 AI 与六维力传感的融合力量

随着工业4.0时代的到来&#xff0c;自动化和智能化成为制造业的趋势。机器人作为实现这一趋势的重要工具&#xff0c;其性能和智能水平直接影响到生产效率和产品质量。然而&#xff0c;传统的机器人系统在应对复杂任务时往往缺乏足够的灵活性和适应性。为了解决这一问题&#x…

【数据库】MySQL数据库SQL语句汇总

目录 1.SQL 通用语法 2.SQL 分类 2.1.DDL 2.2.DML 2.3.DQL 2.4.DCL 3.DDL 3.1.数据库操作 3.1.1.查询 3.1.2.创建 3.1.3.删除 3.1.4.使用 3.2.表操作 3.2.1.查询 3.2.2.创建 3.2.3.数据类型 3.2.3.1.数值类型 3.2.3.2.字符串类型 3.2.3.3.日期时间类型 3.2…

《汽车与驾驶维修》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答&#xff1a; 问&#xff1a;《汽车与驾驶维修》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第二批认定学术期刊。 问&#xff1a;《汽车与驾驶维修》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;中国机械工业联合会…

鸿蒙UI(ArkUI-方舟UI框架)-开发布局

文章目录 开发布局1、布局概述1&#xff09;布局结构2&#xff09;布局元素组成3&#xff09;如何选择布局4&#xff09;布局位置5&#xff09;对子元素的约束 2、构建布局1&#xff09;线性布局 (Row/Column)概述布局子元素在排列方向上的间距布局子元素在交叉轴上的对齐方式(…

数据结构——概述

1、什么是数据结构&#xff1f; 数据结构是计算机存储和管理数据的方式。数据必须依据某种逻辑联系组织在一起存储在计算机内&#xff0c;数据结构研究的就是这种数据的逻辑结构和数据的存储结构 2、逻辑结构——数据本身之间的关系 逻辑结构在计算机中的实现 &#xff08;1…

业务架构、数据架构、应用架构和技术架构

TOGAF(The Open Group Architecture Framework)是一个广泛应用的企业架构框架&#xff0c;旨在帮助组织高效地进行架构设计和管理。 TOGAF 的核心就是由我们熟知的四大架构领域组成:业务架构、数据架构、应用架构和技术架构。 企业数字化架构设计中的最常见要素是4A 架构。 4…

python爬虫入门(实践)

python爬虫入门&#xff08;实践&#xff09; 一、对目标网站进行分析 二、博客爬取 获取博客所有h2标题的路由 确定目标&#xff0c;查看源码 代码实现 """ 获取博客所有h2标题的路由 """url "http://www.crazyant.net"import re…

简历_使用优化的Redis自增ID策略生成分布式环境下全局唯一ID,用于用户上传数据的命名以及多种ID的生成

系列博客目录 文章目录 系列博客目录WhyRedis自增ID策略 Why 我们需要设置全局唯一ID。原因&#xff1a;当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题。 问题&#xff1a;id的规律性太明显、…

win32汇编环境,窗口程序中对多行编辑框的操作

;运行效果 ;win32汇编环境,窗口程序中对多行编辑框的操作 ;比如生成多行编辑框&#xff0c;显示文本、获取文本、设置滚动条、捕获超出文本长度消息等。 ;直接抄进RadAsm可编译运行。重点部分加备注。 ;下面为asm文件 ;>>>>>>>>>>>>>&g…

【Flink系列】5. DataStream API

5. DataStream API DataStream API是Flink的核心层API。一个Flink程序&#xff0c;其实就是对DataStream的各种转换。具体来说&#xff0c;代码基本上都由以下几部分构成&#xff1a; 5.1 执行环境&#xff08;Execution Environment&#xff09; Flink程序可以在各种上下文…