文本摘要简述,基于Pytorch和Hugging Face Transformers构建示例,有源码

文本摘要的常见问题和解决方法概述,以及使用Hugging Face Transformers库构建基于新浪微博数据集的文本摘要示例。

作 者丨程旭源 学习笔记

文章选自公众号:写bug的程旭源

原文链接:文本摘要简述,基于Pytorch和Hugging Face Transformers构建示例,有源码

文本摘要的常见问题和解决方法概述,以及使用Hugging Face Transformers库构建基于新浪微博数据集的文本摘要示例。


1

前言简介

文本摘要旨在将文本或文本集合转换为包含关键信息的简短文本。主流方法有两种类型,抽取式和生成式。常见问题:抽取式摘要的内容选择错误、语句连贯性差、灵活性差。生成式摘要受未登录词、词语重复等问题影响。

文本摘要的分类有很多,比如单文档多文档摘要、多语言摘要、论文生成(摘要、介绍、重点陈述等每个章节的生成)、医学报告生成、情感类摘要(观点、感受、评价等的摘要)、对话摘要等。主流解决方法主要是基于深度学习、强化学习、迁移学习等方法,有大量的相关论文可以解读和研究。

抽取式的代表方法有TextRank、BertSum[1],从原文中抽取出字词组合成新的摘要。TextRank仿照PageRank,句子作为节点,构造无向有权边,权值为句子相似度。

生成式摘要方法是有PGN[2]、GPT、BART[3]、BRIO[4]、GSum[5]、SimCLS[6]、CIT+SE[7]等。

对于生成式摘要,不得不简单再提一下PGN模型:

Pointer Generator Network结构[2]

生成式存在的一个大问题是OOV未登录词,Google的PGN使用 copy mechanism和Coverage mechanism,能对于未遇见过的词直接复制用于生成部分,比如上面的“2-0”,同时避免了重复生成。

2

数据集

文本摘要的数据集有很多,这里使用的是Lcstsm[10]大规模中文短文本摘要语料库,取自于新浪微博,训练集共有240万条,为了快速得到结果和理解过程,可以自己设置采用数据的大小。比如训练集设置10万条。

3

模型选型

预训练模型选的是"csebuetnlp/mT5_multilingual_XLSum",是ACL-IJCNLP 2021的论文XL-Sum[8]中开源的模型,他们也是基于多语言T5模型 (mT5)在自己准备的44中语言的摘要数据集上做了fine-tune得到的模型。mT5[9]是按照与T5类似的方法进行训练的大规模多语言预训练模型。

如何加载模型?使用Hugging Face Transformers库可以两行代码即可加载。

fromtransformersimportAutoTokenizer, AutoModelForSeq2SeqLM

model_checkpoint ="csebuetnlp/mT5_multilingual_XLSum"

tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)

4

数据预处理

加载模型和tokenizer后对自定义文本分词:

mT5 模型采用的是基于 Unigram 切分的 SentencePiece 分词器,Unigram 对于处理多语言语料库特别有用。SentencePiece 可以在不知道重音、标点符号以及没有空格分隔字符(例如中文)的情况下对文本进行分词。

摘要任务的输入和标签都是文本,所以我们要做这几件事:

1、使用 as_target_tokenizer() 函数并行地对输入和标签进行分词,并标签序列中填充的 pad 字符设置为 -100 ,这样可以在计算交叉熵损失时忽略掉;

2、对标签进行移位操作,来准备 decoder_input_ids,有封装好的prepare_decoder_input_ids_from_labels函数。

3、将每一个 batch 中的数据都处理为模型可接受的输入格式:包含 'attention_mask'、'input_ids'、'labels' 和 'decoder_input_ids' 键的字典。

在编程的时候,可以打印出一个 batch 的数据看看:

5

模型训练和评测

因为Transformers包已经帮我们封装好了模型、损失函数等内容,我们只需调用并定义好训练循环即可:

deftrain_loop(dataloader, model, optimizer, lr_scheduler, epoch, total_loss):

progress_bar = tqdm(range(len(dataloader)))

progress_bar.set_description(f'loss:{0:>7f}')

finish_batch_num = (epoch-1) * len(dataloader)

model.train()

forbatch, batch_datainenumerate(dataloader, start=1):

