LangChain+LLM实战---文本分块(Chunking)方法

news2024/11/25 16:01:02

RAG是一个考验技术的工作

基于大模型的企业应用中很大一部分需求就是RAG——检索增强生成。

1

这个流程依然无法描述RAG的复杂性

RAG涉及的内容其实广泛,包括Embedding、分词分块、检索召回(相似度匹配)、chat系统、ReAct和Prompt优化等,最后还有与LLM的交互,整个过程技术复杂度很高。如果你用的LLM非常好,反而大模型这一块是你最不需要关心的。而这些环节里面我们每个都没达到1(比如0.9、0.7…),那么最终的结果可能是这些小数点的乘积。如果我们每个环节都可以做到>1.0,那么最终的结果会比上一个结果高出很多。

今天我们来聊聊分块,很重要的一个环节(没有哪个环节不重要),但它也许是我们容易做到高质量的一个环节。

什么是分块?

在构建RAG这类基于LLM的应用程序中,分块(chunking)是将大块文本分解成小段的过程。当我们使用LLM embedding内容时,这是一项必要的技术,可以帮助我们优化从向量数据库被召回的内容的准确性。在本文中,我们将探讨它是否以及如何帮助提高RAG应用程序的效率和准确性。

6

Pinecone是领先的向量数据库供应商

在向量数据库(如:Pinecone)中索引的任何内容都需要首先Embedding。分块的主要原因是尽量减少我们Embedding内容的噪音

例如,在语义搜索中,我们索引一个文档语料库,每个文档包含一个特定主题的有价值的信息。通过使用有效的分块策略,我们可以确保搜索结果准确地捕获用户查询的需求本质。如果我们的块太小或太大,可能会导致不精确的搜索结果或错过展示相关内容的机会。根据经验,如果文本块尽量是语义独立的,也就是没有对上下文很强的依赖,这样子对语言模型来说是最易于理解的。因此,为语料库中的文档找到最佳块大小对于确保搜索结果的准确性和相关性至关重要

另一个例子是会话Agent。我们使用embedding的块为基于知识库的会话agent构建上下文,该知识库将代理置于可信信息中。在这种情况下,对分块策略做出正确的选择很重要,原因有两个:

  • 首先,它将决定上下文是否与我们的prompt相关。
  • 其次,考虑到我们可以为每个请求发送的tokens数量的限制,它将决定我们是否能够在将检索到的文本合并到prompt中发送到大模型(如OpenAI)。

在某些情况下,比如使用具有32k上下文窗口的GPT-4时,拟合块可能不是问题。尽管如此,当我们使用非常大的块时,我们需要注意,因为这可能会对我们从向量数据库获得的结果的相关性产生不利影响。

在这篇文章中,我们将探讨几种分块方法,并讨论在选择分块大小和方法时应该考虑的权衡。最后,我们将给出一些建议,以确定适合您的应用程序的最佳块大小和方法。

Embedding长短内容

当我们在嵌入内容(也就是embedding)时,我们可以根据内容是短(如句子)还是长(如段落或整个文档)来预测不同的行为。

当嵌入一个句子时,生成的向量集中在句子的特定含义上。当与其他句子Embedding进行比较时,自然会在这个层次上进行比较。这也意味着Embedding可能会错过在段落或文档中找到的更广泛的上下文信息。

当嵌入一个完整的段落或文档时,Embedding过程既要考虑整个上下文,也要考虑文本中句子和短语之间的关系。这可以产生更全面的向量表示,从而捕获文本的更广泛的含义和主题。另一方面,较大的输入文本大小可能会引入噪声或淡化单个句子或短语的重要性,从而在查询索引时更难以找到精确的匹配。

查询的长度也会影响Embeddings之间的关系。较短的查询,如单个句子或短语,将专注于细节,可能更适合与句子级Embedding进行匹配。跨越多个句子或段落的较长的查询可能更适合段落或文档级别的Embedding,因为它可能会寻找更广泛的上下文或主题。

