Attention Is All You Need,Transformer代码实现

这篇论文很早就读过,当时只是简单了解了下其原理,但真正动手实现时还是能发现不少不能忽略的细节问题,这里不说明原理(原理都在文献上),只注重实现。

目录

架构

Transfomer

encoder

 注意力层

前馈神经网络

decoder


架构

 上图便是Transformer的架构,可以看到,它可以分为三个部分,encoder,decoder和Linear projection。encoder又可以分为三个部分:词向量编码,位置编码,多头注意力层和前馈神经网络层(这里做一个部分)。decoder相比encoder多了一个Masked的注意力层,其余和encoder的结构一样。如下图所示

补充:该模型有3个输入,分别是Inputs,Outputs shifted right和Output Probabilities。Inputs作为编码器的输入,Outputs shifted right作为解码器的输入,Output Probabilities用来与解码器的输出做损失,一般Outputs shifted right=Output Probabilities。

Transfomer

先设置好transformer的参数

    #参数
    Embedding_size = 512 #词向量维度
    FF_dimension = 2048 #前馈层维度
    K_size = Q_size = V_size = 64 #注意力的QKV的维度
    n_layers = 6 #编码器与解码器的层数
    n_head = 8 #多头注意力的头数

整体架构清晰了,接下来就是一个框架一个框架去实现,首先把大框架Transformer 列出来

class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        self.encoder = Encoder()
        self.decoder = Decoder()
        self.Linear_projection = nn.Linear(Embedding_size, length_tgv)

 因为Transformer最后的输出要与Output Probabilities做损失,那么需要保证维度一致,所以Linear_projection层要将Embeeding_size(词向量维度)的向量转为length_tgv(output词表的维度)维度。接下来实现encoder与decoder。

encoder

根据架构,很容易初始化

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        self.embed = nn.Embedding(length_srcv, Embedding_size)
        self.position = PositionalEncoding(Embedding_size)
        self.layers = nn.ModuleList(EncoderLayer() for _ in range(n_layers))

 这里用了EncoderLayer将注意力和前馈神经网络合在一起(当然还有Add&Norm操作)

class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        self.Multi_attn = MultiHeadAttention()
        self.FeedForward = FeedForwardPosition()

 注意力层

class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        self.W_Q = nn.Linear(Embedding_size, Q_size*n_head)
        self.W_K = nn.Linear(Embedding_size, K_size*n_head)
        self.W_V = nn.Linear(Embedding_size, V_size*n_head)
        self.Linear = nn.Linear(Q_size*n_head, Embedding_size)
        self.norm = nn.LayerNorm(Embedding_size)

 这里的W_Q,W_K,W_V,是根据文献的多头注意力公式得来的

原始的Q,K,V需要经过W_Q,W_K,W_V线性层,然后再通过注意力机制,因为等会要分头分别计算注意力,所以通过这些线性层他们的维度会变成Q_size*n_head(方便等会分成[Q_size,n_head]这样的维度。

    def forward(self, Q, K, V, attn_mask):
        temp = Q
        batch_size, len_seq, _ = Q.size()
        s_q = self.W_Q(Q).unsqueeze(1).reshape(batch_size, n_head, len_seq, Q_size) #s_q:[1,8,6,64]
        s_k = self.W_K(K).unsqueeze(1).reshape(batch_size, n_head, len_seq, K_size) #s_k:[1,8,6,64]
        s_v = self.W_V(V).unsqueeze(1).reshape(batch_size, n_head, len_seq, V_size) #s_v:[1,8,6,64]

        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_head, 1, 1) #attn_mask:[1,8,6,6]

        attn_score = ScaledDotProductAttention()(s_q,s_k,s_v,attn_mask) #attn_score:[1,8,6,64]
        attn_score = attn_score.transpose(1,2).squeeze(2).reshape(batch_size,len_seq,Q_size*n_head) #attn_score:[1,6,512]
        attn_score = self.Linear(attn_score) #attn_score:[1,6,512]
        attn_score = self.norm(attn_score+temp) #attn_score:[1,6,512]
        return attn_score #attn_score:[1,6,512]

这里注意attn_mask,这是一个矩阵,用来标识输入的句子的填充情况(pad),比如下面这样的例子:

 如果限制最大的输入句子的长度是7,那么很明显第一个和第三个句子不够长,那么就要进行填充pad,但是这些填充字符是没有意义的,注意力机制不需要进行计算,所以这个矩阵记录了哪些是句子,哪些是填充字符,比如设置如果字是1,填充是0,那么attn_mask矩阵应该是这样的:

得到s_q,s_k,s_v之后, 便根据论文给出的计算公式进行注意力点积计算

