(六)、基于 LangChain 实现大模型应用程序开发 | 基于知识库的个性化问答 (文档分割 Splitting)

在上一章中,我们刚刚讨论了如何将文档加载到标准格式中,现在我们要谈论如何将它们分割成较小的块。这听起来可能很简单,但其中有很多微妙之处会对后续工作产生重要影响。

文章目录

  • 1、为什么要做文档分割?
  • 2、文档分割方式
  • 3、基于字符分割:RecursiveCharacterTextSplitter 与 CharacterTextSplitter
    • 3.1、短句分割
    • 3.1、长句分割
  • 4、基于Token分割
  • 5、分割Markdown文档
    • 5.1、分割一个自定义 Markdown 文档
    • 5.2、分割数据库中的 Markdown 文档
  • Reference

1、为什么要做文档分割?

优点:

  • 1、模型大小和内存限制。
  • 2、计算效率。
  • 3、序列长度限制。
  • 4、更好的泛化:通过在多个文档块上进行训练,模型可以更好地学习和泛化到各种不同的文本样式和结构。
  • 5、数据增强:分割文档可以为训练数据提供更多的样本。例如,一个长文档可以被分割成多个部分,并分别作为单独的训练样本。

缺点:

  • 可能导致一些上下文信息的丢失,尤其是在分割点附近。因此,如何进行文档分割是一个需要权衡的问题。

因此,为了确保语义的准确性,我们应该尽量将文本分割为包含完整语义的段落或单元。

2、文档分割方式

Langchain 中文本分割器都根据 chunk_size (块大小)和 chunk_overlap (块与块之间的重叠大小)进行分割:

  • chunk_size 指每个块包含的字符或 Token (如单词、句子等)的数量
  • chunk_overlap 指两个块之间共享的字符数量,用于保持上下文的连贯性,避免分割丢失上下文信息
    在这里插入图片描述

Langchain提供了很多文本切割的工具,区别在怎么确定块与块之间的边界、块由哪些字符/token组成、以及如何测量块大小。其中langchain默认使用RecursiveCharacterTextSplitter:

  • 1、CharacterTextSplitter():按字符来分割文本。
  • 2、MarkdownHeaderTextSplitter():基于指定的标题来分割markdown 文件。
  • 3、TokenTextSplitter():按token来分割文本。
  • 4、SentenceTransformersTokenTextSplitter() : 按token来分割文本
  • 5、RecursiveCharacterTextSplitter():按字符串分割文本,递归地尝试按不同的分隔符进行分割文本。
  • 6、Language() - 用于 CPP、Python、Ruby、Markdown 等。
  • 7、NLTKTextSplitter():使用 NLTK(自然语言工具包)按句子分割文本。
  • 8、SpacyTextSplitter() - 使用 Spacy按句子的切割文本。

3、基于字符分割:RecursiveCharacterTextSplitter 与 CharacterTextSplitter

如何进行文本分割,往往与我们的任务类型息息相关。当我们拆分代码时,这种相关性变得尤为突出。因此,我们引入了一个语言文本分割器,其中包含各种为 Python、Ruby、C 等不同编程语言设计的分隔符。在对这些文档进行分割时,必须充分考虑各种编程语言之间的差异。

我们将从基于字符的分割开始探索,借助 LangChain 提供的 RecursiveCharacterTextSplitter 和 CharacterTextSplitter 工具来实现此目标。

CharacterTextSplitter 是字符文本分割,分隔符的参数是单个的字符串;RecursiveCharacterTextSplitter 是递归字符文本分割,将按不同的字符递归地分割(按照这个优先级[“\n\n”, “\n”, " ", “”]),这样就能尽量把所有和语义相关的内容尽可能长时间地保留在同一位置。因此,RecursiveCharacterTextSplitter 比 CharacterTextSplitter 对文档切割得更加碎片化

RecursiveCharacterTextSplitter 需要关注的是如下4个参数:

  • separators - 分隔符字符串数组
  • chunk_size - 每个文档的字符数量限制
  • chunk_overlap - 两份文档重叠区域的长度
  • length_function - 长度计算函数

⭐从以下尝试可以看出,这就是递归字符文本分割器名字中“递归”的含义,总的来说,我们更建议在通用文本中使用递归字符文本分割器。

3.1、短句分割

