真小白,零基础Transformer代码解析

发布于:2022-11-13 ⋅ 阅读:(1165) ⋅ 点赞:(0)

代码是参考CSDN博主, 代码详解(Pytorch版)_@左左@右右的博客-CSDN博客_transformer代码

同时参考b站up主【NLP从入门到放弃】的注解BERT代码(源码)从零解读【Pytorch-手把手教你从零实现一个BERT源码模型】_哔哩哔哩_bilibili

目录

数据构建

数据集处理

训练集

测试集(希望transformer能达到的效果)

把句子转为词库中词的数字序列转换为tensor

自定义一个MyDataSet去读取这些句子

把句子转为词库中词的数字序列转换为tensor

Transformer Parameters(参数)

FeedForward dimension (前馈神经网络参数)

1...从整个网络结构看,

2...Encoder:编码层

3....Positional Encoding:位置编码

根据位置计算公式编写

4...get_attn_pad_mask:形成一个符号矩阵,

5...EncoderLayer包含两个部分:多头注意力机制和前馈神经网络

6...MultiHeadAttention:多头注意力机制

7...ScaledDotProductAttention 缩放点积注意力机制

8...PoswiseFeedForwardNet:前馈神经网络

9... Decoder解码层

10...get_attn_subsequence_mask:自注意力层的mask部分

12...DecoderLayer


数据构建

数据集处理

训练集

 import math
 import torch
 import numpy as np
 import torch.nn as nn
 import torch.optim as optim
 import torch.utils.data as Data

 
 #device = 'cpu'
 device = 'cuda'
 ​
 # transformer epochs
 epochs = 100
 # epochs = 1000
 ​
 # 训练集
 
sentences = [
     # 中文和英语的单词个数不要求相同
   
     # Encoder的输入:enc_inputs
     # 其一:用来生成Encoder自注意的mask,其二:在多头自注意中计算Q、K、V矩阵
     # Decoder的输入: dec_inputs,enc_inputs,enc_outputs
     # dec_inputs:其一,用来生成decoder自注意的mask矩阵。其二,计算decoder的多头自注意层的Q、K、V矩阵。
     # enc_inputs:用来生成decoder中编码-解码层的mask矩阵,避免受Encoder输入中padding的影响。
     # enc_outputs:计算Decoder中编码-解码层中的K、V矩阵。
 ​
     # enc_input                dec_input           dec_output
     ['我 有 一 个 好 朋 友 P', 'S I have a good friend .', 'I have a good friend . E'],
     ['我 有 零 个 女 朋 友 P', 'S I have zero girl friend .', 'I have zero girl friend . E'],
     ['我 有 一 个 男 朋 友 P', 'S I have a boy friend .', 'I have a boy friend . E']
 ]
 ​

测试集(希望transformer能达到的效果)

 # 中文和英语的单词要分开建立词库
 # Padding Should be Zero 填充应是零
 src_vocab = {'P': 0, '我': 1, '有': 2, '一': 3,
              '个': 4, '好': 5, '朋': 6, '友': 7, '零': 8, '女': 9, '男': 10}
 src_idx2word = {i: w for i, w in enumerate(src_vocab)}
 #enumerate()是python的内置函数
 #enumerate在字典上是枚举、列举的意思
 #对于一个可迭代的(iterable)/可遍历的对象(如列表、字符串),enumerate将其组成一个索引序列,利用它可以同时获得索引和值
 src_vocab_size = len(src_vocab)
 ​
 tgt_vocab = {'P': 0, 'I': 1, 'have': 2, 'a': 3, 'good': 4,
              'friend': 5, 'zero': 6, 'girl': 7,  'boy': 8, 'S': 9, 'E': 10, '.': 11}
 idx2word = {i: w for i, w in enumerate(tgt_vocab)}
 tgt_vocab_size = len(tgt_vocab)
 ​
 src_len = 8  # (源句子的长度)enc_input max sequence length
 tgt_len = 7  # dec_input(=dec_output) max sequence length

