【深度学习】(4) Transformer 中的 Decoder 机制,附Pytorch完整代码

大家好,今天和各位分享一下 Transformer 中的 Decoder 部分涉及到的知识点计算 self-attention 时用到的两种 mask

本文是对前两篇文章的补充,强烈建议大家先看一下:

1.《Transformer代码复现》:https://blog.csdn.net/dgvv4/article/details/125491693

2.《Transformer中的Encoder机制》:https://blog.csdn.net/dgvv4/article/details/125507206


1. Decoder 的 self-attention 中的 mask

本节介绍的 mask 对应模型结构图中的位置:

如下图,decoder 的 self-attention 中使用的 mask 是一个下三角矩阵,当 decoder 预测第一个单词时,给它的输入是一个特殊字符 x1,当 decoder 预测第二个位置时,给它的输入是特殊字符 x1 和目标序列的第一个单词 x2

下面举一个例子:

encoder的输入:  i love you

decoder的输入:  /f 我 爱 你

此时的 decoder 是由4个词组成的向量,Mask 是一个 4*4 大小的矩阵

当 decoder 预测第一个单词 '我' 时, decoder 的输入是一个特殊字符 '/f',mask为[1,0,0,0]

当 decoder 预测第二个单词 '爱' 时, decoder 的输入是一个特殊字符 '/f' 和第一个单词 '我',mask为[1,1,0,0]

代码如下:

import torch
from torch.nn import functional as F

# ------------------------------------------------------ #
#(1)构建下三角形状的mask
# ------------------------------------------------------ #

# 目标序列中有两个句子,分别包含3、4个单词
tgt_len = torch.Tensor([3,4]).to(torch.int32)  
# 目标序列有效单词矩阵 shape=[3,3], shape=[4,4]
tgt_matrix = [torch.ones(L, L) for L in tgt_len]  

# 对每个元素全为1句子矩阵构造一个下三角矩阵
tri_matrix = [torch.tril(mat) for mat in tgt_matrix]
# 第一个句子长度为3,生成3*3大小且下三角区域的元素权威1,其余全为0的矩阵
print(tri_matrix)  # 每个mask的shape=[seq_len,seq_len]


# 构建有效单词的矩阵,通过padding将每个句子的矩阵大小调整成一样的
new_tri_matrix = []  # 保存padding后mask矩阵

for seq_len, matrix in zip(tgt_len, tri_matrix):  # 遍历每个下三角矩阵mask
    matrix = F.pad(matrix, pad=(0,max(tgt_len)-seq_len,0,max(tgt_len)-seq_len))  # 在矩阵的下方和右侧padding成相同相撞
    matrix = torch.unsqueeze(matrix, dim=0)  # 维度扩充[seq_len,seq_len]==>[1,seq_len,seq_len]
    new_tri_matrix.append(matrix)

# 将列表类型变成tensor, 其中值为0对应的元素代表需要mask掉
valid_tri_matrix = torch.cat(new_tri_matrix, dim=0)
print('有效下三角矩阵mask:', valid_tri_matrix)  # shape=[2,4,4]

# 将需要mask的元素用布尔类型表示,True代表需要mask
invalid_tri_matrix = (1 - valid_tri_matrix).to(torch.bool)
print('布尔mask:', invalid_tri_matrix)


# ------------------------------------------------------ #
#(2)对decoder的输入张量做mask
# ------------------------------------------------------ #

# 随机初始化一个 Q @ K^T 的计算结果 [batch, tgt_seq_len, tgt_seq_len]
score = torch.randn(2, max(tgt_len), max(tgt_len))
# 将mask中True元素对应score中的值变成非常小的值
masked_score = score.masked_fill(invalid_tri_matrix, value=-1e10)

# 将mask后的结果经过softmax,得到注意力矩阵
softmax_score = F.softmax(masked_score, dim=-1)

print('原始输入:', score)
print('mask后的输入:', masked_score)

然后构造一个 decoder 的输入 Q@K^T,它的 shape=[batch, seq_len, seq_len],如下面的第三个矩阵。

