Transformer原理到实践详解

本篇博客并没有什么独特见解之处,只是单纯的再一次加强巩固学习。
因为接触到的不少项目用到了,还是要拿过来好好研究学习一下的。
原文链接:https://blog.csdn.net/qq_41664845/article/details/84969266
代码详见:http://nlp.seas.harvard.edu/2018/04/03/attention.html

-Transformer:一种完全基于Attention机制来加速深度学习训练过程的算法模型;
Transformer最大的优势在于其在并行化处理上做出的贡献。

Transformer在Goole的一篇论文Attention is All You Need被提出,为了方便实现调用Transformer Google还开源了一个第三方库,基于TensorFlow的Tensor2Tensor,一个NLP的社区研究者贡献了一个Torch版本的支持:guide annotating the paper with PyTorch implementation。

网络结构

transformer由2个部分组成,一个Encoders和一个Decoders
(如下实例,机器翻译:输入一种语言,经transformer之后输出其英文表示)
在这里插入图片描述
每个Encoders中分别由6个Encoder组成,而每个Decoders中同样也是由6个Decoder组成。
在这里插入图片描述
对于Encoders中的每一个Encoder,他们结构都是相同的,但是并不会共享权值。每层Encoder有2个部分组成,如下图:
在这里插入图片描述
每个Encoder的输入首先会通过一个self-attention层,通过self-attention层帮助Endcoder在编码单词的过程中查看输入序列中的其他单词。

Self-attention的输出会被传入一个全连接的前馈神经网络,每个encoder的前馈神经网络参数个数都是相同的,但是他们的作用是独立的。

每个Decoder也同样具有这样的层级结构,但是在这之间有一个Attention层,帮助Decoder专注于与输入句子中对应的那个单词。
在这里插入图片描述

数据流动

以词嵌入为例,观察从输入到输出的过程中这些数据在各个网络结构中的流动。词嵌入简单理解就是词语在计算机中的表示形式,具体可参见:什么是文本的词嵌入
在这里插入图片描述
将每个单词编码为一个512维度的向量,我们用上面这张简短的图形来表示这些向量。词嵌入的过程只发生在最底层的Encoder。但是对于所有的Encoder来说,你都可以按下图来理解。输入(一个向量的列表,每个向量的维度为512维,在最底层Encoder作用是词嵌入,其他层就是其前一层的output)。另外这个列表的大小和词向量维度的大小都是可以设置的超参数。一般情况下,它是我们训练数据集中最长的句子的长度
在这里插入图片描述
在每个单词进入Self-Attention层后都会有一个对应的输出。Self-Attention层中的输入和输出是存在依赖关系的,而前馈层则没有依赖,所以在前馈层,我们可以用到并行化来提升速率。
Transformer中的每个Encoder接收一个512维度的向量的列表作为输入,然后将这些向量传递到‘self-attention’层,self-attention层产生一个等量512维向量列表,然后进入前馈神经网络,前馈神经网络的输出也为一个512维度的列表,然后将输出向上传递到下一个encoder。

self-attention

假设下面的句子就是我们需要翻译的输入句:

”The animal didn’t cross the street because it was too tired”

这句话中的"it"指的是什么?它指的是“animal”还是“street”?对于人来说,这其实是一个很简单的问题,但是对于一个算法来说,处理这个问题其实并不容易。self attention的出现就是为了解决这个问题,通过self attention,我们能将“it”与“animal”联系起来。

当模型处理单词的时候,self attention层可以通过当前单词去查看其输入序列中的其他单词,以此来寻找编码这个单词更好的线索。

如果你熟悉RNNs,那么你可以回想一下,RNN是怎么处理先前单词(向量)与当前单词(向量)的关系的?RNN是怎么计算他的hidden state的。self-attention正是transformer中设计的一种通过其上下文来理解当前词的一种办法。你会很容易发现…相较于RNNs,transformer具有更好的并行性。
在这里插入图片描述
如上图,是我们第五层Encoder针对单词’it’的图示,可以发现,我们的Encoder在编码单词‘it’时,部分注意力机制集中在了‘animl’上,这部分的注意力会通过权值传递的方式影响到’it’的编码。

self attention计算

