【LangChain系列】第二篇:文档拆分简介及实践

本文涉及的产品
交互式建模 PAI-DSW,每月250计算时 3个月
模型在线服务 PAI-EAS,A10/V100等 500元 1个月
模型训练 PAI-DLC,5000CU*H 3个月
简介: 【5月更文挑战第15天】本文介绍了LangChain中文档拆分的重要性及工作原理。文档拆分有助于保持语义内容的完整性,对于依赖上下文的任务尤其关键。LangChain提供了多种拆分器,如CharacterTextSplitter、RecursiveCharacterTextSplitter和TokenTextSplitter,分别适用于不同场景。MarkdownHeaderTextSplitter则能根据Markdown标题结构进行拆分,保留文档结构。通过实例展示了如何使用这些拆分器,强调了选择合适拆分器对提升下游任务性能和准确性的影响。

[toc]


在上一篇博客中,我们学习了如何使用LangChain的文档加载器将文档加载为标准格式。加载文档后,下一步是将它们拆分为更小的块。这个过程乍一看似乎很简单,但有一些微妙之处和重要的考虑因素会显着影响下游任务的性能和准确性。

一、为什么文档拆分很重要

文档拆分至关重要,因为它可以确保语义相关的内容在同一块中组合在一起。在回答问题或执行依赖于文档中存在的上下文信息的其他任务时,这一点尤为重要。
image.png

考虑以下示例:假设我们有一句关于丰田凯美瑞及其规格的句子。如果我们天真地拆分这个句子,而不考虑上下文,我们最终可能会得到一个包含句子部分的块和另一个包含剩余部分的块。因此,当试图回答有关凯美瑞规格的问题时,我们都不会在任何一个块中获得完整的信息,从而导致答案不正确或不完整。

二、文档拆分在LangChain中是如何工作的?

LangChain中所有文本拆分器的基础是将文本拆分为指定大小的块,相邻块之间有可选的重叠。下图对此进行了说明:
image.png

对应于每个块的大小,可以用字符或标记来衡量。这是在连续块之间共享的文本的一部分,允许跨块维护上下文boundaries.chunk_sizechunk_overlap。

三、文本拆分类型

LangChain提供了几种类型的文本拆分器,每种都有自己的优势和用例。以下是一些最常用的分离器:
image.png

1.CharacterTextSplitter

一个基本的拆分器,它基于单个字符分隔符(如空格或换行符)拆分文本。在处理结构不清晰的文本或想要在特定点拆分文本时,此拆分器非常有用。

2.RecursiveCharacterTextSplitter

用于通用文本拆分,它根据分隔符的层次结构拆分文本,从双换行符开始,然后是单换行符 、空格,最后是单个字符。这种方法旨在通过优先考虑段落和句子等自然边界的拆分来保持文本的结构和连贯性。RecursiveCharacterTextSplitternnn

3.TokenTextSplitter

根据标记计数而不是字符计数拆分文本,因为许多语言模型都具有由标记计数而不是字符计数指定的上下文窗口。标记的长度通常约为四个字符,因此基于标记计数进行拆分可以更好地表示语言模型将如何处理文本。TokenTextSplitter

4.MarkdownHeaderTextSplitter

旨在根据标题结构拆分 Markdown 文档。它将标头元数据保留在生成的块中,从而允许上下文感知拆分和使用文档结构的潜在下游任务。MarkdownHeaderTextSplitter

四、上手实例

让我们探索一些示例,以更好地了解这些文本拆分器的工作原理以及如何有效地使用它们。

1.设置环境

通过导入必要的库并加载 OpenAI API 密钥来设置环境:

import os
from langchain_openai import OpenAI
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY")
)

接下来,我们将导入两个最常用的文本拆分器:

from langchain_text_splitters import (
    CharacterTextSplitter,
    RecursiveCharacterTextSplitter,
)

2.使用CharacterTextSplitter和RecursiveCharacterTextSplitter拆分

让我们从定义一些示例开始,以了解这些分离器的工作原理:

chunk_size = 26
chunk_overlap = 4

r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=chunk_size, chunk_overlap=chunk_overlap
)
c_splitter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)

text1 = "abcdefghijklmnopqrstuvwxyz"
print(r_splitter.split_text(text1))  
# Output: ['abcdefghijklmnopqrstuvwxyz']

text2 = "abcdefghijklmnopqrstuvwxyzabcdefg"
print(r_splitter.split_text(text2))  
# Output: ['abcdefghijklmnopqrstuvwxyz', 'wxyzabcdefg']

