当前位置:   article > 正文

Transformer学习总结_transfomer模型学习心得

transfomer模型学习心得

Transformer的整体结构

Transformer是一种基于自注意力机制的序列处理模型,它由编码器和解码器组成,每个部分都包含多个相同的层,每个层都使用自注意力机制和前馈神经网络。

  • 编码器:编码器由多个相同的层堆叠而成,每一层都包含两个子层:自注意力机制和前馈神经网络。编码器的任务是将输入序列转换为一系列连续的表示,这些表示同时包含了输入序列的每个元素的上下文信息。
  • 解码器:解码器也由多个相同的层堆叠而成,每一层有三个子层:两个自注意力层和一个前馈神经网络。第一个自注意力层只允许关注到前面的位置,第二个自注意力层则接收编码器的输出作为K和V。
  • 自注意力机制:自注意力机制是Transformer的核心,它允许模型在所有位置的输入序列中同时关注不同位置的信息。
  • 前馈神经网络:前馈神经网络在每个注意力层之后进行处理,对注意力层的输出进行进一步的变换。
  • 位置编码:位置编码是通过添加到输入嵌入中来给模型提供序列中单词的位置信息。

一、输入编码

1.词向量的输入

Transformer输入是一个序列数据,以我爱你为例:Encoder 的 inputs就是"I LOVE YOU" 分词后的词向量。输入inputs embedding后需要给每个word的词向量添加位置编码positional encoding。

2.positional encoding获取过程:

1.可以通过数据训练学习得到positional encoding,类似于训练学习词向量,goole在之后的bert中的positional encoding便是由训练得到地。
2.《Attention Is All You Need》论文中Transformer使用的是正余弦位置编码。

位置编码通过使用不同频率的正弦、余弦函数生成,然后和对应的位置的词向量相加,位置向量维度必须和词向量的维度一致。

过程如上图,PE(positional encoding)计算公式如下:

pos表示单词在句子中的绝对位置,pos=0,1,2…,例如:YOU在"I LOVE YOU"中的pos=2;dmodel表示词向量的维度,在这里dmodel=512;2i和2i+1表示奇偶性,i表示词向量中的第几维,例如这里dmodel=512,故i=0,1,2…255。

二、Attention

需要用到的是Self-Attention以及Multi-Head Attention

具体可以见我的上一篇博客Seq2Seq+Attention学习总结-CSDN博客

三、残差连接和 Layer Normalization

输入 x1,x2 经 self-attention 层之后变成 z1,z2,然后和输入 x1,x2 进行残差连接,经过 LayerNorm 后输出给全连接层。全连接层也有一个残差连接和一个 LayerNorm,最后再输出给下一个 Encoder(每个 Encoder Block 中的 FeedForward 层权重都是共享的)

残差连接

我们在上一步得到了经过 self-attention 加权之后输出,也就是 Self-Attention(Q, K, V),然后把他们加起来做残差连接X_{embedding} = Self-Attention(Q,K,V)

Layer Normalization

Layer Normalization 的作用是把神经网络中隐藏层归一为标准正态分布,以起到加快训练速度,加速收敛的作用。

四、Encoder-Decoder 

Encoder-Decoder Attention

解码器中,Transformer block比编码器中多了个encoder-cecoder attention。在encoder-decoder attention中, Q来自于解码器的上一个输出, K 和 V 则来自于与编码器的输出。

由于在机器翻译中,解码过程是一个顺序操作的过程,也就是当解码第 k 个特征向量时,我们只能看到第 k-1 及其之前的解码结果,论文中把这种情况下的multi-head attention叫做masked multi-head attention。

Pytorch实现代码 

Task: 基于Transformer的单词翻译
Date: 2023/11/27
Reference: https://github.com/jadore801120/attention-is-all-you-need-pytorch
           https://github.com/JayParks/transformer
           code by Tae Hwan Jung(Jeff Jung) @graykode, Derek Miller @dmmiller612, modify by shweiTransformer 代码详解(Pytorch版)_transformer代码_@左左@右右的博客-CSDN博客

 库引入

  1. import math
  2. import torch
  3. import numpy as np
  4. import torch.nn as nn
  5. import torch.optim as optim
  6. import torch.utils.data as Data
  7. device = 'cpu'
  8. # device = 'cuda'
  9. # transformer epochs
  10. epochs = 100
  11. # epochs = 1000

