Transformer

 目录

一、 理论知识

1.  编码器

2. 解码器

2.1 Decoder的流程

2.2 Mask含义

2.3 Decoder的输入     在训练和测试不一样

二、 代码复现

1. Transformer整体架构

2. Encoder 

2.1 词向量embedding:

 2.2 位置编码

2.3 EncoderLayer

3. Decoder

3.1 DecoderLayer


参考资料:

B站DASOU整体讲解

李宏毅老师decoder部分讲解

仅是个人笔记

一、 理论知识

整体框架如下:

1.  编码器

由输入部分(1)      +             N个block(2 3)堆叠

 每个Block中包含了:

自注意力机制 + 残差链接 + LayerNorm + FC + 残差链接 + layer Norm

2. 解码器

编码器与解码器的区别:

1. 将self attention 模块改成了 masked self-attention

2. 比编码器多了一个 Cross attention 交叉注意力模块

3. Cross attention 交叉注意力模块的Q K V来源不同

K V来自编码器最后一层的输出;  Q 来自解码器的输出。如下图:

2.1 Decoder的流程

encoder完成编码(encoder是并行同时输入运算的

decoder开始启动(递归单个输出):

会给decoder输入一个  “Start”  的标志,得到decoder的一个输出a;

下一步,把a输入到decoder,得到decoder的一个输出b;

下一步,把b输入到decoder,得到decoder的一个输出c;

.........

直到decoder的输出是一个结束标识符  “END”  。

整个Transformer流程结束。

如下图所示:

2.2 Mask含义

以上图为例:

第一步输入 “start”:     decoder自注意计算只有 “start”,                                 得到输出      “机”

第二步输入 “机”:        decoder自注意计算有 “start”和 “机”,                           得到输出      “器”

第三步输入 “器”:        decoder自注意计算有 “start”和 “机”和 “器”,                 得到输出      “学”

。。。。。。

最后一步输入“习”:  decoder自注意计算有 “start” “机” “器”“学”“习”                得到输出      “END”

过程结束。

Mask就是 和  当前输入  之前的输入做计算;而不是所有的输入做计算。

2.3 Decoder的输入     在训练和测试不一样

测试阶段(没有真实标签):

1.从input sequence中计算embeding,通过N个Encoder块得到各个单词之间关系。

2.使用一个开始的标志进行预测,例如上面的<SS>输入到decoder块中,并结合从Encoder中得到输入序列的关系,从而得到一个输出,即<SS>的下一个单词“Hola”。

3.将["<SS>", "Hola"]作为输入,重复上述过程,直到输出一个结束的提示,如<EOS>

在测试阶段是一个单词一个单词输出,而不是所有单词一起输出。

也就是标题  2.1 Decoder的流程

训练阶段(有真实标签):

  • 模型训练时不宜采用上面的方式:

因为decoder 先前的输出     错误,      然后使用该    错误   作为decoder 当前的输入,有可能会导致后面的输出全部错误;而且递归单步输出,速度太慢。

  • 充分利用label

训练阶段有label,但是label是存在偏移的;通过一个有偏移的label去预测一个没有偏移的label

eg:

decoder输入为     [‘<S>’,  ’机’ ,  ‘器’,   ‘学’,   ‘习’]

             输出为     [’机’ ,  ‘器’,   ‘学’,   ‘习’ , ’<E>’]

在训练阶段我们并行训练:

一次性给出decoder的全部输入,然后一次性预测decoder的全部输出

而不是像测试过程一样,只能用上一个单词的输出来预测下一个,

因此会产生一个问题:由于我们是直接将全部目标序列输入进行训练的,因此模型很容易看到未来信息,所以做Attention计算时使用MASK,只和当前输入 前面的输入做attention。

eg:  

“s”:的自注意计算只有 “s”;         “机”:的自注意计算有 “s”和 “机”

        。。。。。。                          “习”:的自注意计算有 “s” “机” “器”“学”“习”   

二、 代码复现

代码来源

目前看到的transformer时序预测,,,都只用了Encoder,之后使用全连接,decoder未使用attention???

有待继续查找资料,未解决

1. Transformer整体架构

class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        self.encoder = Encoder()  ## 编码层
        self.decoder = Decoder()  ## 解码层
        self.projection = nn.Linear() #全连接---输出

2. Encoder 

包含三个部分:

词向量embedding;

位置编码部分;

注意力层及后续的前馈神经网络

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        self.src_emb = nn.Embedding(src_vocab_size, d_model)  
        ## 这个其实就是去定义生成一个矩阵,大小是 src_vocab_size * d_model

        self.pos_emb = PositionalEncoding(d_model) ## 位置编码

        self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)]) 
        ## 使用ModuleList对多个encoderlayer进行堆叠

