【Transformer系列(1)】encoder(编码器)和decoder(解码器)

简介: 【Transformer系列(1)】encoder(编码器)和decoder(解码器)

前言

这个专栏我们开始学习transformer,自推出以来transformer在深度学习中占有重要地位,不仅在NLP领域,在CV领域中也被广泛应用,尤其是2021年,transformer在CV领域可谓大杀四方。

在论文的学习之前,我们先来介绍一些专业术语。本篇就让我们先来认识一下encoder和decoder吧!


  🍀本人Transformer相关文章导航:

【Transformer系列(1)】encoder(编码器)和decoder(解码器)

【Transformer系列(2)】注意力机制、自注意力机制、多头注意力机制、通道注意力机制、空间注意力机制超详细讲解

【Transformer系列(3)】 《Attention Is All You Need》论文超详细解读(翻译+精读)

【Transformer系列(4)】Transformer模型结构超详细解读




一、encoder

1.1 简介

encoder也就是编码器,负责将输入序列压缩成指定长度的向量,这个向量就可以看成是这个序列的语义,然后进行编码,或进行特征提取(可以看做更复杂的编码)。

简单来说就是机器读取数据的过程,将现实问题转化成数学问题。如下图所示:


1.2 代码实现

在编码器接口中,我们只指定长度可变的序列作为编码器的输入X。 任何继承这个encoder 基类的模型将完成代码实现。

class encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        self.positional_encoding = Positional_Encoding(config.d_model)
        self.muti_atten = Mutihead_Attention(config.d_model,config.dim_k,config.dim_v,config.n_heads)
        self.feed_forward = Feed_Forward(config.d_model)
        self.add_norm = Add_Norm()
    def forward(self,x): # batch_size * seq_len 并且 x 的类型不是tensor,是普通list
        x += self.positional_encoding(x.shape[1],config.d_model)
        # print("After positional_encoding: {}".format(x.size()))
        output = self.add_norm(x,self.muti_atten,y=x)
        output = self.add_norm(output,self.feed_forward)
        return output

Mutihead_Attention():多头注意力机制:

