抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Dekel'Blog

奔赴山海,保持热爱

编码器层(Encoder Layer)

编码器层的输入首先进入自注意力子层(Self-Attention),该子层的作用在于帮助编码器关注句子中的其他词汇,以便更好地编码某个特定词汇。

随后,自注意力子层的输出将传递给一个前馈神经网络(Feed-Forward Neural Network)。结构完全相同的前馈网络被独立地应用于每个位置。

img

输入输出对理解数据流非常重要。编码器层的输入形状为 S x D(请参见下面的图表),其中 S 是源句子长度(例如,英语句子),而 D 是嵌入的维度(也是模型维度,论文中取值为 512)。

编码器的输入和输出形状相同。由于编码器层是相互叠加的,因此,我们希望其输出具有与输入相同的维度,以便它可以轻松地流入下一个编码器层。因此,输出也是 S x D 形状。

img

解码器层(Decoder Layer)

解码器层(decoder layer)也包含前面编码器中提到的两个层,不过区别在于这两个层之间还夹了一个注意力层(Encoder-Decoder Attention)。这个额外的注意力层的作用在于让解码器能够注意到输入句子中与解码任务相关的部分。

img

在一个已经训练好的Transformer模型中,输入是怎么变为输出的呢?首先我们要知道各种各样的张量(向量)是如何在这些组件之间变化的。

与其他的NLP项目一样,我们首先需要把输入的每个单词通过词嵌入(embedding)转化为对应的向量。

img

所有编码器层接收一组向量作为输入(论文中的输入向量的维度是512)。最底下的那个编码器层接收的是嵌入向量,之后的编码器层接收的是前一个编码器层的输出。

向量列表的长度这个超参数是我们可以设置的,一般来说是我们训练集中最长的那个句子的长度。

当我们的输入序列经过词嵌入之后得到的向量会依次通过编码器层中的两个层。

img

注意力机制(Attention)

注意力机制是论文的核心,它在编码器和解码器部分的处理稍有差异。让我们先以编码器部分的注意力层机制为例进行介绍。

上边提到,每个编码器层接受一组向量作为输入。在其内部,输入向量先通过一个自注意力层,再经过一个前馈神经网络层,最后将其将输出给下一个编码器层。

img

不同位置上的单词都要经过自注意力层的处理,之后都会经过一个完全相同的前馈神经网络。

在这里,我们开始看到 Transformer 的一个关键特点,即每个位置上的单词在编码器层中有各自的流通方向。

  1. 在自注意力层中,这些路径之间存在依赖关系。单词和单词之间会有关联,假设一个句子有 50 个单词,那么可以粗略想象成自注意力计算过程中,会构造一个 50 x 50 的关联矩阵。
  2. 前馈神经网络(Feed Forward)层中没有这些依赖关系。每个单词独立通过前馈神经网络,单词和单词之间没有关联,因此各种路径可以在流过前馈网络层的时候并行计算。

自注意力(Self-Attention)

现在让我们看一下自注意力机制。

假设我们要翻译下边这句话:
”The animal didn't cross the street because it was too tired”

这里it指的是什么?是street还是animal?人理解起来很容易,但是对算法来讲就不那么容易了。

当模型处理it这个词的时候,自注意力会让itanimal关联起来。

当模型编码每个位置上的单词的时候,自注意力的作用就是:看一看输入句子中其他位置的单词,试图寻找一种对当前单词更好的编码方式。

如果熟悉 RNNs 模型,回想一下 RNN 如何处理当前时间步的隐藏状态:将之前的隐藏状态与当前位置的输入结合起来。在 Transformer 中,自注意力机制也可以将其他相关单词的“理解”融入到我们当前处理的单词中。

img

当我们在最后一个encoder组建中对it进行编码的时候,注意力机制会更关注The animal,并将其融入到it的编码中。

自注意力的计算(单个)

先画图用向量解释一下自注意力是怎么算的,之后再看一下实际实现中是怎么用矩阵算的。

第一步 对于编码器的每个输入向量x,都会计算三个向量,即query、key和value向量。

这些向量的计算方法是将输入的词嵌入向量与三个权重矩阵相乘。这些权重矩阵是在模型训练阶段通过训练得到的。

什么是 “query”、“key”、“value” 向量?这三个向量是计算注意力时的抽象概念,请继续往下看注意力计算过程。

第二步 计算注意力得分。

假设我们现在在计算输入中第一个单词 Thinking 的自注意力。我们需要使用自注意力给输入句子中的每个单词打分,这个分数决定当我们编码某个位置的单词的时候,应该对其他位置上的单词给予多少关注度。

这个得分是query和key的点乘积得出来的。例如,如果我们处理位置#1的单词的自我注意,第一个分数将是q1和k1的点积。第二个分数是q1和k2的点积。(备注:在使用矩阵处理时,是用 Q 和 K 的转置相乘得到,详见后)。

