文本摘要的常见问题和解决办法概述,以及应用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库能够两行代码即可加载。
from transformers import AutoTokenizer, AutoModelForSeq2SeqLMmodel_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包曾经帮咱们封装好了模型、损失函数等内容,咱们只需调用并定义好训练循环即可:
def train_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() for batch, batch_data in enumerate(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) return total_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值即可:
def test_loop(dataloader, model, tokenizer): preds, labels = [], [] rouge = Rouge() model.eval() with torch.no_grad(): for batch_data in tqdm(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() if isinstance(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()) for pred in decoded_preds] labels += [' '.join(label.strip()) for label in decoded_labels] # rouge 库计算各项 ROUGE 值 scores = rouge.get_scores(hyps=preds, refs=labels, avg=True) result = {key: value['f'] * 100 for key, value in scores.items()} result['avg'] = np.mean(list(result.values())) return result
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": [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], "weight_decay": weight_decay}, {"params": [p for n, p in model.named_parameters() if any(nd in n for nd in no_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.for epoch in range(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'] if rouge_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...
[4]BRIO: Bringing Order to Abstractive Summarization:https://arxiv.org/abs/2203.16...
[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.01...
[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...
[9]mT5: A massively multilingual pre-trained text-to-text transformer:https://github.com/google-res...
[10]Lcsts: A large scale chinese short text summarization dataset:https://arxiv.org/pdf/1506.05...
[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...
[14]Summarization Papers:https://github.com/xcfcode/Su...