Transformer模型Encoder和Decoder的Pytorch逐行实现


摘要

本周学习了Transformer模型,以及Encoder和Decoder原理讲解与其Pytorch逐行实现,主要从6个细节难点去学习了Transformer的原理与实现,详细地学习了Transformer的word embedding、position embedding和mask原理。


一、Transformer

文献参考:https://arxiv.org/abs/1706.03762
文献题目:Attention Is All You Need

1.1 为什么要使用attention

1)因为t时刻的计算依赖于t-1时刻的计算结果,这样会导致模型的并行能力受到限制。
2)虽然在之前学习到的LSTM可以在一定程度上缓解长期依赖的问题,但是对于特别长期的依赖问题,LSTM是无法解决问题的,也就是说在encoder和decoder之间的语义向量无法保存过多的信息。

1.2 Transformer的优点

它采用了Attention机制,将序列中的任意两个位置之间的距离是缩小为一个常量,并且它不类似于RNN的顺序结构,因此具有较好的并行性,也符合现有的GPU框架。

在论文中,Transformer没有使用到传统的CNN和RNN,整个网络由Attention机制组成。

二、Transformer模型Encoder和Decoder原理讲解与其Pytorch逐行实现

如下图所示,这6个方面是Transformer学习的难点。
在这里插入图片描述

2.1 word embedding

关于word embedding,以序列建模为例,考虑source sentence 和 target sentence。
构建序列,序列的字符以其在词表中的索引的形式表示。
src_seq:输入的句子,第一个长度为2,第二个长度为4。
tgt_seq:目标的句子,第一个长度为4,第二个长度为3。

import numpy as np
import torch
import numpy
import torch.nn as nn
import torch.nn.functional as F


batch_size = 2

# 单词表大小
max_num_src_words = 8
max_num_tgt_words = 8

# 序列的最大长度
max_src_seq_len = 5
max_tgt_seq_len = 5
model_dim = 8
max_position_len = 5

src_len = torch.Tensor([2, 4]).to(torch.int32)
tgt_len = torch.Tensor([4, 3]).to(torch.int32)
print(src_len)
print(tgt_len)

2.2 单词索引构成源句子和目标句子

Step 1:用随机数生成句子单词索引,对句子做了padding,也就是对空白位置补0,以保证所有句子的长度一致,最后用torch,cat将两个句子张量拼接在一起。

src_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(1, max_num_src_words, (L,)), (0, max_src_seq_len - L)), 0)
                     for L in src_len])
tgt_seq = torch.cat([torch.unsqueeze(F.pad(torch.randint(1, max_num_tgt_words, (L,)), (0, max_tgt_seq_len - L)), 0)
                     for L in tgt_len])
print(src_seq)
print(tgt_seq)

输出源句子和目标句子

tensor([[6, 7, 0, 0, 0],
        [7, 1, 5, 7, 0]])
tensor([[1, 5, 6, 2, 0],
        [4, 7, 5, 0, 0]])

构造embedding
Step 2:构建word embedding,我们需要得到向量化的句子,也就是先构建词表embedding,再利用索引得到向量化的句子。

src_embedding_table = nn.Embedding(max_num_src_words + 1, model_dim)
tgt_embedding_table = nn.Embedding(max_num_tgt_words + 1, model_dim)
src_embedding = src_embedding_table(src_seq)
tgt_embedding = tgt_embedding_table(tgt_seq)
print(src_embedding)
print(tgt_embedding)

输出src_embedding和tgt_embedding

