t5--文本摘要
-
T5(Text-to-Text Transfer Transformer)模型将翻译、分类、回归、摘要生成等任务都统一转成Text-to-Text任务,从而使得这些任务在训练(pre-train和fine-tune)时能够使用相同的目标函数,在测试时也能使用相同的解码过程。
-
T5模型在NLU和NLG上都具有出色表现,能够完成翻译任务、文本分类、阅读理解、摘要生成任务等多种下游任务。
-
然而,T5刚出来的时候,我们可能没有什么存在感,原因很简单:没有中文版T5可用。
-
不过Google后面放出了多国语言版的T5(mT5),里边包含了中文语言。
-
-
论文链接:mT5: A massively multilingual pre-trained text-to-text transformer Hugging face链接:https://huggingface.co/collections/google/mt5-release-65005f1a520f8d7b4d039509 另外,国内还有一些公司,利用T5模型使用了大量中文数据进行训练。
-
孟子T5预训练生成模型与T5结构相同,但是不包含下游任务,需要在特定任务上 Finetune 后使用。孟子T5预训练生成模型-中文-base
-
iic在mt5模型基础上使用了大量中文数据进行训练,并引入了零样本分类增强的技术。全任务零样本学习-mT5分类增强版-中文-base
-
Encoder-Decoder结构
-
如下图所示,目前基于Transformer的模型架构主要有Encoder-Decoder结构(传统的Transformer结构)、Language model结构 (GPT的结构)和Prefix LM结构(UniLM的结构)。
-
Encoder-Decoder结构:Seq2Seq常用模型,编码器输入中可以看到序列中包括自己的全部字符,解码器的输出只能看到当前字符及之前的字符;
-
LM模型:Encoder-Decoder中的Decoder部分,单向结构,每次只能看到当前及之前的部分;基于前缀的语言模型Prefix LM:前面一部分文本可以看到前缀部分所有内容,后面剩下的内容只能看到自己及之前的内容。
SentencePiece
-
把一个句子看作一个整体,再拆成片段,而没有保留天然的词语的概念。
-
SentencePiece不将空格视为分隔符,而是将字符串作为其原始格式的输入,使用BPE或ULM作为其分词器来构建词汇表。
相对位置编码 不同于RNN、CNN等模型,对于Transformer模型来说,位置编码的加入是必不可少的,因为纯粹的Attention模块是无法捕捉输入顺序的,即无法区分不同位置的Token。为此我们大体有两个选择:
1、将位置信息融入到输入中,这构成了绝对位置编码的一般做法; 2、微调一下Attention结构,使得它有能力分辨不同位置的Token,这构成了相对位置编码的一般做法。 <!--Transformer中有两种常用的位置编码,分别为绝对位置编码和相对位置编码。-->
-
我们可以使用一个阈值k,例如k=2,当超过这个特定的阈值(就是下图中红色背景的部分)
-
即其他的position_embedding距离自身超过2个位置,那么这些位置的position_embedding就和距离最近的position_embedding值一样。例如下图中x1的w3和w4就会变成w2,其他同理。
T5模型中的位置编码
我们先看下苏神博客中的内容,分析下T5模型中相对位置编码公式的由来:
-
T5采用了一个长距离不敏感的相对位置编码,这一设计是考虑到远距离的单词依赖往往比较稀疏且不精细,因此我们需要对周围单词的位置做精确的区分,而远距离单词的位置变化则相对缓慢。
-
如下图所示,T5模型对相对位置进行了一个“分桶”处理,将原始的relative position当成一个个小方块放置在顺序排列的桶中,最后用方块所属的桶号来代替相对距离:
-
在T5中num_buckets=32,max_distance=128源码中将num_buckets/2的距离定义为近的分割线(对于双向attention是8,对单向attention是16)
-
低于这个数值的距离被认为是近的,高于这个数值的距离被认为是远的。
-
这个设计的思路其实也很直观,就是比较邻近的位置(0-7),我们需要比较得精细一些,所以给它们都分配一个独立的位置编码,至于稍远的位置(比如8~11),我们不用区分得太清楚,所以它们可以共用一个位置编码。距离越远,共用的范围就可以越大,直到达到指定范围再clip。
-
数据集和大模型选择
大模型t5---孟子t5模型---中文微调模型---text to text
数据集----nlpcc_2017----新闻数据集
总共5000条数据集---取4900--训练集----100--测试集
通过自定义的Dataloader类进行数据传递,同时添加一个collate-fn函数,可以对batch里的content和label进行tokenizer处理,
通过Dataloader类--在dataloader按照batch进行取数据的时候, 是取出大小等同于batch size的index列表; 然后将列表列表中的index输入到dataset的getitem()函数中,取出该index对应的数据;
collate-fn函数就是手动将抽取出的样本堆叠起来的函数
可以看到, 假设的dataset返回两个数据项: x和y. 那么, 传入collate_fn的参数定义为data, 则其shape为(batch_size, 2,…),可以自定义取出一个batch数据的格式. 该函数的输出就是对dataloader进行遍历, 取出一个batch的数据
(1)--lambda函数 info = args.info # info是已经定义过的 loader = Dataloader(collate_fn=lambda x: collate_fn(x, info)) (2)--创建可被调用的类 class collater(): def __init__(self, *params): self. params = params def __call__(self, data): '''在这里重写collate_fn函数''' # collate_fn = collater(*params) loader = Dataloader(collate_fn=collate_fn)
在这里通过collate-fn来传递tokenizer--
inputs = tokenizer(contents, max_length=384, truncation=True, return_tensors='pt', padding=True)
contents--数据
truncation--超出最大序列长度进行截断
算法构建
构建MengZiT5Model()类
首先要对输入的inputs和labels进行处理,mask取为实际上有的序列中有的token,表示为1,因为同一个batch里的数据要等长。损失函数用的是常规的交叉熵loss,进行5个epochs的训练后得到
class MengZiT5Model(nn.Module): def __init__(self): super().__init__() # 加载预训练模型 self.model = T5ForConditionalGeneration.from_pretrained(model_dir) def forward(self, inputs, labels=None): # 1、encoder的input_ids和attention_mask input_ids = inputs['input_ids'] attention_mask = inputs['attention_mask'] #print(attention_mask) if labels is not None: #2、decoder 的labels train_labels = labels['input_ids'].contiguous() train_labels_mask = labels['attention_mask'] #3、decoder 的input_ids和attention_mask decoder_input_ids = train_labels.new_zeros(train_labels.shape) decoder_input_ids[..., 1:] = train_labels[..., :-1].clone() decoder_attention_mask = train_labels_mask.new_zeros(train_labels_mask.shape) decoder_attention_mask[..., 1:] = train_labels_mask[..., :-1].clone() decoder_attention_mask[..., 0] = 1 #4、送入模型进行预测 outputs = self.model(input_ids=input_ids , attention_mask=attention_mask , decoder_input_ids=decoder_input_ids , decoder_attention_mask=decoder_attention_mask , labels=train_labels) print(outputs.keys()) #5、返回训练时候的Loss值 #print(outputs.keys()) return outputs.loss else: #模型生成 summary_ids = self.model.generate(input_ids , num_beams=4 # 束搜索法 , no_repeat_ngram_size=2 # 确保不重复 , min_length=10 # 长度限制 , max_length=64 , early_stopping=True) #将id转换为输出 summary_ids.shape = [bs, length] outputs = tokenizer.batch_decode(summary_ids, skip_special_tokens=True) return outputs
结果评测
通过rouge库对得到的摘要进行评测。
得到的rouge均在0.7左右。