1、计算self attention的第一步是从每个Encoder的输入向量上创建3个向量(在这个情况下,对每个单词做词嵌入)。所以,对于每个单词,我们创建一个Query向量,一个Key向量和一个Value向量。这些向量是通过词嵌入乘以我们训练过程中创建的3个训练矩阵而产生的。
在这里插入图片描述
我们将X1乘以W^{Q}的权重矩阵得到新向量q1,q1既是“query”的向量。同理,最终我们可以对输入句子的每个单词创建“query”,“key”,“value”的新向量表示形式。

2、计算self attention的第二步计算得分。以上图为例,假设我们在计算第一个单词“thinking”的self attention。我们需要根据这个单词对输入句子的每个单词进行评分。当我们在某个位置编码单词时,分数决定了对输入句子的其他单词的关照程度

通过将query向量和key向量做点积来对相应的单词打分。所以,如果我们处理开始位置的的self attention,则第一个分数为q1和k1的点积,第二个分数为q2和k2的点积。如下图

在这里插入图片描述
3、第三步和第四步的计算,是将第二部的得分除以8*sqrt{dk},(论文中使用key向量的维度是64维,其平方根=8,这样可以使得训练过程中具有更稳定的梯度。这个sqrt{dk}并不是唯一值,经验所得)。然后再将得到的输出通过softmax函数标准化,使得最后的列表和为1。

这个softmax的分数决定了当前单词在每个句子中每个单词位置的表示程度。

4、第五步是将每个Value向量乘以softmax后的得分。这里实际上的意义在于保存对当前词的关注度不变的情况下,降低对不相关词的关注

5、第六步累加加权值的向量。 这会在此位置产生self-attention层的输出(对于第一个单词)。
在这里插入图片描述
总结self-attention的计算过程,(单词级别)就是得到一个我们可以放到前馈神经网络的矢量。 然而在实际的实现过程中,该计算会以矩阵的形式完成,以便更快地处理。

Multi-headed

本文通过使用“Multi-headed”的机制来进一步完善self attention层。“Multi-headed”主要通过下面2中方式改善了attention层的性能:

1. 它拓展了模型关注不同位置的能力。在上面例子中可以看出,”The animal didn’t cross the street because it was too tired”,我们的attention机制计算出“it”指代的为“animal”,这在对语言的理解过程中是很有用的。

2.它为attention层提供了多个“representation subspaces”。由下图可以看到,在self attention中,我们有多个Query / Key / Value权重矩阵(Transformer使用8个attention heads)。这些集合中的每个矩阵都是随机初始化生成的。然后通过训练,用于将词嵌入(或者来自较低Encoder/Decoder的矢量)投影到不同的“representation subspaces(表示子空间)”中。
在这里插入图片描述
通过multi-headed attention,我们为每个“header”都独立维护一套Q/K/V的权值矩阵。然后我们还是如之前单词级别的计算过程一样处理这些数据。

如果对上面的例子做同样的self attention计算,而因为我们有8头attention,所以我们会在八个时间点去计算这些不同的权值矩阵,但最后结束时,我们会得到8个不同的Z矩阵
我们知道在self-attention后面紧跟着的是前馈神经网络,而前馈神经网络接受的是单个矩阵向量,而不是8个矩阵。所以我们需要一种办法,把这8个矩阵压缩成一个矩阵。

我们将这8个矩阵连接在一起然后再与一个矩阵W0相乘。步骤如下图所示:
在这里插入图片描述
整体流程如下:
输入句子 – 词嵌入 – 分8部分分别乘以相应权重矩阵计算Q/K/V值得到z值 – 合并Z乘权重W输出
在这里插入图片描述

添加位置编码

对于输入序列中单词顺序的考虑还是很有必要的,于是我们可以引入位置编码向量;
transformer为每个输入单词的词嵌入上添加了一个新向量-位置向量。

为了解决这个问题,变换器为每个输入嵌入添加了一个向量。这些位置编码向量有固定的生成方式,所以获取他们是很方便的,但是这些信息确是很有用的,他们能捕捉大量每个单词的位置,或者序列中不同单词之间的距离。将这些信息也添加到词嵌入中,然后与Q/K/V向量点击,获得的attention就有了距离的信息了
在这里插入图片描述
位置编码向量不需要训练,它有一个规则的产生方式。如果我们的嵌入维度为4,那么实际上的位置编码就如下图所示:
在这里插入图片描述
在这里插入图片描述

