Transformer逐层分解1

1. Transformer整体结构

首先介绍Transformer的整体结构,下图是Transformer用于中英文翻译的整体结构:

可以看到Transformer由Encoder和Decoder两部分组成,Encoder和Decoder都包含了6个block。

  • Transformer的工作流程大体如下:

    • 第一步:获取输入句子的每一个单词的表示向量X,X右单词的Embedding(Embedding就是从原始数据提取出来的Feature)和单词位置的Embedding相加得到。
    • 第二步:将得到的单词表示向量矩阵(如上图所示,每一行是一个单词的表示x)传入Encoder中,经过6个Encoder block后可以得到句子中所有单词的编码信息矩阵C,如下图,单词向量矩阵用\(X_{n*d}\)表示,n是句子中单词个数,d是表示向量的维度(d=512)。每一个Encoder block输出的矩阵维度与输入完全一致。
    • 第三步:将Encoder输出的编码信息矩阵C传递到Decoder中,Decoder依次会根据当前翻译过的单词1~i翻译下一个单词i+1,如下图所示。在使用过程中,翻译到单词i+1的时候需要通过Mask(掩盖)操作盖住i+1之后的单词。

    上图Decoder接收了Encoder的编码矩阵C,然后首先输入一个翻译开始符"<Begin>",预测第一个单词"I";然后输入翻译开始符"<Begin>"和单词"I",预测单词"have",以此类推。这是Transformer使用时候的大致流程,接下来是里面各个部分的细节。

2. Transformer的输入

Transformer中单词的输入表示x由单词Embedding和位置编码(Positional Encodeing)相加得到。
img

2.1 单词Embedding

单词的Embedding有很多种方式可以获取,例如可以采用Word2Vec、Glove等算法预训练得到,也可以在Transformer中训练得到。

2.2 位置Embedding

Transformer中除了单词的Embedding,还需要使用位置Embedding表示单词出现在句子中的位置。
因为Transformer不采用RNN结构,而是使用全局信息,不能利用单词的顺序信息,而这部分信息对于NLP来说非常重要。所以Transformer中使用位置Embedding保存单词在序列中的相对或绝对位置。

位置Embedding用PE表示,PE的维度与单词Embedding是一样的。PE可以通过训练得到,也可以使用某种公式计算得到。在Transformer中采用了后者,计算公式如下:

其中,pos表示单词在句子中的位置,d表示PE的维度(与词Embedding一样),2i表示偶数的维度,2i+1表示奇数维度(即2i<=d,2i+1<=d)使用这种公式计算PE有以下的好处:

  • 使PE能够适应比训练集里面所有句子更长的句子,假设训练集里面最长的句子是有20个单词,突然来了一个长度为21的句子,则使用公式计算的方法可以计算出第21位的Embedding。
  • 可以让模型容易地计算出相对位置,对于固定长度的间距k,PE(pos+k)可以用PE(pos)计算得到。
    • 因为Sin(A+B) = Sin(A)Cos(B) + Cos(A)Sin(B), Cos(A+B) = Cos(A)Cos(B) - Sin(A)Sin(B)

3. Self-Attention(自注意力机制)

Transformer的内部结构图如下:

  • 左侧为Encoder block
  • 右侧为Decoder block
  • 红色圈中的部分为Multi-Head Attention,是由多个Self-Attention组成的
  1. 可以看到Encoder block包含一个Multi-Head Attention
  2. 而Decoder block包含两个Multi-Head Attention(其中有一个用到Masked)
  3. Multi-Head Attention上方还包括一个Add & Norm层
  4. Add表示残差连接(Residual Connection)用于防止网络退化
  5. Norm表示Layer Normalization,用于对每一层的计划值进行归一化

因为Self-Attention是Transformer的重点,所以我们重点关注Multi-Head Attention 以及Self-Attention,首先详细了解一下Self-Attention的内部逻辑。

3.1 Self-Attention结构


上图是Self-Attention的结构,在计算的时候需要用到矩阵Q(查询),K(键值),V(值)。在实际中,Self-Attention接收的是输入(单词的表示向量x组成的矩阵X)或者上一个Encoder block的输出。而Q,K,V正是通过Self-Attention的输入进行线性变换得到的。

3.2 Q,K,V的计算

Self-Attention的输入矩阵X进行表示,则可以使用线性变换矩阵WQ,WK,WV计算得到Q,K,V。计算如下图所示,注意X,Q,K,V的每一行都表示一个单词。

实现

import numpy as np 
from math import sqrt
import torch 
from torch import nn

class Self_Attention(nn.Module):
    # input: batch_size * seq_len * input_dim
    # Q: batch_size * seq_len * dim_k
    # K: batch_size * seq_len * dim_k
    # V: batch_size * seq_len * dim_v
    def __init__(self,input_dim,dim_k,dim_v):
        super(Self_Attention,self).__init__()
        self.q = nn.Linear(input_dim, dim_k)
        self.k = nn.Linear(input_dim, dim_k)
        self.v = nn.Linear(input_dim, dim_v)
        self._norm_fact = 1 / sqrt(dim_k)
  
    def forward(self,x):
        Q = self.q(x)  # Q: batch_size * seq_len * dim_k
        K = self.k(x)  # K: batch_size * seq_len * dim_k
        V = self.v(x)  # V: batch_size * seq_len * dim_v
      
        # Q * K.T()  batch_size * seq_len * seq_len 
        atten = nn.Softmax(dim=-1)(torch.bmm(Q,K.permute(0,2,1))) * self._norm_fact
        # Q * k.T() * V batch_size * seq_len * dim_v
        output = torch.bmm(atten,V)
      
        return output
  
