当前位置:   article > 正文

文本生成(一)【NLP论文复现】Unified Language Model 文本生成从未如此轻松

nlp论文复现


前言

一篇19年的微软论文,老规矩先放论文链接:https://arxiv.org/abs/1905.03197

最近开始尝试做长文本的摘要生成任务,因此要拿出了几篇 Transformer 时代的文本生成相关的经典论文参考。当我看到UniLM这个Bert的变体模型(甚至连变体都算不上),口中只能叹出,“相见恨晚” 四个字。接着回顾了之前辛苦辛苦手敲的LSTM+Attention的seq2seq模型,脸上浮现出了嫌弃的表情。文本生成在Self-Attention的加持下从未如此简单。


UniLM

这里只阐述论文的关键思路,具体细节还是看论文来的实在啦~

  1. MLM (Mask language model),之所以在文本生成任务上表现较弱,归根到底是其一个非常重要的先验假设:Token之间是相互独立的。
    在这里插入图片描述
    Independence Assumption: As emphasized by the ≈ sign in Eq, BERT factorizes the joint conditional probability p(x¯ | xˆ) based on an independence assumption that all masked tokens x¯ are separately reconstructed. (摘自论文XLNet)
  2. 其 Self-Attention 模块使得每一个Position上的Token都能获取到全文的上下文信息,这与文本生成任务相违背:即文本的生成是具有依赖关系的,Xt+1的生成应该依赖于X<=t,并且Xt+1不能获取X>t的信息,因为那是未来的信息,如果仍然使用MLM中的满Self-attention(姑且这么叫)来做训练模型的文本生成能力,那么在一开始模型就已经知道了所有的答案了,这显然是不合理的。
  3. 那该如何是好?使用Seq2Seq的模型:如老家族的RNN,LSTM,GRU等序列模型,或新家族的Transform类,由于在该类模型中Decoder的信息传递是单向的,每一个输出只决定于Encoder部分的输入和之前的输出,因此也能较好的完成文本生成的任务。
  4. 那有没有什么办法,让MLM / Bert的框架既能优秀的完成文本理解,又能轻松实现文本生成任务呢?UNILM给出了答案:给Self-Attention加上MASK!
  1. 既然满Self-Attention会泄密,那么为什么不能通过MASK:将待预测部分的Token的Attention做选择性的屏蔽,让他只能看到获取到上文的信息,而对输入部分保持理解输入部分的上下文信息,但屏蔽待生成部分的信息。
  2. 图中 Bidirectional LM 与Bert一致,使用满 Self-attention 是模型充分学习全文的上下文信息,提高文本理解能力。
  3. 图中 Seq-to-Seq LM 部分:矩阵 Sij i为行 j 为列 Sij 为空白表示以 i 为 Q,j 为 K 的Attention信息没有被MASK,即 i 能获取 j 的信息。Sij 为黑 则表示该Attention信息被Mask,i 无法获取 即 j 的信息。如图中的MASK设计,S1部分仍为 Bidirectional LM,S2部分的每一个Token只能获取前面的Token的信息,而后面的信息是被MASK的。这符合文本生成的逻辑。
  4. 将以上 两种 LM 方式作为模型的 Pretrain 任务,Bidirectional LM 与 Bert一致,随机MASK Token,并进行预测,Seq-to-Seq LM 则 MASK S2的Token 利用 S1的信息去预测。这种Pretrain机制的设计使得 UNILM 完成文本理解和文本生成任务的能力都得到了提升。
    在这里插入图片描述在这里插入图片描述

How to build UniLM

Tensorflow-GPU 2.0.0
Transformers 3.1.0

Get 2D MASK

  1. 当我们要使用 UniLM 完成文本生成任务时,Self-Attention 的Mask 会变成一个2D动态遮招(每一个sample都不同)这与往常我们通过 Transformers 的 BertTokenizer 模块直接得到的1D的 attention_mask不同。
  2. 2D Mask 取决于我们的输入和输出,而这部分信息可以通过Segment_id进行表示,因此我们只需要将输入文本和目标输出文本同时传递给BertTokenizer,通过返回的Segment_id 构建 2D MASK即可。
