Transformer学习笔记

        Transformer 这一概念是在论文Attention is All You Need 中提出,感兴趣的可以通过链接阅读原文。这篇文章主要讲讲我对Transformer这个模型学习的理解。

什么是Transformer?

        Transformer可以理解为一个黑盒,我们将一段序列输入模型,经过Transformer之后,输出另一段序列,这个序列可以是翻译,可以是摘要。而Transformer最重要的创新在于使用注意力机制,注意力机制能够关注输入序列中感兴趣的部分,并分配一个较大的概率给该部分。这部分是怎么完成的呢?其实就是由多个encoder-decoder对输入的序列进行编解码,我们可以看一下简化的encoder-decoder长什么样。

图1.encoder-decoder组件

         encoders部分其实是由6个encode组成(原论文中设置为6),这个设置其实可以有别的尝试,现在应该是有了,因为论文阅读的还没那么快,最新的研究还需要花点时间,后续慢慢补上。当然decoders的数量设置和encoders相同。对encoder-decoder组件展开后我们可以看到下图。

图2.encoder-decoder组件展开图 

        encoder在结构上都是相同的,但它们并不共享权重。每一层又分为两个子层:

图3.transformer的encoder

         encoder的输入首先流经self-attention——该层帮助encoder在编码特定单词时可查看输入句子中的其他单词,也就是上面提到的分配概率。self-attention在稍后的部分进行讲解,这里先把当前模块讲清楚

        self-attention层的输出送到前馈神经网络。decoder同样具有这两层,但它们之间是还多了一个注意力层,帮助decoder专注于输入句子的相关部分,这里考虑了输入的时序。

 图4.transformer的decoder

Transformer是如何对输入进行编码的?

        现在让我们开始看看各种向量/张量,它们是如何在这些组件之间流动,将训练模型的输入转换为输出。在NLP的应用场景中,输入是一个句子或单词,我们首先使用embedding算法将每个输入词转换为一个向量。每个单词都嵌入到一个大小为高维的向量中(通常长度为512),这个向量的大小是我们可以设置的超参数,基本上它是我们训练数据集中最长句子的长度。

        在这里我用简单的向量进行表示,如下图。

  图5.embedding的向量

        这里要注意一下,embedding仅仅发生在最开始输入的地方,也就是第一个encoder之前,此时会将位置编码一同嵌入,这个后面说。在其他encoder中,它的输入将是位于其下方的encoder的输出。

        输入经过embedding的单词后,它们中的每一个元素都会流经encoder的两层中的每一层。

 图6.向量的输入

        图6中,可以看到,每个向量输入encoder时都有其各自的路径,在self-attention 层中这些路径之间存在依赖关系。而前馈神经网络层中并没有这种位置关系。

向量进入encoder之后是如何进行处理的?

        encoder接收向量作为输入后,它通过将这些向量传递到一个self-attention层,然后传递到一个前馈神经网络,然后将输出向上发送到下一个encoder来处理这个向量。

图7.向量的处理路径

        每个位置的单词都经过一个自注意力过程。然后,它们每个都通过一个前馈神经网络(完全相同)。 接下来看看它实际是怎么实现的。

        计算self-attention的第一步是从encoder的每个输入向量(这里的两个单词)创建三个可学习的向量。因此,对于每个单词,我们创建一个 Query 向量、一个 Key 向量和一个 Value 向量。这些向量是通过可训练的三个矩阵来创建的。

图8.向量的创建过程

         将编码后的x1 乘以 WQ 权重矩阵会得到q1,即与该词关联的Query向量。通过将x1与剩下的WK,WV矩阵相乘,这样就为输入的句子中的每个单词创建了一个Query、一个Key 和一个Value 的映射。注意力的概率就是由以上三个向量得来。