tensor([[[ 4.0562e-01, -4.7774e-01,  1.1197e+00,  2.5271e-01, -2.8610e-01,
          -1.7786e+00, -3.4801e-01,  5.9147e-02],
         [ 4.0562e-01, -4.7774e-01,  1.1197e+00,  2.5271e-01, -2.8610e-01,
          -1.7786e+00, -3.4801e-01,  5.9147e-02],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01]],

        [[ 2.0992e+00,  4.3112e-01,  1.1805e-03,  1.9759e-01, -7.2922e-01,
          -2.1830e+00, -1.0120e-02, -4.0442e-01],
         [ 1.7823e-01, -4.7776e-01, -1.1815e+00, -1.3451e+00, -1.1886e+00,
           1.7289e+00, -3.9730e-01,  6.2238e-01],
         [-1.0617e+00, -2.1093e-01, -1.1825e+00, -6.9534e-01, -4.2309e-01,
           3.7529e-01, -1.8998e-01,  1.1653e-02],
         [-1.0617e+00, -2.1093e-01, -1.1825e+00, -6.9534e-01, -4.2309e-01,
           3.7529e-01, -1.8998e-01,  1.1653e-02],
         [-1.1680e-01,  2.6256e-01,  9.0318e-01,  2.5852e-01, -6.7806e-01,
          -3.2029e-01,  5.9314e-01,  1.4338e-01]]],
       grad_fn=<EmbeddingBackward0>)
tensor([[[-0.6926,  0.3298,  0.8671, -3.5071,  1.3463, -0.5505, -0.5561,
          -0.7192],
         [-1.7076, -0.5672, -0.1689,  0.8521,  1.2127,  0.6972,  0.6094,
           0.5606],
         [-0.6211, -0.0036,  1.6586, -0.0787,  0.2682, -1.5178, -1.5031,
          -1.7362],
         [-1.3177,  1.1583,  0.4895,  0.9380,  0.8419, -0.0501, -1.0925,
          -0.7124],
         [ 1.5036, -0.3206, -0.2090, -0.2941, -0.0675, -0.1443, -0.5557,
           0.5938]],

        [[-1.7076, -0.5672, -0.1689,  0.8521,  1.2127,  0.6972,  0.6094,
           0.5606],
         [-0.5977, -0.7804, -1.0782,  1.7742,  2.0498,  0.4659, -1.1477,
           1.5279],
         [-0.6211, -0.0036,  1.6586, -0.0787,  0.2682, -1.5178, -1.5031,
          -1.7362],
         [ 1.5036, -0.3206, -0.2090, -0.2941, -0.0675, -0.1443, -0.5557,
           0.5938],
         [ 1.5036, -0.3206, -0.2090, -0.2941, -0.0675, -0.1443, -0.5557,
           0.5938]]], grad_fn=<EmbeddingBackward0>)

2.3 构建position embedding

如下图所示,我们根据论文中的公式去构建position embedding。
在这里插入图片描述
Step 3:pe_embedding_table[:, 0::2]得到偶数列编码,pe_embedding_table[:, 1::2]得到奇数列编码。
构建位置索引,再通过nn.embedding就可以直接得到position embedding。

pos_mat = torch.arange(max_position_len).reshape((-1, 1))
i_mat = torch.pow(10000, torch.arange(0, 8, 2).reshape(1, -1) / model_dim)
pe_embedding_table = torch.zeros(max_position_len, model_dim)
# 偶数列
pe_embedding_table[:, 0::2] = torch.sin(pos_mat / i_mat)
# 奇数列
pe_embedding_table[:, 1::2] = torch.cos(pos_mat / i_mat)
# print(pos_mat)
# print(i_mat)
# print(pe_embedding_table)

pe_embedding = nn.Embedding(max_position_len, model_dim)
pe_embedding.weight = nn.Parameter(pe_embedding_table, requires_grad=False)

src_pos = torch.cat([torch.unsqueeze(torch.arange(max(src_len)), 0) for _ in src_len]).to(torch.int32)
tgt_pos = torch.cat([torch.unsqueeze(torch.arange(max(tgt_len)), 0) for _ in tgt_len]).to(torch.int32)

src_pe_embedding = pe_embedding(src_pos)
tgt_pe_embedding = pe_embedding(tgt_pos)
print(src_pe_embedding)
print(tgt_pe_embedding)

输出src_pe_embedding和tgt_pe_embedding

tensor([[[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]],

        [[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]]])