class ScaledDotProductAttention(nn.Module):
    def __init__(self):
        super(ScaledDotProductAttention, self).__init__()
    def forward(self,s_q,s_k,s_v,attn_mask):
        score = torch.matmul(s_q,s_k.transpose(2,3)) / np.sqrt(K_size)
        score = score.masked_fill_(attn_mask, -1e9)
        score = nn.Softmax(dim=1)(score)
        score = torch.matmul(score,s_v)
        return score #score[1,8,6,64]

这里使用masked_fill_函数对score根据attn_mask的形状进行赋值,如果是填充的话会将其对应的位置赋值为-1e9,因为数值太小,在计算过程中不会造成影响。

前馈神经网络

只使用两个卷积层,注意输出的结果需要与原输入相加(见结构图),最后进行归一化

class FeedForwardPosition(nn.Module):
    def __init__(self):
        super(FeedForwardPosition, self).__init__()
        self.conv1 = nn.Conv1d(Embedding_size,FF_dimension, kernel_size = 1)
        self.conv2 = nn.Conv1d(FF_dimension,Embedding_size, kernel_size = 1)
        self.norm = nn.LayerNorm(Embedding_size)
    def forward(self,attn_score):
        temp = attn_score
        output= self.conv1(attn_score.transpose(1,2))
        output = self.conv2(output).transpose(1,2)
        output = self.norm(output + temp)
        return output #output:[1,6,512]

decoder

根据结构,先将decoder初始化

class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.embed = nn.Embedding(length_tgv, Embedding_size)
        self.position = PositionalEncoding(Embedding_size)
        self.layers = nn.ModuleList(DecoderLayer() for _ in range(n_layers))

这里使用DecoderLayer将注意力和前馈网络合在一起,这里要注意的是,decoder比encoder多了一个mask注意力层

class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.mask_Mutil_attn = MultiHeadAttention()
        self.Mutil_attn = MultiHeadAttention()
        self.FeedForward = FeedForwardPosition()

这个mask注意力层的作用是:它需要对经过mask的decoder输入进行注意力计算。

为什么要mask decoder的输入呢?以机器翻译为例,翻译“我要回家了”,那么encoder的input便是

“我要回家了”,decoder的输入是“i have to go home”,如果不mask的话,attention会从上下文得到答案,输入i的时候,它很容易通过下文得到have这个正确答案(有点作弊的意思),所以每次输入的时候,都要对后面的单词mask,这样就形成了一个上三角矩阵,设1为原单词,0为mask,则

 将这个上三角矩阵与填充矩阵attn_mask相加,便得到一个既有mask也有识别pad的的矩阵,再用其计算注意力。Decoder的其余部分与Encoder一样,重复调用即可。

encoder和decoder都实现了,将他们在Transformer中整合到一起,最后实现Transformer

    def forward(self, Encoder_input, Decoder_input):
        Encoder_output = self.encoder(Encoder_input)
        decoder_output = self.decoder(Encoder_output,Decoder_input)
        output = self.Linear_projection(decoder_output)

完整代码:https://github.com/bowspider-man/transformer

"Attention is All You Need" 是一篇由 Vaswani 等人于 2017 年提出的论文,提出了一种名为 Transformer 的模型架构,用于进行序列到序列的任务,如机器翻译。这个模型使用了自注意力机制(self-attention)来捕捉输入序列中不同位置之间的依赖关系,取代了传统的循环神经网络和卷积神经网络。 关于 "Attention is All You Need" 的代码实现,你可以在 GitHub 上找到多个开源的实现版本。其中最著名的是由 Google Brain 团队开发的 TensorFlow 实现和由 Hugging Face 团队开发的 PyTorch 实现。 以下是一个简单的示例代码,展示了如何使用 PyTorch 实现 Transformer 模型: ```python import torch import torch.nn as nn import torch.nn.functional as F class Transformer(nn.Module): def __init__(self, input_dim, hidden_dim, num_heads, num_layers): super(Transformer, self).__init__() self.embedding = nn.Embedding(input_dim, hidden_dim) self.encoder = nn.TransformerEncoder( nn.TransformerEncoderLayer(hidden_dim, num_heads), num_layers ) self.decoder = nn.Linear(hidden_dim, input_dim) def forward(self, x): x = self.embedding(x) x = self.encoder(x) x = self.decoder(x) return F.log_softmax(x, dim=-1) # 创建一个 Transformer 模型实例 model = Transformer(input_dim=1000, hidden_dim=256, num_heads=4, num_layers=6) # 定义输入数据 input_data = torch.tensor([[1, 2, 3, 4, 5]]) # 运行模型 output = model(input_data) ``` 这只是一个简单的示例,实际的代码实现可能会更加复杂,包括数据预处理、训练循环等。你可以根据自己的需求和具体的任务进行相应的修改和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值