文末【机器学习集训营 第十九期】新课秒杀
本期新增大模型和线下,前10人3999秒杀(第11人起则4399),报名加送2年VIP和2年GPU。
不过,据目前我所看到的文章里面,介绍Transformer比较好懂的还是开头所提的这篇《The Illustrated Transformer》,本部分中的核心阐述和大部分配图均来自此文。
接下来,我在此文的基础上加以大量解释、说明,以让之成为全网最通俗易懂的Transformer导论。因为这些解释、说明是你在其他文章中看不到的,而正因为有了这些解释、说明,才足以真正让每一个初学者都能快速理解到底什么是Transformer。
1、Transformer之编码:自注意力/位置编码/求和与归一化
1.1 从机器翻译模型开始谈起
还是考虑上文中已经出现过的机器翻译的模型(值得一提的是,Transformer一开始的提出即是为了更好的解决机器翻译问题)。当我们从外部现象来看的话,这个机器翻译技术就像是一个黑箱操作:输入一种语言,系统输出另一种语言:
当我们拆开这个黑箱后,我们可以看到它是由编码组件、解码组件和它们之间的连接组成。
其中
编码组件部分由一堆编码器/Encoder构成(具体个数可以调,论文中是6个)
解码组件部分也是由相同数量(与编码器对应)的解码器Decoder组成
进一步拆开这个编码器会发现,所有的编码器在结构上都是相同的,但是并不共享参数,且每个编码器都可以分解成两个子层:
从而,当我们把编码器和解码器联合起来看待的话,则整个流程就是(如下图从左至右所示):
首先,从编码器输入的句子会先经过一个自注意力层(即self-attention,下文会具体阐述),它会帮助编码器在对每个单词编码时关注输入句子中的的其他单词
接下来,自注意力层的输出会传递到前馈(feed-forward)神经网络中,每个位置的单词对应的前馈神经网络的结构都完全一样(注意:仅结构相同,但各自的参数不同)
最后,流入解码器中,解码器中除了也有自注意力层、前馈层外,这两个层之间还有一个编码-解码注意力层,用来关注输入句子的相关部分(和seq2seq模型的注意力作用相似)
1.2 将张量引入
我们已经了解了模型的主要部分,接下来我们看一下各种向量或张量(注:张量概念是矢量概念的推广,可以简单理解矢量是一阶张量、矩阵是二阶张量)是怎样在模型的不同部分中,将输入转化为输出的。
像大部分NLP应用一样,我们首先将每个输入单词通过词嵌入算法转换为词向量
具体而言,流程如下
每个单词都被嵌入为512维的向量(512是Transformer论文中设定的一个维度,类似编码器/解码器的数量一样,都是可以设置的超参数。顺带提句,训练集中最长句子的长度论文中也设置的512。为方便后续一系列的图示,这里用4个格子代表512维,即虽然你只看到4维,但你要明白实际背后代表着512维)
最底下的那个编码器接收的是嵌入向量,之后的编码器接收的是前一个编码器的输出
此时,我们可以看出Transformer的一个核心特性
输入序列中每个位置的单词都各自单独的路径流入编码器,即各个单词同时流入编码器中,不是排队进入..
在自注意力self-attention层中,这些路径两两之间是相互依赖的,而前馈层(feed-forward)则没有这些依赖性,所以这些路径在 流经前馈层(feed-forward)时可以并行计算
1.3 什么是自注意力机制:从宏观视角看自注意力机制
下面,咱们再通过一个例子了解自注意力机制的工作原理,假定现在要翻译下列句子:
“The animal didn't cross the street because it was too tired”
上面这个“it”是指的什么呢?它指的是street 还是这个 animal 呢?对人来说很简单的问题(必然是animal,因为animal才可能cross,才可能tired),但是对算法而言并不简单,算法不一定知道it指的是animal还是street。
那self-attention机制咋做呢?一般的文章会这么解释:
当模型处理单词“it”时,self-attention允许将“it”和“animal”联系起来。当模型处理每个位置的词时,self-attention允许模型看到句子中其他位置有关联或相似的单词/信息作为辅助线索,以更好地编码当前单词。
回想一下RNN对隐藏状态的处理:将之前的隐藏状态与当前位置的输入结合起来。在Transformer中,自注意力机制则将对其他单词的“理解”融入到当前处理的单词中。
说的直白点就是,你如果要更好的理解句中某个特定单词的含义,你要把它放到整个语境之中去理解,比如通过对上下文的把握(包括即便遇到那种一词多义的情况,你也能通过其上下文推断其准确含义,这就是上下文的重要性,也是transformer计算每个单词与上下文各个词的相似度的意义所在 )
那上下文哪些词对理解该特定词更重要呢?这个重要程度便用所谓的权重表示(权重来自于该词/向量本身跟其他各个词/向量之间的相似度),权重越大的单词代表与『该词』越相关(某种意义上可以认为是越相似),从而对理解『该词』越重要,然后把该词编码为包括该词在内所有词的加权和
接下来的关键是如何计算自注意力,计算自注意力有两种方式:一种通过向量,一种通过矩阵。下面,先看向量的方式
1.4 通过向量计算自注意力:先三个向量后计算得分且softmax最后加权求和
1.4之1/4 计算自注意力第一步:生成查询向量、键向量和值向量
通过向量方式计算自注意力的第一步,就是从每个编码器的输入向量(即每个单词的词向量)生成三个向量:查询向量query-vec、键向量key-vec、值向量value-vec
查询向量、键向量、值向量这三个向量的维度在论文中设置的是64,在维度上比词嵌入向量更低,因为词嵌入和编码器的输入/输出向量的维度是512,但也不是必须比编码器输入输出的维数小,这样做主要是为了让后续多头注意力的计算更稳定
(在下文你会看到,transformer通过多头注意力机制multi headed attention,对每个512维的输入向量都设置了8个头,不同的头关注每个输入向量不同的部分,而每个头的维度则是:512/8 = 64,且再多说一句,也可以设置为2个头,不一定非得设置为8个头)
至于这三个向量的生成方法是把输入的向量分别乘以三个不同的权重矩阵、、,得到Q、K、V,而这些权重矩阵是在模型训练阶段中训练出来的「对于权重矩阵//如何训练出来的,还是标准老套路:先随机初始化,然后在损失函数中表示出来,最后通过反向传播不断优化学习得出,最终目标是最小化模型的预测误差,至于什么是反向传播,请参见参考文献17」
为形象起见,还是举例来说明,在我们有了权重矩阵后,对于单词 、 分别而言(假定X1是Thinking,X2是Machines):
与 权重矩阵相乘得到与这个单词相关的查询向量 、 与 权重矩阵相乘得到与这个单词相关的键向量 、 与 权重矩阵相乘得到与这个单词相关的值向量
对于单词而言,依上类推:分别与、、相乘得到该单词的查询向量、键向量、值向量
最终使得输入序列的每个单词各自创建一个查询向量、一个键向量和一个值向量
可能有的读者有疑问了,设置这三个向量的用意何在或有何深意,实际上
查询向量Query是当前单词的表示形式,用于对所有其他单词(key)进行评分,我们只需要关注当前正在处理的token的query
键向量Key可以看做是序列中所有单词的标签,是在我们找相关单词时候的对照物
值向量Value是单词的实际表示,一旦我们对每个单词的相关度打分之后,我们就要对value进行相加表示当前正在处理单词的value
1.4之2/4 计算自注意力第二步:计算得分
接下来,我们需要针对这个例子中的第一个单词“Thinking”(pos#1)计算attention分值,即计算每个词对“Thinking”的打分,这个分决定着编码“Thinking”时(某个固定位置时),应该对其他位置上的单词各自给予多少关注度
这个得分通过“Thinking”所对应的查询向量query和所有词的键向量key,依次乘积得出来。所以如果我们是处理位置最靠前的词的attention分值的话
第一个分数是q1和k1的点积(根据点积结果可以判断q1和k1这个向量的相似性)
第二个分数是q1和k2的点积(根据点积结果可以判断q1和k2这个向量的相似性)
1.4之3/4 计算自注意力第三、四步:分数除以8然后softmax下
第三步和第四步分别是:
将分数除以8(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定,也可以使用其它值)
然后通过softmax传递结果,softmax的作用是使所有单词的分数归一化,得到的分数都是正值且它们的和为1
这个softmax分数决定了在编码当下位置(“Thinking”)时,包括当下位置单词(“Thinking”)在内每个单词的所获得的关注度。显然,正在当下位置上的Thinking获得最高的softmax分数(毕竟自己跟自己最相似嘛,所以编码thinking时对Thinking、Machines的注意力分数分配是:0.88 0.12)。
1.4之4/4 计算自注意力第五、六步:值向量乘以softmax分数后对加权值向量求和
第五步是将softmax分值乘以每个值向量,这样操作的意义在于留下我们想要关注的单词的value,并把其他不相关的单词丢掉(例如,让它们乘以0.001这样的小数)
接下来,针对每个单词都进行上述六个步骤的自注意力得分计算,相当于
先是“Thinking”对应的query(q1)与各个不同的key(k1、k2)计算相似度,然后除以8继而softmax,最后softmax值乘以值向量v1、v2并加权求和即: ,注:下图里的b1相当于z1
再是“Machines”对应的query(q2)与各个不同的key(k1、k2)计算相似度,然后也除以8继而softmax,最后softmax值乘以值向量v1、v2并加权求和即: ,注:下图里的b2相当于z2
如此,所有单词的自注意力计算就完成了,得到的向量就可以传给前馈神经网络。然而实际中,这些计算是以矩阵形式完成的,以便算得更快。下面咱们再看看如何用矩阵实现的。
1.5 通过矩阵运算实现自注意力机制
最后,由于我们处理的是矩阵,我们可以将步骤2到步骤6合并为一个公式来计算自注意力层的输出,下图是自注意力的矩阵运算形式:
如果觉得不够形象的话,可以再看下台大李宏毅画的这几个图:
1. 将输入向量、、、按列组合成一个矩阵,与 相乘得到矩阵,其中矩阵包含了所有输入向量对应的query,同理可以得到key和value对应的矩阵和2. 将各个向量对应的key按行组合,与输入向量相乘,得到与输入向量之间的所有关联性分数、、、由此可知,将矩阵转置后,与矩阵相乘得到矩阵,其中矩阵包含了所有输入向量之间的关联性分数 然后再将矩阵给到Softmax激活函数,得到矩阵 3. 最后将各个输入向量对应的value按列组合成一个矩阵,并与矩阵 相乘得到Self-attention的输出矩阵「其中的b1 b2 b3 b4相当于上文的z1 z2 z3 z4」
1.6 多头注意力机制“multi-headed” attention
为进一步完善自注意力层,下面增加一种叫做“多头”注意力(“multi-headed” attention)的机制,并在两方面提高了注意力层的性能:
它扩展了模型专注于不同位置的能力。编码“Thinking”的时候,虽然最后Z1或多或少包含了其他位置单词的信息,但是它实际编码中把过多的注意力放在“Thinking”单词本身(当然了,这也无可厚非,毕竟自己与自己最相似嘛) 且如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”和哪几个词都最有关联,这时模型的“多头”注意机制会起到作用
它给出了注意力层的多个“表示子空间”(representation subspaces)
July注:第一次看到这里的朋友,可能会有疑问,正如知乎上有人问(https://www.zhihu.com/question/341222779?sort=created):为什么Transformer 需要进行Multi-head Attention,即多头注意力机制?
叫TniL的答道:可以类比CNN中同时使用多个滤波器的作用,直观上讲,多头的注意力有助于网络捕捉到更丰富的特征/信息 且论文中是这么说的:Multi-head attention allows the model to jointly attend to information from different representation subspaces at different positions. 关于different representation subspaces,举一个不一定妥帖的例子:当你浏览网页的时候,你可能在颜色方面更加关注深色的文字,而在字体方面会去注意大的、粗体的文字。这里的颜色和字体就是两个不同的表示子空间。同时关注颜色和字体,可以有效定位到网页中强调的内容。使用多头注意力,也就是综合利用各方面的信息/特征(毕竟,不同的角度有着不同的关注点)
叫LooperXX的则答道:在Transformer中使用的多头注意力出现前,基于各种层次的各种fancy的注意力计算方式,层出不穷。而Transformer的多头注意力借鉴了CNN中同一卷积层内使用多个卷积核的思想,原文中使用了 8 个 scaled dot-product attention ,在同一multi-head attention 层中,输入均为 KQV,同时进行注意力的计算,彼此之前参数不共享,最终将结果拼接起来,这样可以允许模型在不同的表示子空间里学习到相关的信息,在此之前的 A Structured Self-attentive Sentence Embedding 也有着类似的思想 简而言之,就是希望每个注意力头,只关注最终输出序列中一个子空间,互相独立,其核心思想在于,抽取到更加丰富的特征信息
OK,接下来,我们将看到对于“多头”注意机制,我们有多个查询/键/值权重矩阵集(Transformer使用8个注意力头,则对于每个编码器/解码器都有8个“ ”的矩阵集合),每一组都是随机初始化,经过训练之后,输入向量可以被映射到不同的子表达空间中,具体而言
1. 在“多头”注意机制下,我们为每个头保持独立的查询/键/值权重矩阵,从而产生不同的查询/键/值矩阵。和之前一样,我们拿 乘以 / / 矩阵来产生查询/键/值矩阵
3. 这给我们带来了一点挑战。前馈层没法一下子接收8个矩阵,它需要一个单一的矩阵(最终这个单一矩阵类似输入矩阵那样,矩阵中每个的行向量对应一个单词,比如矩阵的第一行对应单词Thinking、矩阵的第二行对应单词Machines) 所以我们需要一种方法把这8个矩阵合并成一个矩阵。那该怎么做?其实可以直接把这些矩阵拼接在一起,然后乘以一个附加的权重矩阵![]()
以上基本就是多头自注意力的全部了,接下来把所有矩阵集中展示下,如下图所示
现在我们已经看过什么是多头注意力了,让我们回顾一下之前的一个例子:“The animal didn’t cross the street because it was too tired”,再看一下编码“it”的时候每个头的关注点都在哪里:
编码it,用两个head的时候:其中一个更关注the animal(注意看图中黄线的指向),另一个更关注tired(注意看图中绿线的指向)。
恍然大悟没有?!总有一个头会关注到咱们想关注的点,避免在编码it时只重点关注到了the animal,而没重点关注tired。
1.7 相比CNN/RNN的优势与位置编码
首先,CNN提取的是局部特征,但是对于文本数据,忽略了长距离的依赖,比如来自这里的这个例子
假设有如下这句话:小明在星期天要去露营,他准备叫上小红
在屏幕前的你,你会很快看出来后半句中的「他」指的就是「小明」,因为你可以一眼扫过去 看到差不多整个句子,且一眼看出这些词之间的关联,但对于计算机来说,可没那么容易,毕竟“小明”和“他”之间之间相距8个字
具体而言,对于CNN会怎么处理呢?
1. 一般会从最左侧开始,通过「同时扫描三个词的滑动窗口」从左到右扫描整个句子 2. 且每次往右移动一格
3. 重复这个过程,对整个文本进行处理,便得到一个「卷积核」计算出的所有特征,如下图所示,此时“小明”两个字只共同参与了特征A1的计算,而“他”字则参与了特征A9、A10、A11的计算,其中A9距离A1最近(因为A1中含有“小明”的信息,而A9中含有“他”的信息) 但在此时卷积操作本身并没有同时作用于“小明”和“他”这两个词,所以它无法建立它们之间的任何关系(因为模型的卷积操作都没有同时看到它们)
4. 怎么办呢?好在不管怎么着,特征A1和特征A9之间的距离相较于“小明”和“他”的距离变近了。所以我们可以继续在特征A上再堆叠一层卷积B 然后计算,然后再堆叠卷积C 再计算,直到卷积操作比如E能直接获取到“小明”和“他”的信息
就这样,通过更多的卷积操作,把卷积网络堆叠的更深,以此来让它有机会捕捉“长距离依赖”。换言之,卷积网络主要依靠深度来捕捉长距离依赖。但这个过程太间接了,因为信息在网络中实际传播了太多层。究竟哪些信息被保留,哪些被丢弃了,弄不清楚。所以从实践经验来看,卷积网络捕捉长依赖的能力非常弱。这也是为什么在大多数需要长依赖关系建模的场景中,CNN用的并不多的原因
意味着中包含最多的信息是当前的输入,越往前的输入,随着距离的增加,信息衰减得越多。对于每一个输出隐向量都是如此,包含信息最多得是当前的输入,随着距离拉远,包含前面输入的信息越来越少 但是Transformer这个结构就不存在这个问题,不管当前词和其他词的空间距离有多远,包含其他词的信息不取决于距离,而是取决于两者的相关性,这是Transformer的第一个优势
第二个优势在于,对于Transformer来说,在对当前词进行计算的时候,不仅可以用到前面的词,也可以用到后面的词,而RNN只能用到前面的词(当然,这倒不是个有多严重的问题,因为这可以通过双向RNN来解决)
第三点,RNN是一个顺序的结构,必须要一步一步地计算,只有计算出,才能计算,其次再计算,即隐向量无法同时并行计算,导致RNN的计算效率不高 通过前文的介绍,可以看到Transformer不存在这个问题
不过有个细节值得注意下,不知有读者发现没有,即RNN的结构包含了序列的时序信息,而Transformer却完全把时序信息给丢掉了
毕竟“他欠我100万”,和“我欠他100万”,两者的意思千差万别,故为了解决时序的问题,Transformer的作者用了一个绝妙的办法:位置编码(Positional Encoding)。
即给每个位置编号,从而每个编号对应一个向量,最终通过结合位置向量和词向量,作为输入embedding,就给每个词都引入了一定的位置信息,这样Attention就可以分辨出不同位置的词了:![]()
关于『使用位置编码表示序列的位置』的细节请参看此文的1.1节:从零实现Transformer的简易版与强大版:从300多行到3000多行
2、Transformer之解码:求和与归一化、线性层和softmax层
2.1 求和与归一化
2.2 解码器中的两个注意力层
通过上文的介绍可知,解码器中的自注意力层和编码器中的不太一样,解码器中有两个注意力层,从底至上
一个带masked的Multi-Head Attention,本质是Self-Attention 该自注意力层只允许关注已输出位置的信息,实现方法是在自注意力层的softmax之前进行mask,将未输出位置的权重设置为一个非常大的负数(进一步softmax之后基本变为0,相当于直接屏蔽了未输出位置的信息)
一个不带masked的Multi-Head Attention,本质是Encoder-Decoder Attention
对于第二个注意力层,即Encoder-Decoder Attention
比如当我们要把“Hello Word”翻译为“你好,世界”时 编码器是针对:Hello Word 解码器是针对:你好 世界 Decoder会计算“你好”这个query分别与“Hello”、“Word”这两个key的相似度 很明显,“你好”与“Hello”更相似,从而给“Hello”更大的权重,最终把“你好”对应到“Hello”,达到的效果就是“Hello”翻译为“你好”
2.3 最后的线性层和softmax层
Decoder输出的是一个浮点型向量,如何把它变成一个词?这就是最后一个线性层和softmax要做的事情。
线性层就是一个简单的全连接神经网络,它将解码器生成的向量映射到logits向量中。
假设我们的模型词汇表是10000个英语单词,它们是从训练数据集中学习的。那logits向量维数也是10000,每一维对应一个单词的分数。
更多细节请继续参看此文>>https://blog.csdn.net/v_JULY_v/article/details/130090649
新课上线
七月在线【机器学习集训营 第十九期】上线啦
本期新增大模型和线下,前10人3999秒杀(第11人起则4399),报名加送2年VIP和2年GPU。
↓↓↓扫码抢课↓↓↓
有意找苏苏老师(VX:julyedukefu008)或七月在线其他老师了解课程
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.