X = torch.randn(4,3,2)
print(X)
self_atten = Self_Attention(2, 4, 5)
res = self_atten(X)
print(res.shape)

3.3 Self-Attention的输出

得到矩阵Q、K、V之后就可以计算出Self-Attention的输出了,计算公式如下:

公式中计算矩阵Q和K每一行向量的内积,为了防止内积过大,一次除以\(d_k\)的平方跟。Q乘以K的转置后,得到的矩阵行列数都为n,n为句子单词数,这个矩阵可以表示单词之间的attention强度。下图为Q乘以\(K^T\),1234表示的是句子中的单词。

得到\(QK^T\)之后,使用Softmax计算每一个单词对于其他单词的attention系数,公式中的Softmax是对矩阵的每一行进行Softmax,即每一行的和都变为1。

得到Softmax矩阵之后可以和V相乘,得到最终的输出Z

上图中Softmax矩阵第1行表示单词1与其他所有单词的attention系数,最终单词1的输出\(Z_1\)等于所有单词i的值\(V_i\)根据attention系数的比例加载一起得到,如下图所示:

3.4 Multi-Head Attention

在上一步,我们已经知道怎么通过Self-Attention计算得到输出矩阵Z,而Multi-Head Attention 是由多个Self-Attention组合形成的,下图是论文中Multi-Head Attention的结构图。

从上图可以看到Multi-Head Attention包含多个Self-Attention层,首先将输入X分别传递到h个不同的Self-Attention中,计算得到h个输出矩阵Z。下图是h=8时候的情况,此时会得到8个输出矩阵Z。

得到8个输出矩阵\(Z_1\)\(Z_8\)之后,Multi-Head Attention将他们拼接在一起(Concat),然后传入一个Linear层,得到Multi-Head Attention最终的输出Z。

可以看到Multi-Head Attention输出的矩阵Z与其输入的矩阵X的维度是一样的。

实现

# Muti-head Attention 机制的实现
from math import sqrt
import torch
import torch.nn as nn