text3 = "a b c d e f g h i j k l m n o p q r s t u v w x y z"
print(r_splitter.split_text(text3))  
# Output: ['a b c d e f g h i j k l m', 'l m n o p q r s t u v w x', 'w x y z']
print(c_splitter.split_text(text3))  
# Output: ['a b c d e f g h i j k l m n o p q r s t u v w x y z']

# Set the separator for CharacterTextSplitter
c_splitter = CharacterTextSplitter(
    chunk_size=chunk_size, chunk_overlap=chunk_overlap, separator=" "
)
print(c_splitter.split_text(text3))  
# Output: ['a b c d e f g h i j k l m', 'l m n o p q r s t u v w x', 'w x y z']

这些示例演示了如何根据指定的 和 拆分文本,而如何基于单个字符分隔符(在本例中为空格)拆分文本。

3.真实示例

尝试拆分一些真实世界的例子:

some_text = """When writing documents, writers will use document structure to group content. \
This can convey to the reader, which idea's are related. For example, closely related ideas \
are in sentances. Similar ideas are in paragraphs. Paragraphs form a document. \\n\\n  \
Paragraphs are often delimited with a carriage return or two carriage returns. \
Carriage returns are the "backslash n" you see embedded in this string. \
Sentences have a period at the end, but also, have a space.\
and words are separated by space."""

c_splitter = CharacterTextSplitter(chunk_size=450, chunk_overlap=0, separator=" ")
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=450, chunk_overlap=0, separators=["\n\n", "\n", " ", ""]
)

chunks = c_splitter.split_text(some_text)
print("Chunks: ", chunks)
print("Length of chunks: ", len(chunks))
# Chunks:  ['When writing documents, writers will use document structure to group content. This can convey to the reader, which idea\'s are related. For example, closely related ideas are in sentances. Similar ideas are in paragraphs. Paragraphs form a document. \n\n Paragraphs are often delimited with a carriage return or two carriage returns. Carriage returns are the "backslash n" you see embedded in this string. Sentences have a period at the end, but also,', 'have a space.and words are separated by space.']
# Length of chunks:  2

chunks = r_splitter.split_text(some_text)
print("Chunks: ", chunks)
print("Length of chunks: ", len(chunks))
# Chunks:  ["When writing documents, writers will use document structure to group content. This can convey to the reader, which idea's are related. For example, closely related ideas are in sentances. Similar ideas are in paragraphs. Paragraphs form a document.", 'Paragraphs are often delimited with a carriage return or two carriage returns. Carriage returns are the "backslash n" you see embedded in this string. Sentences have a period at the end, but also, have a space.and words are separated by space.']
# Length of chunks:  2

在此示例中,它基于空格拆分文本,而第一个尝试拆分双换行符,然后是单换行符、空格,最后是单个字符。CharacterTextSplitterRecursiveCharacterTextSplitterCharacterTextSplitterRecursiveCharacterTextSplitter

我们还可以拆分真实世界的文档,例如 PDF 和 Notion 数据库:

from langchain.document_loaders import PyPDFLoader, NotionDirectoryLoader

# Load a PDF document
loader = PyPDFLoader("docs/cs229_lectures/MachineLearning-Lecture01.pdf")
pages = loader.load()

text_splitter = CharacterTextSplitter(
    separator="\n", chunk_size=1000, chunk_overlap=150, length_function=len
)
docs = text_splitter.split_documents(pages)

print("Pages in the original document: ", len(pages))
print("Length of chunks after splitting pages: ", len(docs))
# Pages in the original document:  22
# Length of chunks after splitting pages:  353

此代码使用 加载 PDF 文档,将页面拆分为较小的块,并打印原始页数和生成的块数。PyPDFLoaderCharacterTextSplitter

# Load a Notion database
loader = NotionDirectoryLoader("docs/Notion_DB")
notion_db = loader.load()

docs = text_splitter.split_documents(notion_db)

print("Pages in the original notion document: ", len(notion_db))
print("Length of chunks after splitting pages: ", len(docs))
# Pages in the original notion document:  52
# Length of chunks after splitting pages:  353

类似地,我们可以加载一个 Notion 数据库,将文档拆分为块,并打印原始文档的数量和生成的块。NotionDirectoryLoader

五、Token-based拆分

除了基于字符的拆分之外,LangChain还支持基于令牌的拆分,这在使用具有由令牌计数指定的上下文窗口的语言模型时非常有用:

from langchain.text_splitter import TokenTextSplitter

text_splitter = TokenTextSplitter(chunk_size=1, chunk_overlap=0)
text1 = "foo bar bazzyfoo"
print(text_splitter.split_text(text1))  
# Output: ['foo', ' bar', ' b', 'az', 'zy', 'foo']

text_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0)
docs = text_splitter.split_documents(pages)
print(docs[0])  
# Output: Document(page_content='MachineLearning-Lecture01  \n', metadata={'source': 'docs/cs229_lectures/MachineLearning-Lecture01.pdf', 'page': 0})
print(pages[0].metadata)
# Output: {'source': 'docs/cs229_lectures/MachineLearning-Lecture01.pdf', 'page': 0}

在此示例中,我们使用 to 根据令牌计数拆分文本。我们可以调整 and 参数来控制拆分行为。TokenTextSplitterchunk_sizechunk_overlap

六、Context-aware拆分

LangChain还提供了上下文感知拆分的工具,旨在在拆分过程中保留文档结构和语义上下文。它根据文档的标题结构拆分 Markdown 文档,并将标题元数据保留在生成的块中:MarkdownHeaderTextSplitter

from langchain.document_loaders import NotionDirectoryLoader
from langchain.text_splitter import MarkdownHeaderTextSplitter

markdown_document = """# Title\n\n \
## Chapter 1\n\n \
Hi this is Jim\n\n Hi this is Joe\n\n \
### Section \n\n \
Hi this is Lance \n\n 
## Chapter 2\n\n \
Hi this is Molly"""

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

markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_document)

print(md_header_splits[0])  
# Output: Document(page_content='Hi this is Jim  \nHi this is Joe', metadata={'Header 1': 'Title', 'Header 2': 'Chapter 1'})
print(md_header_splits[1])  
# Output: Document(page_content='Hi this is Lance', metadata={'Header 1': 'Title', 'Header 2': 'Chapter 1', 'Header 3': 'Section'})

在此示例中,我们定义一个带有标题的 Markdown 文档,并根据标题结构拆分文档。生成的块保留标头元数据,这对于利用文档结构的下游任务非常有用。MarkdownHeaderTextSplitter

我们还可以将此拆分器Notion 数据库:

loader = NotionDirectoryLoader("docs/Notion_DB")
docs = loader.load()
txt = " ".join([d.page_content for d in docs])

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(md_header_splits[0])

此代码加载一个 Notion 数据库,将文档内容联接到单个字符串中,拆分字符串,并打印第一个生成的块。MarkdownHeaderTextSplitter

小结

文档拆分是LangChain流水线中的关键步骤,因为它确保语义相关的内容在同一块中组合在一起。LangChain提供了各种文本拆分器,每个拆分器都有自己的优势和用例,允许您根据自己的特定需求选择最合适的拆分器。

无论您是处理通用文本、Markdown 文档、代码片段还是其他类型的内容,LangChain 的文本拆分器都提供了灵活性和自定义选项,可以有效地拆分您的文档。通过了解文档拆分中涉及的细微差别和注意事项,可以优化语言模型和下游任务的性能和准确性。