把句子转为词库中词的数字序列转换为tensor

 ​
 def make_data(sentences):
     """把单词序列转换为数字序列"""
     enc_inputs, dec_inputs, dec_outputs = [], [], []
     for i in range(len(sentences)):
         enc_input = [[src_vocab[n] for n in sentences[i][0].split()]]
         dec_input = [[tgt_vocab[n] for n in sentences[i][1].split()]]
         dec_output = [[tgt_vocab[n] for n in sentences[i][2].split()]]
 ​
         # [[1, 2, 3, 4, 5, 6, 7, 0], [1, 2, 8, 4, 9, 6, 7, 0], [1, 2, 3, 4, 10, 6, 7, 0]]
         enc_inputs.extend(enc_input)
         # [[9, 1, 2, 3, 4, 5, 11], [9, 1, 2, 6, 7, 5, 11], [9, 1, 2, 3, 8, 5, 11]]
         dec_inputs.extend(dec_input)
         # [[1, 2, 3, 4, 5, 11, 10], [1, 2, 6, 7, 5, 11, 10], [1, 2, 3, 8, 5, 11, 10]]
         dec_outputs.extend(dec_output)
 ​
     return torch.LongTensor(enc_inputs), torch.LongTensor(dec_inputs), torch.LongTensor(dec_outputs)
 ​
 ​
 enc_inputs, dec_inputs, dec_outputs = make_data(sentences)

自定义一个MyDataSet去读取这些句子

 ​
 class MyDataSet(Data.Dataset):
     """自定义DataLoader"""
 ​
     def __init__(self, enc_inputs, dec_inputs, dec_outputs):
         super(MyDataSet, self).__init__()
         self.enc_inputs = enc_inputs
         self.dec_inputs = dec_inputs
         self.dec_outputs = dec_outputs
 ​
     def __len__(self):
         return self.enc_inputs.shape[0]
 ​
     def __getitem__(self, idx):
         return self.enc_inputs[idx], self.dec_inputs[idx], self.dec_outputs[idx]
 ​
 ​
 loader = Data.DataLoader(
     MyDataSet(enc_inputs, dec_inputs, dec_outputs), 2, True)
 ​
 ​

把句子转为词库中词的数字序列转换为tensor

Transformer Parameters(参数)

 d_model = 512  # Embedding Size(token embedding和position编码的维度)

FeedForward dimension (前馈神经网络参数)

 # FeedForward dimension (两次线性层中的隐藏层 512->2048->512,线性层是用来做特征提取的),当然最后会再接一个projection层
 d_ff = 2048
 d_k = d_v = 64  #(Q和K的维度需要相同,这里为了方便让K=V)
 n_layers = 6  #(解码器层的编码器的数量,6个Encoder叠加在一起)
 n_heads = 8  # (多头注意力机制中,分为8个头)

1...从整个网络结构看,

分为三个部分:编码层,解码层,输出层

 class Transformer(nn.Module):
     # 构造函数
     def __init__(self):
         super(Transformer, self).__init__()
         self.encoder = Encoder().to(device)  # 编码层
         self.decoder = Decoder().to(device)     # 解码层
         self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False).to(device)
         # 输出层,d_model是解码层每一个token输出维度的大小,之后会做一个tgt_vocab_size大小的softmax
 ​
     # 实现函数
     def forward(self, enc_inputs, dec_inputs):
 ​
         """Transformers的输入:两个序列(编码端的输入,解码端的输入)
         enc_inputs: [batch_size, src_len]   形状:batch_size乘src_len
         dec_inputs: [batch_size, tgt_len]   形状:batch_size乘tgt_len
         """
         # 经过Encoder网络后,得到的输出还是[batch_size, src_len, d_model]
         # 在初始化函数中已经放置好的编码端、解码端、输出层通过数据流动串通起来
 ​
         # enc_outputs:编码端的输入(enc_inputs)通过编码器(encoder)流到编码器的输出(enc_outputs)
         # enc_self_attns:QK转置相乘后softmax的矩阵值,代表每个单词和其他单词的相关性
         enc_outputs, enc_self_attns = self.encoder(enc_inputs)
 ​
         # dec_outputs是decoder主要输出,用于后续的1inear映射;
         # dec_self_attns类比于enc_self_attns是查看每个单词对decoder中输入的其余单词的相关性
         # dec_enc_attns:decoder中每个单词对encoder中每个单词的相关性
         dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)
 ​
         # 将dec_outputs做一个映射,映射到词表大小中
         dec_logits = self.projection(dec_outputs)
         return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns
 ​
 ​
 model = Transformer().to(device)
 # 这里的损失函数里面设置了一个参数 ignore_index=0,因为 "pad" 这个单词的索引为 0,这样设置以后,就不会计算 "pad" 的损失(因为本来 "pad" 也没有意义,不需要计算)
 criterion = nn.CrossEntropyLoss(ignore_index=0)
 optimizer = optim.SGD(model.parameters(), lr=1e-3, momentum=0.99)  # 用adam的话效果不好