class Self_Attention_Muti_Head(nn.Module):
    # input: batch_size * seq_len *input_dim 
    # q: bath_size * seq_len * dim_k
    # k: bath_size * seq_len * dim_k
    # v: bath_size * seq_len * dim_v
    def __init__(self,input_dim,dim_k,dim_v,nums_head):
        super(Self_Attention_Muti_Head, self).__init__()
        assert(dim_k % nums_head ==0)
        assert(dim_v % nums_head ==0)
        self.q = nn.Linear(input_dim, dim_k)
        self.k = nn.Linear(input_dim, dim_k)
        self.v = nn.Linear(input_dim, dim_v)

        self.nums_head = nums_head
        self.dim_k = dim_k
        self.dim_v = dim_v
        self._norm_fact = 1 / sqrt(dim_k)

      
    def forward(self,x):
   
        Q = self.q(x).reshape(-1,x.shape[0],x.shape[1],self.dim_k // self.nums_head)
        K = self.k(x).reshape(-1,x.shape[0],x.shape[1],self.dim_k // self.nums_head)
        V = self.v(x).reshape(-1,x.shape[0],x.shape[1],self.dim_v // self.nums_head)
      
        print(x.shape)
        print(Q.size())

        atten = nn.Softmax(dim=-1)(torch.matmul(Q,K.permute(0,1,3,2))) # Q * K.T() ---> batch_size * seq_len * seq_len
        output = torch.matmul(atten,V).reshape(x.shape[0],x.shape[1],-1) # Q * K.T() * V ---> batch_size * seq_len * dim_v
      
        return output

x = torch.rand(1,3,4)
print(x)

atten = Self_Attention_Muti_Head(4, 4, 4, 2)
y = atten(x)
print(y.shape)

4. Encoder结构

整体结构图中红色部分是Transformer的Encoder block结构,可以看到是由Multi-Head Attention,Add & Norm,Feed Forward,Add & Norm 组成的。刚刚已经了解了Multi-Head Attention的计算过程,现在了解一下Add & Norm 和Feed Forward部分。

4.1 Add & Norm


其中X表示Multi-Head Attention 或者Feed Forward的输入,MultiHeadAttention(X)和FeedForward(X)表示输出(输出与输入X维度是一样的,所以可以相加)

Add指X+MultiHeadAttention(X),是一种残差连接,通常用于解决多层网络训练的问题,可以让网络只关注当前差异的部分,在ResNet中经常用到:

4.2 Feed Forward

Feed Forward层比较简单,是一个两层的全连接层,第一层的激活函数为Relu,第二层不使用激活函数,对应的公式如下。

X是输入,Feed Forward最终得到的输出矩阵的维度与X一致。

4.3 组成Encoder

通过上面描述的Multi-Head Attention,Feed Forward,Add & Norm 就可以构造出一个Encoder block,Encoder block接收输入矩阵\(X_{n{\times}d}\),并输出一个矩阵\(O_{n{\times}d}\)。通过多个Encoder block叠加就可以组成Encoder。

  • 第一个Encoder block的输入为句子单词的表示向量矩阵
  • 后续Encoder block的输入是前一个Encoder block的输出
  • 最后一个Encoder block输出的矩阵就是编码信息矩阵C,这一矩阵后续会用到Decoder中

5. Decoder结构


红色部分为Transformer的Decoder block结构,与Encoder block相似,但是存在一些区别:

  • 包含两个Multi-Head Attention层
  • 第一个Multi-Head Attention层采用了Masked操作
  • 第二个Multi-Head Attention层的K,V矩阵使用Encoder的编码信息矩阵C进行计算,而Q使用上一个Decoder block的输出
  • 最后有一个Softmax层计算下一个翻译单词的概率

5.1 第一个Multi-Head Attention

Decoder block的第一个Multi-Head Attention采用了Masked操作,因为在翻译的过程中是顺序翻译的,即翻译完第i个单词,才可以翻译第i+1个单词。通过Masked操作可以防止第i个单词知道i+1个单词之后的信息。下面以“我有一只猫”翻译成“I have a cat”为例,了解一下Masked操作。

  • 在Decoder的时候,是需要根据之前的翻译,求解当前最有可能的翻译,如下图所示。首先根据输入“<Begin>”预测出第一个单词为“I”,然后根据输入“<Begin> I”预测下一个单词“have”。
  • Decoder可以在训练的过程中使用Teacher Forcing并且并行化训练,即将真嘎却的单词序列(<Begin> I have a cat) 和对应输出(I have a cat <end>) 传递到Decoder。那么在预测第i个输出时,就要将第i+1之后的单词掩盖住。
  • 注意Mask操作是在Self-Attention的Softmax之前使用的,下面用0 1 2 3 4 5 分别表示<Begin> I have a cat <end>
  1. 首先是Decoder的输入矩阵和Mask矩阵,输入矩阵包含“<Begin> I have a cat” (0,1,2,3,4,5)五个单词的表示向量,Mask是与一个5x5的矩阵。在Mask可以发现单词0只能使用单词0的信息,而单词1可以使用单词0,1的信息,即只能使用之前的信息。
  2. 接下来的操作和之前的Self-Attention一样,通过输入矩阵X计算得到Q、K、V矩阵。然后计算Q和\(K^T\)的乘积\(QK^T\)

  1. 在得到\(QK^T\)之后需要进行Softmax,计算attention score,我们在Softmax之前需要使用Mask矩阵遮挡住每一个单词之后的信息,遮挡操作如下:

得到Mask\(QK^T\)之后,在其上进行Softmax,每一行的和都为1,但是单词0在单词1,2,3,4上的attention score都为0。

  1. 使用Mask\(QK^T\)与矩阵V相乘,得到输出Z,则单词1的输出向量Z1是只包含1信息的。
  2. 通过上述步骤就可以得到一个Mask Self-Attention的输出矩阵\(Z_i\),然后和Encoder类似,通过Multi-Head Attention拼接多个输出\(Z_i\)然后计算得到第一个Multi-Head Attention的输出Z,Z与输入X的维度一样。

5.2 第二个 Multi-Head Attention

  • Decoder block第二个Multi-Head Attention变化不大,主要的区别在于其中Self-Attention的K,V矩阵不是使用上一个Decoder block的输出计算的,而是使用Encoder的编码信息矩阵C计算的。
  • 根据Encoder的输出C计算得到K,V,根据上一个Decoder block的输出Z计算Q(如何是第一个Decoder block则使用输入矩阵X进行计算),后续的计算方法与之前描述的一致。
  • 这样做的好处是在Decoder的时候,每一位单词都可以利用到Encoder所有单词的信息(这些信息无需Mask)。

5.3 Softmax预测输出单词

Decoder block最后的部分是利用Softmax预测下一个单词,在之前的网络层我们可以得到一个最终的输出Z,因为Mask的存在,使得单词0的输出Z0只包含单词0的信息,如下:

Softmax根据输出矩阵的每一行预测下一个单词:

这就是Decoder block的定义,与Encoder一样,Decoder是由多个Decoder block组合而成。

Transformer总结

  • Transformer与RNN不同,可以比较好地并行训练。
  • Transformer本身是不能利用单词的顺序信息的,因此需要在输入中添加位置Embedding,否则Transformer就是一个词袋模型。
  • Transformer的重点是Self-Attention结构,其中用到的Q、K、V矩阵通过输出进行线性变换得到。
  • Transformer中Multi-Head Attention中有多个Self-Attention,可以捕获单词之间多种维度上的相关系数attention score。
posted @ 2024-04-09 11:22  野哥李  阅读(163)  评论(0编辑  收藏  举报