将输入张量 score 中与 mask 中True元素对应的位置变成一个非常小的数,如下面的第四个矩阵

# 有效下三角矩阵mask: 
tensor([[[1., 0., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 1., 0.],
         [0., 0., 0., 0.]],

        [[1., 0., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 1., 0.],
         [1., 1., 1., 1.]]])
    
# 布尔mask: 
tensor([[[False,  True,  True,  True],
         [False, False,  True,  True],
         [False, False, False,  True],
         [ True,  True,  True,  True]],

        [[False,  True,  True,  True],
         [False, False,  True,  True],
         [False, False, False,  True],
         [False, False, False, False]]])
    
# 原始输入scorce: 
tensor([[[ 0.5266, -0.7873, -0.2481,  0.5554],
         [-1.3146,  0.1668, -1.6488, -0.5159],
         [-0.1590, -2.1458,  0.0217,  0.4044],
         [ 1.0169,  0.8640, -0.9029,  0.5957]],

        [[-0.6277,  0.0611, -1.3732, -0.6897],
         [-1.3523,  0.6712,  0.0491,  2.2301],
         [ 0.4627,  0.1737,  1.0111, -1.4099],
         [ 0.1994,  0.2538,  0.5689, -0.2558]]])
    
# mask后的输入: 
tensor([[[ 5.2655e-01, -1.0000e+10, -1.0000e+10, -1.0000e+10],
         [-1.3146e+00,  1.6676e-01, -1.0000e+10, -1.0000e+10],
         [-1.5899e-01, -2.1458e+00,  2.1674e-02, -1.0000e+10],
         [-1.0000e+10, -1.0000e+10, -1.0000e+10, -1.0000e+10]],

        [[-6.2770e-01, -1.0000e+10, -1.0000e+10, -1.0000e+10],
         [-1.3523e+00,  6.7119e-01, -1.0000e+10, -1.0000e+10],
         [ 4.6272e-01,  1.7366e-01,  1.0111e+00, -1.0000e+10],
         [ 1.9943e-01,  2.5381e-01,  5.6886e-01, -2.5576e-01]]])

2. Decoder 中特征序列和目标序列之间的 Mask

该部分的 mask 代码对应结构图中的区域如下。这部分的 mask 涉及到目标序列和特征序列,在计算 self-attention 时,是目标序列的 query 和特征序列的 key、value 做计算。其中 key 和 value 是 Encoder 的输出query 是上一个 DecoderBlock 的输出

首先分别构造一个特征序列和一个目标序列,特征序列中第一句话有2个单词,第二句话有4个单词;目标序列中的第一句话有3个单词,第二句话有5个单词。

接下来就需要把特征序列和目标序列的长度各自给统一起来,将特征序列的所有句子都填充成4个单词,目标序列的所有句子都填充成5个单词有效单词区域的元素用 1 来表示,padding 的元素用 0 来表示

代码如下:

# Decoder部分的目标序列对特征序列的muti-head-attention中的mask
# 目标序列和特征序列之间的长度不一样,需要将原序列中和目标序列中padding后的元素mask掉

import torch 
from torch import nn
from torch.nn import functional as F

# ------------------------------------------------------ #
#(1)构造序列
# ------------------------------------------------------ #

src_len = torch.Tensor([2,4]).to(torch.int32)  # 特征序列中有两个句子,分别包含2、4个单词
tgt_len = torch.Tensor([3,5]).to(torch.int32)  # 目标序列中有两个句子,分别包含3、5个单词

# 对序列编码,有效单词位置的元素为1
valid_src_pos = [torch.ones(L) for L in src_len]  # 特征序列 [tensor([1., 1.]), tensor([1., 1., 1., 1.])]
valid_tgt_pos = [torch.ones(L) for L in tgt_len]  # 目标序列 [tensor([1., 1., 1.]), tensor([1., 1., 1., 1., 1.])]