2...Encoder:编码层

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)  # 位置编码情况,这里是固定的正余弦函数,也可以使用类似词向量的nn .Embedding获得一个可以更新学习的位置编码
         self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])  # 使用ModuleList对多个encoder进行堆叠,因为后续的encoder并没有使用词向量和位置编码,所以抽离出来;
 ​
     def forward(self, enc_inputs):
         """
         enc_inputs: [batch_size, src_len]
         """
         # 通过src_emb进行索引定位,把对应的数字的词向量提取出来,然后形成一个矩阵,矩阵形状为[batch_size*src_len*d_model]
         enc_outputs = self.src_emb(enc_inputs)  # [batch_size, src_len, d_model]
         # 这里就是位置编码,把两者相加放入到了这个函数里面,从这里可以去看一下位置编码函数的实现;(3...Positional Encoding:位置编码)
         enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(0, 1)  # [batch_size, src_len, d_model]
 ​
         # Encoder输入序列的pad mask矩阵
         # #liget_attn_pad_mask是为了得到句子中pad的位置信息,给到模型后面,在计算自注意力和交互注意力的时候去掉pad符号的影响,去看一下这个函数(4...get_attn_pad_mask)
         enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)  # [batch_size, src_len, src_len]
 ​
         # 前馈神经网络和自注意层,上一层的输出表示下一层的输入,循环
         enc_self_attns = []  # 在计算中不需要用到,它主要用来保存你接下来返回的attention的值(这个主要是为了你画热力图等,用来看各个词之间的关系
         for layer in self.layers:  # for循环访问nn.ModuleList对象
             # 上一个block的输出enc_outputs作为当前block的输入
             # enc_outputs: [batch_size, src_len, d_model],
             # enc_self_attn: [batch_size, n_heads, src_len, src_len]
             # 传入的enc_outputs其实是input,传入mask矩阵是因为你要做self attention
             enc_outputs, enc_self_attn = layer(enc_outputs,enc_self_attn_mask)
             enc_self_attns.append(enc_self_attn)  # 这个只是为了可视化
         return enc_outputs, enc_self_attns

3....Positional Encoding:位置编码

Transformer 是并行输入计算的,需要知道每个字的位置信息,才能识别出语言中的顺序关系。 首先你需要知道,Transformer 是以字作为输入,将字进行字嵌入之后,再与位置嵌入进行相加(不是拼接,就是单纯的对应位置上的数值进行加和)

为什么是将positional encoding与词向量相加,而不是拼接呢? 拼接相加都可以,只是本身词向量的维度512维就已经蛮大了,再拼接一个512维的位置向量,变成1024维,这样训练起来会相对慢一些,影响效率。

Transformer 的 Decoder的输入与Encoder的输出处理方法步骤是一样地,一个接受source数据,一个接受target数据

位置嵌入的设计应该满足以下条件:

每个字输出的位置编码是唯一的 不同长度的句子之间,任何两个字之间的差值应该保持一致 它的值应该是有界的

根据位置计算公式编写

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        # 从理解来讲,需要注意的就是偶数和奇数在公式上有一个共同部分,我们使用log函数把次方拿下来,方便计算;
        # 假设我的demodel是512,那么公式里的pos代表的从0,1.23..511的每一个位置,2那个符号中j从O取到了.255,那么.2对应取值就是0,2.4..510
        self.dropout = nn.Dropout(p=dropout)
        # 返回一个形状为为size,类型为torch.dtype,里面的每一个值都是0的tensor
        pe = torch.zeros(max_len, d_model)

        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        # 位置编码的计算公式:实现共有部分的计算,10000 2i/d model
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        # 这里需要注意的是pe[:, 0::2]这个用法,就是从0开始到最后面,步长为2,其实代表的就是偶数位置
        pe[:, 0::2] = torch.sin(position * div_term) # position:公式中的pos
        # 这里需要注意的是pe[:, 1::2]这个用法,就是从1开始到最后面,步长为2,其实代表的就是奇数位置
        pe[:, 1::2] = torch.cos(position * div_term)
        # 通过两个pe计算,得到的形状是[max_len*d_model]

        # 将pe的形状设置为[max_len*1*d_model]
        pe = pe.unsqueeze(0).transpose(0, 1)

        # 定义一个缓冲区,可以理解为pe这个参数不参与更新,固定不变
        self.register_buffer('pe', pe)

    # 定义输出
    def forward(self, x):
        """
        x: [seq_len, batch_size, d_model]
        """
        # 词向量和位置编码相加
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)