手动输入了数据集 

  1. # 数据集:两对中文→英语的句子
  2. # S: Symbol that shows starting of decoding input
  3. # E: Symbl that shows starting of decoding output
  4. # P: Symbol that will fill in blank sequence if current batch data size is shorter than time steps
  5. # 训练集(手动编码)
  6. sentences = [
  7. # 中文和英语的单词个数不要求相同
  8. # enc_input dec_input dec_output
  9. ['我 有 一 个 好 朋 友 P', 'S I have a good friend .', 'I have a good friend . E'],
  10. ['我 有 零 个 女 朋 友 P', 'S I have zero girl friend .', 'I have zero girl friend . E'],
  11. ['我 有 一 个 男 朋 友 P', 'S I have a boy friend .', 'I have a boy friend . E']
  12. ]
  13. # 中文和英语的单词要分开建立词库
  14. # Padding Should be Zero
  15. src_vocab = {'P': 0, '我': 1, '有': 2, '一': 3,
  16. '个': 4, '好': 5, '朋': 6, '友': 7, '零': 8, '女': 9, '男': 10}
  17. src_idx2word = {i: w for i, w in enumerate(src_vocab)}
  18. src_vocab_size = len(src_vocab)
  19. tgt_vocab = {'P': 0, 'I': 1, 'have': 2, 'a': 3, 'good': 4,
  20. 'friend': 5, 'zero': 6, 'girl': 7, 'boy': 8, 'S': 9, 'E': 10, '.': 11}
  21. idx2word = {i: w for i, w in enumerate(tgt_vocab)}
  22. tgt_vocab_size = len(tgt_vocab)
  23. src_len = 8 # (源句子的长度)enc_input max sequence length
  24. tgt_len = 7 # dec_input(=dec_output) max sequence length

Transformer模型的参数
d_model:我们需要定义embeding 的维度,论文中设置的512
d_ff: FeedForward 层隐藏神经元个数
d_k = d_v: Q、K、V 向量的维度,其中 Q 与 K 的维度必须相等,V 的维度没有限制,我们都设为 64
n_layers:Encoder 和 Decoder 的个数,也就是图中的Nx
n_heads:多头注意力中 head 的数量

  1. # Transformer Parameters
  2. d_model = 512 # Embedding Size(token embedding和position编码的维度)
  3. # FeedForward dimension (两次线性层中的隐藏层 512->2048->512,线性层是用来做特征提取的),当然最后会再接一个projection层
  4. d_ff = 2048
  5. d_k = d_v = 64 # dimension of K(=Q), V(Q和K的维度需要相同,这里为了方便让K=V)
  6. n_layers = 6 # number of Encoder of Decoder Layer(Block的个数)
  7. n_heads = 8 # number of heads in Multi-Head Attention(有几套头)

数据构建  

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

自定义DataLoader,用于批量加载和处理数据。
DataLoader可以并行地加载数据,以提高训练速度。 

  1. class MyDataSet(Data.Dataset):
  2. def __init__(self, enc_inputs, dec_inputs, dec_outputs):
  3. super(MyDataSet, self).__init__()
  4. self.enc_inputs = enc_inputs
  5. self.dec_inputs = dec_inputs
  6. self.dec_outputs = dec_outputs
  7. def __len__(self):
  8. return self.enc_inputs.shape[0]
  9. def __getitem__(self, idx):
  10. return self.enc_inputs[idx], self.dec_inputs[idx], self.dec_outputs[idx]
  11. loader = Data.DataLoader(
  12. MyDataSet(enc_inputs, dec_inputs, dec_outputs), 2, True)

Transformer模型

PositionalEncoding

  1. class PositionalEncoding(nn.Module):
  2. def __init__(self, d_model, dropout=0.1, max_len=5000):
  3. super(PositionalEncoding, self).__init__()
  4. self.dropout = nn.Dropout(p=dropout)
  5. pe = torch.zeros(max_len, d_model)
  6. position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
  7. div_term = torch.exp(torch.arange(
  8. 0, d_model, 2).float() * (-math.log(10000.0) / d_model))
  9. pe[:, 0::2] = torch.sin(position * div_term)
  10. pe[:, 1::2] = torch.cos(position * div_term)
  11. pe = pe.unsqueeze(0).transpose(0, 1)
  12. self.register_buffer('pe', pe)
  13. def forward(self, x):
  14. """
  15. x: [seq_len, batch_size, d_model]
  16. """
  17. x = x + self.pe[:x.size(0), :]
  18. return self.dropout(x)

