ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Transformer模块初探

2022-08-18 14:33:31  阅读:169  来源: 互联网

标签:Transformer Self Attention 矩阵 编码器 模块 初探 注意力 向量


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 Layer
  • ​ Feed Forward Neural Network(前馈神经网络,缩写为 FFNN)

执行流程

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

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

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

Transformer细节

Transformer输入

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

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

  • 如果一个句子达不到这个长度,那么就填充全为 0 的词向量;
  • 如果句子超出这个长度,则做截断。

​ 编码器(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 相乘。

具体步骤如下:

  • 把 8 个矩阵 {Z0,Z1...,Z7} 拼接起来
  • 把拼接后的矩阵和 WO 权重矩阵相乘
  • 得到最终的矩阵 Z,这个矩阵包含了所有 attention heads(注意力头) 的信息。这个矩阵会输入到 FFNN (Feed Forward Neural Network)层。

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

代码实现矩阵计算 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)

参数说明如下:

  • embed_dim:最终输出的 K、Q、V 矩阵的维度,这个维度需要和词向量的维度一样
  • num_heads:设置多头注意力的数量。如果设置为 1,那么只使用一组注意力。如果设置为其他数值,那么 num_heads 的值需要能够被 embed_dim 整除;
  • dropout:这个 dropout 加在 attention score 后面。

现在来解释一下,为什么 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)

参数说明如下:

  • query:对应于 query矩阵,形状是 (L,N,E) 。其中 L 是输出序列长度,N 是 batch size,E 是词向量的维度;
  • key:对应于 Key 矩阵,形状是 (S,N,E) 。其中 S 是输入序列长度,N 是 batch size,E 是词向量的维度;
  • value:对应于 Value 矩阵,形状是 (S,N,E) 。其中 S 是输入序列长度,N 是 batch size,E 是词向量的维度;
  • key_padding_mask:如果提供了这个参数,那么计算 attention score 时,忽略 Key 矩阵中某些 padding 元素,不参与计算 attention。形状是 (N,S)。其中 N 是 batch size,S 是输入序列长度。
    • 如果 key_padding_mask 是 ByteTensor,那么非 0 元素对应的位置会被忽略
    • 如果 key_padding_mask 是 BoolTensor,那么 True 对应的位置会被忽略

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

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

输出:

  • attn_output:形状是 (L,N,E)
  • attn_output_weights:形状是 (N,L,S)

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有