【大模型从入门到精通23】开源库框架LangChain 深入探讨文本分割3


在这里插入图片描述

理论问题

  1. 文档分割在文本处理中的主要目标是什么?
  2. 片段大小如何影响文档片段的处理?
  3. 为什么片段重叠在文档分割中很重要?它是如何促进数据分析的?
  4. 比较并对比Lang Chain提供的CharacterTextSplitterTokenTextSplitter。它们的主要区别和应用领域是什么?
  5. 解释递归字符文本分割器的概念。它与基础分割器在处理文本方面有何不同?
  6. 在处理代码和Markdown文档的背景下,Lang Chain提供了哪些专业的分割器?这些分割器是如何解决这些文档类型的独特需求的?
  7. 描述一个旨在进行文档分割的开发环境的设置过程。需要哪些关键组件和配置?
  8. 讨论使用RecursiveCharacterTextSplitter分割通用文本的优点和挑战。包括可能调整的参数示例以优化分割。
  9. 通过使用不同分割器分割字母表字符串的例子,说明简单文本分割器和递归文本分割器的操作差异是如何体现的?
  10. 在决定为大型语言模型(LLMs)使用基于字符的分割技术和基于词的分割技术时,应该考虑哪些因素?
  11. 解释如何通过Markdown头部文本分割来保留文档的逻辑组织,以及这对文档分析为何重要。
  12. 概述确保语义连贯性和最优重叠管理的最佳实践。