Decoder

当序列输入时,Encoder开始工作,最后在其顶层的Encoder输出矢量组成的列表,然后我们将其转化为一组attention的集合(K,V)。(K,V)将带入每个Decoder的“encoder-decoder attention”层中去计算(这样有助于decoder捕获输入序列的位置信息)
在这里插入图片描述
对于Decoder,和Encoder一样,我们在每个Decoder的输入做词嵌入并添加上表示每个字位置的位置编码;在Decoder中,self attention只关注输出序列中的较早的位置。这是在self attention计算中的softmax步骤之前屏蔽了特征位置(设置为 -inf)来完成的。

(注意这里的理解,对于decoder的输入不仅仅包含encoder的输出,同时还包含先前的decoder输出:比如翻译‘你好呀’,已经翻译输出了‘你好’,在decoder‘呀’时,也要输入‘你好’的相关信息)

最后的Linear和softmax层

Decoder的输出是浮点数的向量列表。我们是如何将其变成一个单词的呢?这就是最终的线性层和softmax层所做的工作。

线性层是一个简单的全连接神经网络,它是由Decoder堆栈产生的向量投影到一个更大的向量中,称为对数向量

假设实验中我们的模型从训练数据集上总共学习到1万个英语单词(“Output Vocabulary”)。这对应的Logits矢量也有1万个长度-每一段表示了一个唯一单词的得分。在线性层之后是一个softmax层,softmax将这些分数转换为概率。选取概率最高的索引,然后通过这个索引找到对应的单词作为输出。
在这里插入图片描述

训练集学习到的词汇表,对应于预测词am的one-hot表示如下:在这里插入图片描述

loss function

假设我们正在训练一个模型,比如将“merci”翻译成“谢谢”。这意味着我们希望模型计算后的输出为“谢谢”,但由于这种模式还没有接受过训练,所以这种情况不太可能发生。
在这里插入图片描述
这是因为模型的参数(权重)都是随机初始化的,因此(未经训练的)模型对每个单词产生的概率分布是具有无限可能的,但是我们可以通过其实际我们期望的输出进行比较,然后利用反向传播调整所有模型的权重,使得输出更接近所需的输出。

那么如何比较算法预测值与真实期望值呢?

实际上,我们对其做一个简单的减法即可。你也可以了解交叉熵和Kullback-Leibler散度来掌握这种差值的判断方式。

但是,需要注意的是,这只是一个很简单的demo,真实情况下,我们需要输出一个更长的句子,例如。输入:“je suis étudiant”和预期输出:“I am a student”。这样的句子输入,意味着我们的模型能够连续的输出概率分布。其中:

1.每个概率分布由宽度为vocab_size的向量表示(在我们的示例中vocab_size为6,但实际上可能为3,000或10,000维度)
2.第一概率分布在与单词“i”相关联的单元处具有最高概率
3.第二概率分布在与单词“am”相关联的单元格中具有最高概率
4.依此类推,直到第五个输出分布表示’ '符号,意味着预测结束。

现在,因为模型一次生成一个输出,我们可以理解为这个模型从该概率分布(softmax)矢量中选择了具有最高概率的单词并丢弃了其余的单词。

这里有2个方法:一个是贪婪算法(greedy decoding),一个是波束搜索(beam search)。

基于pytorch部分实现:

class EncoderLayer(nn.Module):
    "Encoder is made up of self-attn and feed forward (defined below)"
    def __init__(self, size, self_attn, feed_forward, dropout):
        super(EncoderLayer, self).__init__()
        self.self_attn = self_attn
        self.feed_forward = feed_forward
        self.sublayer = clones(SublayerConnection(size, dropout), 2)
        self.size = size

    def forward(self, x, mask):
        "Follow Figure 1 (left) for connections."
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))
        return self.sublayer[1](x, self.feed_forward)

##Each layer has two sub-layers. The first is a multi-head self-attention mechanism, 
##and the second is a simple, position-wise fully connected feed- forward network.

class SublayerConnection(nn.Module):
    """
    A residual connection followed by a layer norm.
    Note for code simplicity the norm is first as opposed to last.
    """
    def __init__(self, size, dropout):
        super(SublayerConnection, self).__init__()
        self.norm = LayerNorm(size)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x, sublayer):
        "Apply residual connection to any sublayer with the same size."
        return x + self.dropout(sublayer(self.norm(x)))

