系列文章目录

第一章 姿态描述之旋转矩阵、欧拉角、四元数

第二章 坐标系与位姿变换

第三章 手眼标定

第四章 机器人正逆运动学与路径规划

第五章 图解Transformer架构

第六章 图解Bert

第七章 BERT代码解析与实战

第八章 强化学习


目录

系列文章目录

前言

一、Transformer整体架构

二、输入处理

1.词向量

2.位置向量

三、编码器Encoder

1.自注意力机制(Self-Attention)

2.Self-Attention矩阵计算

3.多头注意力机制(Multi-Head Attention)

4.残差连接与层归一化(Add&Normalize)

(1)残差连接

(2)层归一化  

5.前馈神经网络(Feed-Forward Network, FFN)

四、解码器Decoder

1.掩码多头自注意力(Masked Multi-Head Self-Attention)

(1)padding mask

(2)sequence mask

2.交叉注意力(Cross Multi-Head Attention)

五、线性层和softmax

六、损失函数

总结


前言

提示:该博客仅为个人学习记录,若有错误之处,还请批评指正。

参考:图解transformer

        【超详细】【原理篇&实战篇】一文读懂Transformer

        Transformer模型在2017年被google提出,直接基于自注意力机制(Self-Attention)结构,取代了之前NLP任务中常用的RNN神经网络结构,并在WMT2014 Englishto-German和WMT2014 English-to-French两个机器翻译任务上都取得了当时的SOTA。与RNN这类神经网络结构相比,Transformer一个巨大的优点是:模型在处理序列输入时,可以对整个序列输入进行并行计算,不需要按照时间步循环递归处理输入序列。现已成为 NLP、CV 等领域的基础架构,如 BERT、GPT 均基于此。


一、Transformer整体架构

        Transformer 的核心由编码器(Encoder) 和解码器(Decoder) 两部分组成,二者均采用 “多层堆叠” 的结构,且共享相同的输入嵌入和位置编码模块。 Transformer最开始提出来解决机器翻译任务,因此可以看作是seq2seq模型的一种。以机器翻译任务为例,先将Transformer这种特殊的seq2seq模型看作一个黑盒,黑盒的输入是法语文本序列,输出是英语文本序列。编码器的输入文本序列最开始需要经过embedding(嵌入模型)转换,得到每个单词的向量表示,然后加上位置编码送入Encoder处理,再由Decoder部分根据已出现文字序列提取编码器输出信息经处理后得到输出。

        下面,再将上图中的编码器和解码器细节绘出,得到下图。我们可以看到,编码器部分(encoders)由多层编码器(Encoder)组成(Transformer论文中使用的是6层编码器,这里的层数6并不是固定的,可以根据实验效果来修改层数)。同理,解码器部分(decoders)也是由多层的解码器(Decoder)组成(论文里也使用了6层解码器)。每层编码器网络结构是一样的,每层解码器网络结构也是一样的。不同层编码器和解码器网络结构不共享参数。 

接下来,我们看一下单层encoder,单层encoder主要由以下两部分组成,如下图所示

  • Self-Attention Layer
  • Feed Forward Neural Network(前馈神经网络,缩写为 FFNN)

        处理后的输入向量经过一个Self-Attention(自注意力机制)神经网络层进行变换和信息交互得到富有上下文信息的向量。self-attention层处理一个词向量的时候,不仅会使用这个词本身的信息,也会使用句子中其他词的信息(可以类比为:当我们翻译一个词的时候,不仅会只关注当前的词,也会关注这个词的上下文的其他词的信息)。Self-Attention层的输出会经过前馈神经网络得到新的向量。这些向量将被送入下一层encoder,继续相同的操作。

        与编码器对应,如下图,解码器在编码器的self-attention和FFNN中间插入了一个Encoder-Decoder Attention层,这个层帮助解码器聚焦于解码器输出序列最相关的部分。

decoder

        总结一下,我们基本了解了Transformer由编码部分和解码部分组成,而编码部分和解码部分又由多个网络结构相同的编码层和解码层组成。每个编码层由self-attention和FFNN组成,每个解码层由self-attention、FFN和encoder-decoder attention组成。以上便是Transformer的整体结构啦,下面我们开始看模型细节。