Attention Mask

在序列处理过程中,常常需要用填充来保证序列的长度一致,但在注意力模型中,填充的元素不应被考虑。pad mask的作用:在对value向量加权平均的时候,可以让pad对应的alpha_ij=0,排除填充(pad)的元素,这样注意力就不会考虑到pad向量。 

  1. def get_attn_pad_mask(seq_q, seq_k):
  2. #这里的q,k表示的是两个序列(跟注意力机制的q,k没有关系),例如encoder_inputs (x1,x2,..xm)和encoder_inputs (x1,x2..xm)
  3. #seq_q: [batch_size, seq_len]
  4. #seq_k: [batch_size, seq_len]
  5. batch_size, len_q = seq_q.size() # 这个seq_q只是用来expand维度的
  6. batch_size, len_k = seq_k.size()
  7. '''
  8. seq_k.data.eq(0)
  9. 返回一个大小和 seq_k 一样的 tensor,只不过里面的值只有 True 和 False。
  10. 如果 seq_k 某个位置的值等于 0,那么对应位置就是 True,否则即为 False。
  11. '''
  12. pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)
  13. # [batch_size, len_q, len_k] 构成一个立方体(batch_size个这样的矩阵)
  14. return pad_attn_mask.expand(batch_size, len_q, len_k)

排除不在子序列中的元素:创建一个上三角矩阵来创建一个掩码,这个矩阵在主对角线上为1(表示有效元素),在其他位置为0(表示无效元素)。 

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

Scaled Dot Product Attention 

实现缩放点积注意力

  1. class ScaledDotProductAttention(nn.Module):
  2. def __init__(self):
  3. super(ScaledDotProductAttention, self).__init__()
  4. def forward(self, Q, K, V, attn_mask):
  5. """
  6. 查询矩阵 Q: [batch_size, n_heads, len_q, d_k]
  7. 键矩阵 K: [batch_size, n_heads, len_k, d_k]
  8. 值矩阵 V: [batch_size, n_heads, len_v(=len_k), d_v]
  9. 注意力掩码 attn_mask: [batch_size, n_heads, seq_len, seq_len]
  10. 说明:在encoder-decoder的Attention层中len_q(q1,..qt)和len_k(k1,...km)可能不同
  11. """
  12. scores = torch.matmul(Q, K.transpose(-1, -2)) / \
  13. np.sqrt(d_k) # scores : [batch_size, n_heads, len_q, len_k]
  14. # mask矩阵填充scores(用-1e9填充scores中与attn_mask中值为1位置相对应的元素)
  15. # Fills elements of self tensor with value where mask is True.
  16. scores.masked_fill_(attn_mask, -1e9)
  17. attn = nn.Softmax(dim=-1)(scores) # 对最后一个维度(v)做softmax
  18. # scores : [batch_size, n_heads, len_q, len_k] * V: [batch_size, n_heads, len_v(=len_k), d_v]
  19. # context: [batch_size, n_heads, len_q, d_v]
  20. context = torch.matmul(attn, V)
  21. # context:[[z1,z2,...],[...]]向量, attn注意力稀疏矩阵(用于可视化的)
  22. return context, attn

 MultiHeadAttention