def unilm_mask_single(s):
	'''
	s = np.array([0,0,0,0,1,1,1,0,0,0])
	unilm_mask_single(s) = 
	<tf.Tensor: shape=(10, 10), dtype=float32, numpy=
	array([[1., 1., 1., 1., 0., 0., 0., 0., 0., 0.],
	       [1., 1., 1., 1., 0., 0., 0., 0., 0., 0.],
	       [1., 1., 1., 1., 0., 0., 0., 0., 0., 0.],
	       [1., 1., 1., 1., 0., 0., 0., 0., 0., 0.],
	       [1., 1., 1., 1., 1., 0., 0., 0., 0., 0.],
	       [1., 1., 1., 1., 1., 1., 0., 0., 0., 0.],
	       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
	       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
	       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
	       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]], dtype=float32)>
	'''
    idxs = K.cumsum(s, axis=0)
    mask = idxs[None, :] <= idxs[:, None]
    mask = K.cast(mask, K.floatx())
    return mask
    
ids = np.zeros((self.batch_size,self.Max_len),dtype='int32')
seg_id = np.zeros((self.batch_size,self.Max_len),dtype='int32')
mask_att = np.zeros((self.batch_size,self.Max_len,self.Max_len),dtype='int32')

input_dict = self.tokenizer(content,title,max_length=self.Max_len,truncation=True,padding=True)
len_ = len(input_dict['input_ids'])
token_ids = input_dict['input_ids']
segment_ids = input_dict['token_type_ids']
ids[index][:len_] = token_ids
seg_id[index][:len_] = segment_ids
mask_id = unilm_mask_single(seg_id[index])
mask_att[index] = mask_id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

Send 2D MASK to Bert

  1. 通过 Trainsformers.TFBertModel 类创建的Bert实例,其默认接受的attention_mask类型为2维 即[batch_size, MAX_LEN] 之后通过广播的形式传播到 Self-attention矩阵的每一行,因此我们需要修改 Trainsformers.TFBertModel 的逻辑,使其允许接受我们提前计算好的Self-attention矩阵,即[batch_size, MAX_LEN, MAX_LEN]
  2. 具体的:
class TFBertMainLayer(tf.keras.layers.Layer):
	def call(……)
        if len(attention_mask.shape) == 2:
            extended_attention_mask = attention_mask[:, tf.newaxis, tf.newaxis, :]
        elif len(attention_mask.shape) == 3:
            extended_attention_mask = attention_mask[:, tf.newaxis, :, :]
        else:
            raise NotImplementedError
        extended_attention_mask = tf.cast(extended_attention_mask, embedding_output.dtype)
        extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 当你需要使用UniLM完成文本生成任务时,传入你提前计算好的attention_mask矩阵即可,
  2. 当你需要使用UniLM完成文本理解任务时,传入原始的attention_mask序列即可。
  3. 至此你已经完成了 UniLM 模型!

使用UniLM实现新闻标题生成

数据处理部分(略)

数据处理部分只需要将新闻文本和标题同时传给BertTokenizer实例,并通过返回的Segment_id构建attention_mask矩阵即可。

模型训练

技巧1:用自定义损失层来代替损失函数

  1. 通过自定义层,并利用tf.keras.layers.Layer.add_loss 方法,可以允许我们在层的计算中传递Loss,这允许我们利用 input 和 output 计算损失。具体如下:
class Loss(tf.keras.layers.Layer):
    """特殊的层,用来定义复杂loss
    """
    def __init__(self, output_axis=None, **kwargs):
        super(Loss, self).__init__(**kwargs)
        self.output_axis = output_axis

    def call(self, inputs, mask=None):
        loss = self.compute_loss(inputs, mask)
        self.add_loss(loss)
        if self.output_axis is None:
            return inputs
        elif isinstance(self.output_axis, list):
            return [inputs[i] for i in self.output_axis]
        else:
            return inputs[self.output_axis]

    def compute_loss(self, inputs, mask=None):
        raise NotImplementedError

class CrossEntropy(Loss):
    """交叉熵作为loss,并mask掉输入部分
    """
    def compute_loss(self
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/967826
推荐阅读
相关标签
  

闽ICP备14008679号