4...get_attn_pad_mask:形成一个符号矩阵,

pad mask的作用:在对value向量加权平均的时候,可以让pad对应的alpha_ij=0,这样注意力就不会考虑到pad向量

def get_attn_pad_mask(seq_q, seq_k):

    # se_q 和.seq_k 不一定一致,在交互注意力,q来自解码端,k来自编码端,所以告诉模型编码这边pad符号信息就可以,解码端的pad信息在交互注意力层是没有用到的;
    """这里的q,k表示的是两个序列(跟注意力机制的q,k没有关系),例如encoder_inputs (x1,x2,..xm)和encoder_inputs (x1,x2..xm)
    encoder和decoder都可能调用这个函数,所以seq_len视情况而定
    seq_q: [batch_size, seq_len]
    seq_k: [batch_size, seq_len]
    """

    batch_size, len_q = seq_q.size()  # 这个seq_q只是用来expand维度的
    batch_size, len_k = seq_k.size()
    # eq(zero) is PAD token
    # 例如:seq_k = [[1,2,3,4,0], [1,2,3,5,0]]
    # [batch_size, 1, len_k], True is masked
    # 观测输出进来的seq_k里面哪些位置是pad符号(填充为0的位置),将其置为1(表示true)
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)
    # 重复len_q次
    # 得到[batch_size, len_q, len_k] 构成一个立方体(batch_size个这样的矩阵)
    return pad_attn_mask.expand(batch_size, len_q, len_k)

5...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):
        """E
        enc_inputs: [batch_size, src_len, d_model]
        enc_self_attn_mask: [batch_size, src_len, src_len]  mask矩阵(pad mask or sequence mask)
        """
        # enc_outputs: [batch_size, src_len, d_model], attn: [batch_size, n_heads, src_len, src_len]
        # 第一个enc_inputs * W_Q = Q
        # 第二个enc_inputs * W_K = K
        # 第三个enc_inputs * W_V = V
        # enc_self_attn_mask:pad矩阵信息
        # enc_inputs to same Q,K,V(未线性变换前)
        enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs,enc_self_attn_mask)   # 自注意力层
        enc_outputs = self.pos_ffn(enc_outputs)     # 前馈神经网络层
        # enc_outputs: [batch_size, src_len, d_model]
        return enc_outputs, attn

6...MultiHeadAttention:多头注意力机制