# 在计算时需要保证特征序列的长度和目标序列的长度一致,因此将每句话的单词数padding成相同长度
max_src_len = max(src_len)  # 将特征序列的单词数统一成4个
max_tgt_len = max(tgt_len)  # 将目标序列的单词数统一成5个

new_valid_pos = []  # 保存padding后的特征序列和目标序列

for sent in valid_src_pos:  # 遍历每个特征句子
    sent = F.pad(sent, pad=(0, max_src_len - len(sent)))  # 将每句话的长度填充到4
    sent = torch.unsqueeze(sent, dim=0)  # 维度扩充 [max_src_len]==>[1, max_src_len]
    new_valid_pos.append(sent)

for sent in valid_tgt_pos:  # 遍历每个目标句子
    sent = F.pad(sent, pad=(0, max_tgt_len - len(sent)))  # 将每句话的长度填充到5
    sent = torch.unsqueeze(sent, dim=0)  # 维度扩充 [max_tgt_len]==>[1, max_tgt_len]
    new_valid_pos.append(sent)

# 前两个句子属于特征序列,后两个句子属于目标序列。将列表类型在axis=0维度上堆叠
valid_src_pos = torch.cat(new_valid_pos[:2], dim=0)  # tensor([[1., 1., 0., 0.], [1., 1., 1., 1.]])
valid_tgt_pos = torch.cat(new_valid_pos[2:], dim=0)  # tensor([[1., 1., 1., 0., 0.], [1., 1., 1., 1., 1.]])

# ------------------------------------------------------ #
#(2)构造mask
# Q @ K^T 的shape为 [batch, tgt_seq_len, src_seq_len]
# 因此mask的shape也为 [batch, tgt_seq_len, src_seq_len]
# ------------------------------------------------------ #

# 有效特征序列[2,4]==>[2,4,1], 有效目标序列[2,5]==>[2,5,1]
valid_src_pos = torch.unsqueeze(valid_src_pos, dim=-1)  # 值为1的元素代表有效单词,值为0的元素代表padding后的区域
valid_tgt_pos = torch.unsqueeze(valid_tgt_pos, dim=-1)

# 计算目标序列对特征序列有效性关系的矩阵,元素为0代表是padding后的单词
# [b, tgt_seq_len, 1] @ [b, 1, src_seq_len] = [b, tgt_seq_len, src_seq_len]
valid_cross_pos_matrix = torch.bmm(valid_tgt_pos, valid_src_pos.transpose(1,2))
print('有效关系矩阵:', valid_cross_pos_matrix)  # torch.Size([2, 5, 4])

# 得到无效矩阵,1代表需要mask的元素,变成布尔类型,True代表需要mask的元素
invalid_cross_pos_matrix = 1 - valid_cross_pos_matrix
invalid_cross_pos_matrix = invalid_cross_pos_matrix.to(torch.bool)
print('mask矩阵:', invalid_cross_pos_matrix)  # torch.Size([2, 5, 4])

# ------------------------------------------------------ #
#(3)对输入张量做mask
# ------------------------------------------------------ #

# 随机初始化一个 Q @ K^T 的计算结果 [batch, tgt_seq_len, src_seq_len]
score = torch.randn(2, 5, 4)
# mask中True元素对应的score中的元素值变成一个非常小的数
masked_score = torch.masked_fill(score, mask=invalid_cross_pos_matrix, value=-1e10)

print('原输入:', score)
print('打上mask后的输入:', masked_score)

接下来构造 mask它的 shape 是和 Q@K^T 计算后的矩阵的 shape 相同,即 [batch, tgt_seq_len, src_seq_len],其中 tgt_seq_len 代表目标序列中每个句子包含多少个单词,src_seq_len 代表特征序列中每个句子包含多少个单词。

下面的第一个矩阵代表对目标序列和特征序列计算关系矩阵,元素为1代表有效单词,0 代表是经过padding 后得到的单词。

之后计算一个无效区域矩阵将所有 padding 得到的单词区域像素值变成 True,代表需要将这个元素 mask 掉。如下面的第二个矩阵。

