作者: Sreyan Ghosh
创建日期: 2022/07/04
最后修改日期: 2022/08/28
描述: 使用Hugging Face Transformers训练T5进行抽象总结。
自动摘要是自然语言处理(NLP)中的核心问题之一。它涉及与语言理解(例如,识别重要内容)和生成(例如,将识别的内容汇总并重新措辞成摘要)相关的几个挑战。
在本教程中,我们将采用抽象建模方法来解决单文档摘要任务。这里的主要思想是生成一个简短的、单句的新闻摘要,回答“这篇新闻文章讲的是什么?”这个问题。这种摘要方法也被称为抽象摘要,在各个学科的研究人员中引起了越来越多的兴趣。
按照之前的工作,我们旨在使用序列到序列模型来解决这个问题。 文本到文本迁移变换器(T5
) 是一个基于Transformer 的模型,建立在编码器-解码器架构上,经过预训练,涉及多任务混合的无监督和有监督任务,其中每个任务被转换为文本到文本格式。T5在摘要、翻译等多种序列到序列任务中显示出令人印象深刻的结果。
在本笔记本中,我们将使用Hugging Face Transformers对预训练的T5进行微调,以处理抽象摘要任务,数据集为从Hugging Face Datasets加载的XSum
数据集。
!pip install transformers==4.20.0
!pip install keras_nlp==0.3.0
!pip install datasets
!pip install huggingface-hub
!pip install nltk
!pip install rouge-score
import os
import logging
import nltk
import numpy as np
import tensorflow as tf
from tensorflow import keras
# 仅记录错误消息
tf.get_logger().setLevel(logging.ERROR)
os.environ["TOKENIZERS_PARALLELISM"] = "false"
# 你想将数据集拆分为训练和测试的百分比
TRAIN_TEST_SPLIT = 0.1
MAX_INPUT_LENGTH = 1024 # 输入到模型的最大长度
MIN_TARGET_LENGTH = 5 # 模型输出的最小长度
MAX_TARGET_LENGTH = 128 # 模型输出的最大长度
BATCH_SIZE = 8 # 训练模型的批量大小
LEARNING_RATE = 2e-5 # 训练模型的学习率
MAX_EPOCHS = 1 # 我们将训练模型的最大轮数
# 本笔记本是基于来自Hugging Face模型库的t5-small检查点构建的
MODEL_CHECKPOINT = "t5-small"
我们现在将下载极端摘要(XSum)。 该数据集包含BBC文章及其对应的单句摘要。 具体来说,每篇文章都以引言句(即摘要)作为开头,摘要通常由文章的作者专业撰写。该数据集包含226,711篇文章,分为训练集(90%,204,045),验证集(5%,11,332)和测试集(5%,11,334)。
遵循大量文献,我们使用召回导向的临时评估标准(ROUGE)指标来评估我们的序列到序列抽象摘要方法。
我们将使用Hugging Face Datasets库下载我们需要用于训练和评估的数据。这可以通过load_dataset
函数轻松完成。
from datasets import load_dataset
raw_datasets = load_dataset("xsum", split="train")
该数据集具有以下字段:
print(raw_datasets)
数据集({
特征: ['document', 'summary', 'id'],
行数: 204045
})
现在我们来看看数据的样子:
print(raw_datasets[0])
{'document': '在被大雨严重影响的地区之一纽顿斯图尔特,损失的全面评估仍在进行中。\n哈维克的修复工作正在进行,皮布尔郡的许多道路因积水而受到严重影响。\n西海岸主线的火车因拉明顿高架桥的损坏而面临干扰。\n许多企业和家庭因克里河溢出而受到洪水影响。\n首席部长尼古拉·斯特金访问该地区以检查损害。\n水流冲破了一道挡土墙,淹没了维多利亚街(主要购物街)上的许多商业物业。\n遭受严重影响的肉桂咖啡馆老板珍妮特·泰特表示,在洪水发生后,多个机构的应对反应无可挑剔。\n然而,她说,应该进行更多的预防性工作,以确保挡土墙不会失败。\n“这很困难,但我确实认为,对邓弗里斯和尼斯的宣传太多了 - 我完全理解这一点 - 但我们似乎被忽视或遗忘了,”她说。\n“这可能不是真的,但这是我这几天以来的看法。\n“在警告和警报发出后,为什么你们没有准备好更多地帮助我们?”\n与此同时,由于持续降雨,边界地区仍然维持着洪水警报。\n皮布尔遭遇了严重问题,引发了对该地区引入更多防御的呼声。\n苏格兰边界委员会在其网站上发布了受影响最严重的道路清单,司机被敦促不要忽视封闭标志。\n工党副领导亚历克斯·罗利周一在哈维克亲自查看情况。\n他说,重要的是要确保洪水保护计划的正确性,但他支持加快这一进程的呼声。\n“我对损失的数量感到震惊,”他说。\n“显然,对于那些被迫离开家园的人来说,这是令人心碎的,对企业的影响也不容忽视。”\n他说,必须采取“立即措施”来保护最脆弱的地区,并为洪水预防计划制定明确的时间表。\n你是否受到邓弗里斯和加洛韦或边界地区洪水的影响?告诉我们你对这一情况的经历以及处理方式。请通过selkirk.news@bbc.co.uk或dumfries@bbc.co.uk与我们联系。', 'summary': '清理工作在苏格兰边界和邓弗里斯和加洛韦继续进行,因为洪水是由弗兰克风暴引起的。', 'id': '35232142'}
为了演示工作流程,在本笔记本中我们将仅取训练集的一个小的分层平衡拆分(10%)作为我们的训练和测试集。
我们可以使用train_test_split
方法轻松拆分数据集,该方法期望拆分大小和相对要分层的列名。
raw_datasets = raw_datasets.train_test_split(
train_size=TRAIN_TEST_SPLIT, test_size=TRAIN_TEST_SPLIT
)
在将这些文本输入模型之前,我们需要预处理它们并使其
为任务做好准备。这是通过Hugging Face Transformers的Tokenizer
完成的,它将对输入进行分词(包括将令牌转换为对应的预训练词汇中的ID)并将其放入模型所需的格式,同时生成模型所需的其他输入。
from_pretrained()
方法需要一个来自Hugging Face模型库的模型名称。这
与之前声明的MODEL_CHECKPOINT完全相同,我们只需传递该值。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(MODEL_CHECKPOINT)
如果您使用的是我们拥有的五个T5检查点之一,则必须在输入前加上 "summarize:"(模型还可以翻译,需要前缀以知道它必须执行的任务)。
if MODEL_CHECKPOINT in ["t5-small", "t5-base", "t5-large", "t5-3b", "t5-11b"]:
prefix = "summarize: "
else:
prefix = ""
我们将编写一个简单的函数,帮助我们进行与Hugging Face数据集兼容的预处理。总结来说,我们的预处理函数应该:
token_type_ids
、attention_mask
等def preprocess_function(examples):
inputs = [prefix + doc for doc in examples["document"]]
model_inputs = tokenizer(inputs, max_length=MAX_INPUT_LENGTH, truncation=True)
# 设置目标的分词器
with tokenizer.as_target_tokenizer():
labels = tokenizer(
examples["summary"], max_length=MAX_TARGET_LENGTH, truncation=True
)
model_inputs["labels"] = labels["input_ids"]
return model_inputs
要将此函数应用于数据集中所有的句子对,我们只需使用
之前创建的dataset
对象的map
方法。这将对
dataset
中所有拆分的所有元素应用该函数,因此我们的训练和测试
数据将通过一个命令完成预处理。
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
现在我们可以下载预训练模型并对其进行微调。由于我们的任务是
序列到序列(输入和输出都是文本序列),我们使用Hugging Face Transformers库中的
TFAutoModelForSeq2SeqLM
类。与分词器一样,from_pretrained
方法将为我们下载并缓存模型。
from_pretrained()
方法需要一个来自Hugging Face模型库的模型名称。如
前所述,我们将使用t5-small
模型检查点。
from transformers import TFAutoModelForSeq2SeqLM, DataCollatorForSeq2Seq
model = TFAutoModelForSeq2SeqLM.from_pretrained(MODEL_CHECKPOINT)
下载中: 0%| | 0.00/231M [00:00<?, ?B/s]
所有模型检查点层在初始化TFT5ForConditionalGeneration时均已使用。
TFT5ForConditionalGeneration的所有层均已从t5-small模型检查点初始化。
如果您的任务类似于模型检查点训练时的任务,您可以直接使用TFT5ForConditionalGeneration进行预测,而不需要进一步训练。
对于训练序列到序列模型,我们需要一种特殊的数据整理器,
它不仅会将输入填充到批量的最大长度,还会填充
标签。因此,我们在数据集上使用Hugging Face Transformers
库提供的DataCollatorForSeq2Seq
。return_tensors='tf'
确保我们得到 tf.Tensor
对象作为返回。
from transformers import DataCollatorForSeq2Seq
data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf")
接下来,我们定义训练和测试集,以便训练我们的模型。同样,Hugging Face
数据集为我们提供了to_tf_dataset
方法,该方法将帮助我们将数据集与上述
定义的collator
集成。该方法需要某些参数:
generation_dataset
,以便在训练时动态计算 ROUGE
分数。train_dataset = tokenized_datasets["train"].to_tf_dataset(
batch_size=BATCH_SIZE,
columns=["input_ids", "attention_mask", "labels"],
shuffle=True,
collate_fn=data_collator,
)
test_dataset = tokenized_datasets["test"].to_tf_dataset(
batch_size=BATCH_SIZE,
columns=["input_ids", "attention_mask", "labels"],
shuffle=False,
collate_fn=data_collator,
)
generation_dataset = (
tokenized_datasets["test"]
.shuffle()
.select(list(range(200)))
.to_tf_dataset(
batch_size=BATCH_SIZE,
columns=["input_ids", "attention_mask", "labels"],
shuffle=False,
collate_fn=data_collator,
)
)
现在我们将定义我们的优化器并编译模型。损失计算在内部处理,所以我们不需要担心这一点!
optimizer = keras.optimizers.Adam(learning_rate=LEARNING_RATE)
model.compile(optimizer=optimizer)
在 compile() 中未指定损失 - 将使用模型的内部损失计算作为损失。请不要惊慌 - 这是在 Transformers 中训练 TensorFlow 模型的常见方式!要禁用此行为,请传递损失参数,或者如果您希望模型不计算损失,请显式传递 `loss=None`。
为了在训练时动态评估我们的模型,我们将定义 metric_fn
,它将计算真实值和预测值之间的 ROUGE
分数。
import keras_nlp
rouge_l = keras_nlp.metrics.RougeL()
def metric_fn(eval_predictions):
predictions, labels = eval_predictions
decoded_predictions = tokenizer.batch_decode(predictions, skip_special_tokens=True)
for label in labels:
label[label < 0] = tokenizer.pad_token_id # 替换被遮蔽的标签标记
decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
result = rouge_l(decoded_labels, decoded_predictions)
# 我们只会打印 F1 分数,您也可以使用其他聚合指标
result = {"RougeL": result["f1_score"]}
return result
现在我们可以开始训练我们的模型了!
from transformers.keras_callbacks import KerasMetricCallback
metric_callback = KerasMetricCallback(
metric_fn, eval_dataset=generation_dataset, predict_with_generate=True
)
callbacks = [metric_callback]
# 现在我们将使用我们的测试集作为 validation_data
model.fit(
train_dataset, validation_data=test_dataset, epochs=MAX_EPOCHS, callbacks=callbacks
)
WARNING:root:未为 KerasMetricCallback 指定 label_cols,假定您希望使用 'labels' 键。
2551/2551 [==============================] - 652s 250ms/step - loss: 2.9159 - val_loss: 2.5875 - RougeL: 0.2065
<keras.callbacks.History at 0x7f1d002f9810>
为了获得最佳结果,我们建议在整个训练数据集上至少训练模型 5 个周期!
现在我们将尝试在一篇任意文章上进行我们训练过的模型的推理。为此,我们将使用 Hugging Face Transformers 中的 pipeline
方法。Hugging Face Transformers 提供了多种可供选择的管道。对于我们的任务,我们使用 summarization
管道。
pipeline
方法将训练过的模型和分词器作为参数。framework="tf"
参数确保您传递的是用 TF 训练的模型。
from transformers import pipeline
summarizer = pipeline("summarization", model=model, tokenizer=tokenizer, framework="tf")
summarizer(
raw_datasets["test"][0]["document"],
min_length=MIN_TARGET_LENGTH,
max_length=MAX_TARGET_LENGTH,
)
您的 max_length 设置为 128,但您的 input_length 仅为 88。您可能考虑手动减少 max_length,例如 summarizer('...', max_length=44)
[{'summary_text': 'Boss Wagner says he is "a 100% professional and has a winning mentality to play on the pitch."'}]
现在您可以将此模型推送到 Hugging Face 模型中心,并与您所有的朋友、家人、宠物分享:他们都可以使用标识符 "your-username/the-name-you-picked"
加载它,例如:
model.push_to_hub("transformers-qa", organization="keras-io")
tokenizer.push_to_hub("transformers-qa", organization="keras-io")
在您推送模型后,您在未来加载它的方法是这样的!
from transformers import TFAutoModelForSeq2SeqLM
model = TFAutoModelForSeq2SeqLM.from_pretrained("your-username/my-awesome-model")