二、输入处理

1.词向量

        和常见的NLP 任务一样,我们首先会使用词嵌入算法(embedding algorithm),将输入文本序列的每个词转换为一个词向量,本质是将离散的词映射到连续的向量空间。实际应用中的向量一般是 256 维,512 维,或者1024维。但为了简化起见,我们这里使用4维的词向量来进行讲解。

        如下图所示,假设我们的输入文本序列包含了3个词,那么每个词可以通过词嵌入算法得到一个4维向量,于是整个输入被转化成为一个向量序列。在实际应用中,我们通常会同时给模型输入多个句子,如果每个句子的长度不一样,我们会选择一个合适的长度,作为输入文本序列的最大长度;如果一个句子达不到这个长度,那么就填充先填充一个特殊的“padding”词;如果句子超出这个长度,则做截断。最大序列长度是一个超参数,通常希望越大越好,但是更长的序列往往会占用更大的训练显存/内存,因此需要在模型训练时候视情况进行决定。

2.位置向量

        输入序列每个单词被转换成词向量表示还将加上位置向量来得到该词的最终向量表示。

        如下图所示,Transformer模型对每个输入的词向量都加上了一个位置向量。这些向量有助于确定每个单词的位置特征,或者句子中不同单词之间的距离特征。词向量加上位置向量背后的直觉是:将这些表示位置的向量添加到词向量中,得到的新向量,可以为模型提供更多有意义的信息,比如词的位置,词之间的距离等。常用实现是正弦余弦编码,通过不同频率的正弦 / 余弦函数生成位置向量,确保 “位置相近的向量相似度高,位置远的相似度低”。

三、编码器Encoder

        编码器部分一般由几层的编码器堆叠而成,编码部分的输入文本序列经过输入处理之后得到了一个向量序列,这个向量序列将被送入第1层编码器,第1层编码器输出的同样是一个向量序列,再接着送入下一层编码器。第1层编码器的输入是融合位置向量的词向量,下一层编码器的输入则是上一层编码器的输出

        下图展示了向量序列在双层encoder中的流动:融合位置信息的词向量进入self-attention层,self-attention的输出每个位置的向量再输入FFN神经网络得到每个位置的新向量,然后再输入到下一层编码器

1.自注意力机制(Self-Attention)

举个例子,假设我们想要翻译的句子是:

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

        这个句子中的 it 是一个指代词,那么 it 指的是什么呢?它是指 animal 还是street?这个问题对人来说,是很简单的,但是对模型来说并不是那么容易。但是,如果模型引入了Self Attention机制之后,便能够让模型把it和animal关联起来了。同样的,当模型处理句子中其他词时,Self Attention机制也可以使得模型不仅仅关注当前位置的词,还会关注句子中其他位置的相关的词,进而可以更好地理解当前位置的词。

        与RNN对比一下,RNN 在处理序列中的一个词时,会考虑句子前面的词传过来的hidden state,而hidden state仅仅包含了前面的词的信息,且距离越远,信息量越少;而Self Attention机制会让当前词会直接关注到自己句子中前后相关的所有词语,如下图 it的例子:

        上图所示的it是一个真实的例子,是当Transformer在第5层编码器编码“it”时的状态,可视化之后显示it有一部分注意力集中在了“The animal”上,并且把这两个词的信息融合到了"it"中。

