Transformer深入理解(持续更新)

Transformer深入理解(持续更新)

编码器:原文是6个编码器堆叠(数字6没有什么神奇之处,你也可以尝试其他数字)

解码组件部分也是由相同数量(与编码器对应)的解码器(decoder)组成的。

所有的编码器在结构上都是相同的,但它们没有共享参数。每个编、解码器都可以分解成两个子层:自注意力层和前馈神经网络,

我们首先将每个输入单词通过词嵌入算法转换为词向量,每个单词都被嵌入为512维的向量

将输入序列进行词嵌入之后,每个单词都会流经编码器中的两个子层。

接下来我们看看Transformer的一个核心特性,在这里输入序列中每个位置的单词都有自己独特的路径流入编码器。在自注意力层中,这些路径之间存在依赖关系。而前馈(feed-forward)层没有这些依赖关系。因此在前馈(feed-forward)层时可以并行执行各种路径。

在这里插入图片描述

位置编码操作:

位置编码操作的scale

self.embed_scale = tf.math.sqrt(tf.cast(embed_dim, tf.float32))

embedded_tokens = embedded_tokens * self.embed_scale
embedded_positions = self.position_embeddings(positions)
return embedded_tokens + embedded_positions

代码在这里进行了一个放大的操作,乘上一个数得到更大的结果,可能位置编码的影响就减少了

理解编码器:

1、词嵌入:每个单词被嵌入为E维向量

2、位置编码:词嵌入与未知编码向量相加得到基于时间步的嵌入,输入到注意力层,这里可以使用函数式位置编码,也可以使用嵌入式位置编码向量

3、自注意力层:自注意力计算:词向量和三个权重矩阵(E×K)相乘计算三个K维向量(QKV)查询向量q、键向量k、值向量v;q分别和各个词向量的k进行点乘,得到一个分数,除以缩放以后,通过softmax输出结果,这里如果有mask,需要注意力结果进行遮盖,各个词向量的v再呈上softmax分数,最后求和得到q对应的最终K维向量

4、多头注意力:它给出了注意力层的多个“表示子空间”,多个查询/键/值权重矩阵集,8个头的话,就形成8个最终K维向量,然后拼接后形成8K维的向量,再和一个权重矩阵(8K×E)相乘,得到E维的向量

5、前向反馈网络:每个单词的自注意力结果向量再输入到一个全连接前馈网络,这个全连接有两层,第一层的激活函数是ReLU,第二层是一个线性激活函数。

理解解码器:

1、词嵌入、位置编码、mask的输入

2、自注意力层:需要对每个位置的单词进行注意力,也是有mask的标签遮蔽以及序列pad遮蔽

3、编码-解码注意力层:输入的query是自注意力层的输出,key和value是编码器的输出。

4、前馈神经网络:如前

理解多头注意力和自注意力:

https://zhuanlan.zhihu.com/p/231631291

著名的Transformer是基于Attention机制构建的,当前最流行的Attention机制是Scaled-Dot Attention,数学公式为:

在这里插入图片描述

通俗理解是:本来有两个句子,现需要对比两个句子;于是第一个句子用矩阵 Q Q Q 表示出来,第二个句子用矩阵 K K K 和矩阵 V V V 表示出来;计算的时候先用 Q Q Q K K K 点积,再经过softmax激活,得到两个句子的相似性,值的大小也表示着我需要把注意力放在 V V V的哪个位置上;再用得到的结果乘 V V V ,得到最终提取到的特征。

其中:

Q Q Q : n × d k n×d_{k} n×dk : n n n 表示的是seq_len,也就是第一个句子的长度; d K d_K dK表示的是第一个句子中每个字的特征向量维度,在BERT中是768;

K K K : m × d k m×d_{k} m×dk : m m m 表示的是seq_len,也就是第二个句子的长度 ; 这里 d K d_K dK Q Q Q中的一样;

V V V​ : m × d v m×d_{v} m×dv​ : m m m​表示的是seq_len,也就是第二个句子的长度; d v d_v dv​​ 表示第二个句子中每个字的特征向量维度。

d k \sqrt{d_k} dk ​ :缩放因子起到调节作用,使得内积不至于太大(太大的话softmax后就非0即1了,不够“soft”了)。

这里有几个点需要注意:

  • Q和K在特征向量维度上是一致的,因为在点积计算的时候两个句子虽然长度不一样,但是特征维度需要一样;
  • K和V在seq_len维度上是一致的,因为K和V 表示的是同一个句子,但是Q和K的矩阵shape不一样
  • 以上的结论表示的通用情况下,自注意力是两个一样的句子比较,也就是我注意我自己,是一种特殊情况。

矩阵计算如下:

Q和K计算后为: n × d k ⋅ d k × m = n × m n×d_k · d_k × m = n × m n×dkdk×m=n×m ,缩放因子和softmax并不改变矩阵shape,再和 V 计算后 n × m ⋅ m × d v n × m · m × d_v n×mm×dv , 总的计算路径为 ( n ∗ d k ) ∗ ( d k ∗ m ) ∗ ( m ∗ d v ) = n ∗ d v (n*d_k) * (d_k * m) * (m * d_v) = n * d_v (ndk)(dkm)(mdv)=ndv 也就是说,经过这层计算后,是把 n ∗ d k n*d_k ndk 序列变成了 n ∗ d v n*d_v ndv​ 的序列。