相关实践学习
阿里云百炼xAnalyticDB PostgreSQL构建AIGC应用
通过该实验体验在阿里云百炼中构建企业专属知识库构建及应用全流程。同时体验使用ADB-PG向量检索引擎提供专属安全存储,保障企业数据隐私安全。
AnalyticDB PostgreSQL 企业智能数据中台:一站式管理数据服务资产
企业在数据仓库之上可构建丰富的数据服务用以支持数据应用及业务场景;ADB PG推出全新企业智能数据平台,用以帮助用户一站式的管理企业数据服务资产,包括创建, 管理,探索, 监控等; 助力企业在现有平台之上快速构建起数据服务资产体系
目录
相关文章
|
1月前
|
JSON 数据可视化 NoSQL
基于LLM Graph Transformer的知识图谱构建技术研究:LangChain框架下转换机制实践
本文介绍了LangChain的LLM Graph Transformer框架,探讨了文本到图谱转换的双模式实现机制。基于工具的模式利用结构化输出和函数调用,简化了提示工程并支持属性提取;基于提示的模式则为不支持工具调用的模型提供了备选方案。通过精确定义图谱模式(包括节点类型、关系类型及其约束),显著提升了提取结果的一致性和可靠性。LLM Graph Transformer为非结构化数据的结构化表示提供了可靠的技术方案,支持RAG应用和复杂查询处理。
104 2
基于LLM Graph Transformer的知识图谱构建技术研究:LangChain框架下转换机制实践
|
2月前
LangChain-06 RAG With Source Doc 通过文档进行检索增强
LangChain-06 RAG With Source Doc 通过文档进行检索增强
39 3
|
4月前
|
自然语言处理 搜索推荐 机器人
langchain 简介
langchain 简介
146 1
|
4月前
|
存储 自然语言处理 算法
【LangChain】如何本地部署基于chatGPT的实时文档和表格数据的助手,在自己的数据上构建chatGPT?
本文介绍了如何使用LangChain库和FAISS工具在本地部署一个基于chatGPT的实时文档和表格数据助手,详细阐述了项目原理、搭建步骤、环境配置、代码修改和运行流程,以及如何在自己的数据上构建和使用chatGPT。
62 1
|
4月前
|
机器学习/深度学习 自然语言处理 搜索推荐
LangChain在个性化内容生成中的实践
【8月更文第3天】随着人工智能技术的发展,个性化内容生成已经成为许多应用的核心竞争力。LangChain 是一种开源框架,旨在简化语言模型的应用开发,尤其是针对自然语言处理任务。本文将探讨 LangChain 如何帮助开发者根据用户的偏好生成定制化的内容,从挑战到实践策略,再到具体的案例分析和技术实现。
264 1
|
6月前
|
存储 人工智能 机器人
LangChain结合LLM做私有化文档搜索
我们知道LLM(大语言模型)的底模是基于已经过期的公开数据训练出来的,对于新的知识或者私有化的数据LLM一般无法作答,此时LLM会出现“幻觉”。针对“幻觉”问题,一般的解决方案是采用RAG做检索增强。
LangChain结合LLM做私有化文档搜索
|
7月前
|
机器学习/深度学习 人工智能
【LangChain系列】第九篇:LLM 应用评估简介及实践
【5月更文挑战第23天】本文探讨了如何评估复杂且精密的语言模型(LLMs)应用。通过创建QA应用程序,如使用GPT-3.5-Turbo模型,然后构建测试数据,包括手动创建和使用LLM生成示例。接着,通过手动评估、调试及LLM辅助评估来衡量性能。手动评估借助langchain.debug工具提供执行细节,而QAEvalChain则利用LLM的语义理解能力进行评分。这些方法有助于优化和提升LLM应用程序的准确性和效率。
573 8
|
7月前
|
存储 机器学习/深度学习 人工智能
【LangChain系列】第八篇:文档问答简介及实践
【5月更文挑战第22天】本文探讨了如何使用大型语言模型(LLM)进行文档问答,通过结合LLM与外部数据源提高灵活性。 LangChain库被介绍为简化这一过程的工具,它涵盖了嵌入、向量存储和不同类型的检索问答链,如Stuff、Map-reduce、Refine和Map-rerank。文章通过示例展示了如何使用LLM从CSV文件中提取信息并以Markdown格式展示
314 2
|
7月前
|
机器学习/深度学习 自然语言处理 数据挖掘
【LangChain系列】第七篇:工作流(链)简介及实践
【5月更文挑战第21天】LangChain是一个框架,利用“链”的概念将复杂的任务分解为可管理的部分,便于构建智能应用。数据科学家可以通过组合不同组件来处理和分析非结构化数据。示例中展示了如何使用LLMChain结合OpenAI的GPT-3.5-turbo模型,创建提示模板以生成公司名称和描述。顺序链(SimpleSequentialChain和SequentialChain)则允许按顺序执行多个步骤,处理多个输入和输出
1022 1
|
7月前
|
人工智能 安全 API
【LangChain系列】第十篇:数据保护简介及实践
【5月更文挑战第24天】本文探讨了在使用大型语言模型时保护个人数据的重要性,特别是涉及敏感信息如PII(个人可识别信息)的情况。为了降低数据泄露风险,文章介绍了数据匿名化的概念,通过在数据进入LLM前替换敏感信息。重点讲解了Microsoft的Presidio库,它提供了一个可定制的文本匿名化工具。此外,文章还展示了如何结合LangChain库创建一个安全的匿名化流水线,包括初始化匿名器、添加自定义识别器和操作符,以及在问答系统中集成匿名化流程。通过这种方式,可以在利用LLMs的同时保护数据隐私。
179 0

热门文章

最新文章