以下为Self-Attention的计算过程。

        第1步:对输入编码器的词向量X进行线性变换得到:Query向量: q1,q2,Key向量: k1,k2,Value向量: v1,v2。这3个向量X分别和3个W相乘得到的,即q=X\cdot W^{Q},而这个矩阵W也是是模型要学习的参数。

        Query 向量,Key 向量,Value 向量,其实它们就是 3 个向量,给它们加上一个名称,可以让我们更好地理解 Self-Attention 的计算过程和逻辑。attention计算的逻辑常常可以描述为:query和key计算相关或者叫attention得分,然后根据attention得分对value进行加权求和。

        第2步:计算Attention Score(注意力分数)。假设我们现在计算第一个词Thinking 的Attention Score(注意力分数),需要根据Thinking 对应的词向量,对自身和句子中的其他词向量都计算一个注意力分数。这些分数决定了我们在编码Thinking这个词时,需要对句子中其他位置的词向量分配的权重。Attention score是根据"Thinking" 对应的 Query 向量和其他位置的每个词的 Key 向量进行点积得到的。Thinking的第一个Attention Score就是q1和k1的点积,第二个分数就是q1和k2的点积。这个计算过程在下图中进行了展示,下图里的具体得分数据是为了表达方便而自定义的。

        

        第3步:把每个分数除以 \sqrt{d_{k}}d_{k}是Key向量的维度。也可以除以其他数,除以一个数是为了在反向传播时,求梯度时更加稳定。避免注意力分数过大,导致 Softmax 后梯度消失。

        第4步:接着把这些分数经过一个Softmax函数,Softmax可以将分数归一化,这样使得分数都是正数并且加起来等于1, 如下图所示。 这些分数决定了Thinking词向量,对其他所有位置的词向量分别有多少的注意力。

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

        第6步:把第5步得到的Value向量相加,就得到了Self Attention在当前位置(这里的例子是第1个位置)对应的输出。

        最后,在下图展示了 对第一个位置词向量计算Self Attention 的全过程。最终得到的当前位置词向量会继续输入到前馈神经网络。注意:上面的6个步骤每次只计算一个位置的输出向量,在实际的代码实现中,Self Attention的计算过程是使用矩阵快速并行计算的,一次就得到所有位置的输出向量。

2.Self-Attention矩阵计算

将self-attention计算6个步骤中的向量放一起​,便可以进行矩阵计算啦。首先,我们把所有词向量放到一个矩阵X中,然后分别和3个权重矩阵W^{^{Q}}W^{^{K}}W^{^{V}}相乘,得到 Q,K,V 矩阵。矩阵X中的每一行,表示句子中的每一个词的词向量。Q,K,V 矩阵中的每一行表示每一个词向量X对应的Query向量,Key向量,Value 向量。

        由于我们使用了矩阵来计算,我们可以把上面的第 2 步到第 6 步压缩为一步,直接得到 Self Attention 的输出。

3.多头注意力机制(Multi-Head Attention)

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

  • 增强模型关注不同位置,特征的能力。在上面的例子中,第一个位置的输出z1包含了句子中其他每个位置的很小一部分信息,但z1仅仅是单个向量,所以可能仅由第1个位置的信息主导了。而当我们翻译句子:The animal didn’t cross the street because it was too tired时,我们不仅希望模型关注到"it"本身,还希望模型关注到"The"和“animal”,甚至关注到"tired"。这时,多头注意力机制会有帮助。
  • 多头注意力机制赋予attention层多个“子表示空间”。下面我们会看到,多头注意力机制会有多组W^{^{Q}}W^{^{K}}W^{^{V}}的权重矩阵(在 Transformer 的论文中,使用了 8 组注意力),因此可以将词向量X变换到更多种子空间进行表示。接下来我们也使用8组注意力头(attention heads))。每一组注意力的权重矩阵都是随机初始化的,但经过训练之后,每一组注意力的权重W^{^{Q}}W^{^{K}}W^{^{V}}可以把输入的向量映射到一个对应的“子表示空间”。

        在多头注意力机制中,我们为每组注意力设定单独的 WQ, WK, WV 参数矩阵。将输入X和每组注意力的WQ, WK, WV 相乘,得到8组 Q, K, V 矩阵。

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

由于前馈神经网络层接收的是 1 个矩阵(其中每行的向量表示一个词),而不是 8 个矩阵,所以我们直接把8个子矩阵拼接起来得到一个大的矩阵,然后和另一个权重矩阵W^O相乘做一次变换,映射到前馈神经网络层所需要的维度。

总结一下就是:

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