tensor([[[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]],

        [[ 0.0000e+00,  1.0000e+00,  0.0000e+00,  1.0000e+00,  0.0000e+00,
           1.0000e+00,  0.0000e+00,  1.0000e+00],
         [ 8.4147e-01,  5.4030e-01,  9.9833e-02,  9.9500e-01,  9.9998e-03,
           9.9995e-01,  1.0000e-03,  1.0000e+00],
         [ 9.0930e-01, -4.1615e-01,  1.9867e-01,  9.8007e-01,  1.9999e-02,
           9.9980e-01,  2.0000e-03,  1.0000e+00],
         [ 1.4112e-01, -9.8999e-01,  2.9552e-01,  9.5534e-01,  2.9995e-02,
           9.9955e-01,  3.0000e-03,  1.0000e+00]]])

2.4 构造encoder的self-attention mask

Step 4:因为句子的长度不一,于是我们会用padding去补0,之后便可得到有效位置矩阵。现在我们需要去得到有效矩阵,于是我们通过torch.bmm将有效位置矩阵与有效位置矩阵的转置矩阵的1,2维进行相乘(0维是batch维,不参与运算),便可得到有效矩阵。

valid_encoder_pos = torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L), (0, max(src_len) - L)), 0)
                                               for L in src_len]), 2)
valid_encoder_pos_matrix = torch.bmm(valid_encoder_pos, valid_encoder_pos.transpose(1, 2))
print(valid_encoder_pos_matrix)

输出有效矩阵