索引也可能是非同质的,并且包含“不同”大小的块的Embedding。这可能会在查询结果相关性方面带来挑战,但也可能产生一些积极的后果。一方面,由于长内容和短内容的语义表示不一致,查询结果的相关性可能会波动。另一方面,非同构索引可能捕获更大范围的上下文和信息,因为不同的块大小表示文本中的不同粒度级别。这可以更灵活地容纳不同类型的查询。

分块需要考虑的因素

在确定最佳分块策略时,有几个因素会对我们的选择起到至关重要的影响。以下是一些事实我们需要首先记在心里:

  1. 被索引内容的性质是什么? 这可能差别会很大,是处理较长的文档(如文章或书籍),还是处理较短的内容(如微博或即时消息)?答案将决定哪种模型更适合您的目标,从而决定应用哪种分块策略。

  2. 您使用的是哪种Embedding模型,它在多大的块大小上表现最佳?例如,sentence-transformer[1]模型在单个句子上工作得很好,但像text- embedt-ada -002[2]这样的模型在包含256或512个tokens的块上表现得更好。

  3. 你对用户查询的长度和复杂性有什么期望?用户输入的问题文本是简短而具体的还是冗长而复杂的?这也直接影响到我们选择分组内容的方式,以便在嵌入查询和嵌入文本块之间有更紧密的相关性。

  4. 如何在您的特定应用程序中使用检索结果? 例如,它们是否用于语义搜索问答摘要其他目的?例如,和你底层连接的LLM是有直接关系的,LLM的tokens限制会让你不得不考虑分块的大小。

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

分块的方法

分块有不同的方法,每种方法都可能适用于不同的情况。通过检查每种方法的优点和缺点,我们的目标是确定应用它们的正确场景。

固定大小分块

这是最常见、最直接的分块方法:

我们只需决定块中的tokens的数量,以及它们之间是否应该有任何重叠。一般来说,我们会在块之间保持一些重叠,以确保语义上下文不会在块之间丢失。在大多数情况下,固定大小的分块将是最佳方式。与其他形式的分块相比,固定大小的分块在计算上更加经济且易于使用,因为它在分块过程中不需要使用任何NLP库。

下面是一个使用LangChain执行固定大小块处理的示例:

1
2
3
4
5
6
7
8
text = "..." # your text
from langchain.text_splitter import CharacterTextSplitter
text_splitter = CharacterTextSplitter(
    separator = "\n\n",
    chunk_size = 256,
    chunk_overlap  = 20
)
docs = text_splitter.create_documents([text])

Content-Aware:基于内容意图分块

这是一系列方法的组合,利用我们正在分块的内容的性质,并对其应用更复杂的分块。下面是一些例子:

句分割——Sentence splitting

正如我们之前提到的,许多模型都针对Embedding句子级内容进行了优化。当然,我们会使用句子分块,有几种方法和工具可以做到这一点,包括:

  • Naive splitting: 最幼稚的方法是用句号(。)和“换行”来分割句子。虽然这可能是快速和简单的,但这种方法不会考虑到所有可能的边缘情况。这里有一个非常简单的例子:
1
2
text = "..." # 你的文本
docs = text.split(".")
  • NLTK[3]: 自然语言工具包(NLTK)是一个流行的Python库,用于处理自然语言数据。它提供了一个句子标记器,可以将文本分成句子,帮助创建更有意义的分块。例如,要将NLTK与LangChain一起使用,您可以这样做:
1
2
3
4
text = "..." # 你的文本
from langchain.text_splitter import NLTKTextSplitter
text_splitter = NLTKTextSplitter()
docs = text_splitter.split_text(text)
  • spaCy[4]: spaCy是另一个用于NLP任务的强大Python库。它提供了一个复杂的句子分割功能,可以有效地将文本分成单独的句子,从而在生成的块中更好地保存上下文。例如,要将space与LangChain一起使用,您可以这样做:
1
2
3
4
text = "..." # 你的文本
from langchain.text_splitter import SpacyTextSplitter
text_splitter = SpaCyTextSplitter()
docs = text_splitter.split_text(text)

