其他分享
首页 > 其他分享> > Transformer模块初探

Transformer模块初探

作者:互联网

Transformer笔记

前言背景

	Transformer 依赖于 Self Attention 的知识。Attention 是一种在深度学习中广泛使用的方法,Attention的思想提升了机器翻译的效果。

​ 2017 年,Google 提出了 Transformer 模型,用 Self Attention 的结构,取代了以往 NLP 任务中的 RNN 网络结构,在 WMT 2014 Englishto-GermanWMT 2014 English-to-French两个机器翻译任务上都取得了当时 SOTA 的效果。这个模型的其中一个优点,就是使得模型训练过程能够并行计算。在 RNN 中,每一个 time step 的计算都依赖于上一个 time step 的输出,这就使得所有的 time step 必须串行化,无法并行计算,如下图所示。

RNN

​ 而在 Transformer 中,所有 time step 的数据,都是经过 Self Attention 计算,使得整个运算过程可以并行化计算。

​ Transformer 使用了 Seq2Seq任务中常用的结构——包括两个部分:EncoderDecoder。一般的结构图,都是像下面这样。

Transformer内部剖析

​ 首先,我们将整个模型视为黑盒。在机器翻译任务中,接收一种语言的句子作为输入,然后将其翻译成其他语言输出。

​ 中间部分的 Transformer 可以拆分为 2 部分:左边是编码部分(encoding component),右边是解码部分(decoding component)。

​ 其中编码部分是多层的编码器(Encoder)组成(Transformer 的论文中使用了 6 层编码器,这里的层数 6 并不是固定的,你也可以根据实验效果来修改层数)。同理,解码部分也是由多层的解码器(Decoder)组成(论文里也使用了 6 层的解码器)。

注意:每一个编码器在结构上都是一样的,但它们的权重参数是不同的

​ 每一个编码器里面,可以分为 2 层:

执行流程

​ 输入编码器的文本数据,首先会经过一个 Self Attention 层,这个层处理一个词的时候,不仅会使用这个词本身的信息,也会使用句子中其他词的信息(你可以类比为:当我们翻译一个词的时候,不仅会只关注当前的词,也会关注这个词的上下文的其他词的信息)。

​ 接下来,Self Attention 层的输出会经过前馈神经网络。

​ 同理,解码器也具有这两层,但是这两层中间还插入了一个 Encoder-Decoder Attention 层,这个层能帮助解码器聚焦于输入句子的相关部分(类似于 seq2seq 模型 中的 Attention)。

Transformer细节

Transformer输入

​ 和通常的 NLP 任务一样,我们首先会使用词嵌入算法(embedding algorithm),将每个词转换为一个词向量。实际中向量一般是 256 或者 512 维。为了简化起见,这里将每个词的转换为一个 4 维的词向量

​ 那么整个输入的句子是一个向量列表,其中有 3 个词向量。在实际中,每个句子的长度不一样,我们会取一个适当的值,作为向量列表的长度。

​ 编码器(Encoder)接收的输入都是一个向量列表,输出也是大小同样的向量列表,然后接着输入下一个编码器。第一个编码器的输入是词向量,而后面的编码器的输入是上一个编码器的输出。下面,我们来看这个向量列表在编码器里面是如何流动的。

​ 在 Self Attention 层中,这些路径之间是有依赖关系的;而在 Feed Forward (前馈神经网络)层中,这些路径之间是没有依赖关系的。另外,这些词向量在经过 Feed Forward 层中可以并行计算

Encoder

​ 一个编码器接收的输入是一个向量列表,它会把向量列表输入到 Self Attention 层,然后经过 feed-forward neural network (前馈神经网络)层,最后得到输出,传入下一个编码器。

​ 每个位置的词都经过 Self Attention 层,得到的每个输出向量z都单独经过前馈神经网络层,每个向量经过的前馈神经网络都是一样的。

Self-Attention 整体理解

假设我们想要翻译的句子是:

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

这个句子中的 it 是一个指代词,那么 it 指的是什么呢?它是指animal还是street?这个问题对人来说,是很简单的,但是对算法来说并不是那么容易。

