(21-5)基于Gemma 2B模型的智能文本摘要系统:文本摘要

9.5  文本摘要

文本摘要是自然语言处理(NLP)中的一项重要任务,旨在将长文本缩减为包含其核心信息的短文本。在本节的内容中,将详细讲解本项目实现文本摘要的过程。

9.5.1  方法和策略

在文本摘要应用中有许多可用的技术和方法,在本项目中将仅关注三种策略:Stuffing、MapReduce 和 Refine。

1. Stuffing(填充)

填充是一种抽取式摘要方法,它通过选择文本中最重要的句子或短语来创建摘要。这些句子被认为“填充”了原始文本的主要信息。具体填充方法通常依赖于特征工程,比如词频、句子位置、共引用等,来决定哪些部分是重要的。填充方法的优点是简单快速,但它可能不会很好地捕捉到文本的深层含义或上下文关系。

2. MapReduce(映射-归约)

MapReduce是一种计算模型,它允许在分布式系统上并行处理大量数据。在文本摘要中,MapReduce可以用来加速抽取重要句子或短语的过程。MapReduce将文本分割成多个部分(Map步骤),然后并行地识别每个部分中的关键句子,再将这些结果汇总起来(Reduce步骤),可以有效地生成摘要。MapReduce方法适用于需要处理大量数据或长文本的情况,因为它可以显著提高处理速度。

3. Refine(细化)

细化是一种迭代过程,它可能在抽取式或生成式摘要之后使用,目的是提高摘要的质量和连贯性,这可能包括去除重复信息、调整句子结构以提高可读性、确保摘要中的信息是准确和相关的。细化过程可以是自动化的,也可以涉及人工编辑,具体取决于所需的摘要质量和资源的可用性。

上述三种策略可以单独使用,也可以组合使用,以达到不同的摘要目的。例如,可以先使用填充方法快速抽取关键句子,然后通过MapReduce并行处理来加速这个过程,最后通过细化步骤来提高摘要的质量和可读性。

在实施上述三种策略时,需要考虑如下所示的因素:

  1. 目标受众:摘要的内容和风格应该适合目标受众。例如,技术受众可能需要包含术语和详细数据的摘要,而非技术受众可能需要更简洁、更易于理解的摘要。
  2. 文本类型:不同类型的文本可能需要不同的摘要策略。例如,新闻文章可能更侧重于事实和结果,而故事或小说可能需要保留情节和角色发展。
  3. 摘要长度:确定摘要的期望长度,并根据这个长度来调整策略。较长的摘要可能需要更细致的细化过程,而较短的摘要可能需要更精确的填充选择。

通过精心选择和调整这些策略,可以生成既准确又有用的摘要,帮助用户快速理解长文本的核心内容。

9.5.2  文档分割

在实际应用中,文档分割是处理大型文档以适应大型语言模型(LLM)的上下文窗口限制的重要步骤。在进行文档分割时,需要考虑如下所示的几个关键点:

(1)理解令牌(Tokens):在NLP中,文本通常被分割成令牌,这些令牌可以是单词、短语或字符。在英语中,一般可以近似认为1个令牌对应3到4个字符或大约3/4个单词。因此,75个单词的文本大约包含100个令牌。

(2)上下文长度限制:不同的模型有不同的令牌限制。例如,Gemma模型的上下文长度限制是8192个令牌,这大约相当于6100个单词。

(3)分割策略:根据文档的结构和内容,可以采用不同的分割策略:

  1. 基于字符的分割:使用特定的字符(如换行符\n或段落分隔符\n\n)来分割文档,这通常用于标识新句子或段落的开始。
  2. 基于文档结构的分割:根据文档的章节或小节等结构来分割,这对于结构化文档(如学术论文、技术手册)特别有用。

(4)场景分析

  1. 场景A:如果整个文档可以被模型一次性处理(即文档长度不超过模型的令牌限制),并且填充(Stuffing)策略可行,那么可以直接将整个文档作为输入。
  2. 场景B:即使整个文档可以被模型一次性处理,但如果填充策略生成的摘要质量较差,可能需要考虑基于文档结构的分割。例如,Kaggle的文档模板通常有清晰的小节划分,这使得在保持语义连贯性的同时进行分割变得容易。
  3. 场景C:如果文档缺乏清晰的结构,或者写入内容混乱且长度较长,使得模型难以处理,那么可能需要采用基于字符的分割策略。