class MultiHeadAttention(nn.Module):
    """这个Attention类可以实现:
    Encoder的Self-Attention
    Decoder的Masked Self-Attention
    Encoder-Decoder的Attention
    输入:seq_len x d_model
    输出:seq_len x d_model
    """

    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        # 前三行:得到一个映射矩阵,将d_model映射到d_k * n_heads
        self.W_Q = nn.Linear(d_model, d_k * n_heads,bias=False)  # q,k必须维度相同,不然无法做点积
        self.W_K = nn.Linear(d_model, d_k * n_heads, bias=False)
        self.W_V = nn.Linear(d_model, d_v * n_heads, bias=False)
        # 这个全连接层可以保证多头attention的输出仍然是seq_len x d_model
        self.fc = nn.Linear(n_heads * d_v, d_model, bias=False)

    def forward(self, input_Q, input_K, input_V, attn_mask):
        """
        input_Q: [batch_size, len_q, d_model]
        input_K: [batch_size, len_k, d_model]
        input_V: [batch_size, len_v(=len_k), d_model]
        attn_mask: [batch_size, seq_len, seq_len]
        """
        residual, batch_size = input_Q, input_Q.size(0)
        # 下面的多头的参数矩阵是放在一起做线性变换的,然后再拆成多个头,这是工程实现的技巧
        # B: batch_size, S:seq_len, D: dim
        # (B, S, D) -proj-> (B, S, D_new) -split-> (B, S, Head, W) -trans-> (B, Head, S, W)
        #           线性变换               拆成多头

        # Q: [batch_size, n_heads, len_q, d_k]
        Q = self.W_Q(input_Q).view(batch_size, -1, n_heads, d_k).transpose(1, 2)
        # K: [batch_size, n_heads, len_k, d_k] # K和V的长度一定相同,维度可以不同
        K = self.W_K(input_K).view(batch_size, -1, n_heads, d_k).transpose(1, 2)
        # V: [batch_size, n_heads, len_v(=len_k), d_v]
        V = self.W_V(input_V).view(batch_size, -1, n_heads, d_v).transpose(1, 2)

        # 因为是多头,所以mask矩阵要扩充成4维的,将pad的信息重复到n个头上
        # attn_mask: [batch_size, seq_len, seq_len] -> [batch_size, n_heads, seq_len, seq_len]
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)

        # context: [batch_size, n_heads, len_q, d_v], attn: [batch_size, n_heads, len_q, len_k]
        # ScaledDotProductAttention,缩放点积注意力机制见(7)
        context, attn = ScaledDotProductAttention()(Q, K, V, attn_mask)
        # 下面将不同头的输出向量拼接在一起
        # context: [batch_size, n_heads, len_q, d_v] -> [batch_size, len_q, n_heads * d_v]
        context = context.transpose(1, 2).reshape(batch_size, -1, n_heads * d_v)

        # 这个全连接层可以保证多头attention的输出仍然是seq_len x d_model
        output = self.fc(context)  # [batch_size, len_q, d_model]
        return nn.LayerNorm(d_model).to(device)(output + residual), attn

7...ScaledDotProductAttention 缩放点积注意力机制

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

    def forward(self, Q, K, V, attn_mask):
        """
        Q: [batch_size, n_heads, len_q, d_k]
        K: [batch_size, n_heads, len_k, d_k]
        V: [batch_size, n_heads, len_v(=len_k), d_v]
        attn_mask: [batch_size, n_heads, seq_len, seq_len]
        说明:在encoder-decoder的Attention层中len_q(q1,..qt)和len_k(k1,...km)可能不同
        """
        # K.transpose(-1, -2),K的转置
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k)  # scores : [batch_size, n_heads, len_q, len_k]

        # mask矩阵填充scores(用-1e9填充scores中与attn_mask中值为1位置相对应的元素)
        # Fills elements of self tensor with value where mask is True.
        scores.masked_fill_(attn_mask, -1e9)

        attn = nn.Softmax(dim=-1)(scores)  # 对最后一个维度(v)做softmax
        # scores : [batch_size, n_heads, len_q, len_k] * V: [batch_size, n_heads, len_v(=len_k), d_v]
        # context: [batch_size, n_heads, len_q, d_v]
        context = torch.matmul(attn, V)
        # context:[[z1,z2,...],[...]]向量, attn注意力稀疏矩阵(用于可视化的)
        return context, attn

8...PoswiseFeedForwardNet:前馈神经网络

Pytorch中的Linear只会对最后一维操作,所以正好是我们希望的每个位置用同一个全连接网络

具有两层线性层的全连接网络,主要目的:考虑到注意力机制可能对复杂过程的拟合程度不够,通过前馈神经网络来增强模型的能力。

class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        self.fc = nn.Sequential(
            # d_model:代表词嵌入的维度,同时也是两个线性层的输入维度和输出维度
          	# d_ff:代表第一个线性层的输出维度和第二个线性层的输入维度
            nn.Linear(d_model, d_ff, bias=False),
            nn.ReLU(),
            nn.Linear(d_ff, d_model, bias=False)
        )

    def forward(self, inputs):
        """
        inputs: [batch_size, seq_len, d_model]
        """
        residual = inputs
        output = self.fc(inputs)
        # [batch_size, seq_len, d_model]
        return nn.LayerNorm(d_model).to(device)(output + residual)

9... Decoder解码层