当模型在处理(翻译)it 的时候,Self Attention机制能够让模型把itanimal关联起来。

同理,当模型处理句子中的每个词时,Self Attention机制使得模型不仅能够关注这个位置的词,而且能够关注句子中其他位置的词,作为辅助线索,进而可以更好地编码当前位置的词。

如果你熟悉 RNN,回忆一下:RNN 在处理一个词时,会考虑前面传过来的hidden state,而hidden state就包含了前面的词的信息。而 Transformer 使用Self Attention机制,会把其他单词的理解融入处理当前的单词。

当我们在第五层编码器中(编码部分中的最后一层编码器)编码“it”时,有一部分注意力集中在“The animal”上,并且把这两个词的信息融合到了"it"这个单词中。

Self-Attention 细节

计算Query 向量,Key 向量,Value 向量

​ 计算 Self Attention 的第 1步是:对输入编码器的每个词向量Xn,都创建 3 个向量,分别是:Query 向量,Key 向量,Value 向量。这 3 个向量是词向量Xn分别和 3 个矩阵WQ、WK、WV相乘得到的,而这个矩阵是我们要学习的参数。

注意:这 3 个新得到的向量一般比原来的词向量的长度更小。假设这 3 个向量的长度是 dkey=64,而原始的词向量或者最终输出的向量的长度是 512(这 3 个向量的长度,和最终输出的向量长度,是有倍数关系的)。关于 Multi-head Attention,后面会给出实际代码。这里为了简化,假设只有一个 head 的 Self-Attention。

​ 上图中,有两个词向量:Thinking 的词向量 X1 和 Machines 的词向量 X2。以 X1 为例, X1 乘以 WQ得到 q1,q1 就是 X1对应的 Query 向量。同理, X1 乘以 WK 得到 k1, k1是X1 对应的 Key 向量;X1乘以 WV得到 v1,v1 是 X1对应的 Value 向量。

计算 Attention Score(注意力分数)

​ 第 2 步,是计算 Attention Score(注意力分数)。假设我们现在计算第一个词 Thinking 的 Attention Score(注意力分数),需要根据 Thinking 这个词,对句子中的其他每个词都计算一个分数。这些分数决定了我们在编码Thinking这个词时,需要对句子中其他位置的每个词放置多少的注意力。

​ 这些分数,是通过计算 "Thinking" 对应的 Query 向量和其他位置的每个词的 Key 向量的点积,而得到的。例如我们计算句子中第一个位置单词的 Attention Score(注意力分数),那么第一个分数就是 q1 和k1的内积,第二个分数就是q1和 k2 的点积。

​ 第 3 步就是把每个分数除以 根号dk( dk是 Key 向量的长度)。你也可以除以其他数,除以一个数是为了在反向传播时,求取梯度更加稳定

​ 第 4 步,接着把这些分数经过一个 Softmax 层,Softmax可以将分数归一化,这样使得分数都是正数并且加起来等于 1。这些分数决定了在编码当前位置(这里的例子是第一个位置)的词时,对所有位置的词分别有多少的注意力。很明显,在上图的例子中,当前位置(这里的例子是第一个位置)的词会有最高的分数,但有时,关注到其他位置上相关的词也很有用。

​ 第 5 步,得到每个位置的分数后,将每个分数分别与每个 Value 向量相乘。这种做法背后的直觉理解就是:对于分数高的位置,相乘后的值就越大,我们把更多的注意力放到了它们身上;对于分数低的位置,相乘后的值就越小,这些位置的词可能是相关性不大的,这样我们就忽略了这些位置的词。

​ 第 6 步是把上一步得到的向量相加,就得到了 Self Attention 层在这个位置(这里的例子是第一个位置)的输出。

​ 上面这张图,包含了 Self Attention 的全过程,最终得到的当前位置(这里的例子是第一个位置)的向量会输入到前馈神经网络。但这样每次只能计算一个位置的输出向量,在实际的代码实现中,Self Attention 的计算过程是使用矩阵来实现的,这样可以加速计算,一次就得到所有位置的输出向量。下面让我们来看,如何使用矩阵来计算所有位置的输出向量。