##In addition to the two sub-layers in each encoder layer, the decoder inserts a third sub-layer, 
##which performs multi-head attention over the output of the encoder stack. Similar to the encoder, 
##we employ residual connections around each of the sub-layers, followed by layer normalization.

class DecoderLayer(nn.Module):
    "Decoder is made of self-attn, src-attn, and feed forward (defined below)"
    def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
        super(DecoderLayer, self).__init__()
        self.size = size
        self.self_attn = self_attn
        self.src_attn = src_attn
        self.feed_forward = feed_forward
        self.sublayer = clones(SublayerConnection(size, dropout), 3)
 
    def forward(self, x, memory, src_mask, tgt_mask):
        "Follow Figure 1 (right) for connections."
        m = memory
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
        x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
        return self.sublayer[2](x, self.feed_forward)
       
##we compute the attention function on a set of queries simultaneously, packed together into a matrix Q. 
##The keys and values are also packed together into matrices K and V. 

def attention(query, key, value, mask=None, dropout=None):
    "Compute 'Scaled Dot Product Attention'"
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-2, -1)) \
             / math.sqrt(d_k)
    if mask is not None:
        scores = scores.masked_fill(mask == 0, -1e9)
    p_attn = F.softmax(scores, dim = -1)
    if dropout is not None:
        p_attn = dropout(p_attn)
    return torch.matmul(p_attn, value), p_attn
       
 ##In addition to attention sub-layers, each of the layers in our encoder and decoder contains a fully connected feed-forward network, 
 ##which is applied to each position separately and identically.
 ## This consists of two linear transformations with a ReLU activation in between.

class PositionwiseFeedForward(nn.Module):
    "Implements FFN equation."
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionwiseFeedForward, self).__init__()
        self.w_1 = nn.Linear(d_model, d_ff)
        self.w_2 = nn.Linear(d_ff, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        return self.w_2(self.dropout(F.relu(self.w_1(x))))

##Since our model contains no recurrence and no convolution, in order for the model to make use of the order of the sequence, 
##we must inject some information about the relative or absolute position of the tokens in the sequence. To this end, 
##we add “positional encodings” to the input embeddings at the bottoms of the encoder and decoder stacks. 

class PositionalEncoding(nn.Module):
    "Implement the PE function."
    def __init__(self, d_model, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        
        # Compute the positional encodings once in log space.
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2) *
                             -(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)
        self.register_buffer('pe', pe)
        
    def forward(self, x):
        x = x + Variable(self.pe[:, :x.size(1)], 
                         requires_grad=False)
        return self.dropout(x)
  • 6
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Transformer是一种用于自然语言处理的模型,它在Google的论文"Attention is All You Need"中被提出。为了方便实现和调用Transformer,Google开源了一个基于TensorFlow的第三方库Tensor2Tensor。此外,也有研究者贡献了一个基于PyTorch的Transformer实现。\[2\] Transformer的网络结构包括编码器(Encoder)和解码器(Decoder)两部分。编码器负责将输入序列转换为一系列的隐藏表示,而解码器则根据编码器的输出和之前的预测结果生成目标序列。具体来说,Transformer由多个编码器层和解码器层组成,每个层都包含自注意力机制和前馈神经网络。\[3\] 在实践中,我们可以使用一些工具代码来帮助实现Transformer模型。例如,我们可以使用深拷贝来复制模块,使用layernorm计算来进行归一化操作,使用注意力机制相关的代码来计算自注意力和多头注意力,使用全连接网络来进行特征映射,使用mask来处理解码器的输出,以及使用embedding计算概率等。\[3\] 如果你对Transformer实践感兴趣,你可以参考一些相关的链接和文献,如Illustrated Transformer网站、CSDN博客和Harvard大学的Annotated Transformer项目。此外,你还可以在GitHub上找到完整的代码和测试用例。\[3\] #### 引用[.reference_title] - *1* [Transformer(四)--实现验证:transformer 机器翻译实践](https://blog.csdn.net/nocml/article/details/125711025)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Transformer原理实践详解](https://blog.csdn.net/Enjoy_endless/article/details/88344750)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [transformer原理及代码实践](https://blog.csdn.net/THUChina/article/details/123441732)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值