理解mask:

什么是掩码张量:

​ 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量

掩码张量的作用:

​ 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.

transformer中的mask主要分为两个部分:padding mask 和 sequence mask。

padding mask 处理不定长数据

padding mask就是去除padding部分的影响。比如在nlp中,每个句子的长度不一样,但是框架中只支持矩阵运算,因此要想办法弄成相同的长度,长的剪掉,短的用零补够。
可是attention的本意实在句子中的词间进行操作,如果直接attention,却有可能把注意力放在了补零的位置,这是无意义的,所以要把他事先排除掉。具体的做法是,把这些位置加上一个非常大的负数(可以是负无穷),这样的话,经过softmax,这些位置的概率就会接近0.

sequence mask 处理防止标签外漏

sequence mask是为了使decoder不能看到未来的信息。也就是对于一个序列,在时间步t,我们的解码输出应该只依赖于t时刻之前的输出,而不能依赖于t之后的输出。因此我们需要把t之后的信息隐藏起来。怎么做呢?只需产生一个下三角矩阵,上三角的值为0,下三角的值全为1,对角线也是1.把这个矩阵作用在一个序列上,即达到了我们的目的。

sequence mask 一般是通过生成一个上三角为0的矩阵来实现的,上三角区域对应要mask的部分。

对于decoder的self-attention,同时需要padding mask 和 sequence mask,具体实现就是两个mask相加。对于其他情况,只用padding mask。

对于Decoder,在batch训练时会同时需要padding mask和sequence mask。

在测试时为单例,仅需要加上sequence mask,这样可以固定住每一步生成的词,让前面生成的词在self-attention时不会包含后面词的信息,这样也使测试和训练保持了一致。

不过,在测试过程中,预测生成的句子长度逐步增加,因此每一步都会要生成新的sequence mask矩阵的,是维度逐步增加的下三角方阵。

keras源码解释:

mask是个加法操作,-inf 负无穷在softmax的时候是0,代表注意力是0,就是没注意到

  def _masked_softmax(self, attention_scores, attention_mask=None):
    # Normalize the attention scores to probabilities.
    # `attention_scores` = [B, N, T, S]
    if attention_mask is not None:
      # The expand dim happens starting from the `num_heads` dimension,
      # (<batch_dims>, num_heads, <query_attention_dims, key_attention_dims>)
      # 这里是扩展维度,比如输入的补齐mask需要扩展到softmax的维度
      mask_expansion_axes = [-len(self._attention_axes) * 2 - 1]
      for _ in range(len(attention_scores.shape) - len(attention_mask.shape)):
        attention_mask = array_ops.expand_dims(
            attention_mask, axis=mask_expansion_axes)
    return self._softmax(attention_scores, attention_mask)

softmax源码:这里是个进行mask是个加法操作。

@keras_export('keras.layers.Softmax')
class Softmax(Layer):
  """Softmax activation function.

  Example without mask:

  >>> inp = np.asarray([1., 2., 1.])
  >>> layer = tf.keras.layers.Softmax()
  >>> layer(inp).numpy()
  array([0.21194157, 0.5761169 , 0.21194157], dtype=float32)
  >>> mask = np.asarray([True, False, True], dtype=bool)
  >>> layer(inp, mask).numpy()
  array([0.5, 0. , 0.5], dtype=float32)

  Input shape:
    Arbitrary. Use the keyword argument `input_shape`
    (tuple of integers, does not include the samples axis)
    when using this layer as the first layer in a model.

  Output shape:
    Same shape as the input.

  Args:
    axis: Integer, or list of Integers, axis along which the softmax
      normalization is applied.
  Call arguments:
    inputs: The inputs, or logits to the softmax layer.
    mask: A boolean mask of the same shape as `inputs`. Defaults to `None`. The
      mask specifies 1 to keep and 0 to mask.

  Returns:
    softmaxed output with the same shape as `inputs`.
  """

  def __init__(self, axis=-1, **kwargs):
    super(Softmax, self).__init__(**kwargs)
    self.supports_masking = True
    self.axis = axis

  def call(self, inputs, mask=None):
    if mask is not None:
      # Since mask is 1.0 for positions we want to keep and 0.0 for
      # masked positions, this operation will create a tensor which is 0.0 for
      # positions we want to attend and -1e.9 for masked positions.
      adder = (1.0 - math_ops.cast(mask, inputs.dtype)) * (
          _large_compatible_negative(inputs.dtype))

      # Since we are adding it to the raw scores before the softmax, this is
      # effectively the same as removing these entirely.
      inputs += adder
    if isinstance(self.axis, (tuple, list)):
      if len(self.axis) > 1:
        return math_ops.exp(inputs - math_ops.reduce_logsumexp(
            inputs, axis=self.axis, keepdims=True))
      else:
        return backend.softmax(inputs, axis=self.axis[0])
    return backend.softmax(inputs, axis=self.axis)

训练和推理:

训练和推理使用了完整的一次attention mask

推理输出和训练输出一样,只不过推理的时候一次次利用上一个输出填充进序列而已,因为不知道decoder的下一个输入是什么样的,但是训练的时候因为知道完整的序列,所以使用mask直接就可以全部一次性输出

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值