注意力是怎么计算的?

        计算self-attention的主要是计算一个score,假设计算图8中第一个单词“Thinking”的自注意力。我们需要根据这个词对输入句子的每个词进行评分(分配概率),当我们在某个位置对单词进行编码时,分数决定了将多少注意力分配到输入句子的其他部分上。

        这个分数的计算方法是将Query向量与我们正在评分的各个单词的Key向量进行点积(dot product),所以,对于图8,我们正在处理位置 #1 中单词(Thinking)的自注意力,第一个分数将是 q1 和 k1 的点积,第二个分数是 q1 和 k2 的点积。

       图9

        接下来将得到的分数除以8, 为什么是8呢?论文中使用的key向量维度的平方根 (64),这能够产生更具稳定性的梯度,(这里可能还有其他可能的值)。

        然后通过 softmax 层操作得到结果, Softmax 将分数进行归一化,因此它们都是正数并且加之后的和为1。经过softmax之后的这个值就是为这个单词分配的注意力,Thinkin这个词具有最高的 softmax 分数,但是,有时关注与当前词相关的另一个词是有用的。

        接下来就是将softmax得到的值与每个value向量相乘,这里的操作就是保持我们感兴趣的这个词的关注度不变,并降低其他词的关注度。之后对加权后的value向量求和,这里(对于第一个词thinking)会产生self-attention层的输出。

图10

        关于self-attention的计算到这里就结束了,最后将加权后的value(z)向量输入到前馈神经网络中。为了加速计算,上述的过程都是以矩阵进行的,接下来看看他是怎么操作的。

图11.self-attendtion的矩阵计算 

         首先计算Query, Key, 和Value矩阵,将embedding之后的向量,转换到X 中,并将其乘以可训练的权重矩阵(WQ、WK、WV)。X 矩阵中的每一行对应于输入句子中的一个词,实际的长度为512。将图11的过程描述为公式后,可以看下图。

图12

         实际上,论文中的self-attention应用了多头注意力机制,接下来来聊聊什么是多头注意力机制(multi-headed attention)。

什么是多头注意力机制?

多头注意力机制从两个方面提升了模型表现:

1、扩展了模型专注于不同位置的能力

2、它为注意力层提供了多个“representation subspaces”。

        对于多头注意力,模型可以产生有多组Query, Key, 和Value权重矩阵(Transformer使用八个注意力头,所以我们最终为每个encoder-decoder提供了八组)。每一个WQ、WK、WV都是随机初始化的。每个矩阵用于将输入(或来自较前一层encoder-decoder的输出向量)投影到不同的表示子空间中。

图13 

        通过multi-headed attention,我们为每个头维护单独的 Q/K/V 权重矩阵,从而产生不同的 Q/K/V 矩阵。正如图12中的那样,我们将 X 乘以 WQ/WK/WV 矩阵以生成 Q/K/V 矩阵。最后输出的z矩阵使用不同的权重矩阵进行 8 次不同的计算。

 图14

        那么这里还有一个问题,前馈神经网络的权重不需要八个矩阵,它只需要一个矩阵(每个单词一个向量),所以就需要一种方法将这八个压缩成一个矩阵。那么怎么做呢?只需要将矩阵连接起来,然后通过一个额外的权重矩阵 WO 将它们相乘。

 图15

        以上差不多就是多头自注意力的全部内容。让我尝试将它们全部放在一个图中:

图16 多头注意力机制 

        值得注意的是,只有在z0的位置上需要进行embedding操作,其他位置的输入不需要进行embedding。

什么是位置编码?

        模型中缺少了一个非常重要的信息,即单词顺序的表示方法。transformer 为每个输入的embedding添加了位置向量。这些向量遵循模型学习的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。一旦它们被投影到 Q/K/V 向量中,并且在点积注意力的过程中,这些值添加到embedding向量中,就会在embedding向量之间提供有意义的距离信息。

 图17

        为了让模型了解单词的顺序,在这里添加了位置编码向量,他的设置遵行特定的模式:

         这一模式看起来像什么呢?我们可以用下图进行表示:

图18 

        图18是一个20个词的位置编码向量,其中的每行对应一个向量的位置编码,因此,第一行就是我们添加到输入序列中第一个词的位置编码向量。每行包含 512 个值,每个值都在 1 到 -1 之间。我们可以清楚的看到图18中中间部分的分割,这是因为左半部分的值由一个函数(正弦)生成,右半部分由另一个函数(余弦)生成,即上述公式。然后将它们进行concatenated操作形成每个位置编码向量。

残差组件

        每个encoder中的每个子层(self-attention,ffnn)都有一个残差连接,再进行LN( layer-normalization)。

图19 

        如果我们要可视化与自注意力相关的向量和层操作,它看起来会是这样:

图20 

        当然这也适用于decoder,假设考虑一个由 2 个encoder-decoder组成的 Transformer,那么它会是这样的:

图21 

        到这里encoder的部分差不多都讲完了。接下来是decoder的部分。

什么是decoder?

        encoder首先处理输入序列,然后将最后一层encoder的输出转换为一组注意力向量 K 和 V,他们将输入decoder中的encoder-decoder attention(cross attention)层。解码阶段的每一步都从输出序列中输出一个元素。下图可以直观的展示:

       图22

        在decoder中有一个mask的处理,在mask self attention中的计算和encoder中的一样,但是仅仅关注输入向量本身以及其左边的向量。值得注意的是decoder的输入中有两个特殊的符号,一个表示开始,一个表示结束。,假设我们将thinking machine翻译成西班牙语Máquina de pensar。

        首先输入start token(ST表示),经过embedding之后进入mask self attention,如下图所示:

图23       

         接下来输入Máquina,Máquina处输出的向量由ST和它自身决定,如下图:

 图24

        之后的de,pensar部分,以及eos与上述操作一样,下图可以直观展示:

 图25

        那么其中的矩阵计算是怎么样的呢?其实和encoder相同,只是后一个输入仅仅与自身以及其之前的输入有关,具体怎么样操作的呢?我们可以看下图:

图26 

        当输入ST时编码后的向量与WQ,WK,WV相乘得到q,k,v向量,q1与k1相乘后经过softmax得到一个分数,v1与该得分相乘,就是mask self attention在第一个位置上的输出。第二个位置上的输出为,第二个位置上得到的q2与前一位置的k1相乘,之后得到加权的输出,再加上q2乘以k2得到的加权输出相加,如下图所示:

 图27

        之后的步骤重复该过程,直到达到一个特殊符号(结束符),表明transformer的decoder已完成其输出。就像在encoder中对输入所做的一样,我们将位置编码embedding并添加到这些decoder的输入中,表示每个单词的位置。如下图所示:

 图28

decoder中的self attention层与encoder中的self attention层的计算方式略有不同:

1、在decoder中,self attention层只允许关注输出序列中较早的位置。通过在 self-attention 计算中的 softmax 步骤之前屏蔽未来位置(将它们设置为 -inf),由mask multi-headed attention来完成的。

2、Encoder-Decoder Attention层就像multi-headed attention层一样,除了它从它下面的层创建它的查询矩阵,并从encoder的输出中获取key和value矩阵。

图29

Encoder-Decoder Attention的怎么处理数据的?       

        这里就直接上图吧。

图30

        Encoder-Decoder Attention也叫cross attention输入的q由self attention(mask)的输入产生,并与encoder的输出生成的k1与k2相乘,之后再与encoder输出的v1和v2做加权和,得到的矩阵输入到FC层中。注意,这里还有残差结构,图中没有画出来。

        最后的输出层就是一个普通的Linear layer加一个softmax,得到当前词的置信度。

        还有一个需要注意的点,就是decoder的输出会作为下一次的输入,如下图:

图31 

        这里假设decoder在第一个输入产生了错误结果,很有可能就会导致接下来的输出结果都是错的。

        因为sequence的预测,是根据当前已预测出来的sequence来预测下一个词,所以不能把未来的信息给考虑进来,但训练时会将正确的句子信息输入decoder,那么实际mask怎么操作的呢?

        首先对输入的句子做词嵌入映射,得到词嵌入矩阵,之后与encoder一样加入位置编码(PE),输入self attention层计算softmax(Q*KT/d),d表示dk的平方根,Q*KT/d得到的矩阵通过Mask矩阵将未来的信息抹掉,Mask矩阵是一个下三角矩阵,如下图:

 图32

        其中黄色部分的值为1,紫色部分的值为0,在第0时刻(第0行),只有0时刻的词自己与自己的相似度,第1时刻,有0时刻和1时刻的相似度。然后通过第0时刻的输出更新第1时刻的输入,即通过softmax输出第0时刻的概率分布,将得到的结果补充进下一步的输入(即调整一下mask矩阵),直到<eos>。

示例代码可以参考:理解transformer源码_athrunsunny的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

athrunsunny

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

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

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

打赏作者

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

抵扣说明:

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

余额充值