batch_data = batch_data.to(device)

outputs = model(**batch_data)

loss = outputs.loss

optimizer.zero_grad()

loss.backward()

optimizer.step()

lr_scheduler.step()

total_loss += loss.item()

progress_bar.set_description(f'loss:{total_loss/(finish_batch_num + batch):>7f}')

progress_bar.update(1)

returntotal_loss

训练的过程中,不断的优化模型权重参数,那么如何评估模型的性能、如何确保模型往更好的方向优化呢?用什么评估指标呢?

我们定义一个测试循环负责评估模型的性能,指标就使用ROUGE(Recall-Oriented Understudy for Gisting Evaluation),它可以度量两个词语序列之间的词语重合率。

ROUGE 值的召回率,表示*被参考摘要 reference summary*有多大程度上被*生成摘要 generated summary*覆盖,精确率则表示*生成摘要*中有多少词语与*被参考摘要*相关。

那么,如果我们只比较词语,召回率是:

Recall = 重叠词数/被参考摘要总词数

Precision=重叠词数/生成摘要的总词数

已经有rouge的Python包[11],使用pip安装即可

pip install rouge

rouge库官方示例,"f" stands for f1_score, "p" stands for precision, "r" stands for recall.

ROUGE-1 度量 uni-grams 的重合情况,ROUGE-2 度量 bi-grams 的重合情况,而 ROUGE-L 则通过在生成摘要和参考摘要中寻找最长公共子串来度量最长的单词匹配序列。

另外要注意的是,rouge 库默认使用空格进行分词,中文需要按字切分,也可以使用分词器分词后再计算。

Transformers对解码过程也进行了封装,我们只需要调用 generate() 函数就可以自动地逐个生成预测 token。

例如我们使用马来亚大学的介绍生成一个摘要:“马来亚大学是马来西亚历史最悠久的高等教育学府。“

所以,在测试过程中,我们通过generate() 函数获取预测结果,然后将预测结果和正确标签都处理为 rouge 库接受的格式,最后计算各项的ROUGE值即可:

deftest_loop(dataloader, model, tokenizer):

preds, labels = [], []

rouge = Rouge()

model.eval()

withtorch.no_grad():

forbatch_dataintqdm(dataloader):

batch_data = batch_data.to(device)

# 获取预测结果

generated_tokens = model.generate(batch_data["input_ids"],

attention_mask=batch_data["attention_mask"],

max_length=max_target_length,

num_beams=beam_search_size,

no_repeat_ngram_size=no_repeat_ngram_size,

).cpu().numpy()

ifisinstance(generated_tokens, tuple):

generated_tokens = generated_tokens[0]

decoded_preds = tokenizer.batch_decode(generated_tokens,

skip_special_tokens=True,

clean_up_tokenization_spaces=False)

label_tokens = batch_data["labels"].cpu().numpy()

# 将标签序列中的 -100 替换为 pad token ID 以便于分词器解码

label_tokens = np.where(label_tokens !=-100, label_tokens, tokenizer.pad_token_id)

decoded_labels = tokenizer.batch_decode(label_tokens,

skip_special_tokens=True,

clean_up_tokenization_spaces=False)

# 处理为 rouge 库接受的文本列表格式

preds += [' '.join(pred.strip())forpredindecoded_preds]

labels += [' '.join(label.strip())forlabelindecoded_labels]

# rouge 库计算各项 ROUGE 值

scores = rouge.get_scores(hyps=preds, refs=labels, avg=True)

result = {key: value['f'] *100forkey, valueinscores.items()}

result['avg'] = np.mean(list(result.values()))

returnresult

6

模型保存

优化器我们选使用AdamW,并且通过 get_scheduler()函数定义学习率调度器。

在每一个epoch中,调用上面定义的train_loop和test_loop,模型在验证集上的rouge分数用来调整超参数和选出最好的模型,最后使用最好的模型跑测一下测试集来评估最终的性能。

""" Train the model """

total_steps = len(train_dataloader) * num_train_epochs

# Prepare optimizer and schedule (linear warmup and decay)

no_decay = ["bias","LayerNorm.weight"]