以上就是多头注意力的全部内容。最后将所有内容放到一张图中:

        学习了多头注意力机制,让我们再来看下当我们前面提到的it例子,不同的attention heads (注意力头)对应的“it”注意到了哪些内容。下图中的绿色和橙色线条分别表示2组不同的attentin heads:

        当我们编码单词"it"时,其中一个 attention head (橙色注意力头)最关注的是"the animal",另外一个绿色 attention head 关注的是"tired"。因此在某种意义上,"it"在模型中的表示,融合了"animal"和"tire"的部分表达。

4.残差连接与层归一化(Add&Normalize)

        到目前为止,我们计算得到了self-attention的输出向量。而单层encoder里后续还有两个重要的操作:残差连接(Add)和层标准化(layer-normalization),如下图所示,在经过多头注意力机制得到矩阵Z之后,并没有直接传入全连接神经网络,而是经过了一步Add&Normalize。

        Add & Norm 层由 Add 和 Norm 两部分组成,其计算公式如下:

        其中 X表示 Multi-Head Attention 或者 Feed Forward 的输入,MultiHeadAttention(X) 和 FeedForward(X) 表示注意力计算或前向传播后的输出 (输出与输入 X 维度是一样的,所以可以相加)。

       将 Self-Attention 层的层标准化(layer-normalization),残差连接和涉及的向量计算细节都进行可视化,如下所示:

        编码器和和解码器的子层里面都有层标准化(layer-normalization)。假设一个 Transformer 是由 2 层编码器和两层解码器组成的,将全部内部细节展示起来如下图所示。

(1)残差连接

        Add,就是在z的基础上加了一个残差块X,加入残差块的目的是为了防止在深度神经网络的训练过程中发生退化的问题,退化的意思就是深度神经网络通过增加网络的层数,Loss逐渐减小,然后趋于稳定达到饱和,然后再继续增加网络层数,Loss反而增大。

(2)层归一化  

        归一化可以缓解梯度消失问题,加快收敛速度;达到类似正则化的效果,防止过拟合降低调参难度,可以使用较大的学习率。使用到的归一化方法是Layer Normalization。

        Layer Normalization是在同一个样本中不同神经元之间进行归一化,而BN是在同一个batch中不同样本之间的同一位置的神经元之间进行归一化。
        BN是对于相同的维度进行归一化,但是NLP中输入的都是词向量,一个300维的词向量,单独去分析它的每一维是没有意义地,在每一维上进行归一化也是适合地,因此这里选用的是LN。

5.前馈神经网络(Feed-Forward Network, FFN)

       结构固定为:

        即 “线性层→ReLU 激活→线性层”,其中每个位置的向量独立经过 FFN,不改变序列长度。

四、解码器Decoder

        编码器一般有多层,第一个编码器的输入是一个序列文本,最后一个编码器输出是一组序列向量,这组序列向量会作为解码器的K、V输入,其中K,V=解码器输出序乘以W^{^{K}}W^{^{V}}后的向量表示。这些注意力向量将会输入到每个解码器的Encoder-Decoder Attention层,这有助于解码器把注意力集中到输入序列的合适位置,如下图所示。

1.掩码多头自注意力(Masked Multi-Head Self-Attention)

      与Encoder的Multi-Head Attention计算原理一样,只是多加了一个mask码。mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果,其实就是对覆盖掩码的地方不做注意计算。Transformer 模型里面涉及两种 mask,分别是 padding mask 和 sequence mask。

(1)padding mask

        在解码器Encoder中,因为每个批次输入序列长度是不一样的,我们要对输入序列进行对齐。具体来说,就是给在较短的序列后面填充 0。但是如果输入的序列太长,则是截取左边的内容,把多余的直接舍弃。因为这些填充的位置,其实是没什么意义的,所以我们的attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过 softmax,这些位置的概率就会接近0!

(2)sequence mask

        sequence mask 是为了使得 编码器Decoder 不能看见未来的信息。对于一个序列,在 time_step 为 t 的时刻,我们的解码输出应该只能依赖于 t 时刻之前的输出,而不能依赖 t 之后的输出。因此我们需要想一个办法,把 t 之后的信息给隐藏起来。这在训练的时候有效,因为训练的时候每次我们是将target数据完整输入进decoder中地,预测时不需要,预测的时候我们只能得到前一时刻预测出的输出。