3

spacy-llm package

递归分割

递归分块使用一组分隔符以分层和迭代的方式将输入文本分成更小的块。如果分割文本开始的时候没有产生所需大小或结构的块,那么这个方法会使用不同的分隔符或标准对生成的块递归调用,直到获得所需的块大小或结构。这意味着虽然这些块的大小并不完全相同,但它们仍然会逼近差不多的大小。

这里有一个例子,如何配合LangChain使用递归分块:

1
2
3
4
5
6
7
8
9
text = "..." # 你的文本
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
    # 设置一个非常小的块大小。
    chunk_size = 256,
    chunk_overlap  = 20
)

docs = text_splitter.create_documents([text])
专门的分块

Markdown和LaTeX是您可能遇到的结构化和格式化内容的两个例子。在这些情况下,可以使用专门的分块方法在分块过程中保留内容的原始结构。

4-2

本文就是用markdown写作的

  • Markdown: Markdown是一种轻量级的标记语言,通常用于格式化文本。通过识别Markdown语法(例如,标题、列表和代码块),您可以根据其结构和层次结构智能地划分内容,从而生成语义更连贯的块。例如:
1
2
3
4
5
from langchain.text_splitter import MarkdownTextSplitter
markdown_text = "..."

markdown_splitter = MarkdownTextSplitter(chunk_size=100, chunk_overlap=0)
docs = markdown_splitter.create_documents([markdown_text])
  • LaTex: LaTeX是一种文档准备系统和标记语言,通常用于学术论文和技术文档。通过解析LaTeX命令和环境,您可以创建尊重内容逻辑组织的块(例如,节、子节和方程),从而产生更准确和上下文相关的结果。例如:
1
2
3
4
from langchain.text_splitter import LatexTextSplitter
latex_text = "..."
latex_splitter = LatexTextSplitter(chunk_size=100, chunk_overlap=0)
docs = latex_splitter.create_documents([latex_text])

确定应用程序的最佳块大小

如果常见的分块方法(如固定分块)不容易适用于您的用例,这里有一些指针可以帮助您找到最佳的块大小。

  • 清洗数据 :在确定应用程序的最佳块大小之前,您需要首先预处理清洗数据以确保质量。例如,如果您的数据是从web爬取的,您可能需要删除HTML标记或特定的元素,保证文本的“纯洁”,减少文本的噪音。
  • 选择一个范围的块大小 :一旦你的数据被预处理,下一步是选择一个范围的潜在块大小进行测试。如前所述,选择时应考虑内容的性质(例如短文本还是长文档)、将要使用的Embedding模型及其功能(如token限制)。目标是在保留上下文和保持准确性之间找到平衡。从探索各种块大小开始,包括较小的块(例如,128或256个tokens)用于捕获更细粒度的语义信息,较大的块(例如,512或1024个tokens)用于保留更多上下文。
  • 评估每个块大小的性能 :很傻,但是很稳重的一种方式。为了测试不同大小的块,您可以把不同大小的块进行标记。使用可以覆盖你的业务场景效果的数据集,为要测试的各个大小的块创建Embedding,并将它们保存下来。然后,你可以运行一系列查询来评估质量,并比较不同块大小的性能。这是一个反复测试的过程,在这个过程中,针对不同的查询测试不同的块大小,直到找到最佳的块大小。

结论

好了,说了这么多,最后我要说的结论你可能会失望,那就是我们自己也还没有找到最佳的分块方式,哈哈。但是对于不同的业务场景,我们现在比刚开始做RAG应用的时候会有经验很多了。

引用:

1.sentence-transformer:https://huggingface.co/sentence-transformers

2.text- embedt-ada -002:New and improved embedding model

3.NLTK:NLTK :: Natural Language Toolkit

4.spaCy:spaCy · Industrial-strength Natural Language Processing in Python

5.Chunking Strategies for LLM Applications:Chunking Strategies for LLM Applications | Pinecone

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

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

相关文章