optimizer_grouped_parameters = [

{"params": [pforn, pinmodel.named_parameters()ifnotany(ndinnforndinno_decay)],"weight_decay": weight_decay},

{"params": [pforn, pinmodel.named_parameters()ifany(ndinnforndinno_decay)],"weight_decay":0.0}

]

warmup_steps = int(total_steps * warmup_proportion)

optimizer = AdamW(

optimizer_grouped_parameters,

lr=learning_rate,

betas=(adam_beta1, adam_beta2),

eps=adam_epsilon

)

lr_scheduler = get_scheduler(

'linear',

optimizer,

num_warmup_steps=warmup_steps,

num_training_steps=total_steps

)

# Train!

logger.info("***** Running training *****")

logger.info(f"Num examples -{len(train_data)}")

logger.info(f"Num Epochs -{num_train_epochs}")

logger.info(f"Total optimization steps -{total_steps}")

total_loss =0.

best_avg_rouge =0.

forepochinrange(num_train_epochs):

print(f"Epoch{epoch+1}/{num_train_epochs}\n"+30*"-")

total_loss = train_loop(train_dataloader, model, optimizer, lr_scheduler, epoch, total_loss)

dev_rouges = test_loop(dev_dataloader, model, tokenizer)

logger.info(f"Dev Rouge1:{dev_rouges['rouge-1']:>0.2f}Rouge2:{dev_rouges['rouge-2']:>0.2f}RougeL:{dev_rouges['rouge-l']:>0.2f}")

rouge_avg = dev_rouges['avg']

ifrouge_avg > best_avg_rouge:

best_avg_rouge = rouge_avg

logger.info(f'saving new weights to{output_dir}...\n')

save_weight =f'epoch_{epoch+1}_rouge_{rouge_avg:0.4f}_weights.bin'

torch.save(model.state_dict(), os.path.join(output_dir, save_weight))

logger.info("Done!")

7

总结

文本摘要和文本生成是自然语言处理中非常重要和常见的任务,本文使用生成式方法做文本摘要,文本生成还可以应用于其他场景下,比如给定一个句子,生成多个与其相似、语义相同的句子,这里也Mark一下,后面再写一篇相关文章。

文本摘要还存在很多问题,有待研究者们进一步探索,比如:长程依赖、新颖性、暴露偏差、评估和损失不匹配、缺乏概括性、虚假事实、不连贯等,此外,还有摘要任务特定的问题,比如:如何确定关键信息,而不仅仅是简单地句子压缩。每个问题的解决或方法优化都能发表论文,相关的论文有太多了。后续可以抽关键的解决方案聊聊。

代码近期整理后上传Github,链接见文末留言处。

欢迎文末留言,随意交流~

Reference

[1]

Text Summarization with Pretrained Encoders:https://arxiv.org/abs/1908.08345

[2]

Get To The Point: Summarization with Pointer-Generator Networks:http://arxiv.org/abs/1704.04368

[3]

BART: Denoising sequence-to-sequence pre-training for natural language generation, translation, and comprehension:https://aclanthology.org/2020.acl-main.703.pdf

[4]

BRIO: Bringing Order to Abstractive Summarization:https://arxiv.org/abs/2203.16804v1

[5]

GSum: A General Framework for Guided Neural Abstractive Summarization:https://arxiv.org/abs/2010.08014

[6]

SimCLS: A Simple Framework for Contrastive Learning of Abstractive Summarization:https://arxiv.org/abs/2106.01890v1

[7]

Abstractive Summarization with Combination of Pre-trained Sequence-to-Sequence and Saliency Models:https://arxiv.org/abs/2003.13028

[8]

XL-Sum: Large-Scale Multilingual Abstractive Summarization for 44 Languages:https://github.com/csebuetnlp/xl-sum

[9]

mT5: A massively multilingual pre-trained text-to-text transformer:https://github.com/google-research/multilingual-t5

[10]

Lcsts: A large scale chinese short text summarization dataset:https://arxiv.org/pdf/1506.05865.pdf

[11]

rouge:https://github.com/pltrdy/rouge

[12]

summarization:https://xiaosheng.run/

[13]

Deep reinforcement and transfer learning for abstractive text summarization:https://www.sciencedirect.com/science/article/abs/pii/S0885230821000796

[14]

Summarization Papers:https://github.com/xcfcode/Summarization-Papers

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容