在上一篇文章中,我们介绍了大模型开发框架LangChain的基础功能加载文档,今天我们要介绍的是文档分割,也就是把文档分割成小段落。 开始之前,请你先想想为什么需要对文档做分割?如果可以想明白,那么学习下边内容就会更加轻松。 现在回答前边的问题,大模型要学习的内容是很多的,加载的文档也是很多的,如果不对文档分割,是没有办法把文档的内容喂给大模型的, 一是内存无法承接过量的数据,二是不利于大模型的学习,三是不利于结果的查询。

按字符数分隔

按字符数分隔,很好理解,就是每几个字符做一次分隔。 直接来看代码示例吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from langchain.document_loaders import UnstructuredWordDocumentLoader,PyPDFium2Loader,DirectoryLoader,PyPDFLoader,TextLoader
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter, CharacterTextSplitter

def load_pdf(directory_path):
    data = []
    for filename in os.listdir(directory_path):
        if filename.endswith(".pdf"):
            print(filename)
            # print the file name
            loader = PyPDFium2Loader(f'{directory_path}/{filename}')
            print(loader)
            data.append(loader.load())
    return data

def load_word(directory_path):
    data = []
    for filename in os.listdir(directory_path):
        # check if the file is a doc or docx file
        # 检查所有doc以及docx后缀的文件
        if filename.endswith(".doc") or filename.endswith(".docx"):
            # langchain自带功能,加载word文档
            loader = UnstructuredWordDocumentLoader(f'{directory_path}/{filename}')
            data.append(loader.load())

    return data
    
#注意这里填写的是文件夹目录    
documents = load_word("you directory_path")

r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=26, #块长度
    chunk_overlap=4 #重叠字符串长度
)
all_page_contents = [doc.page_content for doc in documents[0]]
for content in all_page_contents:
    print(r_splitter.split_text(content))

下边选取一段代码用kimi解释一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#注意这里填写的是文件夹目录    
documents = load_word("you directory_path")
# 初始化递归字符文本分割器,用于将文本分割成块
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=26, #块长度
    chunk_overlap=4 #重叠字符串长度
)
# 提取所有文档的页面内容
all_page_contents = [doc.page_content for doc in documents[0]]
# 遍历所有页面内容,并使用文本分割器进行分割
for content in all_page_contents:
    print(r_splitter.split_text(content))

我们看一下代码的输出结果:大家要特别关注一下重叠的字符串。 [‘卡莎莎乡村度假区是隐居乡里的第***个乡村民宿建设项’, ‘宿建设项目, 是我们在四川省的第一个在地共生项目,位于’, ‘目,位于乐山市马边县劳动镇枇杷老山顶,是一个彝汉多族’, ‘彝汉多族群杂居的村落。现有民宿是在原来的5处传统民宿’, ‘传统民宿的基础上改建而成。’, ‘马边县地处乐山、宜宾、凉山三市州结合部,素有“金山’, ‘有“金山银水“的美誉,是一个天然聚宝盆,也是一个蕴含’, ‘一个蕴含广阔发展前景、亟待挖掘开发的”处女地“。早在’, ‘“。早在西汉年间,汉族先民就沿马边河走廊进入马边聚落’, ‘马边聚落并栖息于此;元明时期,彝汉两族纳入以彝族首领’, ‘彝族首领为世袭土司的马湖路府。公元1589年,朝廷派’, ‘,朝廷派兵平息“三雄之乱”,并增设马湖府安边厅,“马’, ‘厅,“马边”之名由此得来。马边自古就是汉族和南方夷族’, ‘南方夷族杂居之地,在历史长河中沿袭多个民族文化并繁衍’, ‘化并繁衍至今形成独具特色的马边古彝文化。主要包括以彝’, ‘包括以彝族祈福、祭祀、经诵为主的毕摩文化、以山歌、情’, ‘山歌、情歌、传说、民俗为主的大众文化,’, ‘卡莎莎民宿度假区目前建成6个独立院落和4个树屋,同’, ‘树屋,同时还建成游客中心、茶文化博物馆、手工制茶工坊’, ‘制茶工坊、茶厂等产业配套设施。’, ‘马边县’]