tensor([[[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减去有效矩阵,并将其赋成bool型便可得到无效矩阵。

invalid_encoder_pos_matrix = 1 - valid_encoder_pos_matrix
mask_encoder_self_attention = invalid_encoder_pos_matrix.to(torch.bool)
print(invalid_encoder_pos_matrix)
print(mask_encoder_self_attention)

输出无效矩阵

tensor([[[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]]])

通过softmax得到mask矩阵。

score = torch.randn(batch_size, max(src_len), max(src_len))
masked_score = score.masked_fill(mask_encoder_self_attention, -1e9)
prob = F.softmax(masked_score, -1)
print(src_len)
print(score)
print(masked_score)
print(prob)

查看输出结果

tensor([2, 4], dtype=torch.int32)
tensor([[[-0.2043, -0.3917, -1.0137, -1.1826],
         [-0.0821,  0.7781, -1.1928,  0.2586],
         [-0.2179,  0.8631, -0.9092,  0.0432],
         [ 0.3567,  1.0154, -1.0005,  0.2797]],

        [[-0.9328, -0.6068,  1.0793,  1.0886],
         [-0.8354,  0.6814,  1.3121,  1.2134],
         [ 2.0781, -0.4271, -2.4216,  0.0345],
         [-0.9104, -1.5460,  0.7630,  0.5593]]])
tensor([[[-2.0433e-01, -3.9166e-01, -1.0000e+09, -1.0000e+09],
         [-8.2061e-02,  7.7813e-01, -1.0000e+09, -1.0000e+09],
         [-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09],
         [-1.0000e+09, -1.0000e+09, -1.0000e+09, -1.0000e+09]],

        [[-9.3281e-01, -6.0677e-01,  1.0793e+00,  1.0886e+00],
         [-8.3541e-01,  6.8140e-01,  1.3121e+00,  1.2134e+00],
         [ 2.0781e+00, -4.2715e-01, -2.4216e+00,  3.4491e-02],
         [-9.1038e-01, -1.5460e+00,  7.6298e-01,  5.5926e-01]]])
tensor([[[0.5467, 0.4533, 0.0000, 0.0000],
         [0.2973, 0.7027, 0.0000, 0.0000],
         [0.2500, 0.2500, 0.2500, 0.2500],
         [0.2500, 0.2500, 0.2500, 0.2500]],

        [[0.0574, 0.0796, 0.4295, 0.4335],
         [0.0457, 0.2083, 0.3914, 0.3546],
         [0.8181, 0.0668, 0.0091, 0.1060],
         [0.0892, 0.0473, 0.4756, 0.3879]]])

2.5 构造intra-attention的mask

这里的步骤和Step 4差不多,但是要注意Q @ K ^ T,即decoder Q与encoder K ^ T的相关有效性。

valid_decoder_pos = torch.unsqueeze(torch.cat([torch.unsqueeze(F.pad(torch.ones(L), (0, max(tgt_len) - L)), 0)
                                               for L in tgt_len]), 2)
valid_cross_pos_matrix = torch.bmm(valid_decoder_pos, valid_encoder_pos.transpose(1, 2))
invalid_cross_pos_matrix = 1 - valid_cross_pos_matrix
mask_cross_attention = invalid_cross_pos_matrix.to(torch.bool)
print(valid_cross_pos_matrix)
print(invalid_cross_pos_matrix)
print(mask_cross_attention)

查看输出结果

tensor([[[1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 0., 0.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [0., 0., 0., 0.]]])
tensor([[[0., 0., 1., 1.],
         [0., 0., 1., 1.],
         [0., 0., 1., 1.],
         [0., 0., 1., 1.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [1., 1., 1., 1.]]])
tensor([[[False, False,  True,  True],
         [False, False,  True,  True],
         [False, False,  True,  True],
         [False, False,  True,  True]],

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

2.6 构成decoder self-attention的mask

这里的步骤和Step 4差不多,但是这里用到torch.tril生成三角矩阵,并且对行和列都进行了补0。

valid_decoder_tri_matrix = torch.cat([torch.unsqueeze(F.pad(torch.tril(torch.ones((L, L))),
                                                            (0, max(tgt_len) - L, 0, max(tgt_len) - L)), 0)
                                      for L in tgt_len])
invalid_decoder_tri_matrix = 1 - valid_decoder_tri_matrix
invalid_decoder_tri_matrix = invalid_decoder_tri_matrix.to(torch.bool)
print(valid_decoder_tri_matrix)
print(invalid_decoder_tri_matrix)

查看三角矩阵

tensor([[[1., 0., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 1., 0.],
         [1., 1., 1., 1.]],

        [[1., 0., 0., 0.],
         [1., 1., 0., 0.],
         [1., 1., 1., 0.],
         [0., 0., 0., 0.]]])
tensor([[[False,  True,  True,  True],
         [False, False,  True,  True],
         [False, False, False,  True],
         [False, False, False, False]],

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

通过无效矩阵做softmax,得到mask。

score = torch.randn(batch_size, max(tgt_len), max(tgt_len))
masked_score = score.masked_fill(invalid_decoder_tri_matrix, -1e9)
prob = F.softmax(masked_score, -1)
print(tgt_len)
print(prob)

输出得到的mask

tensor([4, 3], dtype=torch.int32)
tensor([[[1.0000, 0.0000, 0.0000, 0.0000],
         [0.3802, 0.6198, 0.0000, 0.0000],
         [0.6869, 0.2416, 0.0716, 0.0000],
         [0.1204, 0.1475, 0.1840, 0.5481]],

        [[1.0000, 0.0000, 0.0000, 0.0000],
         [0.5099, 0.4901, 0.0000, 0.0000],
         [0.1156, 0.8410, 0.0434, 0.0000],
         [0.2500, 0.2500, 0.2500, 0.2500]]])

最后一行是因为补了一行的0,然后概率相等,各自都是0.2500。

2.7 构建scaled self-attention

def scaled_dot_product_attention(Q, K, V, attn_mask):
    # shape of Q,K,V : (batch_size*num_head. seq_len, model_dim/num_head)
    score = torch.bmm(Q, K.transpose(-2, -1)/torch.sqrt(model_dim))
    masked_score = score.masked_fill(attn_mask, -1e9)
    prob = F.softmax(masked_score, -1)
    context = torch.bmm(prob, V)
    return context

总结

在本周的学习中,主要学习了Transformer模型,是一种避免循环的模型结构,完全依赖于注意力机制对输入输出的全局依赖关系进行建模。Transformer模型的优势是突破了 RNN 模型不能并行计算的限制;相比CNN来说,计算两个位置之间的关联所需的操作次数不随距离增长;自注意力可以产生更具可解释性的模型。本周对论文只是进行简单地阅读,在下周会对论文进行详细地阅读并理解,并对Transformer模型进行详细地解释说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值