实践问题

  1. 编写一个名为split_by_char的Python函数,接受两个参数:text(一个字符串)和chunk_size(一个整数)。该函数应返回一个列表,其中每个元素都是text的子字符串,长度为chunk_size,除了最后一个可能较短的元素。使用循环实现此功能。

  2. 修改split_by_char函数以接受一个额外的可选参数chunk_overlap(一个整数,默认值为0)。修改函数以在相邻片段之间包含chunk_overlap个字符的重叠。确保第一个片段仍从文本的开头开始,并相应地调整函数。

  3. 创建一个名为TokenTextSplitter的Python类,其初始化器接受两个参数:chunk_sizechunk_overlap(默认值为0)。在此类中实现一个名为split_text的方法。该方法应接受一个字符串text并返回一个列表,其中每个元素最多包含chunk_size个词,同时考虑到片段之间的chunk_overlap。为了这个任务,假设词是由空格分隔的。

  4. 编写一个名为recursive_split的函数,接受三个参数:text(一个字符串)、max_chunk_size(一个整数)和separators(一个字符串列表,按照它们应该被应用的顺序排列)。该函数应递归地在separators中的第一个分隔符处分割text,如果text的长度大于max_chunk_size。如果在第一个分隔符处的分割没有将任何片段的大小减少到max_chunk_size以下,它应尝试使用列表中的下一个分隔符进行分割,以此类推。该函数应返回一个列表,其中每个元素的大小小于或等于max_chunk_size。为了简化,可以假设每个分隔符字符串在text中最多出现一次。

  5. 实现一个名为MarkdownHeaderTextSplitter的Python类,其初始化器接受一个参数:headers_to_split_on(一个元组列表,每个元组包含两个元素:一个字符串表示Markdown头部语法,如###,以及一个字符串表示头部级别的名称,如“Header 1”或“Header 2”)。添加一个名为split_text的方法,该方法接受一个字符串markdown_text并根据headers_to_split_on中指定的头部将其分割成片段。每个片段应包含头部及其后续文本,直到遇到相同或更高优先级的下一个头部。该方法应返回这些片段的列表。根据headers_to_split_on中的头部确定分割优先级,列表中的早期条目具有更高的优先级。

文档分割理论

一、文档分割的主要目标

文档分割在文本处理中的主要目的是创建语义上意义明确的块,以促进高效的数据检索和分析。确保数据被组织成既易于管理又便于各种应用分析的形式。

二、文档块大小的影响
  • 块大小决定了每个文档块的长度,这影响了数据分析的粒度。
    • 较大的块大小可能保留更多的上下文,但处理起来较为笨重;
    • 较小的块大小可能导致上下文丢失,但更易于进行详细的分析。
三、块重叠的重要性
  • 块重叠确保关键信息不会在块的边界处丢失,保持上下文连续性。
    • 这种重叠使得数据检索和分析更加连贯,防止紧密相关的信息被分割开。
四、不同类型的文本分割器
  • CharacterTextSplitter基于特定数量的字符来分割文本,适用于简单的块化需求,不太关注语义完整性。
  • TokenTextSplitter基于词元(token)来分割文本,特别适用于准备符合大型语言模型(LLM)特定词元限制的数据,从而确保块与模型的处理能力对齐。
五、递归字符文本分割器
  • 递归字符文本分割器更为复杂,它能够基于层次化的分隔符(如段落、句子、词)递归地分割文本,允许进行保持语义连贯性的精细分割。
六、针对特定文档类型的分割器
  • Language Text Splitter识别特定编程语言的语法和分隔符来适当分割代码块。
  • Markdown Header Text Splitter根据Markdown文档的标题级别分割文档,并向块元数据添加标题信息以增强上下文。
七、开发环境的搭建
  • 搭建文档分割的开发环境涉及导入必要的库、配置API密钥、确保所有依赖项正确安装,以及可能需要添加路径以访问自定义模块。
  • 这样做是为了确保环境已经准备好进行高效的文档处理。
八、RecursiveCharacterTextSplitter的优势
  • RecursiveCharacterTextSplitter通过精细分割保持语义完整性,适应文档结构。
  • 调整参数如块大小、块重叠和递归深度可以优化分割器对于特定文本的表现。
九、不同分割器的应用实例
  • 分割字母表字符串时,简单的分割器可能会均匀地划分文本,而递归分割器会考虑文本内的语义单元,得到更好地保留原意或结构的块。
十、选择字符分割与词元分割
  • 在为LLM选择字符分割与词元分割技术时,需要考虑的因素包括:
    • 模型的词元限制;
    • 语义完整性的优先级;
    • 处理文本的性质。
  • 词元分割使块更接近于模型的处理能力,有可能提高分析准确性。
十一、Markdown标题文本分割
  • Markdown标题文本分割通过基于标题级别分割文档来保留文档的逻辑结构。
    • 这种方法对于文档分析非常重要,因为它确保了结果块保持原始结构和上下文,便于更好地理解和导航内容。
十二、确保语义连贯性和最优重叠管理的最佳实践
  • 确保语义连贯性和最优重叠管理的最佳实践包括:
    • 优先采用维护文本意义和上下文的策略;
    • 实验不同的重叠大小以找到防止冗余同时保持连续性的平衡点;
    • 增强块元数据以提供上下文信息。

实践题目解答

1. 编写一个名为split_by_char的Python函数
def split_by_char(text, chunk_size):
    """
    将文本分割成指定大小的片段。

    参数:
    - text (str): 要分割的文本。
    - chunk_size (int): 每个片段的大小。

    返回:
    - list: 文本片段的列表。
    """
    chunks = []  # 初始化列表以保存片段
    for start_index in range(0, len(text), chunk_size):
        # 将片段添加到列表中,该片段是文本的一个子串,从start_index开始到start_index + chunk_size结束
        chunks.append(text[start_index:start_index + chunk_size])
    return chunks

# 示例使用
text = "这是一个示例文本,用于演示目的。"
chunk_size = 10

chunks = split_by_char(text, chunk_size)
for i, chunk in enumerate(chunks):
    print(f"片段 {i+1}: {chunk}")
2. 修改split_by_char函数以支持重叠
def split_by_char(text, chunk_size, chunk_overlap=0):
    """
    将文本分割成指定大小的片段,支持片段重叠。

    参数:
    - text (str): 要分割的文本。
    - chunk_size (int): 每个片段的大小。
    - chunk_overlap (int, 可选): 片段之间的重叠大小,默认为0。

    返回:
    - list: 文本片段的列表。
    """
    chunks = []  # 初始化列表以保存片段
    for start_index in range(0, len(text) - chunk_overlap, chunk_size - chunk_overlap):
        # 将片段添加到列表中,该片段是文本的一个子串,从start_index开始到start_index + chunk_size结束
        chunks.append(text[start_index:start_index + chunk_size])
    return chunks

# 示例使用
text = "这是一个示例文本,用于演示目的。"
chunk_size = 10
chunk_overlap = 4

chunks = split_by_char(text, chunk_size, chunk_overlap)
for i, chunk in enumerate(chunks):
    print(f"片段 {i+1}: {chunk}")
3. 创建一个名为TokenTextSplitter的Python类
class TokenTextSplitter:
    def __init__(self, chunk_size, chunk_overlap=0):
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap

    def split_text(self, text):
        tokens = text.split()  # 根据空格将文本分割成词
        chunks = []
        start_index = 0

        while start_index < len(tokens):
            # 确保结束索引不超过词的长度
            end_index = min(start_index + self.chunk_size, len(tokens))
            chunk = ' '.join(tokens[start_index:end_index])
            chunks.append(chunk)
            # 更新下一个片段的起始索引,考虑到重叠
            start_index += self.chunk_size - self.chunk_overlap
            if self.chunk_overlap >= self.chunk_size:
                print("警告: chunk_overlap 应小于 chunk_size 以避免重叠问题。")
                break

        return chunks

# 示例使用
text = "这是一个示例文本,用于演示目的。"
chunk_size = 4
chunk_overlap = 2

splitter = TokenTextSplitter(chunk_size, chunk_overlap)
chunks = splitter.split_text(text)
for i, chunk in enumerate(chunks):
    print(f"片段 {i+1}: {chunk}")
4. 编写一个名为recursive_split的函数
def recursive_split(text, max_chunk_size, separators):
    if not separators:  # 基本情况:没有更多的分隔符可以尝试
        return [text]

    if len(text) <= max_chunk_size:  # 如果当前片段在大小限制内
        return [text]

    # 尝试使用第一个分隔符分割文本
    separator = separators[0]
    parts = text.split(separator)

    if len(parts) == 1:  # 如果文本不包含分隔符,则尝试下一个分隔符
        return recursive_split(text, max_chunk_size, separators[1:])

    chunks = []
    current_chunk = ""
    for part in parts:
        if len(current_chunk + part) > max_chunk_size and current_chunk:
            # 如果添加当前部分超过 max_chunk_size,则保存当前片段并重置
            chunks.append(current_chunk.strip())
            current_chunk = part + separator
        else:
            # 否则,将部分添加到当前片段
            current_chunk += part + separator

    # 确保最后的片段也被添加
    if current_chunk.strip():
        chunks.extend(recursive_split(current_chunk.strip(), max_chunk_size, separators))

    # 展平列表,以防递归调用产生嵌套列表
    flat_chunks = []
    for chunk in chunks:
        if isinstance(chunk, list):
            flat_chunks.extend(chunk)
        else:
            flat_chunks.append(chunk)

    return flat_chunks

# 示例使用
text = "这是一个示例文本,用于演示目的。这是另一段文本,用于进一步的演示。"
max_chunk_size = 15
separators = ["。", ","]

chunks = recursive_split(text, max_chunk_size, separators)
for i, chunk in enumerate(chunks):
    print(f"片段 {i+1}: {chunk}")
5. 实现MarkdownHeaderTextSplitter
import re

class MarkdownHeaderTextSplitter:
    def __init__(self, headers_to_split_on):
        self.headers_to_split_on = sorted(headers_to_split_on, key=lambda x: len(x[0]), reverse=True)
        self.header_regex = self._generate_header_regex()

    def _generate_header_regex(self):
        # 生成一个匹配任何指定头部的正则表达式模式
        header_patterns = [re.escape(header[0]) for header in self.headers_to_split_on]
        combined_pattern = '|'.join(header_patterns)
        return re.compile(r'(' + combined_pattern + r')\s*(.*)')

    def split_text(self, markdown_text):
        chunks = []
        current_chunk = []
        lines = markdown_text.split('\n')

        for line in lines:
            # 检查行是否以指定的头部开始
            match = self.header_regex.match(line)
            if match:
                # 如果已经在收集一个片段,则先保存再开始新的片段
                if current_chunk:
                    chunks.append('\n'.join(current_chunk).strip())
                    current_chunk = []

            # 将当前行添加到片段中
            current_chunk.append(line)

        # 不要忘记添加最后一个片段
        if current_chunk:
            chunks.append('\n'.join(current_chunk).strip())

        return chunks

# 示例使用
headers_to_split_on = [
    ("#", "一级标题"),
    ("##", "二级标题"),
    ("###", "三级标题"),
]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
markdown_text = """
# 一级标题
这是一个一级标题下的文本。
## 二级标题
这是一个二级标题下的文本。
### 三级标题
这是一个三级标题下的文本。
"""

chunks = splitter.split_text(markdown_text)
for i, chunk in enumerate(chunks):
    print(f"片段 {i+1}:\n{chunk}\n---")

以上代码实现了每个练习题的要求。

  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水木流年追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值