这个Attention类可以实现:
    Encoder的Self-Attention
    Decoder的Masked Self-Attention
    Encoder-Decoder的Attention

  1. class MultiHeadAttention(nn.Module):
  2. """
  3. 输入:seq_len x d_model
  4. 输出:seq_len x d_model
  5. """
  6. def __init__(self):
  7. super(MultiHeadAttention, self).__init__()
  8. self.W_Q = nn.Linear(d_model, d_k * n_heads,
  9. bias=False) # q,k必须维度相同,不然无法做点积
  10. self.W_K = nn.Linear(d_model, d_k * n_heads, bias=False)
  11. self.W_V = nn.Linear(d_model, d_v * n_heads, bias=False)
  12. # 这个全连接层可以保证多头attention的输出仍然是seq_len x d_model
  13. self.fc = nn.Linear(n_heads * d_v, d_model, bias=False)
  14. def forward(self, input_Q, input_K, input_V, attn_mask):
  15. """
  16. input_Q: [batch_size, len_q, d_model]
  17. input_K: [batch_size, len_k, d_model]
  18. input_V: [batch_size, len_v(=len_k), d_model]
  19. attn_mask: [batch_size, seq_len, seq_len]
  20. """
  21. residual, batch_size = input_Q, input_Q.size(0)
  22. # 下面的多头的参数矩阵是放在一起做线性变换的,然后再拆成多个头,这是工程实现的技巧
  23. # B: batch_size, S:seq_len, D: dim
  24. # (B, S, D) -proj-> (B, S, D_new) -split-> (B, S, Head, W) -trans-> (B, Head, S, W)
  25. # 线性变换 拆成多头
  26. # Q: [batch_size, n_heads, len_q, d_k]
  27. Q = self.W_Q(input_Q).view(batch_size, -1,
  28. n_heads, d_k).transpose(1, 2)
  29. # K: [batch_size, n_heads, len_k, d_k] # K和V的长度一定相同,维度可以不同
  30. K = self.W_K(input_K).view(batch_size, -1,
  31. n_heads, d_k).transpose(1, 2)
  32. # V: [batch_size, n_heads, len_v(=len_k), d_v]
  33. V = self.W_V(input_V).view(batch_size, -1,
  34. n_heads, d_v).transpose(1, 2)
  35. # 因为是多头,所以mask矩阵要扩充成4维的
  36. # attn_mask: [batch_size, seq_len, seq_len] -> [batch_size, n_heads, seq_len, seq_len]
  37. attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)
  38. # context: [batch_size, n_heads, len_q, d_v], attn: [batch_size, n_heads, len_q, len_k]
  39. context, attn = ScaledDotProductAttention()(Q, K, V, attn_mask)
  40. # 下面将不同头的输出向量拼接在一起
  41. # context: [batch_size, n_heads, len_q, d_v] -> [batch_size, len_q, n_heads * d_v]
  42. context = context.transpose(1, 2).reshape(
  43. batch_size, -1, n_heads * d_v)
  44. # 这个全连接层可以保证多头attention的输出仍然是seq_len x d_model
  45. output = self.fc(context) # [batch_size, len_q, d_model]
  46. return nn.LayerNorm(d_model).to(device)(output + residual), attn

Feed Forward Neural Network 

构建一个前馈神经网络(Feed Forward Neural Network)
并且在网络中使用残差连接(Residual Connection)和层标准化(Layer Normalization)
Pytorch中的Linear只会对最后一维操作,所以正好是我们希望的每个位置用同一个全连接网络

  1. class PoswiseFeedForwardNet(nn.Module):
  2. def __init__(self):
  3. super(PoswiseFeedForwardNet, self).__init__()
  4. self.fc = nn.Sequential(
  5. nn.Linear(d_model, d_ff, bias=False),
  6. nn.ReLU(),
  7. nn.Linear(d_ff, d_model, bias=False)
  8. )
  9. def forward(self, inputs):
  10. """
  11. inputs: [batch_size, seq_len, d_model]
  12. """
  13. residual = inputs
  14. output = self.fc(inputs)
  15. # [batch_size, seq_len, d_model]
  16. return nn.LayerNorm(d_model).to(device)(output + residual)

 Encoder - Decoder

Encoder Layer

  1. class EncoderLayer(nn.Module):
  2. def __init__(self):
  3. super(EncoderLayer, self).__init__()
  4. self.enc_self_attn = MultiHeadAttention()
  5. self.pos_ffn = PoswiseFeedForwardNet()
  6. def forward(self, enc_inputs, enc_self_attn_mask):
  7. """
  8. enc_inputs: [batch_size, src_len, d_model]
  9. enc_self_attn_mask: [batch_size, src_len, src_len] mask矩阵(pad mask or sequence mask)
  10. """
  11. # 通过调用self.enc_self_attn对象的方法,将输入数据enc_inputs进行多头自注意力处理,并将结果保存在enc_outputs中
  12. # 同时,处理过程中产生的注意力分布图被保存在attn中
  13. # enc_outputs: [batch_size, src_len, d_model], attn: [batch_size, n_heads, src_len, src_len]
  14. # 第一个enc_inputs * W_Q = Q
  15. # 第二个enc_inputs * W_K = K
  16. # 第三个enc_inputs * W_V = V
  17. enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs,
  18. enc_self_attn_mask) # enc_inputs to same Q,K,V(未线性变换前)
  19. # 将经过自注意力处理后的enc_outputs输入到self.pos_ffn对象中进行前馈网络处理,并将结果保存在enc_outputs中
  20. enc_outputs = self.pos_ffn(enc_outputs)
  21. # enc_outputs: [batch_size, src_len, d_model]
  22. return enc_outputs, attn