然后构造一个和 self-attention 中 Q@K^T 计算结果 shape 相同的输入 source,如下面的第三个矩阵。

然后对输入 source 添加 mask将 mask 中元素 True 对应的 source 元素变成一个非常小的值,这样在梯度反向传播过程中 padding 的元素梯度更新非常小,降低 padding 区域对有效单词区域的影响。如下面的第四个矩阵。

# 有效关系矩阵: 
tensor([[[1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]]])

# mask矩阵: 
tensor([[[False, False,  True,  True],
         [False, False,  True,  True],
         [False, False,  True,  True],
         [ True,  True,  True,  True],
         [ True,  True,  True,  True]],

        [[False, False, False, False],
         [False, False, False, False],
         [False, False, False, False],
         [False, False, False, False],
         [False, False, False, False]]])        

# 原输入: 
tensor([[[ 1.4030, -0.0176, -2.9678, -0.5551],
         [ 2.6138, -0.8088,  0.6641, -0.0128],
         [-0.0370, -0.3206, -0.6634,  0.3626],
         [ 1.1978,  1.9831, -0.3541, -0.8766],
         [ 0.0655,  0.4267, -0.3459,  1.8217]],

        [[-0.2351, -1.3515,  0.4783, -0.9379],
         [ 0.2302, -1.5482, -0.0825,  1.0711],
         [-0.3793, -0.9595,  0.9457, -1.5746],
         [ 0.3685,  1.1116, -2.3528, -0.3916],
         [-1.2416,  0.9410, -0.5407,  0.8035]]])

# 打上mask后的输入: 
tensor([[[ 1.4030e+00, -1.7624e-02, -1.0000e+10, -1.0000e+10],
         [ 2.6138e+00, -8.0884e-01, -1.0000e+10, -1.0000e+10],
         [-3.7038e-02, -3.2057e-01, -1.0000e+10, -1.0000e+10],
         [-1.0000e+10, -1.0000e+10, -1.0000e+10, -1.0000e+10],
         [-1.0000e+10, -1.0000e+10, -1.0000e+10, -1.0000e+10]],

        [[-2.3507e-01, -1.3515e+00,  4.7825e-01, -9.3789e-01],
         [ 2.3023e-01, -1.5482e+00, -8.2474e-02,  1.0711e+00],
         [-3.7931e-01, -9.5949e-01,  9.4568e-01, -1.5746e+00],
         [ 3.6855e-01,  1.1116e+00, -2.3528e+00, -3.9157e-01],
         [-1.2416e+00,  9.4099e-01, -5.4066e-01,  8.0347e-01]]])
  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