class Mutihead_Attention(nn.Module):
    def __init__(self,d_model,dim_k,dim_v,n_heads):
        super(Mutihead_Attention, self).__init__()
        self.dim_v = dim_v
        self.dim_k = dim_k
        self.n_heads = n_heads
        self.q = nn.Linear(d_model,dim_k)
        self.k = nn.Linear(d_model,dim_k)
        self.v = nn.Linear(d_model,dim_v)
        self.o = nn.Linear(dim_v,d_model)
        self.norm_fact = 1 / math.sqrt(d_model)
    def generate_mask(self,dim):
        # 此处是 sequence mask ,防止 decoder窥视后面时间步的信息。
        # padding mask 在数据输入模型之前完成。
        matirx = np.ones((dim,dim))
        mask = torch.Tensor(np.tril(matirx))
        return mask==1
    def forward(self,x,y,requires_mask=False):
        assert self.dim_k % self.n_heads == 0 and self.dim_v % self.n_heads == 0
        # size of x : [batch_size * seq_len * batch_size]
        # 对 x 进行自注意力
        Q = self.q(x).reshape(-1,x.shape[0],x.shape[1],self.dim_k // self.n_heads) # n_heads * batch_size * seq_len * dim_k
        K = self.k(x).reshape(-1,x.shape[0],x.shape[1],self.dim_k // self.n_heads) # n_heads * batch_size * seq_len * dim_k
        V = self.v(y).reshape(-1,y.shape[0],y.shape[1],self.dim_v // self.n_heads) # n_heads * batch_size * seq_len * dim_v
        # print("Attention V shape : {}".format(V.shape))
        attention_score = torch.matmul(Q,K.permute(0,1,3,2)) * self.norm_fact
        if requires_mask:
            mask = self.generate_mask(x.shape[1])
            attention_score.masked_fill(mask,value=float("-inf")) # 注意这里的小Trick,不需要将Q,K,V 分别MASK,只MASKSoftmax之前的结果就好了
        output = torch.matmul(attention_score,V).reshape(y.shape[0],y.shape[1],-1)
        # print("Attention output shape : {}".format(output.shape))
        output = self.o(output)
        return output

Feed_Forward() : 两个Linear中连接Relu即可,目的是为模型增添非线性信息,提高模型的拟合能力。

class Feed_Forward(nn.Module):
    def __init__(self,input_dim,hidden_dim=2048):
        super(Feed_Forward, self).__init__()
        self.L1 = nn.Linear(input_dim,hidden_dim)
        self.L2 = nn.Linear(hidden_dim,input_dim)
    def forward(self,x):
        output = nn.ReLU()(self.L1(x))
        output = self.L2(output)
        return output

Add_Norm():残差连接以及LayerNorm

class Add_Norm(nn.Module):
    def __init__(self):
        self.dropout = nn.Dropout(config.p)
        super(Add_Norm, self).__init__()
    def forward(self,x,sub_layer,**kwargs):
        sub_output = sub_layer(x,**kwargs)
        # print("{} output : {}".format(sub_layer,sub_output.size()))
        x = self.dropout(x + sub_output)
        layer_norm = nn.LayerNorm(x.size()[1:])
        out = layer_norm(x)
        return out

1.3 transformer中的使用

1.3.1transformer中encoder的组成

transformer 中 encoder 由 6 个相同的层组成,每个层包含 2 个部分:

  • Multi-Head Self-Attention
  • Position-Wise Feed-Forward Network


1.3.2 每个Block的组成

自注意力机制 + 残差链接 + LayerNorm + FC + 残差链接 + layer Norm此时的输出 = 一个 Block 的输出;


1.3.3 每个Block 中的具体实现步骤

(1)原始的输入向量b 与输出向量a残差相加,得到向量a+b;

【注意】 b是原始的输入向量,下图中输出向量a是考虑整个序列的输入向量得到的结果

(2)将向量 a+b 通过 Layer Normation 得到向量c ;

也就是下图左边部分:

(3)将向量c 通过 FC layer 得到向量d ;

(4)向量c 与向量d 残差相加 ,得到向量e ;

(5)向量e 通过 Layer Norm 输出 向量f;

(6)此时得到的输出向量f 才是 encoder中每个Block中的一个输出向量;

以上步骤就是下图右边部分:

上述步骤,便是原始论文transformer中encoder的设计啦~


二、decoder

2.1 简介

decoder,也就是解码器,负责根据encoder部分输出的语义向量c来做解码工作。以翻译为例,就是生成相应的译文。

简单来说,就是就数学问题,并转换为现实世界的解决方案。

【注意】生成的序列是不定长的。而且上一时刻的输出通常要作为下一时刻的输入。


2.2 代码实现

在上面1.2encoder的代码实现中,我们已经实现了大部分decoder的模块。

但是encoder和decoder实现还是有区别的:

  • decoder的Muti_head_Attention引入了Mask机制
  • decoder与encoder中模块的拼接方式不同
class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.positional_encoding = Positional_Encoding(config.d_model)
        self.muti_atten = Mutihead_Attention(config.d_model,config.dim_k,config.dim_v,config.n_heads)
        self.feed_forward = Feed_Forward(config.d_model)
        self.add_norm = Add_Norm()
    def forward(self,x,encoder_output): # batch_size * seq_len 并且 x 的类型不是tensor,是普通list
        # print(x.size())
        x += self.positional_encoding(x.shape[1],config.d_model)
        # print(x.size())
        # 第一个 sub_layer
        output = self.add_norm(x,self.muti_atten,y=x,requires_mask=True)
        # 第二个 sub_layer
        output = self.add_norm(output,self.muti_atten,y=encoder_output,requires_mask=True)
        # 第三个 sub_layer
        output = self.add_norm(output,self.feed_forward)
        return output

2.3 transformer中的使用

2.3.1transformer中decoder的组成

在transformer中decoder 也是由 6 个相同的层组成,每个层包含 3 个部分:

  • Multi-Head Self-Attention
  • Multi-Head Context-Attention
  • Position-Wise Feed-Forward Network


2.3.2 transformer中encoder和decoder的区别

我们先来看看这个图

(1)第一级中: 将self attention 模块加入了Masked模块,变成了 Masked self-attention, 这样以来就只考虑解码器的当前输入和当前输入的左侧部分, 不考虑右侧部分; ( 注意,第一级decoder的key, query, value均来自前一层decoder的输出,但加入了Mask操作,即我们只能attend到前面已经翻译过的输出的词语,因为翻译过程我们当前还并不知道下一个输出词语,这是我们之后才会推测到的。)

(2)第二级中:引入了 Cross attention 交叉注意力模块 在 masked self-attention 和全连接层 之间加入;

(3)Cross attention 交叉注意力模块的输入 Q,K,V 不是来自同一个模块,K,V 来自编码器的输出, Q来自解码器的输出;

【注意】 解码器的输出一个一个产生的


2.3.3 Masked self attention模块

举个栗子吧~以翻译为例:

  • 输入:我是路人贾
  • 输出: I am Jia

由上一节可知,输入“我是路人贾”这一步是在encoder中进行了编码,那么这里我们具体讨论decoder的操作,也就是加了 Masked self attention模块后如何得到输出(“I am Jia”)的过程。

第1步:

  • 初始输入: 起始符 + Positional Encoding(位置编码)
  • 中间输入:(我是路人贾)Encoder Embedding
  • 最终输出:产生预测“I”

第2步:

  • 初始输入:起始符 + “I”+ Positonal Encoding
  • 中间输入:(我是路人贾)Encoder Embedding
  • 最终输出:产生预测“am”

第3步:

  • 初始输入:起始符 + “I”+ “Love”+ Positonal Encoding
  • 中间输入:(我是路人贾)Encoder Embedding
  • 最终输出:产生预测“Jia”

其实这个原理很简单,主要想表达的就是因为变成了Masked self attention ,所以只考虑输入向量本身, 和输入向量的之前的向量,即左侧向量,而不去考虑后边(右侧)向量。

另外,求相关性的分数时,q,k ,v 同样的也只会考虑当前输入向量的左侧向量部分,而不去考虑输入向量后面的右侧部分。

这里介绍一下论文在Decoder的输入上,对Outputs的Shifted Right操作。

Shifted Right 实质上是给输出添加起始符/结束符,方便预测第1个Token/结束预测过程。

还是看看我们上一个栗子~

正常的输出序列位置关系如下:

  • 0-"I"
  • 1-"am"
  • 2-"Jia"

但在执行的过程中,我们在初始输出中添加了起始符,相当于将输出整体右移1位(Shifted Right),所以输出序列变成如下情况:

  • 0-【起始符】
  • 1-“I”
  • 2-“am”
  • 3-“Jia”

这样我们就可以通过起始符预测“I”,也就是通过起始符预测实际的第1个输出啦。


2.3.4 Cross attetion模块

Cross attetion模块称为交叉注意力模块,是因为向量 q , k , v 不是来自同一个模块

而是将来自解码器的输出向量q 与来自编码器的输出向量 k , v运算。

具体讲来:

向量 q 与向量 k之间相乘求出注意力分数α1 '

注意力分数α1 '再与向量 v 相乘求和,得出向量 b (图中表示为向量 v ) ;


2.3.5 具体实现步骤

(1)经过 Masked self attention:

解码器之前的输出作为当前解码器的输入,并且训练过程中真实标签的也会输入到解码器中,此时这些输入, 通过一个Masked self-attention ,得到输出q向量,注意到这里的q是由解码器产生的;

(2)经过 Cross attention:

将向量q 与来自编码器的输出向量 k , v 运算。具体讲来就是向量 q 与向量 k之间相乘求出注意力分数α1 ',注意力分数α1 '再与向量 v 相乘求和,得出向量 b  ;

(3)经过全连接层:

之后向量 b 便被输入到feed−forward 层, 也即全连接层, 得到最终输出;

上述步骤,便是原始论文transformer中decoder的设计啦~


三、encoder-decoder

刚才已经分别了解了encoder和decoder,接下来我们再来看看encoder-decoder这个框架吧。

3.1 简介

encoder-decoder 模型主要是 NLP 领域里的概念。它并不特值某种具体的算法,而是一类算法的统称encoder-decoder 算是一个通用的框架,在这个框架下可以使用不同的算法来解决不同的任务。

其实整个过程我们可以看做是一个游戏——《你画我猜》。玩家1从系统中抽取拿到题卡,然后通过画画的方式描述该词。玩家2就通过画来猜出题目中的词是什么东东。我们拿目前应用最深入的机器翻译问题举个栗子:

(毕贾索已上线~)

就酱就酱~大家懂就行~


3.2 代码实现

encoder-decoder框架包含了一个编码器和一个解码器,并且还拥有可选的额外的参数。在前向传播中,编码器的输出用于生成编码状态,这个状态又被解码器作为其输入的一部分。

class EncoderDecoder(nn.Module):
    """
    A standard Encoder-Decoder architecture. Base for this and many 
    other models.
    """
    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
        super(EncoderDecoder, self).__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = src_embed
        self.tgt_embed = tgt_embed
        self.generator = generator
    def forward(self, src, tgt, src_mask, tgt_mask):
        "Take in and process masked src and target sequences."
        return self.decode(self.encode(src, src_mask), src_mask,
                            tgt, tgt_mask)
    def encode(self, src, src_mask):
        return self.encoder(self.src_embed(src), src_mask)
    def decode(self, memory, src_mask, tgt, tgt_mask):
        return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)

3.3 注意问题

  • 不论输入和输出的长度是什么,中间的“向量c”长度都是固定的(这是它的缺陷所在)。
  • 根据不同的任务可以选择不同的编码器和解码器(例如,CNN、RNN、LSTM、GRU等)
  • encoder-decoder的一个显著特征就是:它是一个end-to-end的学习算法。
  • 只要符合这种框架结构的模型都可以统称为encoder-decoder模型。

本文参考:

李宏毅机器学习

相关文章
|
2月前
|
机器学习/深度学习 XML 自然语言处理
Transformer 架构—Encoder-Decoder
Transformer 架构—Encoder-Decoder
86 1
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
Transformer:Attention机制、前馈神经网络、编码器与解码器
Transformer:Attention机制、前馈神经网络、编码器与解码器
77 1
|
3月前
|
机器学习/深度学习 编解码
LeViT-UNet:transformer 编码器和CNN解码器的有效整合
LeViT-UNet:transformer 编码器和CNN解码器的有效整合
47 0
|
机器学习/深度学习 自然语言处理 搜索推荐
Transformer之十万个为什么?
这篇博文主要结合个人理解和思考,通过整理和自己的解释列出关于Transformer的部分重要问题(持续更新),看完后会对Transformer有个更好的理解。
Transformer之十万个为什么?
|
2月前
|
机器学习/深度学习 自然语言处理 机器人
编码器-解码器(Encoder-Decoder)结构
编码器-解码器(Encoder-Decoder)结构
183 5
|
2月前
|
机器学习/深度学习 存储 自然语言处理
Transformer中的FFN介绍
Transformer中的FFN介绍
49 0
Transformer中的FFN介绍
|
2月前
|
传感器 人工智能 芯片
BEV+Transformer
BEV+Transformer
24 3
|
8月前
|
机器学习/深度学习
自动编码器(Autoencoder
自动编码器(Autoencoder)是一种无监督式学习模型,旨在通过降低数据维度来提高机器学习模型的性能。它由编码器(Encoder)和解码器(Decoder)两个主要部分组成。编码器的作用是将输入数据压缩成低维度的隐向量,从而捕获数据的主要特征;解码器的作用是将隐向量还原回原始数据空间。自动编码器可以实现类似 PCA 的数据降维和数据压缩功能。
67 2
|
5月前
|
机器学习/深度学习 算法 索引
Transformer中解码器decoder的详细讲解(图文解释)
Transformer中解码器decoder的详细讲解(图文解释)
176 0
Transformer中解码器decoder的详细讲解(图文解释)
|
机器学习/深度学习 人工智能 自然语言处理
Transformer Encoder 简介
Transformer Encoder 简介
204 0