Optional——优雅判空

初始化 Optional提供了三个初始化方法: SpringBootTest public class OptionalTest {Testpublic void testOptional() {Optional.empty();Optional.ofNullable(null);Optional.of(null);} }empty返回一个空的Optional对象。 of遇到空会报错,但是使用Op…

Python | 安装、环境配置及包的安装

Python | 安装、环境配置及包的安装 一、前言二、python安装及编辑器配置2.1 python安装2.2 python调试2.3 python编辑器 | PyCharm2.3.1 PyCharm下载2.3.2 PyCharm安装2.3.3 PyCharm启动界面2.3.4 PyCharm初步设置2.3.5 PyCharm环境配置(含Python Interpreter配置)2.3.5.1 New…

2003-2022年飞机航线信息数据

2003-2022年飞机航线信息数据 时间:2003-2022年指标:起点城市、起点城市所属地级市、起点城市所属省份、起点机场、终点城市、终点城市所属地级市、终点城市所属省份、终点机场、航空公司、航班、机型、出发时间、到达时间、准点率、班次_周一、班次_周…

pip安装apex报错ERROR: Could not build wheels for cryptacular.......

问题:在训练模型的时候需要安装apex包,遂即使用以下命令 pip install apex但是报错了,报错信息如下: WARNING: Building wheel for cryptacular failed: [Errno 2] No such file or directory: C:\\Users\\XXX\\AppData\\Local\…

Corel VideoStudio 会声会影2024剪辑中间的视频怎么删 剪辑中音乐太长怎么办

我很喜欢视频剪辑软件Corel VideoStudio 会声会影2024,因为它使用起来很有趣。它很容易使用,但仍然给你很多功能和力量。视频剪辑软件Corel VideoStudio 会声会影2023让我与世界分享我的想法!“这个产品的功能非常多,我几乎没有触…

解决找不到msvcp120.dll,无法继续执行代码的办法,msvcp120.dll丢失的解决办法

在使用电脑的过程中出现了“找不到msvcp120.dll,无法继续执行代码”,通常出现这种错误的原因是因为电脑中的msvcp120.dll文件丢失,但是文件丢失就会导致电脑出现软件不能打开的情况,也可能会导致电脑出现其他的问题,所以今天就给大…

【学习草稿】

【数据分析】 1、相关性分析 对变量之间相关关系的分析&#xff0c;即相关性分析。其中比较常用的是线性相关分析&#xff0c;用来衡量它的指标是线性相关系数&#xff0c;又叫皮尔逊相关系数&#xff0c;通常用r表示&#xff0c;取值范围是[-1,1]。 r的绝对值<0.3 ,低度线性…

spring报错 @EnableAsync annotation metadata was not injected

报错 报错 internalAsyncAnnotationProcessor 这个spring内部的后处理器 创建失败&#xff0c;进而导致 EnableAsync 注解元数据没有注入容器 分析问题 查了 博客 是配置类放到原始项目路径下导致的问题。 博主的路径虽然正确&#xff0c;但发现是相似的问题&#xff0c;最…

linux 驱动——将模块编译进内核

文章目录 新增 C 文件修改 Makefile 文件修改 Kconfig 文件模块使能内核启动日志参考 linux 驱动——字符设备驱动 linux 驱动——字符设备驱动(自动生成设备节点文件) linux 驱动——将模块编译进内核 前面两节介绍的驱动都是以模块的形式&#xff0c;需要手动加载&#xff0…

【kubernetes】pod的生命周期

文章目录 1、概述2、pod的生命期3、pod阶段4、容器状态5、容器重启策略6、pod状况6.1 Pod就绪态6.2 Pod就绪态的状态6.3 Pod网络就绪 7、容器探针7.1 检查机制7.2 探测结果7.3 探测类型 8、Pod的终止8.1 强制终止Pod8.2 Pod的垃圾收集 1、概述 pod遵循预定义的生命周期&#x…

matlab中的mapminmax函数初步理解和应用