2.1 词向量embedding:

将某种格式的输入数据,例如文本,转变为模型可以处理的向量表示

借助torch提供的nn.Embedding

self.src_emb = nn.Embedding(src_vocab_size, d_model) 
                        #      文字个数    每个文字的维度

最终效果就是   输入(batch,len_seq)---------- 输出(batch,len_seq,dim)

 2.2 位置编码

所有的时间步是同时输入,并行推理的,因此要融合进位置编码的信息

## PositionalEncoding 代码实现
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        ## pe[:, 0::2]  是从0开始到最后面,偶数位置
        pe[:, 1::2] = torch.cos(position * div_term)
        ##pe[:, 1::2]   从1开始到最后面,奇数位置
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer('pe', pe)  
        ## 定一个缓冲区,这个参数不更新
 
    def forward(self, x):
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)

最终效果就是   输入(batch,len_seq,dim)---------- 输出(batch,len_seq,dim)

2.3 EncoderLayer

下图中的  2+3 部分

  • 先实现 MultiHeadAttention

需要一个小功能函数:scaled_dot_product_attention   没太懂。。。。

class ScaledDotProductAttention(nn.Module):
    def __init__(self):
        super(ScaledDotProductAttention, self).__init__()

    def forward(self, Q, K, V, attn_mask):
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)
        scores.masked_fill_(attn_mask, -1e9) 
        attn = nn.Softmax(dim=-1)(scores)
        context = torch.matmul(attn, V)
        return context, attn

整个scaled_dot_product_attention所做的工作就是就是Q*K的转置,然后除以一个标量,然后softmax一下,最后乘以V向量。

在其中加入了两个mask操作。这是为了将Q和K中一些填充的为零的向量清除掉。

实现 MultiHeadAttention:    没太懂。。。。

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        self.W_Q = nn.Linear(d_model, d_k * n_heads)
        self.W_K = nn.Linear(d_model, d_k * n_heads)
        self.W_V = nn.Linear(d_model, d_v * n_heads)
        self.linear = nn.Linear(n_heads * d_v, d_model)
        self.layer_norm = nn.LayerNorm(d_model)
 
    def forward(self, Q, K, V, attn_mask):
        ##Q K V:   [batch_size x len x d_model]

        residual, batch_size = Q, Q.size(0)

        q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2)  
        k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2)  
        v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2) 
        # q_s  k_s  v_s  :  [batch_size x n_heads x len x d_k] 
 
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)
        # [batch_size x n_heads x len x len],就是把pad信息重复了n个头上
 
 
        ## ScaledDotProductAttention函数
        ## 得到的结果有两个:context: [batch_size x n_heads x len x d_v]
        ##                     attn: [batch_size x n_heads x len x len_k]
        context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
        

        context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) 
        # context: [batch_size x len_q x n_heads * d_v]

        output = self.linear(context)  # output: [batch_size x len_q x d_model]

        return self.layer_norm(output + residual), attn  

  • 前馈神经网络
class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
        self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
        self.layer_norm = nn.LayerNorm(d_model)
    def forward(self, inputs):
        residual = inputs     # 要先进行维度交换(B,len,dim)---(B,dim,len)
        output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))                                  
        output = self.conv2(output).transpose(1, 2)  # (B,dim,len)---(B,len,dim)
        return self.layer_norm(output + residual)

#########################  注意是nn.Conv1d
Conv1d 输入输出以及卷积核维度:                 
                                              对应文本数据
input  (批大小, 数据的通道数, 数据长度)-------------------(B,dim,len)
output (批大小, 产生的通道数, 卷积后长度)-----------------(B,dim,len)

卷积后的长度:(n - k + 2 * p ) / s + 1
k: 卷积核大小,p: 使用边界填充,s: 步长。

最终效果就是   输入(batch,len_seq,dim)---------- 输出(batch,len_seq,dim)

  • 实现EncoderLayer
class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        self.enc_self_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()
    def forward(self, enc_inputs, enc_self_attn_mask):
        enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) 
        enc_outputs = self.pos_ffn(enc_outputs) 
        return enc_outputs, attn

3. Decoder

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)   # 同encoder
        self.pos_emb = PositionalEncoding(d_model)             # 同encoder
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

3.1 DecoderLayer

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()     # 同Encoder
        # Decoder输入只有其自己的MultiHeadAttention

        self.dec_enc_attn = MultiHeadAttention()
        # Decoder同Encoder的交互层

        self.pos_ffn = PoswiseFeedForwardNet()         # 同Encoder

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值