Encoder

  1. class Encoder(nn.Module):
  2. def __init__(self):
  3. super(Encoder, self).__init__()
  4. self.src_emb = nn.Embedding(src_vocab_size, d_model) # token Embedding
  5. self.pos_emb = PositionalEncoding(
  6. d_model) # Transformer中位置编码时固定的,不需要学习
  7. self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])
  8. def forward(self, enc_inputs):
  9. """
  10. enc_inputs: [batch_size, src_len]
  11. """
  12. enc_outputs = self.src_emb(
  13. enc_inputs) # [batch_size, src_len, d_model]
  14. enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(
  15. 0, 1) # [batch_size, src_len, d_model]
  16. # Encoder输入序列的pad mask矩阵
  17. enc_self_attn_mask = get_attn_pad_mask(
  18. enc_inputs, enc_inputs) # [batch_size, src_len, src_len]
  19. enc_self_attns = [] # 在计算中不需要用到,它主要用来保存你接下来返回的attention的值(这个主要是为了你画热力图等,用来看各个词之间的关系
  20. for layer in self.layers: # for循环访问nn.ModuleList对象
  21. # 上一个block的输出enc_outputs作为当前block的输入
  22. # enc_outputs: [batch_size, src_len, d_model], enc_self_attn: [batch_size, n_heads, src_len, src_len]
  23. enc_outputs, enc_self_attn = layer(enc_outputs,
  24. enc_self_attn_mask) # 传入的enc_outputs其实是input,传入mask矩阵是因为你要做self attention
  25. enc_self_attns.append(enc_self_attn) # 这个只是为了可视化
  26. return enc_outputs, enc_self_attns