Decoder部分包含三个部分:词向量embedding,位置编码部分,Decoder的blocks

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)  # Decoder输入的embed词表
        self.pos_emb = PositionalEncoding(d_model)   # 位置编码
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])  # Decoder的blocks
    # 接受三个输入
    def forward(self, dec_inputs, enc_inputs, enc_outputs):
        """
        dec_inputs: [batch_size, tgt_len]
        enc_inputs: [batch_size, src_len]
        enc_outputs: [batch_size, src_len, d_model]   # 用在Encoder-Decoder Attention层
        """
        dec_outputs = self.tgt_emb(dec_inputs)  # [batch_size, tgt_len, d_model]
        dec_outputs = self.pos_emb(dec_outputs.transpose(0, 1)).transpose(0, 1).to(device)  # [batch_size, tgt_len, d_model]

        # Decoder输入序列的pad mask矩阵(这个例子中decoder是没有加pad的,实际应用中都是有pad填充的)
        # get_attn_pad_mask:形成一个符号矩阵
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs).to(device)  # [batch_size, tgt_len, tgt_len]
        # Masked Self_Attention:当前时刻是看不到未来的信息的(上三角为1的矩阵)见(10)
        dec_self_attn_subsequence_mask = get_attn_subsequence_mask(dec_inputs).to(device)  # [batch_size, tgt_len, tgt_len]
        # Decoder中把两种mask矩阵相加(既屏蔽了pad的信息,也屏蔽了未来时刻的信息)##两个矩阵相加,大于0的为1,不大于0的为0,为1的在之后就会被fill到无限小
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequence_mask), 0).to(device)
        # [batch_size, tgt_len, tgt_len]; torch.gt比较两个矩阵的元素,大于则返回1,否则返回0

        # 这个mask主要用于encoder-decoder attention层(交互注意力机制)
        # get_attn_pad_mask主要是enc_inputs的pad mask矩阵
        # (因为enc是处理K,V的,求Attention时是用v1,v2,..vm去加权的,要把pad对应的v_i的相关系数设为0,这样注意力就不会关注pad向量)
        # dec_inputs只是提供expand的size的
        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)  # [batc_size, tgt_len, src_len]
        dec_self_attns, dec_enc_attns = [], []
        for layer in self.layers:
            # dec_outputs: [batch_size, tgt_len, d_model], dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len], dec_enc_attn: [batch_size, h_heads, tgt_len, src_len]
            # Decoder的Block是上一个Block的输出dec_outputs(变化)和Encoder网络的输出enc_outputs(固定)
            dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask,
                                                             dec_enc_attn_mask)
            dec_self_attns.append(dec_self_attn)
            dec_enc_attns.append(dec_enc_attn)
        # dec_outputs: [batch_size, tgt_len, d_model]
        return dec_outputs, dec_self_attns, dec_enc_attns

10...get_attn_subsequence_mask:自注意力层的mask部分

做自注意力层的mask部分,当单词看不到之后,使用一个上三角为1 的矩阵

def get_attn_subsequence_mask(seq):
    attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
    # attn_shape: [batch_size, tgt_len, tgt_len]
    subsequence_mask = np.triu(np.ones(attn_shape), k=1)  # 生成一个上三角矩阵
    subsequence_mask = torch.from_numpy(subsequence_mask).byte()
    return subsequence_mask  # [batch_size, tgt_len, tgt_len]

12...DecoderLayer

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.dec_self_attn = MultiHeadAttention()
        self.dec_enc_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
        """
        dec_inputs: [batch_size, tgt_len, d_model]
        enc_outputs: [batch_size, src_len, d_model]
        dec_self_attn_mask: [batch_size, tgt_len, tgt_len]
        dec_enc_attn_mask: [batch_size, tgt_len, src_len]
        """
        # dec_outputs: [batch_size, tgt_len, d_model], dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len]
        dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs,dec_self_attn_mask)  # 这里的Q,K,V全是Decoder自己的输入
        # dec_outputs: [batch_size, tgt_len, d_model], dec_enc_attn: [batch_size, h_heads, tgt_len, src_len]
        dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs,dec_enc_attn_mask)  # Attention层的Q(来自decoder) 和 K,V(来自encoder)
        # [batch_size, tgt_len, d_model]
        dec_outputs = self.pos_ffn(dec_outputs)
        # dec_self_attn, dec_enc_attn这两个是为了可视化的
        return dec_outputs, dec_self_attn, dec_enc_attn

本文含有隐藏内容,请 开通VIP 后查看