(5)实际测试:为了验证这些策略,可以设计两个测试案例:

  1. 格式化良好的假写入(Fake Writeup):使用遵循特定格式和结构的文档进行测试。
  2. 混乱的假写入(Messy Writeup):使用结构不清晰、内容混乱的文档进行测试。

在实施文档分割时,需要考虑以下因素:

  1. 文档的自然断点:利用文档的自然断点(如标题、子标题、段落等)进行分割,以保持语义的完整性。
  2. 摘要的目的:根据摘要的目的选择最合适的分割策略。例如,如果目标是生成一个高层次的概述,可能需要更粗粒度的分割;如果目标是深入分析特定部分,可能需要更细粒度的分割。
  3. 模型的性能:考虑模型处理不同长度文本的能力,以及它在不同长度文本上的表现。

通过这些策略和考虑,可以更有效地处理大型文档,生成满足特定需求的高质量摘要。

(1)在下面代码中提供了两个示例文本,分别代表结构化(clean,干净的)和非结构化(messy,混乱的)的文档,这些文本模拟了在进行文本摘要时可能遇到的不同类型的输入。

example_clean_writeup = """
# Context section
This section only contains 2 links 
# Data context
link to the competition data page
# Overview of the Approach
this section should describe the models or algorithms used, describe the data preprocessing, feature engineering, and/or feature selection strategy, described the validation strategy.
# Details of the submission
this section should include what was special, creative, important, and/or impactful about the submission. And also, what was tried and didn’t work.
# Sources
this section should include links to helpful resources like research papers, past winning write-up solutions, forum posts, helpful notebooks, etc."""

example_messy_writeup = """
This section only contains 2 links, and here the link to the competition data page.
Partial section.
Another partial section.
Extensive model secondi which describes the models or algorithms used, describe the data preprocessing, feature engineering, and/or feature selection strategy, described the validation strategy.
Special section should include what was special, creative, important, and/or impactful about the submission. And also, what was tried and didn’t work. Last section should include links to helpful resources like research papers, past winning write-up solutions, forum posts, helpful notebooks, etc."""

对上面两个不同类型文档的具体说明如下所示。

  1. 结构化文档(example_clean_writeup)
  • 特点:包含清晰的标题和子标题,如“Context section”、“Data context”、“Overview of the Approach”、“Details of the submission”和“Sources”。
  • 分割策略:可以直接利用这些标题作为分割点,因为它们自然地将文档分成了逻辑上独立的部分。
  • 摘要方法:对于这种类型的文档,可以使用基于标题的分割策略,然后对每个部分单独进行摘要,最后将这些摘要组合起来。
  1. 非结构化文档(example_messy_writeup)
  • 特点:缺少明确的标题和结构,文本流动较为自由,没有明显的分割点。
  • 分割策略:可能需要依赖于段落的开始和结束,或者使用基于字符数的分割策略,如每1000个字符分割一次。
  • 摘要方法:对于这种类型的文档,可能需要先进行一些预处理,比如尝试识别和创建逻辑段落,然后再应用摘要技术。

(2)LangChain提供了一系列的工具和方法,可以帮助开发者轻松地实现复杂的NLP任务,如文档分割,而无需从头开始编写所有代码。请看下面的代码,使用LangChain中的CharacterTextSplitter分割结构化文档example_clean_writeup。这个分割器允许我们根据字符数进行分割,并可以设置分割符、每个块的大小以及块之间的重叠字符数。

text_splitter = CharacterTextSplitter(separator='#', chunk_size=100, chunk_overlap=10)
texts_clean_writeup = text_splitter.split_text(example_clean_writeup)

print([i[:50] for i in texts_clean_writeup])

执行后会输出:

['# Context section\nThis section only contains 2 lin', 'Data context\nlink to the competition data page', 'Overview of the Approach\nthis section should descr', 'Details of the submission\nthis section should incl', 'Sources\nthis section should include links to helpf']

上面的输出表明分割器按照我们的指令工作,成功地根据每个部分的标题进行了分割,并且保留了一些重叠字符(在这个例子中是10个字符),以帮助维持上下文的连贯性。

(3)在处理非结构化或“混乱”的文档时,选择一个合适的分割策略非常重要。在下面的代码中,CharacterTextSplitter 被配置为使用换行符 \n 作为分隔符,这通常用于表示新的句子或段落的开始。