Decoder Layer

  1. class DecoderLayer(nn.Module):
  2. def __init__(self):
  3. super(DecoderLayer, self).__init__()
  4. self.dec_self_attn = MultiHeadAttention()
  5. self.dec_enc_attn = MultiHeadAttention()
  6. self.pos_ffn = PoswiseFeedForwardNet()
  7. def forward(self, dec_inputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask):
  8. """
  9. dec_inputs: [batch_size, tgt_len, d_model]
  10. enc_outputs: [batch_size, src_len, d_model]
  11. dec_self_attn_mask: [batch_size, tgt_len, tgt_len]
  12. dec_enc_attn_mask: [batch_size, tgt_len, src_len]
  13. """
  14. # dec_outputs: [batch_size, tgt_len, d_model], dec_self_attn: [batch_size, n_heads, tgt_len, tgt_len]
  15. dec_outputs, dec_self_attn = self.dec_self_attn(dec_inputs, dec_inputs, dec_inputs,
  16. dec_self_attn_mask) # 这里的Q,K,V全是Decoder自己的输入
  17. # dec_outputs: [batch_size, tgt_len, d_model], dec_enc_attn: [batch_size, h_heads, tgt_len, src_len]
  18. dec_outputs, dec_enc_attn = self.dec_enc_attn(dec_outputs, enc_outputs, enc_outputs,
  19. dec_enc_attn_mask) # Attention层的Q(来自decoder) 和 K,V(来自encoder)
  20. # [batch_size, tgt_len, d_model]
  21. dec_outputs = self.pos_ffn(dec_outputs)
  22. # dec_self_attn, dec_enc_attn这两个是为了可视化的
  23. return dec_outputs, dec_self_attn, dec_enc_attn

 ​​​​​​​Decoder

  1. class Decoder(nn.Module):
  2. def __init__(self):
  3. super(Decoder, self).__init__()
  4. self.tgt_emb = nn.Embedding(
  5. tgt_vocab_size, d_model) # Decoder输入的embed词表
  6. self.pos_emb = PositionalEncoding(d_model)
  7. self.layers = nn.ModuleList([DecoderLayer()
  8. for _ in range(n_layers)]) # Decoder的blocks
  9. def forward(self, dec_inputs, enc_inputs, enc_outputs):
  10. """
  11. dec_inputs: [batch_size, tgt_len]
  12. enc_inputs: [batch_size, src_len]
  13. enc_outputs: [batch_size, src_len, d_model] # 用在Encoder-Decoder Attention层
  14. """
  15. dec_outputs = self.tgt_emb(
  16. dec_inputs) # [batch_size, tgt_len, d_model]
  17. dec_outputs = self.pos_emb(dec_outputs.transpose(0, 1)).transpose(0, 1).to(
  18. device) # [batch_size, tgt_len, d_model]
  19. # Decoder输入序列的pad mask矩阵(这个例子中decoder是没有加pad的,实际应用中都是有pad填充的)
  20. dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs).to(
  21. device) # [batch_size, tgt_len, tgt_len]
  22. # Masked Self_Attention:当前时刻是看不到未来的信息的
  23. dec_self_attn_subsequence_mask = get_attn_subsequence_mask(dec_inputs).to(
  24. device) # [batch_size, tgt_len, tgt_len]
  25. # Decoder中把两种mask矩阵相加(既屏蔽了pad的信息,也屏蔽了未来时刻的信息)
  26. dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequence_mask),
  27. 0).to(device) # [batch_size, tgt_len, tgt_len]; torch.gt比较两个矩阵的元素,大于则返回1,否则返回0
  28. # 这个mask主要用于encoder-decoder attention层
  29. # get_attn_pad_mask主要是enc_inputs的pad mask矩阵(因为enc是处理K,V的,求Attention时是用v1,v2,..vm去加权的,要把pad对应的v_i的相关系数设为0,这样注意力就不会关注pad向量)
  30. # dec_inputs只是提供expand的size的
  31. dec_enc_attn_mask = get_attn_pad_mask(
  32. dec_inputs, enc_inputs) # [batc_size, tgt_len, src_len]
  33. dec_self_attns, dec_enc_attns = [], []
  34. for layer in self.layers:
  35. # 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]
  36. # Decoder的Block是上一个Block的输出dec_outputs(变化)和Encoder网络的输出enc_outputs(固定)
  37. dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask,
  38. dec_enc_attn_mask)
  39. dec_self_attns.append(dec_self_attn)
  40. dec_enc_attns.append(dec_enc_attn)
  41. # dec_outputs: [batch_size, tgt_len, d_model]
  42. return dec_outputs, dec_self_attns, dec_enc_attns

 Transformer

  1. class Transformer(nn.Module):
  2. def __init__(self):
  3. super(Transformer, self).__init__()
  4. self.encoder = Encoder().to(device)
  5. self.decoder = Decoder().to(device)
  6. self.projection = nn.Linear(
  7. d_model, tgt_vocab_size, bias=False).to(device)
  8. def forward(self, enc_inputs, dec_inputs):
  9. """Transformers的输入:两个序列
  10. enc_inputs: [batch_size, src_len]
  11. dec_inputs: [batch_size, tgt_len]
  12. """
  13. # tensor to store decoder outputs
  14. # outputs = torch.zeros(batch_size, tgt_len, tgt_vocab_size).to(self.device)
  15. # enc_outputs: [batch_size, src_len, d_model], enc_self_attns: [n_layers, batch_size, n_heads, src_len, src_len]
  16. # 经过Encoder网络后,得到的输出还是[batch_size, src_len, d_model]
  17. enc_outputs, enc_self_attns = self.encoder(enc_inputs)
  18. # dec_outputs: [batch_size, tgt_len, d_model], dec_self_attns: [n_layers, batch_size, n_heads, tgt_len, tgt_len], dec_enc_attn: [n_layers, batch_size, tgt_len, src_len]
  19. dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(
  20. dec_inputs, enc_inputs, enc_outputs)
  21. # dec_outputs: [batch_size, tgt_len, d_model] -> dec_logits: [batch_size, tgt_len, tgt_vocab_size]
  22. dec_logits = self.projection(dec_outputs)
  23. return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns
  24. model = Transformer().to(device)
  25. # 这里的损失函数里面设置了一个参数 ignore_index=0,因为 "pad" 这个单词的索引为 0,这样设置以后,就不会计算 "pad" 的损失(因为本来 "pad" 也没有意义,不需要计算)
  26. criterion = nn.CrossEntropyLoss(ignore_index=0)
  27. optimizer = optim.SGD(model.parameters(), lr=1e-3,
  28. momentum=0.99) # 用adam的话效果不好