2.交叉注意力(Cross Multi-Head Attention)

        解码(decoding )阶段的每一个时间步都输出一个翻译后的单词(这里的例子是英语翻译),解码器当前时间步的输出又重新作为输入Q,再与编码器的输出K、V共同作为下一个时间步解码器的输入。然后重复这个过程,直到输出一个结束符。如下图所示:

解码器中的 Self Attention 层,和编码器中的 Self Attention 层的区别:

  1. 在解码器里,Self Attention 层只允许关注到输出序列中早于当前位置之前的单词。具体做法是:在 Self Attention 分数经过 Softmax 层之前,屏蔽当前位置之后的那些位置(将attention score设置成-∞)。
  2. 解码器 Attention层是使用前一层的输出来构造Query 矩阵,而Key矩阵和 Value矩阵来自于编码器最终的输出。

五、线性层和softmax

        Decoder 最终的输出是一个向量,其中每个元素是浮点数。我们怎么把这个向量转换为单词呢?这是线性层和softmax完成的。

        线性层就是一个普通的全连接神经网络,可以把解码器输出的向量,映射到一个更大的向量,这个向量称为 logits 向量:假设我们的模型有 10000 个英语单词(模型的输出词汇表),此 logits 向量便会有 10000 个数字,每个数表示一个单词的分数。然后,Softmax 层会把这些分数转换为概率(把所有的分数转换为正数,并且加起来等于 1)。然后选择最高概率的那个数字对应的词,就是这个时间步的输出单词。

六、损失函数

        Transformer训练的时候,需要将解码器的输出和label一同送入损失函数,以获得loss,最终模型根据loss进行方向传播。这一小节,我们用一个简单的例子来说明训练过程的loss计算:把“merci”翻译为“thanks”。

        我们希望模型解码器最终输出的概率分布,会指向单词 ”thanks“(在“thanks”这个词的概率最高)。但是,一开始模型还没训练好,它输出的概率分布可能和我们希望的概率分布相差甚远,如下图所示,正确的概率分布应该是“thanks”单词的概率最大。但是,由于模型的参数都是随机初始化的,所示一开始模型预测所有词的概率几乎都是随机的。

        只要Transformer解码器预测了一组概率,我们就可以把这组概率和正确的输出概率做对比,然后使用反向传播来调整模型的权重,使得输出的概率分布更加接近整数输出。

        那我们要怎么比较两个概率分布呢?:我们可以简单的用两组概率向量的的空间距离作为loss(向量相减,然后求平方和,再开方),当然也可以使用交叉熵(cross-entropy)]和KL 散度(Kullback–Leibler divergence)。

        由于上面仅有一个单词的例子太简单了,我们可以再看一个复杂一点的句子。句子输入是:“je suis étudiant” ,输出是:“i am a student”。这意味着,我们的transformer模型解码器要多次输出概率分布向量:

  • 每次输出的概率分布都是一个向量,长度是 vocab_size(前面约定最大vocab size,也就是向量长度是 6,但实际中的vocab size更可能是 30000 或者 50000)
  • 第1次输出的概率分布中,最高概率对应的单词是 “i”
  • 第2次输出的概率分布中,最高概率对应的单词是 “am”
  • 以此类推,直到第 5 个概率分布中,最高概率对应的单词是 “<eos>”,表示没有下一个单词了

于是我们目标的概率分布长下面这个样子:

        我们用例子中的句子训练模型,希望产生图中所示的概率分布 我们的模型在一个足够大的数据集上,经过足够长时间的训练后,希望输出的概率分布如下图所示:

        我们希望模型经过训练之后可以输出的概率分布也就对应了正确的翻译。当然,如果你要翻译的句子是训练集中的一部分,那输出的结果并不能说明什么。我们希望模型在没见过的句子上也能够准确翻译。


总结

Transformer是大模型的基石,是具身智能基础学习必由之路。

更多推荐