想想这个分割结果有没有什么问题?因为这个分割是按字符数分割的,所以一个句子会被分割成两个块,同样也有可能2个比较短的句子被合并为一个块,这样就会影响大模型的对语义理解的学习效果。 这所以会这样,是因为RecursiveCharacterTextSplitter这个方法有一个参数separators,用于指定分隔符。默认的是["\n\n", “\n”, " “, “”],这些 适用于英文,对中文不友好。所以,如果是中文,我们需要指定分隔符,这样就可以保证一个句子不会被分割成两个块。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
r_splitter = RecursiveCharacterTextSplitter(
    chunk_size=10, #块长度
    chunk_overlap=10, #重叠字符串长度
    separators=[
        "\u200B",  # 空格
        "\uff0c",  # 全角逗号(,)
        "\u3001",  # 全角顿号(、)
        "\uff0e",  # 全角句号(。)
        "\u3002"  # 全角句号(。)
    ] #对于中文标点,需要写对应的Unicode编码
)

下边是分割结果: [‘卡莎莎乡村度假区是隐居乡里的第***个乡村民宿建设项目’, ‘,是我们在四川省的第一个在地共生项目’, ‘,位于乐山市马边县劳动镇枇杷老山顶’, ‘,是一个彝汉多族群杂居的村落’, ‘。现有民宿是在原来的5处传统民宿的基础上改建而成’, ‘。\n\n 马边县地处乐山’, ‘、宜宾’, ‘、凉山三市州结合部’, ‘,素有“金山银水“的美誉’, ‘,是一个天然聚宝盆’, ‘,也是一个蕴含广阔发展前景’, ‘、亟待挖掘开发的”处女地“’, ‘。早在西汉年间’, ‘,汉族先民就沿马边河走廊进入马边聚落并栖息于此;元明时期’, ‘,彝汉两族纳入以彝族首领为世袭土司的马湖路府’, ‘。公元1589年’, ‘,朝廷派兵平息“三雄之乱”’, ‘,并增设马湖府安边厅’, ‘,“马边”之名由此得来’, ‘。马边自古就是汉族和南方夷族杂居之地’, ‘,在历史长河中沿袭多个民族文化并繁衍至今形成独具特色的马边古彝文化’, ‘。主要包括以彝族祈福’, ‘、祭祀’, ‘、经诵为主的毕摩文化’, ‘、以山歌、情歌、传说’, ‘、民俗为主的大众文化’, ‘,\n\n卡莎莎民宿度假区目前建成6个独立院落和4个树屋’, ‘,同时还建成游客中心’, ‘、茶文化博物馆’, ‘、手工制茶工坊’, ‘、茶厂等产业配套设施’, ‘。\n\n 马边县’]

按Token 分割

下边我们看另一种常见的分割方式,按Token分隔。先来解释一下什么是token,用过GPT工具的都知道,大部分gpt工具都是按token来计费的,一个token就是大 模型学习的最小词语单位。任何形式的文档,最后都需要转换为token,让大模型来学习。大模型返回的结果,也是由很多token组成,经过转义后,成为我们看到的 结果。 默认,每个token是4个字符。 直接来看代码示例吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from langchain.document_loaders import UnstructuredWordDocumentLoader,PyPDFium2Loader,DirectoryLoader,PyPDFLoader,TextLoader
import os
from langchain.text_splitter import TokenTextSplitter

def load_word(directory_path):
    data = []
    for filename in os.listdir(directory_path):
        # check if the file is a doc or docx file
        # 检查所有doc以及docx后缀的文件
        if filename.endswith(".doc") or filename.endswith(".docx"):
            # langchain自带功能,加载word文档
            loader = UnstructuredWordDocumentLoader(f'{directory_path}/{filename}')
            data.append(loader.load())

    return data
    
documents = load_word("/Users/liuzhifeng/miniconda3/envs/ai_all_stack")


text_splitter = TokenTextSplitter(chunk_size=10, chunk_overlap=0)

all_page_contents = [doc.page_content for doc in documents[0]]
for content in all_page_contents:
    print(text_splitter.split_text(content))

下边是分割结果: [‘卡莎莎乡’, ‘村度假区是’, ‘隐居乡里的’, ‘第***个乡村’, ‘民宿建设’, ‘项目,是�’, ‘�们在四川�’, ‘�的第一个在’, ‘地共生项目’, ‘,位于乐�’, ‘��市马边’, ‘县劳动镇�’, ‘��杷老山�’, ‘��,是一个�’, ‘�汉多族�’, ‘�杂居的村�’, ‘��。现有民’, ‘宿是在原来的’, ‘5处传统民’, ‘宿的基础上’, ‘改建而成’, ‘。\n\n 马�’, ‘�县地处乐�’, ‘��、宜宾、凉’, ‘山三市州�’, ‘�合部,�’, ‘�有“金山’, ‘银水“的�’, ‘�誉,是一个’, ‘天然聚宝盆’, ‘,也是一个�’, ‘��含广阔�’, ‘�展前景、’, ‘亟待挖掘’, ‘开发的”处女’, ‘地“。早在�’, ‘��汉年间�’, ‘��汉族先�’, ‘�就沿马�’, ‘��河走�’, ‘�进入马�’, ‘�聚落并�’, ‘�息于此�’, ‘�元明时期�’, ‘��彝汉两�’, ‘�纳入以彝’, ‘族首领为’, ‘世袭土司的’, ‘马湖路�’, ‘�。公元1589年’, ‘,朝廷�’, ‘�兵平息“’, ‘三雄之乱”�’, ‘�并增设�’, ‘��湖府安�’, ‘��厅,“�’, ‘��边”之名’, ‘由此得来。�’, ‘��边自古’, ‘就是汉族�’, ‘�南方夷族杂’, ‘居之地,�’, ‘�历史长�’, ‘�中沿袭多’, ‘个民族文化’, ‘并繁衍至’, ‘今形成独具’, ‘特色的马�’, ‘��古彝文化’, ‘。主要包括’, ‘以彝族祈�’, ‘��、祭祀、’, ‘经诵为主的’, ‘毕摩文化’, ‘、以山歌、�’, ‘�歌、传说、�’, ‘�俗为主的大�’, ‘�文化,\n\n’, ‘卡莎莎民’, ‘宿度假区�’, ‘�前建成6�’, ‘�独立院�’, ‘�和4个树�’, ‘��,同时�’, ‘�建成游�’, ‘�中心、茶文’, ‘化博物馆、’, ‘手工制茶�’, ‘�坊、茶厂’, ‘等产业配�’, ‘�设施。\n\n ‘, ’ 马边县’]

总结:文档分割是大模型开发中的一个重要步骤,不同的分割方式会影响大模型的学习效果,所以在分割文档的时候,需要根据文档的内容,选择合适的分割方式。