# 导入文本分割器
from langchain.text_splitter import RecursiveCharacterTextSplitter, CharacterTextSplitter

chunk_size = 20 #设置块大小
chunk_overlap = 10 #设置块重叠大小

# 初始化递归字符文本分割器
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=chunk_size,
    chunk_overlap=chunk_overlap
)
# 初始化字符文本分割器
c_splitter = CharacterTextSplitter(
    chunk_size=chunk_size,
    chunk_overlap=chunk_overlap
)


text = "在AI的研究中,由于大模型规模非常大,模型参数很多,在大模型上跑完来验证参数好不好训练时间成本很高,所以一般会在小模型上做消融实验来验证哪些改进是有效的再去大模型上做实验。"  #测试文本
# 递归字符分割器
r_splitter.split_text(text)
# 可以看到,分割结果中,第二块是从“大模型规模非常大,模”开始的,刚好是我们设定的块重叠大小

['在AI的研究中,由于大模型规模非常大,模',
 '大模型规模非常大,模型参数很多,在大模型',
 '型参数很多,在大模型上跑完来验证参数好不',
 '上跑完来验证参数好不好训练时间成本很高,',
 '好训练时间成本很高,所以一般会在小模型上',
 '所以一般会在小模型上做消融实验来验证哪些',
 '做消融实验来验证哪些改进是有效的再去大模',
 '改进是有效的再去大模型上做实验。']

# 字符文本分割器
c_splitter.split_text(text)
# 可以看到字符分割器没有分割这个文本,因为字符文本分割器默认以换行符为分隔符,因此需要设置“,”为分隔符。

['在AI的研究中,由于大模型规模非常大,模型参数很多,在大模型上跑完来验证参数好不好训练时间成本很高,所以一般会在小模型上做消融实验来验证哪些改进是有效的再去大模型上做实验。']

设置空格分隔符。可以看到出现了提示"Created a chunk of size 23, which is longer than the specified 20",意思是“创建了一个长度为23的块,这比指定的20要长。”。


# 是因为CharacterTextSplitter优先使用我们自定义的分隔符进行分割,所以在长度上会有较小的差距
c_splitter = CharacterTextSplitter(
    chunk_size=chunk_size,
    chunk_overlap=chunk_overlap,
    separator=','
)
c_splitter.split_text(text)

Created a chunk of size 23, which is longer than the specified 20
['在AI的研究中,由于大模型规模非常大',
 '由于大模型规模非常大,模型参数很多',
 '在大模型上跑完来验证参数好不好训练时间成本很高',
 '所以一般会在小模型上做消融实验来验证哪些改进是有效的再去大模型上做实验。']

3.1、长句分割

some_text = """在编写文档时,作者将使用文档结构对内容进行分组。 \
    这可以向读者传达哪些想法是相关的。 例如,密切相关的想法\
    是在句子中。 类似的想法在段落中。 段落构成文档。 \n\n\
    段落通常用一个或两个回车符分隔。 \
    回车符是您在该字符串中看到的嵌入的“反斜杠 n”。 \
    句子末尾有一个句号,但也有一个空格。\
    并且单词之间用空格分隔"""

print(len(some_text)) # 177
# CharacterTextSplitter默认的字分割符是双换行符即\n\n
c_splitter = CharacterTextSplitter(
    chunk_size=80,
    chunk_overlap=0,
    separator=' '
)
c_splitter.split_text(some_text)
['在编写文档时,作者将使用文档结构对内容进行分组。 这可以向读者传达哪些想法是相关的。 例如,密切相关的想法 是在句子中。 类似的想法在段落中。 段落构成文档。',
 '段落通常用一个或两个回车符分隔。 回车符是您在该字符串中看到的嵌入的“反斜杠 n”。 句子末尾有一个句号,但也有一个空格。 并且单词之间用空格分隔']

对于递归字符分割器,依次传入分隔符列表,分别是双换行符、单换行符、空格、空字符,
因此在分割文本时,首先会采用双换行符进行分割,同时依次使用其他分隔符进行分割(谁放列表前,谁优先级就大)。
意思就是先通过\n\n分割,然后在分割出来的每一段里继续用 [“\n”, " ", “”]分割,但每次分割要尽可能满足chunk_size和chunk_overlap