matlab中的mapminmax函数初步认识 一、mapminmax 顾名思义&#xff1a;映射最大最小 二、语法及举例 2.1 语法1 [Y,PS] mapminmax(X) 将矩阵X映射形成矩阵Y, Y中每行中的最小值对应-1&#xff0c;最大值对应1。PS是一个包含映射信息的结构体。 举例&#xff1a; clc cle…

4.第一个Java程序的讲解—Hello World

本文将写一个程序输出 Hello World &#xff0c;然后逐句讲解 ~ 文章目录 一、输出 Hello World二、代码讲解2.1 package com.goole.demo;2.1.1 .idea、out、src2.1.2 解释 2.2 public class Main2.2.1 解释2.2.2 创建新类 2.3 public static void main(String[] args)2.3.1 解…

测试用例的设计方法(全):判定表驱动分析方法

目录 判定表驱动分析方法 一. 方法简介 二. 实战演习 判定表驱动分析方法 一. 方法简介 1.定义&#xff1a;判定表是分析和表达多逻辑条件下执行不同操作的情况的工具。 2.判定表的优点 能够将复杂的问题按照各种可能的情况全部列举出来&#xff0c;简明并避免遗漏。因此…

找不到d3dx9_43.dll如何修复?d3dx9_43.dll丢失的解决办法分享

在电脑使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到d3dx9_43.dll”。这个错误通常出现在运行某些游戏或应用程序时&#xff0c;它会导致程序无法正常运行。那么&#xff0c;如何解决找不到d3dx9_43.dll的问题呢&#xff1f;下面我将分享…

贰[2],QT异常处理

1&#xff0c;异常&#xff1a;QT编译警告 warning LNK4042: 对象被多次指定&#xff1b;已忽略多余的指定 处理办法&#xff0c;检查.pri文件&#xff0c;是否关联了多个相同的文件(头文件.h/源文件.cpp) 2&#xff0c;异常&#xff1a;C4819: 该文件包含不能在当前代码页(936…

FreeRTOS_任务通知

目录 1. 任务通知简介 2. 发送任务通知 2.1 函数 xTaskNotify() 2.2 函数 xTaskNotifyFromISR() 2.3 函数 xTaskNotifyGive() 2.4 函数 vTaskNotifyGiveFromISR() 2.5 函数 xTaskNotifyAndQuery() 2.6 函数 xTaskNotifyAndQueryFromISR() 3. 任务通知通用发送函数 3.…

85.x的平方根(力扣)

目录 问题描述 代码解决以及思想 知识点 问题描述 代码解决以及思想 class Solution { public:int mySqrt(int x) {int left 0; // 定义左边界int right x; // 定义右边界&#xff0c;初始值取 xwhile (left < right) { // 当左边界小于或等于…

CC1101 一款低功耗sub- 1ghz收发器芯片 适用于无线遥控智能家居

产品描述 CC1101是一个低成本的sub- 1ghz收发器,专为极低功耗的无线应用而设计。 该电路主要用于工业、科学和医学)和SRD (Short Range Device)频带,在315,433,868和915兆赫&#xff0c;但可以轻松可编程用于其他操作频率在300-348 MHz、387-464 MHz,以及779-928 MHz频段。射…

【网络管理——操作系统与安全】

文章目录 一、安装WindowsServer操作系统1、新建虚拟机2、进入Windows虚拟机进行相关配置 二、Windows用户账户管理与配置1、创建用户账户2、创建用户组 三、Windows操作系统的本地安全策略设置1、配置用户账户密码策略2、配置用户账户锁定策略3、配置组策略安全选项4、配置审核…

CSDN每日一题学习训练——Java版(两数相加、二叉树的锯齿形层序遍历、解数独)

版本说明 当前版本号[20231106]。 版本修改说明20231106初版 目录 文章目录 版本说明目录两数相加题目解题思路代码思路补充说明参考代码 二叉树的锯齿形层序遍历题目解题思路代码思路参考代码 解数独题目解题思路代码思路补充说明参考代码 两数相加 题目 给你两个 非空 的…