img

第三步 将计算获得的注意力分数除以 8。

为什么选 8?是因为key向量的维度是 64,取其平方根,这样让梯度计算的时候更稳定。默认是这么设置的,当然也可以用其他值。

img

第四步 除 8 之后将结果扔进 softmax 计算,使结果归一化,softmax 之后注意力分数相加等于 1,并且都是正数。

这个 softmax 之后的注意力分数表示 在计算当前位置的时候,其他单词受到的关注度的大小。显然在当前位置的单词肯定有一个高分,但是有时候也会注意到与当前单词相关的其他词汇。

第五步 将每个 value 向量乘以注意力分数。这是为了强化我们想要关注的单词的 value,并尽量抑制其他不相关的单词(通过乘以一个接近于零的数,如 0.001)。这个过程被称为“缩放”或者“加权”,可以使得我们更加关注与目标单词相关的单词。

第六步 将上一步的结果相加,输出本位置的注意力结果。

img

这就是自注意力的计算。计算得到的向量直接传递给前馈神经网络。但是为了处理的更迅速,实际是用矩阵进行计算的。接下来我们看一下怎么用矩阵计算。

自注意力的计算(矩阵)

第一步是计算 Query、Key 和 Value 矩阵。我们通过将嵌入打包到矩阵 X 中,并将其乘以我们训练的权重矩阵来实现这一点。

img

​ X矩阵中的每一行对应于输入句子中的一个单词。
​可再次看到嵌入向量维度(512,图中的 4 个框)和 q/k/v 向量维度(64,图中的 3 个框)的差异

最后,由于我们使用矩阵计算,因此可以将步骤 2 到 6 合并为一个公式,以计算自注意力层的输出。

img

通过将输入向量 x 与注意力头的权重矩阵相乘,可以得到对应的 query、key 和 value 向量。单个头获取的这三个向量维度是64,比嵌入向量的维度小,8个头的输出连接后变为 512。因此嵌入向量、编码器层的输入输出维度都是512。

img

对于上图的解释:

  1. 假定输入的英文句子是“The quick brown fox“,句子长度 S 为4,参考“编码器层”章节的解释,注意力子层的输入形状为(4 x 512)。
  2. 自注意力层使用三个权重矩阵进行初始化——Query(Wq)、Key(Wk)和Value(Wv)。这些权重矩阵的尺寸都是 D x d,在论文中d取值为64,即权重矩阵的尺寸为 512 x 64。在训练模型时,我们将训练这些矩阵的权重。
  3. 在第一次计算(图中的Calc 1)中,我们通过将输入(注意:代码实现中是三个不同的输入,编码器层都是X,解码器层不同,见代码中的解释)与各自的Query、Key和Value权重矩阵相乘,计算出Q、K和V矩阵(尺寸为 S x d,示例中为 4 x 64)。
  4. 在第二次计算中,参考Attention计算公式,首先将Q和Kᵀ矩阵相乘,得到一个尺寸为 S x S(示例中为 4 x 4)的矩阵,然后将其除以√d的标量。然后对矩阵进行softmax运算,使得每一行的和都为1。这个矩阵可以理解为句子中每个词之间的关联度。
  5. 上面 S x S 的矩阵再和V矩阵相乘,得到尺寸为 S x d(示例中为 4 x 64)的矩阵。经过后续的连接操作后,传入下一层。

多头注意力

论文进一步改进了自注意力层,增加了一个机制,也就是多头注意力机制。这样做有两个好处:

第一个好处,它扩展了模型专注于不同位置的能力。

在上面例子里只计算一个自注意力的的例子中,编码“Thinking”的时候,虽然最后 Z1 或多或少包含了其他位置单词的信息,但是它实际编码中还是被“Thinking”单词本身所支配。

如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时模型的“多头”注意力机制会起到作用。

第二个好处,它给了注意层多个“表示子空间”。

就是在多头注意力中同时用多个不同的 WV*W**V* 权重矩阵(Transformer 使用8个头部,因此我们最终会得到8个计算结果),每个权重都是随机初始化的。经过训练每个 WV*W**V* 都能将输入的矩阵投影到不同的表示子空间。

img

如果我们做和上面相同的自注意力计算,只不过八次使用不同的权重矩阵,我们最后得到八个不同的Z矩阵。

img

但是这会存在一点问题,多头注意力出来的结果会进入一个前馈神经网络,这个前馈神经网络可不能一下接收8个注意力矩阵,它的输入需要是单个矩阵(矩阵中每个行向量对应一个单词),所以我们需要一种方法把这8个压缩成一个矩阵。

怎么做呢?我们将这些矩阵连接起来,然后将乘以一个附加的权重矩阵

img

以上就是多头自注意力的全部内容。让我们把多头注意力上述内容 放到一张图里看一下子:

img