text_splitter = CharacterTextSplitter(separator='\n', chunk_size=100, chunk_overlap=50)
texts_messy_writeup = text_splitter.split_text(example_messy_writeup)
print([i[:50] for i in texts_messy_writeup])

对上述配置参数的具体说明如下所示:

  1. separator: '\n' - 选择换行符作为文本分割的依据。在许多文本中,换行符用来分隔句子或段落,这有助于保持句子的完整性。
  2. chunk_size: 100 - 每个分割块的目标字符数。较小的块可以确保每个块保持较短,从而避免在单个块中包含过长、难以处理的文本。
  3. chunk_overlap: 50 - 设置当前块与前一个块之间的重叠字符数。重叠可以帮助模型在处理块的边界时保持上下文连贯性,尤其是在没有明确分隔符的情况下。

执行后会输出:

['This section only contains 2 links, and here the l', 'Partial section.\nAnother partial section.', 'Extensive model secondi which describes the models', 'Special section should include what was special, c']

(4)当在分割的文本块中有换行符\n时,由于设置了50个字符的重叠,第二个块可能会包含前一个块的部分内容。这种重叠确保了文本的连续性,尤其是在分割点处的上下文信息很重要的情况下。对于结构化文档,例如HTML格式化的文本,LangChain提供了HTMLHeaderTextSplitter,它可以根据HTML标题标签(如<h1>, <h2>等)来分割文本。这在处理具有明确结构的文档时非常有用,例如博客文章、教程或技术文档。在下面的代码中,使用HTMLHeaderTextSplitter根据 HTML 标题标签分割文档。

headers_to_split_on = [
    ("h1", "Header 1"),
    ("h2", "Header 2")
]

text_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on, return_each_element=False)
texts_html_writeup = text_splitter.split_text(writeup)

print('Length writup:', len(writeup))
print('Number of splits:', len(texts_html_writeup))
print('Element returned:', type(texts_html_writeup[0]))
print('Length of each split:', [len(i.page_content) for i in texts_html_writeup])

print(); print([(i.page_content[:50], i.metadata) for i in texts_html_writeup])

上述代码会将根据 HTML 标题进行拆分,然后打印输出每个拆分的长度和元数据。执行后会输出:

Length writup: 9864
Number of splits: 6
Element returned: <class 'langchain_core.documents.base.Document'>
Length of each split: [511, 1567, 1040, 1203, 1031, 1260]

[('We used an approach similar to audio spectrogram c', {'Header 2': 'TLDR'}), ('We extracted 18 lip points, 20 pose points (includ', {'Header 2': '1. Data Preprocessing'}), ('These augmentations are used in both CNN training ', {'Header 2': '2. Augmentation'}), ('Train on one fold with a random split (8 folds in ', {'Header 2': '3. Training'}), ('We rewrote all our models in Keras and transferred', {'Header 2': '4. Submissions, Conversion and Ensemble'}), ('Depthwise convolution models performed very well f', {'Header 2': '5. PS. Need BETTER TFlite DepthwiseConv2D'})]

对上面的输出结果说明如下:

  1. 基于 HTML 标题(h1 和 h2,但我们也可以指定更多)拆分成了 6 个部分。
  2. 每个拆分都是由一个 Document 类组成,这是一种专门设计用来存储文本和元数据的结构。具体来说,每个 Document 包含页面内容和标题(作为元数据)。
  3. 可以将元数据信息重新添加到页面内容中,以便我们的模型能够访问部分标题,从而获得更好的上下文。

(6)接下来通过下面的代码迭代由 HTMLHeaderTextSplitter 生成的 texts_html_writeup 列表,对于列表中的每个 Document 对象执行以下操作:

  1. 合并元数据和内容:通过连接 text.metadata.values()(这是一个包含所有元数据的字典值的列表)和 text.page_content(即分割后的文本内容),创建了一个新的字符串 final_content。在这两个部分之间插入了换行符 \n,以确保它们在文本中是分开的。
  2. 替换旧内容:将 Document 对象的 page_content 属性替换为新创建的富集内容 final_content。这意味着每个 Document 对象现在不仅包含分割后的文本,还包含与其相关的元数据。
  3. 打印输出结果:如果迭代的索引 i 小于 2,即只有前两个分割块被打印出来。打印了富集后的 final_content,其中包括元数据和文本内容。

