赞
踩
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。
需要用到的是Self-Attention以及Multi-Head Attention
具体可以见我的上一篇博客Seq2Seq+Attention学习总结-CSDN博客
输入 x1,x2 经 self-attention 层之后变成 z1,z2,然后和输入 x1,x2 进行残差连接,经过 LayerNorm 后输出给全连接层。全连接层也有一个残差连接和一个 LayerNorm,最后再输出给下一个 Encoder(每个 Encoder Block 中的 FeedForward 层权重都是共享的)
我们在上一步得到了经过 self-attention 加权之后输出,也就是 Self-Attention(Q, K, V),然后把他们加起来做残差连接
Layer Normalization 的作用是把神经网络中隐藏层归一为标准正态分布,以起到加快训练速度,加速收敛的作用。
在解码器中,Transformer block比编码器中多了个encoder-cecoder attention。在encoder-decoder attention中, Q来自于解码器的上一个输出, K 和 V 则来自于与编码器的输出。
由于在机器翻译中,解码过程是一个顺序操作的过程,也就是当解码第 k 个特征向量时,我们只能看到第 k-1 及其之前的解码结果,论文中把这种情况下的multi-head attention叫做masked multi-head attention。
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博客
- 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
手动输入了数据集
- # 数据集:两对中文→英语的句子
- # S: Symbol that shows starting of decoding input
- # E: Symbl that shows starting of decoding output
- # P: Symbol that will fill in blank sequence if current batch data size is shorter than time steps
-
- # 训练集(手动编码)
- sentences = [
- # 中文和英语的单词个数不要求相同
- # 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']
- ]
-
- # 中文和英语的单词要分开建立词库
- # 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)}
- 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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
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 的数量
- # Transformer Parameters
- d_model = 512 # Embedding Size(token embedding和position编码的维度)
- # FeedForward dimension (两次线性层中的隐藏层 512->2048->512,线性层是用来做特征提取的),当然最后会再接一个projection层
- d_ff = 2048
- d_k = d_v = 64 # dimension of K(=Q), V(Q和K的维度需要相同,这里为了方便让K=V)
- n_layers = 6 # number of Encoder of Decoder Layer(Block的个数)
- n_heads = 8 # number of heads in Multi-Head Attention(有几套头)
- 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)
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
自定义DataLoader,用于批量加载和处理数据。
DataLoader可以并行地加载数据,以提高训练速度。
- class MyDataSet(Data.Dataset):
- 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)
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- 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[:, 1::2] = torch.cos(position * div_term)
- pe = pe.unsqueeze(0).transpose(0, 1)
- 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)
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
在序列处理过程中,常常需要用填充来保证序列的长度一致,但在注意力模型中,填充的元素不应被考虑。pad mask的作用:在对value向量加权平均的时候,可以让pad对应的alpha_ij=0,排除填充(pad)的元素,这样注意力就不会考虑到pad向量。
- def get_attn_pad_mask(seq_q, seq_k):
- #这里的q,k表示的是两个序列(跟注意力机制的q,k没有关系),例如encoder_inputs (x1,x2,..xm)和encoder_inputs (x1,x2..xm)
- #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()
- '''
- seq_k.data.eq(0)
- 返回一个大小和 seq_k 一样的 tensor,只不过里面的值只有 True 和 False。
- 如果 seq_k 某个位置的值等于 0,那么对应位置就是 True,否则即为 False。
- '''
- pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)
- # [batch_size, len_q, len_k] 构成一个立方体(batch_size个这样的矩阵)
- return pad_attn_mask.expand(batch_size, len_q, len_k)
排除不在子序列中的元素:创建一个上三角矩阵来创建一个掩码,这个矩阵在主对角线上为1(表示有效元素),在其他位置为0(表示无效元素)。
- def get_attn_subsequence_mask(seq):
- # seq: [batch_size, tgt_len]
- 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]
实现缩放点积注意力
- 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)可能不同
- """
- 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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这个Attention类可以实现:
Encoder的Self-Attention
Decoder的Masked Self-Attention
Encoder-Decoder的Attention
- class MultiHeadAttention(nn.Module):
- """
- 输入:seq_len x d_model
- 输出:seq_len x d_model
- """
-
- def __init__(self):
- super(MultiHeadAttention, self).__init__()
- 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维的
- # 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]
- 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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
构建一个前馈神经网络(Feed Forward Neural Network)
并且在网络中使用残差连接(Residual Connection)和层标准化(Layer Normalization)
Pytorch中的Linear只会对最后一维操作,所以正好是我们希望的每个位置用同一个全连接网络
- class PoswiseFeedForwardNet(nn.Module):
- def __init__(self):
- super(PoswiseFeedForwardNet, self).__init__()
- self.fc = nn.Sequential(
- 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)
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Encoder Layer
- 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_inputs: [batch_size, src_len, d_model]
- enc_self_attn_mask: [batch_size, src_len, src_len] mask矩阵(pad mask or sequence mask)
- """
- # 通过调用self.enc_self_attn对象的方法,将输入数据enc_inputs进行多头自注意力处理,并将结果保存在enc_outputs中
- # 同时,处理过程中产生的注意力分布图被保存在attn中
- # 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_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs,
- enc_self_attn_mask) # enc_inputs to same Q,K,V(未线性变换前)
-
- # 将经过自注意力处理后的enc_outputs输入到self.pos_ffn对象中进行前馈网络处理,并将结果保存在enc_outputs中
- enc_outputs = self.pos_ffn(enc_outputs)
- # enc_outputs: [batch_size, src_len, d_model]
- return enc_outputs, attn
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Encoder
- class Encoder(nn.Module):
- def __init__(self):
- super(Encoder, self).__init__()
- self.src_emb = nn.Embedding(src_vocab_size, d_model) # token Embedding
- self.pos_emb = PositionalEncoding(
- d_model) # Transformer中位置编码时固定的,不需要学习
- self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)])
-
- def forward(self, enc_inputs):
- """
- enc_inputs: [batch_size, src_len]
- """
- enc_outputs = self.src_emb(
- enc_inputs) # [batch_size, src_len, d_model]
- enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(
- 0, 1) # [batch_size, src_len, d_model]
- # Encoder输入序列的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, enc_self_attn = layer(enc_outputs,
- enc_self_attn_mask) # 传入的enc_outputs其实是input,传入mask矩阵是因为你要做self attention
- enc_self_attns.append(enc_self_attn) # 这个只是为了可视化
- return enc_outputs, enc_self_attns
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Decoder Layer
- 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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Decoder
- 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填充的)
- dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs).to(
- device) # [batch_size, tgt_len, tgt_len]
- # Masked Self_Attention:当前时刻是看不到未来的信息的
- dec_self_attn_subsequence_mask = get_attn_subsequence_mask(dec_inputs).to(
- device) # [batch_size, tgt_len, tgt_len]
-
- # Decoder中把两种mask矩阵相加(既屏蔽了pad的信息,也屏蔽了未来时刻的信息)
- 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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- 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)
-
- def forward(self, enc_inputs, dec_inputs):
- """Transformers的输入:两个序列
- enc_inputs: [batch_size, src_len]
- dec_inputs: [batch_size, tgt_len]
- """
- # tensor to store decoder outputs
- # outputs = torch.zeros(batch_size, tgt_len, tgt_vocab_size).to(self.device)
-
- # enc_outputs: [batch_size, src_len, d_model], enc_self_attns: [n_layers, batch_size, n_heads, src_len, src_len]
- # 经过Encoder网络后,得到的输出还是[batch_size, src_len, d_model]
- enc_outputs, enc_self_attns = self.encoder(enc_inputs)
- # 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]
- dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(
- dec_inputs, enc_inputs, enc_outputs)
- # dec_outputs: [batch_size, tgt_len, d_model] -> dec_logits: [batch_size, tgt_len, tgt_vocab_size]
- 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的话效果不好
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- for epoch in range(epochs):
- for enc_inputs, dec_inputs, dec_outputs in loader:
- """
- enc_inputs: [batch_size, src_len]
- dec_inputs: [batch_size, tgt_len]
- dec_outputs: [batch_size, tgt_len]
- """
- enc_inputs, dec_inputs, dec_outputs = enc_inputs.to(
- device), dec_inputs.to(device), dec_outputs.to(device)
- # outputs: [batch_size * tgt_len, tgt_vocab_size]
- outputs, enc_self_attns, dec_self_attns, dec_enc_attns = model(
- enc_inputs, dec_inputs)
- # dec_outputs.view(-1):[batch_size * tgt_len * tgt_vocab_size]
- loss = criterion(outputs, dec_outputs.view(-1))
- print('Epoch:', '%04d' % (epoch + 1), 'loss =', '{:.6f}'.format(loss))
-
- optimizer.zero_grad()
- loss.backward()
- optimizer.step()
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
贪心编码
- def greedy_decoder(model, enc_input, start_symbol):
- enc_outputs, enc_self_attns = model.encoder(enc_input)
- # 初始化一个空的tensor: tensor([], size=(1, 0), dtype=torch.int64)
- dec_input = torch.zeros(1, 0).type_as(enc_input.data)
- terminal = False
- next_symbol = start_symbol
- while not terminal:
- # 预测阶段:dec_input序列会一点点变长(每次添加一个新预测出来的单词)
- dec_input = torch.cat([dec_input.to(device), torch.tensor([[next_symbol]], dtype=enc_input.dtype).to(device)],
- -1)
- dec_outputs, _, _ = model.decoder(dec_input, enc_input, enc_outputs)
- projected = model.projection(dec_outputs)
- prob = projected.squeeze(0).max(dim=-1, keepdim=False)[1]
- # 增量更新(我们希望重复单词预测结果是一样的)
- # 我们在预测是会选择性忽略重复的预测的词,只摘取最新预测的单词拼接到输入序列中
- # 拿出当前预测的单词(数字)。我们用x'_t对应的输出z_t去预测下一个单词的概率,不用z_1,z_2..z_{t-1}
- next_word = prob.data[-1]
- next_symbol = next_word
- if next_symbol == tgt_vocab["E"]:
- terminal = True
- # print(next_word)
-
- # greedy_dec_predict = torch.cat(
- # [dec_input.to(device), torch.tensor([[next_symbol]], dtype=enc_input.dtype).to(device)],
- # -1)
- greedy_dec_predict = dec_input[:, 1:]
- return greedy_dec_predict
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
预测阶段
- # 测试集
- sentences = [
- # enc_input dec_input dec_output
- ['我 有 一 个 好 朋 友 P', '', '']
- ]
-
- enc_inputs, dec_inputs, dec_outputs = make_data(sentences)
- test_loader = Data.DataLoader(
- MyDataSet(enc_inputs, dec_inputs, dec_outputs), 2, True)
- enc_inputs, _, _ = next(iter(test_loader))
-
- print()
- print("=" * 30)
- print("利用训练好的Transformer模型将中文句子'我 有 一 个 好 朋 友' 翻译成英文句子: ")
- for i in range(len(enc_inputs)):
- greedy_dec_predict = greedy_decoder(model, enc_inputs[i].view(
- 1, -1).to(device), start_symbol=tgt_vocab["S"])
- print(enc_inputs[i], '->', greedy_dec_predict.squeeze())
- print([src_idx2word[t.item()] for t in enc_inputs[i]], '->',
- [idx2word[n.item()] for n in greedy_dec_predict.squeeze()])
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- ============================================================
- 利用训练好的Transformer模型将中文句子'我 有 一 个 好 朋 友' 翻译成英文句子:
- tensor([1, 2, 3, 4, 5, 6, 7, 0]) -> tensor([ 1, 2, 3, 4, 5, 11])
- ['我', '有', '一', '个', '好', '朋', '友', 'P'] -> ['I', 'have', 'a', 'good', 'friend', '.']
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。