以下是使用PyTorch实现Transformer和Self-Attention的示例代码: ## Self-Attention ```python import torch import torch.nn as nn class SelfAttention(nn.Module): def __init__(self, embed_size, heads): super(SelfAttention, self).__init__() self.embed_size = embed_size self.heads = heads self.head_dim = embed_size // heads assert (self.head_dim * heads == embed_size), "Embed size needs to be divisible by heads" self.values = nn.Linear(self.head_dim, self.head_dim, bias=False) self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False) self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False) self.fc_out = nn.Linear(heads * self.head_dim, embed_size) def forward(self, values, keys, queries, mask): # Get number of training examples N = queries.shape[0] value_len, key_len, query_len = values.shape[1], keys.shape[1], queries.shape[1] # Split embedding into self.heads pieces values = values.reshape(N, value_len, self.heads, self.head_dim) keys = keys.reshape(N, key_len, self.heads, self.head_dim) queries = queries.reshape(N, query_len, self.heads, self.head_dim) # Transpose to get dimensions batch_size * self.heads * seq_len * self.head_dim values = values.permute(0, 2, 1, 3) keys = keys.permute(0, 2, 1, 3) queries = queries.permute(0, 2, 1, 3) # Calculate energy energy = torch.matmul(queries, keys.permute(0, 1, 3, 2)) if mask is not None: energy = energy.masked_fill(mask == 0, float("-1e20")) # Apply softmax to get attention scores attention = torch.softmax(energy / (self.embed_size ** (1/2)), dim=-1) # Multiply attention scores with values out = torch.matmul(attention, values) # Concatenate and linearly transform output out = out.permute(0, 2, 1, 3).reshape(N, query_len, self.heads * self.head_dim) out = self.fc_out(out) return out ``` ## Transformer ```python import torch import torch.nn as nn from torch.nn.modules.activation import MultiheadAttention class TransformerBlock(nn.Module): def __init__(self, embed_size, heads, dropout, forward_expansion): super(TransformerBlock, self).__init__() self.attention = MultiheadAttention(embed_dim=embed_size, num_heads=heads) self.norm1 = nn.LayerNorm(embed_size) self.norm2 = nn.LayerNorm(embed_size) self.feed_forward = nn.Sequential( nn.Linear(embed_size, forward_expansion * embed_size), nn.ReLU(), nn.Linear(forward_expansion * embed_size, embed_size) ) self.dropout = nn.Dropout(dropout) def forward(self, value, key, query, mask): attention_output, _ = self.attention(query, key, value, attn_mask=mask) x = self.dropout(self.norm1(attention_output + query)) forward_output = self.feed_forward(x) out = self.dropout(self.norm2(forward_output + x)) return out class Encoder(nn.Module): def __init__(self, src_vocab_size, embed_size, num_layers, heads, device, forward_expansion, dropout, max_length): super(Encoder, self).__init__() self.embed_size = embed_size self.device = device self.word_embedding = nn.Embedding(src_vocab_size, embed_size) self.position_embedding = nn.Embedding(max_length, embed_size) self.layers = nn.ModuleList([ TransformerBlock(embed_size, heads, dropout, forward_expansion) for _ in range(num_layers) ]) self.dropout = nn.Dropout(dropout) def forward(self, x, mask): N, seq_length = x.shape positions = torch.arange(0, seq_length).expand(N, seq_length).to(self.device) out = self.dropout(self.word_embedding(x) + self.position_embedding(positions)) for layer in self.layers: out = layer(out, out, out, mask) return out class DecoderBlock(nn.Module): def __init__(self, embed_size, heads, forward_expansion, dropout, device): super(DecoderBlock, self).__init__() self.norm = nn.LayerNorm(embed_size) self.attention = MultiheadAttention(embed_size, heads) self.transformer_block = TransformerBlock(embed_size, heads, dropout, forward_expansion) self.dropout = nn.Dropout(dropout) def forward(self, x, value, key, src_mask, trg_mask): attention_output, _ = self.attention(x, x, x, attn_mask=trg_mask) query = self.dropout(self.norm(attention_output + x)) out = self.transformer_block(value, key, query, src_mask) return out class Decoder(nn.Module): def __init__(self, trg_vocab_size, embed_size, num_layers, heads, forward_expansion, dropout, device, max_length): super(Decoder, self).__init__() self.embed_size = embed_size self.device = device self.word_embedding = nn.Embedding(trg_vocab_size, embed_size) self.position_embedding = nn.Embedding(max_length, embed_size) self.layers = nn.ModuleList([ DecoderBlock(embed_size, heads, forward_expansion, dropout, device) for _ in range(num_layers) ]) self.fc_out = nn.Linear(embed_size, trg_vocab_size) self.dropout = nn.Dropout(dropout) def forward(self, x, enc_out, src_mask, trg_mask): N, seq_length = x.shape positions = torch.arange(0, seq_length).expand(N, seq_length).to(self.device) x = self.dropout(self.word_embedding(x) + self.position_embedding(positions)) for layer in self.layers: x = layer(x, enc_out, enc_out, src_mask, trg_mask) out = self.fc_out(x) return out ``` 这些代码可以用于实现Transformer和Self-Attention模型。但这只是示例,你需要根据你的数据和任务来调整这些代码的各种超参数和结构。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

立Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值