现在我们已经看过什么是多头注意力了,让我们回顾一下之前的一个例子,再看一下编码“it”的时候每个头的关注点都在哪里:

img

如果我们把所有的头的注意力都可视化一下,就是下图这样,但是看起来事情好像突然又复杂了。

img

编码器(Encoder)

使用位置编码表示序列的位置

到现在我们还没提到过如何表示输入序列中词汇的位置。

Transformer 在每个输入的嵌入向量中添加了位置向量。这些位置向量遵循某些特定的模式,这有助于模型确定每个单词的位置或不同单词之间的距离。将这些值添加到嵌入矩阵中,一旦它们被投射到Q、K、V中,就可以在计算点积注意力时提供有意义的距离信息。

img

位置编码向量和嵌入向量的维度是一样的,比如下边都是四个格子:

img

举个例子,当嵌入向量的长度为4的时候,位置编码长度也是4

一直说位置向量遵循某个模式,这个模式到底是什么。

参考论文:Convolutional Sequence to Sequence Learning 论文 arXiv:1705.03122 的 Semantic Scholar 引用数

在下面的图中,每一行对应一个位置编码。所以第一行就是我们输入序列中第一个单词的位置编码,之后我们要把它加到词嵌入向量上。

看个可视化的图,这里表示的是一个句子有20个词,词嵌入向量的长度为512。可以看到图像从中间一分为二,因为左半部分是由正弦函数生成的。右半部分由余弦函数生成。然后将它们二者拼接起来,形成了每个位置的位置编码。

img

但是需要注意注意一点,上图的可视化是官方Tensor2Tensor库中的实现方法,将sin和cos拼接起来。但是和论文原文写的不一样,论文原文的3.5节写了位置编码的公式,论文不是将两个函数concat起来,而是将sin和cos交替使用。论文中公式的写法可以看这个代码:transformer_positional_encoding_graph,其可视化结果如下:

img

全连接的前馈网络(Feed-Forward Networks)

除了注意力子层外,我们的编码器和解码器中的每一层都包含一个全连接的前馈网络,该网络被单独且相同地应用于每个位置。这包括两个线性变换,它们之间有ReLU激活函数。

虽然FFN的网络架构在各个位置上都是相同的,但它们在每个位置使用的是不同的权重参数。这可能就是论文作者为了强调这个,加上PositionWise的原因。

另一种描述方法是,这是两个具有1内核大小的卷积。输入和输出的维度是dmodel=512,而内部层的维度是 dff=2048。

子层之间的连接(残差和层归一化)

原始论文

在继续往下讲之前,我们还需再提一下编码器层中的一个细节:每个编码器层中的每个子层(自注意力层、前馈神经网络)都有一个残差连接(图中的Add),之后是做了一个层归一化(layer-normalization)(图中的Normalize)。

img

将过程中的向量相加和layer-norm可视化如下所示:

img

当然在解码器子层中也是这样的。

我们现在画一个有两个编码器和解码器的Transformer,那就是下图这样的:

img

解码器(Decoder)

现在我们已经介绍了编码器的大部分概念,因为Encoder的Decoder差不多,我们基本上也知道了解码器是如何工作的。那让我们直接看看二者是如何协同工作的。

解码器首先处理输入序列,将最后一个编码器层的输出转换为一组注意向量K和V。注意:参考实现中为直接用,见EncoderDecoder.forward,DecoderLayer.forward。

每个解码器层将在“encoder-decoder attention”层中使用编码器传过来的K和V,这有助于解码器将注意力集中在输入序列中的适当位置:

img

输出步骤会一直重复,直到遇到句子结束符 表明transformer的解码器已完成输出。

每一步的输出都会在下一个时间步喂给给底部解码器,解码器会像编码器一样运算并输出结果(每次往外蹦一个词)。

跟编码器一样,在解码器中我们也为其添加位置编码,以指示每个单词的位置。

img

解码器中的自注意力层和编码器中的不太一样: 在解码器中,自注意力层只允许关注已输出位置的信息。实现方法是在自注意力层的softmax之前进行mask,将未输出位置的信息设为极小值。

“encoder-decoder attention”层的工作原理和前边的多头自注意力差不多,但是Q、K、V的来源不用,Q是从下层创建的(比如解码器的输入和下层decoder组件的输出),但是其K和V是来自编码器最后一个组件的输出结果。

最后的线性层和softmax层

Decoder输出的是一个浮点型向量(512维),如何把它变成一个词?

这就是最后一个线性层和softmax要做的事情。

线性层就是一个简单的全连接神经网络,它将解码器生成的向量映射到logits向量中。假设我们的模型词汇表是10000个英语单词,它们是从训练数据集中学习的。那logits向量维数也是10000,每一维对应一个单词的分数。

然后,softmax层将这些分数转化为概率(全部为正值,加起来等于1.0),选择其中概率最大的位置的词汇作为当前时间步的输出。

img

评论

看完了不如留下点什么吧