执行后将打印输出前两个分割块的富集内容(enriched content,指的是在原始文本的基础上添加额外信息或元数据,以提供更多的上下文、提高可读性、或者为进一步的分析和处理提供便利。):

TLDR
We used an approach similar to audio spectrogram classification using the EfficientNet-B0 model, with numerous augmentations and transformer models such as BERT and DeBERTa as helper models. The final solution consists of one EfficientNet-B0 with an input size of 160x80, trained on a single fold from 8 randomly split folds, as well as DeBERTa and BERT trained on the full dataset. A single fold model using EfficientNet has a CV score of 0.898 and a leaderboard score of ~0.8.  
We used only competition data.

1. Data Preprocessing
We extracted 18 lip points, 20 pose points (including arms, shoulders, eyebrows, and nose), and all hand points, resulting in a total of 80 points. During training, we applied various augmentations. We implemented standard normalization. Instead of dropping NaN values, we filled them with zeros after normalization. We interpolated the time axis to a size of 160 using 'nearest' interpolation: yy = F.interpolate(yy[None, None, :], size=self.new_size, mode='nearest'). Finally, we obtained a tensor with dimensions 160x80x3, where 3 represents the (X, Y, Z) axes.  
Only 61 points were kept, including 40 lip points and 21 hand points. For left and right hand, the one with less NaN was kept. If right hand was kept, mirror it to left hand.  
Augmentations, normalization and NaN-filling were applied sequentially.  
Sequences longer than 96 were interpolated to 96. Sequences shorter than 96 were unchanged.  
Apart from raw positions, hand-crafted features were also used, including motion, distances, and cosine of angles.  
Motion features consist of future motion and history motion, which can be denoted as:  
$$ Motion_{future} = position_{t+1} - position_{t} $$ $$ Motion_{history} = position_{t} - position_{t-1} $$  
Full 210 pairwise distances among 21 hand points were included.  
There are 5 vertices in a finger (e.g. thumb is [0,1,2,3,4]), and therefore, there are 3 angles: <0,1,2>, <1,2,3>, <2,3,4>. So 15 angles of 5 fingers were included.  
Randomly selected 190 pairwise distances and randomly selected 8 angles among 40 lip points were included.

在上面的输出结果中,其中每个块的标题和其他元数据都放在了文本内容的前面。这样可以提高可读性,并为后续的处理步骤(如摘要生成或进一步的分析)提供额外的上下文信息。

(7)接下来进一步处理已经通过 HTMLHeaderTextSplitter 分割的文档块,使用CharacterTextSplitter 来对每个由 HTML 标题分割得到的块进行进一步的分割,以便处理非常大的文档。

text_splitter = CharacterTextSplitter(chunk_size=2000, chunk_overlap=100)

splits = text_splitter.split_documents(texts_html_writeup)
print('Number of final splits:', len(splits))
print('Length of each final split:', [len(i.page_content) for i in splits])

print(); print([(i.page_content[:50], i.metadata) for i in splits])

执行后会输出:

Number of final splits: 6
Length of each final split: [516, 1589, 1056, 1215, 1071, 1302]


[('TLDR\nWe used an approach similar to audio spectrog', {'Header 2': 'TLDR'}), ('1. Data Preprocessing\nWe extracted 18 lip points, ', {'Header 2': '1. Data Preprocessing'}), ('2. Augmentation\nThese augmentations are used in bo', {'Header 2': '2. Augmentation'}), ('3. Training\nTrain on one fold with a random split ', {'Header 2': '3. Training'}), ('4. Submissions, Conversion and Ensemble\nWe rewrote', {'Header 2': '4. Submissions, Conversion and Ensemble'}), ('5. PS. Need BETTER TFlite DepthwiseConv2D\nDepthwis', {'Header 2': '5. PS. Need BETTER TFlite DepthwiseConv2D'})]

通过使用 CharacterTextSplitter中的方法split_documents,可以对每个 HTML 标题下的文本进行更细粒度的控制,这对于处理非常大的文档特别有用。这种方法确保了即使在处理非常大的文档时,每个块也不会超过模型的上下文长度限制,从而可以有效地利用模型的能力。

注意:分割策略的选择应基于文档的具体特点和您的需求,在某些情况下,可能需要尝试不同的分割策略和参数,以找到最佳的分割效果。

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码农三叔

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

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

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

打赏作者

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

抵扣说明:

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

余额充值