使用矩阵计算 Self-Attention

​ 第一步是计算 Query,Key,Value 的矩阵。首先,我们把所有词向量放到一个矩阵 X 中,然后分别和 3 个权重矩阵WQ、WK、WV 相乘,得到 Q,K,V 矩阵。

​ 矩阵 X 中的每一行,表示句子中的每一个词的词向量,长度是 512。Q,K,V 矩阵中的每一行表示 Query 向量,Key 向量,Value 向量,向量长度是 64。接着,由于我们使用了矩阵来计算,我们可以把上面的第 2 步到第 6 步压缩为一步,直接得到 Self Attention 的输出。

多头注意力机制(multi-head attention)

​ Transformer 的论文通过增加多头注意力机制(一组注意力称为一个 attention head),进一步完善了 Self Attention 层。这种机制从如下两个方面增强了 attention 层的能力:

  1. 它扩展了模型关注不同位置的能力。在上面的例子中,第一个位置的输出 z1 包含了句子中其他每个位置的很小一部分信息,但 z1 可能主要是由第一个位置的信息决定的。当我们翻译句子:The animal didn’t cross the street because it was too tired时,我们想让机器知道其中的it指代的是什么。这时,多头注意力机制会有帮助。
  2. 多头注意力机制赋予 attention 层多个“子表示空间”。下面我们会看到,多头注意力机制会有多组 WQ、WK、WV的权重矩阵(在 Transformer 的论文中,使用了 8 组注意力(attention heads)。因此,接下来我也是用 8 组注意力头 (attention heads))。每一组注意力的WQ、WK、WV的权重矩阵都是随机初始化的。经过训练之后,每一组注意力可以看作是把输入的向量映射到一个”子表示空间“。

​ 在多头注意力机制中,我们为每组注意力维护单独的WQ、WK、WV权重矩阵,将输入 X 和每组注意力的WQ、WK、WV相乘,得到8组Q, K, V 矩阵。

​ 接着,我们把每组 K, Q, V 计算得到每组的 Z 矩阵,就得到 8 个 Z 矩阵。

​ 接下来就有点麻烦了,因为前馈神经网络层(FFNN)接收的是 1 个矩阵(其中每行的向量表示一个词),而不是 8 个矩阵。所以我们需要一种方法,把 8 个矩阵整合为一个矩阵。我们可以把这8个矩阵拼接起来,然后和另一个权重矩阵WO 相乘。

具体步骤如下:

这就是多头注意力的全部内容。用一张图总结如下:

代码实现矩阵计算 Attention

使用 PyTorch 库的实现

​ PyTorch 提供了 MultiheadAttention 来实现 attention 的计算。

torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True,                                    add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)

参数说明如下:

现在来解释一下,为什么 num_heads 的值需要能够被 embed_dim 整除。这是为了把词的隐向量长度平分到每一组,这样多组注意力也能够放到一个矩阵里,从而并行计算多头注意力。

例如,我们前面说到,8 组注意力可以得到 8 组 Z 矩阵,然后把这些矩阵拼接起来,得到最终的输出。如果最终输出的每个词的向量维度是 512,那么每组注意力的向量维度应该是 512/8=64,如果不能够整除,那么这些向量的长度就无法平均分配。

​ 定义了MultiheadAttention 的对象后,调用时传入的参数如下:

forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None)

参数说明如下:

注意:在前面的讲解中,我们的 K、Q、V 矩阵的序列长度都是一样的。但是在实际中,K、V 矩阵的序列长度是一样的,而 Q 矩阵的序列长度可以不一样。这种情况发生在:在解码器部分的Encoder-Decoder Attention层中,Q 矩阵是来自解码器下层,而 K、V 矩阵则是来自编码器的输出。

在完成了编码(encoding)阶段之后,我们开始解码(decoding)阶段。解码(decoding )阶段的每一个时间步都输出一个翻译后的单词(这里的例子是英语翻译)。

输出:


标签:Transformer,Self,Attention,矩阵,编码器,模块,初探,注意力,向量
来源: https://www.cnblogs.com/afei688/p/16598561.html