训练过程

  1. for epoch in range(epochs):
  2. for enc_inputs, dec_inputs, dec_outputs in loader:
  3. """
  4. enc_inputs: [batch_size, src_len]
  5. dec_inputs: [batch_size, tgt_len]
  6. dec_outputs: [batch_size, tgt_len]
  7. """
  8. enc_inputs, dec_inputs, dec_outputs = enc_inputs.to(
  9. device), dec_inputs.to(device), dec_outputs.to(device)
  10. # outputs: [batch_size * tgt_len, tgt_vocab_size]
  11. outputs, enc_self_attns, dec_self_attns, dec_enc_attns = model(
  12. enc_inputs, dec_inputs)
  13. # dec_outputs.view(-1):[batch_size * tgt_len * tgt_vocab_size]
  14. loss = criterion(outputs, dec_outputs.view(-1))
  15. print('Epoch:', '%04d' % (epoch + 1), 'loss =', '{:.6f}'.format(loss))
  16. optimizer.zero_grad()
  17. loss.backward()
  18. optimizer.step()

句子预测 

贪心编码

  1. def greedy_decoder(model, enc_input, start_symbol):
  2. enc_outputs, enc_self_attns = model.encoder(enc_input)
  3. # 初始化一个空的tensor: tensor([], size=(1, 0), dtype=torch.int64)
  4. dec_input = torch.zeros(1, 0).type_as(enc_input.data)
  5. terminal = False
  6. next_symbol = start_symbol
  7. while not terminal:
  8. # 预测阶段:dec_input序列会一点点变长(每次添加一个新预测出来的单词)
  9. dec_input = torch.cat([dec_input.to(device), torch.tensor([[next_symbol]], dtype=enc_input.dtype).to(device)],
  10. -1)
  11. dec_outputs, _, _ = model.decoder(dec_input, enc_input, enc_outputs)
  12. projected = model.projection(dec_outputs)
  13. prob = projected.squeeze(0).max(dim=-1, keepdim=False)[1]
  14. # 增量更新(我们希望重复单词预测结果是一样的)
  15. # 我们在预测是会选择性忽略重复的预测的词,只摘取最新预测的单词拼接到输入序列中
  16. # 拿出当前预测的单词(数字)。我们用x'_t对应的输出z_t去预测下一个单词的概率,不用z_1,z_2..z_{t-1}
  17. next_word = prob.data[-1]
  18. next_symbol = next_word
  19. if next_symbol == tgt_vocab["E"]:
  20. terminal = True
  21. # print(next_word)
  22. # greedy_dec_predict = torch.cat(
  23. # [dec_input.to(device), torch.tensor([[next_symbol]], dtype=enc_input.dtype).to(device)],
  24. # -1)
  25. greedy_dec_predict = dec_input[:, 1:]
  26. return greedy_dec_predict

预测阶段

  1. # 测试集
  2. sentences = [
  3. # enc_input dec_input dec_output
  4. ['我 有 一 个 好 朋 友 P', '', '']
  5. ]
  6. enc_inputs, dec_inputs, dec_outputs = make_data(sentences)
  7. test_loader = Data.DataLoader(
  8. MyDataSet(enc_inputs, dec_inputs, dec_outputs), 2, True)
  9. enc_inputs, _, _ = next(iter(test_loader))
  10. print()
  11. print("=" * 30)
  12. print("利用训练好的Transformer模型将中文句子'我 有 一 个 好 朋 友' 翻译成英文句子: ")
  13. for i in range(len(enc_inputs)):
  14. greedy_dec_predict = greedy_decoder(model, enc_inputs[i].view(
  15. 1, -1).to(device), start_symbol=tgt_vocab["S"])
  16. print(enc_inputs[i], '->', greedy_dec_predict.squeeze())
  17. print([src_idx2word[t.item()] for t in enc_inputs[i]], '->',
  18. [idx2word[n.item()] for n in greedy_dec_predict.squeeze()])

 结果输出

  1. ============================================================
  2. 利用训练好的Transformer模型将中文句子'我 有 一 个 好 朋 友' 翻译成英文句子:
  3. tensor([1, 2, 3, 4, 5, 6, 7, 0]) -> tensor([ 1, 2, 3, 4, 5, 11])
  4. ['我', '有', '一', '个', '好', '朋', '友', 'P'] -> ['I', 'have', 'a', 'good', 'friend', '.']

参考文献

《Attention Is All You Need》

【论文精读】Transformer论文逐段精读 - 知乎

Transformer详解 - matjv

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/591649
推荐阅读
相关标签
  

闽ICP备14008679号