'''
# 默认字分割符是一个列表即["\n\n", "\n", " ", ""]
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=80,
    chunk_overlap=0,
    separators=["\n\n", "\n", " ", ""]
)
r_splitter.split_text(some_text)

['在编写文档时,作者将使用文档结构对内容进行分组。     这可以向读者传达哪些想法是相关的。 例如,密切相关的想法    是在句子中。 类似的想法在段落中。',
 '段落构成文档。',
 '段落通常用一个或两个回车符分隔。     回车符是您在该字符串中看到的嵌入的“反斜杠 n”。     句子末尾有一个句号,但也有一个空格。',
 '并且单词之间用空格分隔']

如果需要按照句子进行分隔,则还要用正则表达式添加一个句号分隔符


r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=30,
    chunk_overlap=0,
    separators=["\n\n", "\n", "(?<=\。 )", " ", ""]
)
r_splitter.split_text(some_text)
['在编写文档时,作者将使用文档结构对内容进行分组。',
 '这可以向读者传达哪些想法是相关的。 例如,密切相关的想法',
 '是在句子中。 类似的想法在段落中。 段落构成文档。',
 '段落通常用一个或两个回车符分隔。',
 '回车符是您在该字符串中看到的嵌入的“反斜杠 n”。',
 '句子末尾有一个句号,但也有一个空格。',
 '并且单词之间用空格分隔']

4、基于Token分割

LLM 的上下文窗口长度限制一般是按照 Token 来计数的。因此,以 LLM 的视角,按照 Token 对文本进行分隔,通常可以得到更好的结果。 通过一个实例理解基于字符分割和基于 Token 分割的区别

# 使用token分割器进行分割,
# 将块大小设为1,块重叠大小设为0,相当于将任意字符串分割成了单个Token组成的列
from langchain.text_splitter import TokenTextSplitter
text_splitter = TokenTextSplitter(chunk_size=1, chunk_overlap=0)
text = "foo bar bazzyfoo"
text_splitter.split_text(text)
# 可以看出token长度和字符长度不一样,token通常为4个字符
# 注:目前 LangChain 基于 Token 的分割器还不支持中文
['foo', ' bar', ' b', 'az', 'zy', 'foo']

5、分割Markdown文档

5.1、分割一个自定义 Markdown 文档

分块的目的是把具有上下文的文本放在一起,我们可以通过使用指定分隔符来进行分隔,但有些类型的文档(例如 Markdown )本身就具有可用于分割的结构(如标题)。

Markdown 标题文本分割器会根据标题或子标题来分割一个 Markdown 文档,并将标题作为元数据添加到每个块中。

# 定义一个Markdown文档

markdown_document = """# Title\n\n \
## 第一章\n\n \
李白乘舟将欲行\n\n 忽然岸上踏歌声\n\n \
### Section \n\n \
桃花潭水深千尺 \n\n 
## 第二章\n\n \
不及汪伦送我情"""


print(markdown_document)
# Title

 ## 第一章

 李白乘舟将欲行

 忽然岸上踏歌声

 ### Section 

 桃花潭水深千尺 

 
## 第二章

 不及汪伦送我情
from langchain.text_splitter import MarkdownHeaderTextSplitter#markdown分割器


# 定义想要分割的标题列表和名称
headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]

markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on
)#message_type

md_header_splits = markdown_splitter.split_text(markdown_document)
print(len(md_header_splits),md_header_splits)

print(md_header_splits[0])
print(md_header_splits[1])
print(md_header_splits[2])
3 
[Document(page_content='李白乘舟将欲行  \n忽然岸上踏歌声', metadata={'Header 1': 'Title', 'Header 2': '第一章'}), Document(page_content='桃花潭水深千尺', metadata={'Header 1': 'Title', 'Header 2': '第一章', 'Header 3': 'Section'}), Document(page_content='不及汪伦送我情', metadata={'Header 1': 'Title', 'Header 2': '第二章'})]
page_content='李白乘舟将欲行  \n忽然岸上踏歌声' metadata={'Header 1': 'Title', 'Header 2': '第一章'}
page_content='桃花潭水深千尺' metadata={'Header 1': 'Title', 'Header 2': '第一章', 'Header 3': 'Section'}
page_content='不及汪伦送我情' metadata={'Header 1': 'Title', 'Header 2': '第二章'}

5.2、分割数据库中的 Markdown 文档

在上一章中,我们尝试了 Notion 数据库的加载,Notion 文档就是一个 Markdown 文档。我们在此处加载 Notion 数据库中的文档并进行分割。

from langchain.document_loaders import NotionDirectoryLoader#Notion加载器
loader = NotionDirectoryLoader("./data/Notion_DB")
docs = loader.load()
txt = ' '.join([d.page_content for d in docs])# 如果Notion_DB下有多个md文件,那就拼一起


headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
]


#加载文档分割器
markdown_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=headers_to_split_on
)

md_header_splits = markdown_splitter.split_text(txt)#分割文本内容

print(len(md_header_splits), md_header_splits[0])#分割结果
8 
page_content="This is a living document with everything we've learned working with people while running a startup. And, of course, we continue to learn. ...."
 metadata={'Header 1': "Blendle's Employee Handbook (1)"}

Reference

  • [1] 吴恩达老师的教程
  • [2] DataWhale组织

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手机app、pc客户端(芯象推送到wvp视频平台)

手机app&#xff08;芯象推送到wvp视频平台&#xff09; 下载安装 进入苹果应用商店&#xff0c;搜索芯象&#xff0c;点击下载&#xff0c;下载成功之后点击打开 注册账号进行登录&#xff0c;下图是主界面&#xff0c;点击开始直播进入直播配置界面 推流直播 选择本地推流&a…

IDEA调用接口超时,但Postman可成功调用接口

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

SpringCloud微服务通信两种方式Feign和Dubbo:Feign基本使用、自定义配置、使用优化;Dubbo基本实现

RestTemplate存在的问题 代码可读性差&#xff0c;编程体验不统一参数复杂&#xff0c;URL难以维护 Feign远程调用 Feign简介 ​ Feign是SpringCloud提供的一个声明式的伪Http客户端&#xff0c;它使得调用远程服务就像调用本地服务一样简单&#xff0c;只需要创建一个接口…

【广州华锐互动】VR虚拟现实技术助力太空探险:穿越时空,探索宇宙奥秘

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐走进我们的生活。在教育领域&#xff0c;VR技术的应用也日益广泛&#xff0c;为学生提供了更加生动、直观的学习体验。本文将以利用VR开展太空探险学习为主题&#xff0c;探讨如何将这一先进技术…

【数据库】你听说过矢量数据库吗?

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️其他领域】 文章目录 前言什么是向量/矢量数据库嵌入模型使用向量数据库的优势与传统数据库的对比其他方面 AWS 如何支持您的矢量数据库需求&#xff1f;Amazon OpenSearch ServiceAmazon Aurora Pos…

毕业设计JSP 2384网上diy蛋糕店管理系统【程序源码+讲解视频+调试运行】

一、摘要 本文将介绍一个功能全面、易于使用的网上DIY蛋糕店管理系统。该系统包括用户和管理员两种用户&#xff0c;每种用户都有相应的功能模块。系统实现了网站首页、用户注册/登录、蛋糕展示、综合排行、购物车、蛋糕DIY和用户中心等功能&#xff0c;同时管理员还可以进行管…

Java —— 抽象类和接口

目录 1. 抽象类 1.1 抽象类概念 1.2 抽象类语法与特性 1.3 抽象类的作用 2. 接口 2.1 接口的概念 2.2 接口的语法规则与特性 2.3 实现多个接口(解决多继承的问题) 2.4 接口间的继承 2.5 抽象类和接口的区别 2.6 接口的使用实例 2.7 Clonable 接口和深拷贝 2.7.1 Cloneable接口 …

【前端学java】java中的Object类(8)

往期回顾&#xff1a; 【前端学java】JAVA开发的依赖安装与环境配置 &#xff08;0&#xff09;【前端学 java】java的基础语法&#xff08;1&#xff09;【前端学java】JAVA中的packge与import&#xff08;2&#xff09;【前端学java】面向对象编程基础-类的使用 &#xff08…

归并排序详解:递归实现+非递归实现(图文详解+代码)

文章目录 归并排序1.递归实现2.非递归实现 归并排序 时间复杂度&#xff1a;O ( N * logzN ) 每一层都是N,有log2N层空间复杂度&#xff1a;O&#xff08;N&#xff09;&#xff0c;每个区间都会申请内存&#xff0c;最后申请的数组大小和array大小相同稳定性&#xff1a;稳定 …

Linux从 全栈开发 centOS 7 到 运维

Linux从 全栈开发centOS 7 到 运维 一 Linux 入门概述1.1 操作系统1.2 Linux 简介1.3 Linux 系统组成1.4 Linux 发行版1.4 Linux 应用领域1.5 Linux vs Windows 二 环境搭建【狂神说Java】服务器购买及宝塔部署环境说明为什么程序员都需要一个自己的服务器服务器如何购买买完服…

中国农业开启加速度,龙江农业迎来黄金期

​ “中国下一个发展动力将是大农业&#xff0c;而黑龙江大农业正在成为世界农业中心。” 在前不久举办的首届龙商大会暨中国&#xff08;黑龙江&#xff09;国际绿色食品产业高质量发展论坛&#xff08;下文简称“论坛”&#xff09;上&#xff0c;大北农科技集团股份有限公…

OpenCV快速入门:直方图、掩膜、模板匹配和霍夫检测

文章目录 前言一、直方图基础1.1 直方图的概念和作用1.2 使用OpenCV生成直方图1.3 直方图归一化1.3.1 直方图归一化原理1.3.2 直方图归一化公式1.3.3 直方图归一化代码示例1.3.4 OpenCV内置方法&#xff1a;normalize()1.3.4.1 normalize()方法介绍1.3.4.2 normalize()方法参数…

Javaweb之Ajax的详细解析

1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xff0c;所以…

前缀和(c++,超详细,含二维)

前缀和与差分 当给定一段整数序列a1,a2,a3,a4,a5…an; 每次让我们求一段区间的和&#xff0c;正常做法是for循环遍历区间起始点到结束点&#xff0c;进行求和计算&#xff0c;但是当询问次数很多并且区间很长的时候 比如&#xff0c;10^5 个询问和10^6区间长度&#xff0c;相…

Java语法基础

回顾 1、了解编程语言 2、编程语言分类 ​ 机器语言、汇编语言、高级语言 3、了解java ​ 跨平台&#xff08;.class文件&#xff09; .java&#xff08;源文件&#xff09; ​ .java ----编译---->.class 4、jdk 、jre、jvm 5、开发 写代码 eclipse idea 记事本 …

企业级SSD还是一个巨大的蓝海~

根据Allied Market Research市场分析报告显示&#xff0c;2020 年全球企业级 SSD 市场规模为 178.5 亿美元&#xff0c;预计到 2030 年将达到 468.9 亿美元&#xff0c;2021 年至 2030 年的复合年增长率为 10.2%。 扩展阅读&#xff1a;华为展望&#xff5c;2030年数据中心存储…

科技云报道:全球勒索攻击创历史新高,如何建立网络安全的防线?

科技云报道原创。 最简单的方式&#xff0c;往往是最有效的&#xff0c;勒索软件攻击就属于这类。 近两年&#xff0c;随着人类社会加速向数字世界进化&#xff0c;勒索软件攻击成为网络安全最为严重的威胁之一。今年以来&#xff0c;勒索软件攻击在全球范围内呈现快速上升态…

亚马逊、eBay如何提升测评环境的安全性?解决砍单和F号问题

跨境平台的风控不是一层不会变的&#xff0c;特别年底风控最为严格。亚马逊的风控升级都是大规模持续进行的。如果测评环境没有相应更新&#xff0c;可能会导致大量订单被取消&#xff0c;账号被F&#xff0c;甚至店铺被关联&#xff0c;因此针对风控升级至关重要。 今年&…

微信私域运营工具CRM

为什么要做微信私域&#xff1f; 客户在哪里&#xff1f;微信&#xff01;在中国&#xff0c;不论男女老少&#xff0c;90%的人每天使用微信至少5次&#xff0c;每次使用时间超过90分钟&#xff0c;已经成为像吃饭穿衣一样的生活必需品。因此&#xff0c;我们的目标客户就在微…

【数据结构】详解链表结构

目录 引言一、链表的介绍二、链表的几种分类三、不带头单链表的一些常用接口3.1 动态申请一个节点3.2 尾插数据3.3 头插数据3.4 尾删数据3.5 头删数据3.6 查找数据3.7 pos位置后插入数据3.8 删除pos位置数据3.9 释放空间 四、带头双向链表的常见接口4.1创建头节点&#xff08;初…