Transformer翻译模型Decoder详解(Masking)

写这个博客的原因在于:大部分解释Transformer的文章都只注重讲解Encoder部分,在Encoder中又侧重讲解self-attention原理。为了读者更好地理解整个Transformer的训练过程,我决定结合代码写一篇在理解了Encoder部分怎么理解Decoder模块的博文。

参考文章:https://jalammar.github.io/illustrated-transformer/
参考代码:https://github.com/Kyubyong/transformer

pre: Encoder

根据以上参考文章及代码理解Encoder的self-attention原理非常容易,这里不再赘述。需要说明的是以下维度:
德文输入X.shape:[batch_size, max_len]
英文标注Y.shape:[batch_size, max_len]
Encoder输出维度
[batch_size, max_len, hidden_units]
也就是代码里的[N, T_q, C]

Decoder

在训练过程中,Transformer同所有seq2seq模型一样,会用到source data以及不断生成的target data的部分数据(理解就是RNN的因果关系,训练过程中不像BiRNN一样使用未来数据,因此需要Masking)。

需要说明的是代码中的key maskingquery masking是对于文本padding部分的掩盖,目的是使Encoder不过多的关注于padding这种无效信息。

causality
代码中的causality部分是是对未来信息的掩盖。这部分代码位于modules.py中。

if causality:
      diag_vals = tf.ones_like(outputs[0, :, :]) # (T_q, T_k)
      tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense() # (T_q, T_k)
      masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1]) # (h*N, T_q, T_k)
   
      paddings = tf.ones_like(masks)*(-2**32+1)
      outputs = tf.where(tf.equal(masks, 0), paddings, outputs) # (h*N, T_q, T_k)

下面我通过对比Decoder中的self-attentionEncoder-Decoder attention两个模块说明Decoder在代码中是如何具体同时attention源数据及生成数据的。这对理解Decoder如何使用数据很关键。
同Encoder一样,使用多个block叠加:

with tf.variable_scope("num_blocks_{}".format(i)):

block中包含使用源数据的self-attention【目标数据自身关注,因此需要掩盖未来数据来模拟逐词生成、类似于单向RNN】,
和使用生成数据的vanilla attention【目标数据关注于源数据,也就是en关注于de,由于源数据是存在的,因此没有属于未来的数据,不需要进行掩盖未来数据的操作,类似于BiRNN】。

self-attention(自身关注,需掩盖未来数据)

self.dec = multihead_attention(queries=self.dec, 
                                keys=self.dec, 
                                num_units=hp.hidden_units, 
                                num_heads=hp.num_heads, 
                                dropout_rate=hp.dropout_rate,
                                is_training=is_training,
                                causality=True, 
                                scope="self_attention")

vanilla attention(关注源数据,causality=False)

self.dec = multihead_attention(queries=self.dec, 
                                keys=self.enc, 
                                num_units=hp.hidden_units, 
                                num_heads=hp.num_heads,
                                dropout_rate=hp.dropout_rate,
                                is_training=is_training, 
                                causality=False,
                                scope="vanilla_attention")

在这个对比中,主要的输入参数不同是:

  • keys
  • causality

keys输入用来计算关注的权重,在代码中key=value,同时用来计算权重以及attention之后的结果。
self-attention:关注self.dec,也就是自身关注,设置causality=True掩盖训练数据集中的未来数据。
vanilla attention:关注self.enc,也就是关注数据集中的源数据,设置causality=False来取消掩盖未来数据(因为训练集的X是已知的)。

causality的不同,具体代码如本文的第一段代码所示,在此复制过来进行分析:

if causality:
      diag_vals = tf.ones_like(outputs[0, :, :]) # (T_q, T_k)
      tril = tf.linalg.LinearOperatorLowerTriangular(diag_vals).to_dense() # (T_q, T_k)
      masks = tf.tile(tf.expand_dims(tril, 0), [tf.shape(outputs)[0], 1, 1]) # (h*N, T_q, T_k)
   
      paddings = tf.ones_like(masks)*(-2**32+1)
      outputs = tf.where(tf.equal(masks, 0), paddings, outputs) # (h*N, T_q, T_k)

这里主要是使用了
tf.linalg.LinearOperatorLowerTriangular().to_dense()
这个函数生成mask,该函数的作用是将:

1111
1111
1111
1111

变成:

1000
1100
1110
1111

而后通过:

paddings = tf.ones_like(masks)*(-2**32+1)
outputs = tf.where(tf.equal(masks, 0), paddings, outputs) # (h*N, T_q, T_k)

将未来数据的权重设置为无穷小,以达到在训练过程中不关注未来数据的作用。也就是生成第一个词时关注第0个token,生成第二个词时关注第0及第1个token,如上表格所示。

而在vanilla attention中设置causality=False关注源数据的所有token。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Transformer模型中的Decoder是一个由多个Decoder Layer组成的堆叠结构,每个Decoder Layer包括两个子层:一个自注意力子层和一个前馈子层。Decoder的输入是一个由Encoder输出的上下文向量和一个可变长度的目标序列。Decoder的任务是生成一个与目标序列等长的输出序列,其中每个位置的输出都是基于之前的输出和上下文向量计算得到的。 Decoder的自注意力子层与Encoder的自注意力子层类似,但是在计算注意力权重时,Decoder会对目标序列进行遮挡,以确保模型只关注未来的输入。具体来说,对于位置i,Decoder只会考虑目标序列中在位置j<=i的输入,这种遮挡方式被称为“Masked Self-Attention”。 Decoder的前馈子层包括两个全连接层和一个激活函数,其中第一个全连接层的输出维度与输入维度相同,第二个全连接层的输出维度与模型的隐藏状态维度相同。这样做的目的是扩展模型的表示能力,提高模型在序列生成任务上的性能。 在Decoder中,每个位置的输出都是基于之前的输出和上下文向量计算得到的。具体来说,对于位置i,Decoder会首先将上一时刻的输出作为输入,并且计算出一个上下文向量,然后使用该向量和目标序列中第i个位置的输入作为输入,计算出该位置的输出。这个过程可以通过堆叠多个Decoder Layer来增强模型的表示能力,从而提高模型在序列生成任务上的性能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值