李沐读 Transformer 视频笔记

您所在的位置:网站首页 query词根 李沐读 Transformer 视频笔记

李沐读 Transformer 视频笔记

2023-04-08 10:55| 来源: 网络整理| 查看: 265

原始题目Attention Is All You Need中文名称注意力就是你所需的一切发表时间2017年6月12日平台NIPS 2017来源Google Brain文章链接https://arxiv.org/abs/1706.03762开源代码https://github.com/huggingface/transformers/blob/main/README_zh-hans.md视频讲解https://www.bilibili.com/video/BV1pu411o7BE介绍

Transformer 模型,是大家投票分数最高的一个模型。这篇文章可以认为是最近三年以内,深度学习里面最重要的文章之一。它可以认为是开创了继 MLP、 CNN 和 RNN 之后的第四大类模型。在上个月,斯坦福大学联合了 100 多名作者,写了一篇 200 多页的综述文章,讲述了 Transformer 模型和它之后的一些变种,他们甚至提议说将这一类 Transformer 模型 叫做基础模型,可以见它对整个领域的影响力是有多大。

题目

标题是说 Attention Is All You Need,就是说你就需要 注意力 就行了。当然在英语中这也是一句合法的话,就是说对小孩说,集中一下注意力,不要东看西看。 这个标题成为了一个梗:xxx is all you need。后续出了很多篇文章,然后把 xxx 换成任何想要的词,只要你换掉 xxx,基本上文章能够上头条。

作者

该文有 8 个作者,作者绝大部分都是在 google. 有两个作者不在 google,它做了一个注释: 这两位作者在 google 实习的时候完成的工作。每一个作者后面都打了一个星号,星号在论文里面一般叫做同样贡献。一般来说我们会把前面两个、三个作者打 星号,那么前面两三个作者对这篇文章的贡献是差不多的。因为在机器学习领域,一般会按照作者对文章的贡献,从大到小排序,也就是说第一作者通常对文章的贡献是比较大的。很多时候在绝大部分文章里面,第一作者贡献了 80% 的工作。有一些情况是说,第一二三作者有同样贡献,这个也是常见的。但是说你整个文章 8 个作者,对整个文章的贡献都是均等,这个情况是比较少见的。星号的注释是: Listing order is random,整个作者的排名是比较随机的。第一作者通常会拿到整篇文章最大的 credit,大家都觉得这一篇文章是第一作的功劳。当作者是随机的话,第一作者的位置是赚的。星号的注释是: Jakob 提出了要把 RNN 换成 self-attention;xx 实现了第一个模型; 后面几个人做了一些实验; xxx 最后写了文章; 后面的两个作者重构了一遍 这篇文章的一个开源代码 tensor2tensor。 把每个作者做的贡献写在这里是一个比较好的做法,当一篇文章有比较多作者的时候,能把每个作者对这个文章的贡献,明明白白写出来是一件很好的事情。这也是告诉大家说,这个名字也不是随便挂名的,然后每个人确实需要明明白白给这篇文章做出贡献才能挂名。

摘要

摘要的第一句话是: 在主流的 序列转录模型 里面(所谓的 序列转录模型 就是说 给定一个序列,生成另外一个序列。如:机器翻译任务是:给一句英文,生成一句中文,所以机器翻译模型是一个序列转录模型)。序列转录模型 主要是依赖于比较复杂的 循环神经网络或者卷积 神经网络,一般是用一个叫做 encoder 和 decoder 的架构。这里是假设你知道 encoder-decoder 的架构,CNN 和 RNN 的架构。

第二句话是:在性能最好这些模型里面,通常会 在 编码器和解码器 之间 使用一个叫做 注意力机制 的模块。这篇文章讲的是: 要做序列到序列的生成,现在主流的模型是什么。

第三句话是:这篇文章提出了一个新的简单的架构。比较有意思的是:之前我们都说我们提供一个 novel,就是比较有意思的架构,现在基本上因为我们的模型其实现在都挺复杂,如果你能做一个 simple 的架构,其实也挺好了,只要你的结果好,大家其实还是挺喜欢简单的架构。ResNet,其实也是一个比较简单的架构,所以大家挺喜欢的,这个也是整个研究氛围的一个转变,只要你结果好, simple 这个词不再是一个贬义词而是一个褒义词。这个模型的名字叫Transformer,中文翻译叫做 变形金刚。 你可以说把你的模型取一些大家很熟知的一些名词,就很容易被记住。但是如果你的文章没有出名,大家去搜你文章的时候,根本就搜不到你的文章,还是搜到的是 变形金刚,取名字也是一个非常重要的事情。好的文章一般有一个比较好的名字,之前讲过的 Resnet 的名字其实挺好的,Resnet 很好记。我们讲到第一篇文章 AlexNet,那篇文章根本就没有提到 AlexNet 这个名字,他根本没有给自己的文章取个名字,估计作者也没想到自己会那么火。 AlexNet 的作者只是说 做了一个神经网络。因为 AlexNet 是开创性的工作,大家重复你的结果的时候,总要给你的模型取个名字,所以大家起了一个名字叫做 Alexnet,Alex 来自第一作者的名字。所以文章的作者来讲,如果不想让别人给你取名字的话,给自己取一个比较好的名字比较重要。

Transformer 仅仅依赖于注意力机制,而没有用之前的 循环 或者 卷积。这就是它的贡献了,它提出了一个新的简单模型,跟之前大家表现很好的模型的架构都长得不一样。

接下来说做了两个机器翻译的实验,显示这个模型在性能上特别好。可以并行度更好,然后用更少的时间来训练,模型达到了 28.4 的 BLEU score。

BLEU score 是 在机器翻译里面大家经常用的一个衡量标准。 如果你不做机器翻译的话,你可能不一定明白 BLEU score 是干什么事情,但是没关系。文章写道:在一个 英语到德语 的一个翻译任务,Transformer 比目前的最好的结果 好两个 BLEU。然后 在一个 英语到法语 的翻译的任务上,作者 做了一个 单模型,比所有的模型效果都要好。 它只在 8 个GPU上训练了 3.5 天。 最后说 Transformer 架构能够泛化到一些别的任务,效果很好。

那基本上你可以看到是说文中 提出了一个新的模型,主要用在 机器翻译这个任务上面。所以这篇文章一开始写的时候是针对机器翻译这个小任务写的。所以文章整个写作的时候,作者假设你 前提知识 是知道的,然后提出一个模型,然后 主要在机器翻译上结果很好。一开始做的是机器翻译,这个比较相对来说小一点的领域上面,之所以说小是因为你会发现机器翻译也就那么几家公司关心。能够提供 机器翻译服务的公司,全世界范围来讲也就那么多家,但是随着之后 bert 、gbt,把 Transformer 用在更多的自然语言处理的任务上的时候,整个 Transformer 就火起来了。 最近大家都知道 Transformer 还用在了 图片、video 上面,几乎什么东西都能用。所以这篇文章它是真正的火出圈在其通用性。但是你第一次读这个文章的时候,你可能看到机器翻译这一块,你可能不那么感兴趣。当然了,现在我们知道这篇文章非常重要,因此需要精读。

结论

跟我们之前的做法一样,接下来直接跳到结论。结论的第一句话是说: 文章介绍了 Transformer 模型,这是第一个做序列转录的模型。该模型 仅仅使用注意力,把之前 所有的循环层全部换成了 multi-headed self-attention。 基本上可以看到这篇文章主要是提出了这样一个层。第二句话是说: 在机器翻译这个任务上面,Transformer 能够训练的比其他的架构都要快很多,而且在实际的效果比较好。然后第三段是说:对于这种 纯基于注意力机制的模型 感到非常的激动,未来想把它用在一些别的任务上面,作者觉得可以用在 文本以外的数据上,包括了 图片、语音、video 上。然后作者说 使得生成不那么时序化,也是另外一个研究的方向。其实现在看起来作者多多少少是预测的未来,Transformer 真的在各种别的 数据模态 上以及 使得生成不那么时序化 做的比较好,虽然这些工作基本上都不是由本篇文章作者完成的,都是由别人完成的,但是本文的作者基本上是看准了大方向。最后一句话是说:这篇文章所有代码放在 tensor2tensor 这个库里面。这也是比较有意思的写法,把整个代码放在了结论的最后。但是现在我们知道如果你有代码的话,通常你会把这个代码放在你摘要的最后一句话,因为现在神经网络的文章里面细节通常是比较多的,简简单单的一篇文章,很难把所有的细节写清楚,所以你最好第一时间公布你的代码,让别人能够很方便的重复你的文章,然后这样能扩大你文章的影响力。

1. 引言

第一段导言写的是比较短,可以认为是前面摘要的前面一半的扩充。第一段话是说:在时序模型里面,在 2017 年 时候,最常用的是 RNN ,RNN 包括了LSTM 、GRU。在这里面有两个比较主流的模型:一个是语言模型;另外一个是当输出的结构化信息比较多的时候,会用一个叫做 编码器和解码器的架构。

第二段话是讲 RNN 的特点是什么,同样也是它的缺点是什么。在 RNN 里面,给一个序列,它的计算是把这个序列 从左往右 一步一步往前做。假设一个序列是一个句子,它就是一个词一个词的看。对第 t 个词,它会计算一个输出叫做 h_t (该词 的隐藏状态),然后该词的 h_t 是由 前面一个词的隐藏状态 h_{t-1} 和当前 第 t 个词本身 决定的。这样它就可以把前面学到的历史信息通过 h_{t-1} 放到当下,然后和 当前的词 做一些计算 得到输出。这也是 RNN 为何能够有效处理时序信息的一个关键所在。它把之前的信息全部放在隐藏状态里面,然后一个一个循环下去。但它的问题也来自于此,第一个问题是: RNN 是一个时序一步一步计算的过程,比较难以并行。就是说在算 第 t 个词的时候,算出 h_t 的时候,必须要保证 前面那个词的 h_{t-1} 输入完成。假设 句子有 100 个词的话,那么需要 时序的算 100 步,导致说在这个时间上,无法并行。现在在主流的 GPU 和那些加速器,比如说 TPU ,都是成千上万个线程,无法在这个上面并行的话,导致 并行度比较低,使得在计算上性能比较差。第二个:也是因为这个原因,历史信息是一步一步的往后传递的,如果 时序比较长的话,那么 很早期的那些时序信息,在后面的时候可能会丢掉,如果不想丢掉的话,那可能得要 h_t 比较大。但是问题是:如果 h_t 比较大,在 每一个时间步 都得把它存下来,导致内存开销是比较大。当然文章中也提到,这方面大家在过去这些年做了非常多的改进,不管是并行的改进,以及做一些分解的方法使得 RNN 能够提升并行度,但是本质上还是没有解决太多问题。

第三段,讲的是 attention 在 RNN 上的应用。在这篇文章之前,attention 已经被成功地用在 编码器-解码器 架构里面了。它主要是用在 怎么样把编码器的东西很有效的传给解码器,attention 是跟 RNN 是一起使用的。

最后一段讲的是:这篇文章提出来的 Transformer 是一个新的模型,不再使用之前被大家使用的 循环神经层,而是 纯基于注意力机制。Transformer 是可以并行的,因为之前攻击的是时序神经网络要按时序地做运算,现在做了 attention 之后,可以完全并行。因为 Transformer 现在 纯用 attention,所以它的并行度是比较高,这样子的话 Transformer 能够在比较短的时间之内,做到一个跟之前模型比可能更好的结果。

总体来看这个导言是写的比较短的,可以认为是摘要的前面几句话的一个稍微的扩充版本,对自己提出的方法也就是一句话带过。这么写的原因,我觉得应该是因为这篇文章提出来的东西是比较多,Transformer 是一个比较不一样的一个网络,里面有一些核心的东西在里面,然后发表在 NeurIPS 上面,NeurIPS 是一个篇幅比较短的一个会议,它是一个单列的,然后也就 8 页,所以导致要在这么一个短的模板里面写下很多东西是很难的,那就得压缩掉一些东西。

2. 相关工作

第一段是:如何使用卷积神经网络,来替换掉 循环神经网络,使得减少时序的计算。提到了参考文献 [16] [18] [9]的工作。这些工作主要的问题是: 用卷积神经网络对于比较长的序列难以建模。这是因为卷积做计算的时候,每一次它去看一个一个比较小的一个窗口,比如说看一个 3x3 的一个像素块,如果两个像素隔得比较远的话,得需要用很多层卷积,一层一层上去,才能够最后把这两个隔得远的像素给融合起来。但是文章中说如果使用 Transformer 里面的注意力机制,每一次能看到所有的像素,一层就能够把整个序列看到,相对来说就没有这个问题(较长的序列难以建模)。但是文章中又提到 卷积一个好处是可以做多个输出通道,一个输出通道可以认为是它可以去识别不一样的模式,所以文章中说本文的模型也想要 多输出通道的效果,所以提出了 Muti-Headed Attention,即 多头的注意力机制,可以模拟卷积神经网络多输出通道的效果。

第二段讲的是 自注意力机制,这个是 Transformer 里面一个关键性的点。文章中说 自注意力机制 之前已经有人提出来了,并不是本文的创新。

文章中提到 memory networks,在 17 年的时候这也算是一个研究的重点吧,如果大家不知道的话,可以跳过。

在我们 best knowledge 里面,Transformer 是第一个只依赖于 自注意力,来做这种 encode到decode 的架构 的模型。

关键是说要讲清楚,跟你论文相关的那些论文是谁,跟你的联系是什么,以及说你跟他们的区别是什么。

3. 模型架构

深度神经网络的论文里面最重要的就是这一章。那一章讲你的神经网络长什么样子。第一句话说这些序列模型里面,现在比较好的是一个叫做 编码器和解码器的架构。然后文章中解释一下什么是编码器解码器,对编码器来讲,它会将一个长为 n 的 (x_1,...,x_n)的输入,假设输入是一个句子,有 n 个词,那么 x_t 表示第 t 个词,序列编码器会把 x_t 表示为 z_t, z_t 是 x_t 对应的一个向量的表示。假设 输入 是一个句子,那么 z_t 就表示第 t 个词的一个向量的表示,这就是编码器的输出。这样一些原始的一些输入就变成一个机器学习可以理解的一系列的向量。 有了 编码器的输出,然后解码器会生成一个长为 m 的一个序列。首先注意到 n 和 m 是可以不一样长的,比如说英文句子翻译中文句子,那么两个句子很有可能是不一样长的。 解码器跟编码器的不一样的是:在解码器里面,词是一个一个生成的。对编码器来讲,很有可能是一次性看全整个句子,比如做机器翻译的时候,可以把整个英语的句子给你。但是在解码的时候,只能一个一个的生成,这个东西叫做一个叫做自回归,叫做 auto-regressivet,在自回归里面,模型的输入又是模型的输出。

自回归:具体来说,在最开始给定 z,那么要去生成第一个输出 y_1,拿到 y_1 之后,就可以去生成 y_2。一般来说要生成 y_t,可以把之前所有的 y_1 到 y_{t-1} 全部拿到。也就是说在机器翻译的时候是一个词一个词地往外蹦,所以 在过去时刻的输出,也会作为当前时刻的输入,所以这个叫做自回归。

Transformer 是使用了一个 编码器-解码器 的架构,具体来说是将一些 自注意力 和 point-wise,fully connected layers 一个一个堆在一起。在下面的 图 1 给大家展现这个架构。 如果你讲 Transformer 的话,很有可能就是把这个图复制一下,然后放到你的 ppt 里面给大家讲。这就意味着说,如果你写论文的话,有一张比较漂亮的能够把整个全局画清楚的图是非常重要的,因为很有可能别人讲你的论文的时候,就是把这个图搬过去。如果你的图画的不够好的话,别人可能还花半天来讲这些东西,如果你画的很好的话,就是一张图能够搞定所有东西,所以就是说在神经网络年代,会画图是一个很基础的技能。但具体到这篇文章的话,这张图画的是挺好的,但是如果你就读到这个地方的话,你会发现其实看不懂这个图的,因为你什么东西都不知道,什么东西没解释,当然我们可以现在可以做一个事后诸葛亮,给大家来讲一下这个到底是在干什么事情。

drawing3.1 编码器和解码器堆叠

首先你看到是一个 编码器和解码器 的架构,左边那块 是编码器,右边那块 是解码器。Inputs 是编码器的输入,比如说中文翻英文,那么 Inputs 就是中文的句子。Outputs 是 解码器的输入,在解码器在做 预测 的时候是没有输入的,实际上 Outputs 就是解码器在 之前时刻 的一些输出 在这个地方作为输入,所以 解码器的输入 写成了 Outputs 。shifted right 是一个一个往右移。输入进来,先进入一个嵌入层,那是大家都要干的事情,就是 进来是一个一个词,嵌入层把 输入 表述成一个向量。positional encoding 等会再讲。左边这块 就是核心编码器的架构, Nx 表示该层有 N 个,即 N 个这样层堆叠在一起。比如 Resnet,一个残差块是一个块,然后把 N 个块堆叠在一起,堆叠成了你的模型。在这个地方你可以认为 这个层 叫做 Transformer block,也是 Transformer 的一个块。具体来看,第一个叫做 Multi-headed Attention,再有 一个前馈神经网络,然后 残差连接,Norm 等会儿再讲。基本上可以看到是说一个 注意力层,再加上一个 MLP(Feed Forward) ,然后在中间有一些 残差连接,然后再有一些的 Normalization。然后你的编码器的输出,就会作为你的解码器的一个输入。解码器 跟 编码器 有点像,解码器的 block 的上面两部分和编码器是一样的。但是下面的一部分叫做:Masked的一个多头注意力机制。

解码器其实就是这三部分组成一个块,然后把它重复 N 次,就组成了 解码器。最后解码器的输出进入一个输出层,然后做一个 softmax 就会得到你的输出,这个输出层是标准的神经网络的做法。Transformer 确实是一个标准的 编码器-解码器 的架构。只是 具体的编码器和解码器构成,跟之前的 编码器-解码器 架构 是不一样的。还有一个不同是:编码器的输出怎么传递给解码器是和之前是不一样的。

接下来我们看一下每一个具体模块是怎么实现的。其实下面的文章跟上面写的很像,就是非常的简洁,所以你第一次读的话,很有可能会遇到一些困难,但没关系,我们这里会给大家仔细的讲一下。首先他给大家介绍了一下它的编码器,从编码器它是用一个,n 等于六个的一个完全一样的层,也就是之前我们画的这一块,给大家看一下,就是说,他把这个东西叫做 layer,然后再用,重复六个 layer 出来,他说每个 layer 里面会有两个 sub-layers,就是一个子层,第一个 sub-layer 叫做 multi-head self-attention,这个词已经出现很多次了,但是现在没有解释,他在之后才会解释。第二个子层是用的,名字很长,他说是 simple,然后是 position-wise fully connected,feed-forward network,后面这个词是一个词,它说白了就是一个MLP,然后所以他为什么加一个simple,这里就是一个MLP,但是他为了显得,fancy一点,就把名字搞得特别长,但我们之后再来解释。 他说对每一个子层,他用了一个残差连接,但我们上一期已经讲过残差连接了。他说最后我们在使用一个叫做,layer normalization的东西,解释完这一些之后,他说我这个子层,其实它的公式要写出来就是长成这个样子的。给大家画个线,就是说你的输入 x 进来,然后先进入你的那个子层,你是自注意力也好,mlp 也好,然后因为是残差连接,他就把输入和输出加在一起,最后进入他的LayerNorm.然后说他说为了简单起见,因为我的残差连接需要你的输入和输出是一样大小,如果不一样大小的话,你得做投影。从而为了简单起见,我就把每一个层它的输出的维度变成 512,也就是说你对每一个词,你不管在哪一层,我都做了,是 512的长度的表示。 这个我们之前讲的 CNN 是不一样的,或者我们之前做 MLP 的时候,经常会把维度往要么是往下减,要么 CNN 的话是空间维度往下减,但是channel维度往上拉,但是这个地方其实它就是固定长度来表示,使得这个模型相对来说是比较简单的,然后调参也就调一个参就行了,另外一个参数说你要复制多少块,所以这个简单设计影响到后面一系列网络。他说,bert 怎么样 GBT 怎么样,实际上也就是两个参数是可以调的,你就要多少层,然后每一层里面那个维度有多大,也就是这两个参数。

接下来给大家解释一下什么是 LayerNorm,可能你不做这一块的话,可能之前是不知道 LayerNorm。LayerNorm也是因为Transformer,这篇文章被大家广为知道。 再给大家解释一下,另外一个是说如果你写篇文章的话,你说我用了别人的东西,最好在文章里面,真的讲一下它是什么东西,你不能真的指望别人都知道所有的细节,能能够花几句话讲清楚是不错,不然的话别人还得去点开那个链接去看一下到底,是什么东西,是给大家带来了困难。

接下来我们通过跟batch norm来对比来解释一下什么是layernorm,以及说为什么我们在这些变长的应用里面不使用batchnorm。 所以我们考了一个最简单的二维输入的情况,二维输入的话我就是输入是一个矩阵,然后我的每一行是一个样本,这是我的 x,这个是我的batch,然后我的每一列是我的一个特征,那么这写的就是一个feature。 batchnorm 的时候干的事情,就是说每一次,我去把我的每一个列,就是每一个特征,把它在一个小 mini-batch 里面,它的均值变成 0 方差变成 1。 你怎么把一个向量变成均值为零,方差为一,这是你把它的这个向量本身的均值减掉,然后再除以它的方差就行了,这个地方你算均值的时候,是在每一个小批量里面,就这条向量里面算出它的一个均值,算出它的一个方差。这个是在训练的时候,你可以做小批量。在预测的时候,你会把一个全局的一个均值给算出来。这个你认为是以整个数据扫一遍之后,在所有数据上那些平均的那个均值方差存起来,在预测的时候再使用。当然 batchnorm 还会去学一个,能把它一个伽马出来,就是说我可以把这个向量通过学习可以放成一个,任意方差为某个值,均值为某个值的一个东西。layernorm 跟 batch norm在很多时候是几乎是一样的,除了他做的,方法有点不一样之外。如果同样的是我这一个二维输入的话, layernorm 干的事情就是对每个样本,他做了,normalization,而不是对每个特征做了,就之前我是把每一个列它的均值变0方差变1,现在是我把每一个行变成,均值为零,方差为1,这个行就表示的是一个样本。所以你可以认为这个 layernorm 就是整个把,数据转置一下,放到 batchnorm 里面出来的结果,再转置回去一下,基本上可以得到自己的东西了,这个是你当你的输入是二维的时候,最简单的情况。但是在我们的Transformer里面,或者说正常的 RNN 里面,它的输入是一个三维的东西。因为它,输的是一个序列的样本,就是每一个样本其实是里面有很多个元素对吧,它是一个序列,你给一个句子里面有 n 个词,所以每个词有个向量的话,还还有一个 batch 的话,那么就是个3D的东西。

drawing

我们把它画出来,就是长成这个样子的这个地方,还是你的 batch,还是你的样本,列不再是我的特征了,而是我的那个序列的长度,我们,写成 sequence,然后对每一个sequence就是对每个词,我当然是有自己的向量嘛,那我再画一个额外的维度画在这个地方,这个就是我的feature了,如果在之前的话,Transformer里面这个地方就是长,这个东西长的就是n,那feature就是 d,d在刚刚我们设成了512,那么如果你还是用 batch normalization的话,你每次是取一个特征,然后把它的每个样本里面所有的元素,这那个序列的元素,以及它的整个batch全部搞出来,把他的均值变成零,方差变成1就是说我这么切一下,切一块出来,把它拉成个向量,然后跟之前作一样的运算。 如果是layernorm的话,那么就是对每个样本就是,就是这么切一下我用黄色来表示,就那就是用这么切一下,这么横着切一下,就这两种切法不一样,但说切法不一样,它是会带来不一样的结果。具体来说为什么 layer norm 用的多一点,一个原因是说,在持续的这些序列模型里面,你的每个样本的长度可能会发生变化,比如说我们这个地方,我们可能会我们的样本是一个,第一个样本的长度是呃这样长的,第二个样本可能会长一点,第三个样本可能会短一点,第四个样本是中间长,可能是,长度是这样子变换的。那些没有的东西,一般我们是把它放上零进去,那我们看一下这两种切法会有不一样什么的结果。如果是用 batch norm 的话,我切出来效果就是一个跟画出来结果一样,对每一个特征你切出来东西会是一个这样子的东西,剩下的东西当然是填的是零了。如果是,layernorm 的话他其实会,就第一个样本,它切出来长度是一个这样子的长度,第二个样本当然会长一点,是这样子的长度,第三个是短一点,第四个是中等长度,这里的主要的问题是在算 均值 和方差的上面,对于 batchnorm来说,我算均值的时候其实是通过这样子来算的对吧,但是我画线阴影的区域的只是有效值,别的值的话其实没什么太多用,你会发现你的如果你的样本长度变化比较大的时候,你每次做,小批量的时候,你算出来的均值方差,它的抖动相对来说是比较大的,而且这个另外一个问题是说,因为我们记得我们在做预测的时候,我们要把这个全局的均值和方差记录下来,那么这个全局的均值方差,如果碰到一个新的预测样本,如果特别特别长,怎么办,我碰到一个那么那么长的东西,那么,我是不是在训练的时候没见过那么伸出去那么多,那么我在之前算的均值和方差,很有可能是不那么好用的。 但反过来讲,对layernorm相对来说没有太多这个问题,是因为它是每个样本自己来算我的均值和方差,我也不需要存在一个全局的一个均值方差,因为这东西是对每个样本来做的,所以相对来说你不管样本是长还是短,反正我算均值是在你自己里面算的,这样子的话相对来说它稳定一些。 这也是 layernorm 大家去看那篇文章的时候,他是给,大家这么解释的。但实际上来说我们知道哈一个很,好用的一个东西,原文写的东西可能和之后大家的理解是不一样的,在,在之后又有一篇文章来解释为什么 layernorm有效是更多是从一个对梯度呀,对于输入的那些normalization,然后提升它的 lipschitz 常数来解释的。

再讲完编码器的架构之后,我们来看一下解码器,解码器跟编码器是一个很像的东西,首先它跟编码器一样是由 n 等于6个,同样的层构成的,每个层里面跟编码器有两个一样的子层,但是不一样的在于说,解码器里面用了一个 第三个子层 ,它同样是一个多头的注意力机制,他说跟编码器一样我们同样的用了残差连接和 layernorm 。另外一个是我们知道在解码器的时候,他做的是一个自回归,也就是说你当前的输出的输入集是上面一些时刻的输出,意味着是说你在做预测的时候,你当然不能看到之后的那些时刻的输出。但是我们知道在注意力机制里面,他每一次能看到整个完整的输入,所以这个地方我们要避免这个情况发生。也就是说在解码器训练的时候,再预测第 t 个时刻的,输出的时候你不应该看到t时刻以后的那些输入,它的做法是通过一个带掩码的注意力机制。如果你回过头来看这个图的话,你会发现这个地方是有一个 masked 的,你别的那些黄色的块都是多头的注意力,但是这个地方是有个masked,保证你输入进来的时候,在 t 时间是不会看到 t 时间以后的那些输入,从而保证你训练和预测的时候行为是一致的。

在看完我们的编码器和解码器的架构之后,我们来看一下每一个子层具体是怎么定义的,当然我们先要看到的是注意力层。第一段话就是一个对注意力的一个非常一般化的介绍,属于你,懂的话,你看完之后就懂了,如果你不懂的话,你看完之后可能还是不懂,但不管怎么样,我们就按照这一段话给大家来解释一遍。

首先他说,注意力函数是一个将一个 query,和一些 key-value 对 映射成一个输出的一个函数,这里面所有的query呀 key value和output,它都是一些向量,具体来说你的 output 是你的 valve 的一个加权和,所以就导致说你的,输出的维度跟你的 value 的维度是一样的。 另外一个是说这个权重是怎么来的,对于每一个value的权重,它是这个 value 对应的 key,和你这个查询这个 query 的相似度算来的,这个相似度,或者叫做compatibility function,不同的注意力机制有不同的算法。

如果我们画一个简单示意图,可以长成这样子,假设我有三个 value 和三个对应的 key,假设我们现在给一个query,这个 query 跟 第一个 和 第二个 key 比较近,就是放在这个地方,那么你的输出就是,这三个 v 的相加,但是这个地方的权重会比较大一点,这个地方权重也可能比较大,但是这个地方的权重就会比较小一点,因为这个权重是等价于你的,query 和 你对应的key 的那个相似度。 同样道理,我假设再给你一个query,但是他是跟最后那一个 key 比较像的话,那么这样子你再去算他的 v 的时候,就会发现它对后面的权重会比较高一点,中间权重也还不错,最后的权重是比较小一点,就会得到一个新的输出。虽然你的 key value 并没有变,但是随着你 query 的改变,因为权重分配不一样,导致你的输出会有不一样,这就是注意力机制。

因为不同的相似函数导致不一样的注意力的版本,所以接下来这一章就讲的是 Transformer 自己用到的这一个注意力是什么样子计算的。他取的名字叫做 scaled dot-product attention,虽然名字比较长,实际上是最简单的注意力机制了,他说我这个里面我的 query 和 key,它的长度是等长的,都等于 dk,因为你可以不等长,不正常是有别的办法算的,然后它的 value 它的是 dv,当然你的输出也一样的是 dv 了。 他说我具体计算的是说,我对我每一个 query 和我的 key 做内积,然后把它作为相似度。你可以认为两个向量做内积的事儿,如果这两个向量的 模长 是一样的话,那么你的内积的值越大,就是它的余弦值,那么就表示这两个向量的相似度就越高。如果你的内积比如零了,那就等于是两个向量正交的,就是没有相似度。然后算出来之后,他再除以 根号dk,就是你这个向量的长度,然后再用一个 softmax 来得到你的权重。 因为你给一个query,假设给 n 个 key value pair 的话,那么就会算出 n 个值,因为这个 query 会跟每个 key 做内积。 算出来之后再放进 softmax 就会得到 n 个非负的,而且加起来和等于一的一个权重。 对于权重我们觉得当然是非负加起来等于一,就是比较好的权重。然后我们把这些权重作用在我们的 value 上面,就会得到我们的输出。

在实际中当然,我们不能一个一个这么做,运算算起来比较慢,所以他下面给了一个在实际中的时候,我们应该怎么样算的。他说我的 query 可以写成一个矩阵,就是我其实可能不止一个 query,我有 n 个query。 那我们画出来就是一个假设,是一个 Q 是 n 行,维度等于 dk ,同样道理的话 K,也是一个同样的东西,但你的可能会长一点或者 短一点 都没关系。设 query 数和 key 个数可能是不一样的,但是它的长度一定是一样的,这样子我才能做内积。然后给定这两个矩阵,我把它一乘就会得到一个 n 乘以 m 的一个东西对吧,所以这个东西里面它的每一行,就这个蓝色的线就是一个 query,对所有 key 的那一个内积值,然后我们再除以 根号 dk,在做 softmax,所谓的 softmax 就是对每一行做 softmax。然后每一行之间是独立的,这样子就会得到我的权重。然后再乘以我的 V,V 是 m 行,列数是 dv 的一个矩阵。然后这两个矩阵乘的话就会得到一个长为 n 乘以 dv 的一个东西。我们写的这个地方n乘以 dv,那么这个地方每一行,它就是我们要的一个输出了。所以这里你可以看到是说对于组 key value 对,和你 n 个 query 的话,我可以通过两次矩阵乘法做完整个计算。这些 query ,key value 在实际中对应的就是我的序列。所以这样导致基本上可以并行的计算里面每个元素,因为矩阵乘法是一个非常好并行的东西。

image

接下来一段他说我提出来的注意力机制跟别的的区别是什么样子。他说一般有两种比较常见的注意力机制。一种叫做加性的注意力机制,它可以处理你的,query 和你的 key 不等长的情况;另外一个叫做点积的注意力机制,他说 点积的注意力跟我的机制是一样的,除了我这里 除了一个个数之外。所以你可以看到它的名字,它叫做 scaled 就是除了那个东西,然后是 点积注意力机制。

接下来他说这两种注意力机制,其实都差不多,但是它选用的是点乘,这是因为这个实现起来比较简单,而且会比较高效,因为这就是两次矩阵乘法就能算好。当然你需要解释一下,你为什么不直接用最简单的 点乘注意力,你为什么要这里要出一个根号dk。他说呀,当你的dk不是很大的时候,其实你除不除都没关系。但是当你的 dk 比较大的时候,也就是说两个向量,它的长度比较长的时候,那么你做点积的时候,这些值可能就会比较大,但也可能是比较小了。当你的值相对来说比较大的时候,你之间的相对的那些差距就会变大,就导致说你值最大的那一个值做出来 softmax 就会更加靠近于1,剩下那些值就会更加靠近于零。就是你的值就会更加像两端靠拢,当你出现这样子的情况的时候,你算梯度的时候,你会发现梯度比较小。 因为 softmax 最后的结果是什么,最后的结果就是我希望我的预测值,置信的地方尽量靠近1,不置信的地方尽量靠近零,这样子的时候,我说我的收敛就差不多了,这时候你的梯度就会变得比较小,那你就会跑不动。所以他说我们在 Transformer 里面一般用的,dk 比较大,之前说过是 512,所以除以一个根号 dk 是一个不错的选择。

整个这个注意力的计算,他在上面有张图给大家画了出来。可以看到你在里面要两个矩阵,一个是 query 一个是 key 做矩阵乘法,然后再除以 根号dk,这个地方我们一会儿讲,然后再做 softmax,做出来结果最后跟你的值的那个矩阵做矩阵乘法就会得到你的输出了。这个是通过计算图来展示你这个是怎么做的。

image

另外一个我们要讲到是怎么样做 mask。mask 主要是为了避免,你在第 t 时间的时候看到以后时间的东西。具体来说,我们假设我们的 query 和 key 是等长的,他们长度都为 n,而且在时间上是能对应起来的。然后对第 t 时间步的 qt ,这是我的 query,那么我在做计算的时候,我应该只是看 k1 一直到 kt-1,而不应该去看了 kt 和它之后的东西,因为 kt 在当前时刻还没有。 但是我们知道在注意力机制的时候,其实你会看到所有,qt 会跟所有 k 里面的东西全部做运算,就是 kt 一直算算算算到 kn。那这个时候怎么办,就是说我们发现其实你算还是可以算的,就是说你把这些值全部给你算出来。,然后在算出来之后,我们只要保证说在计算权重的时候,就是算输出的时候不要用到后面的一些东西就行了。具体来说他就在你这个地方加了一个 mask。mask 的意思是说,对于 qt 和 kt 和它之后的计算那些值,我给你换成一个非常大的负数(这里应该是 q 和 k 结果做了替换)。比如说 负的一十次方。那么这一个那么大的负数在,进入 softmax 做指数的时候,它就会变成零。所以导致 softmax 之后出来的这些东西,它的它对应的那些权重都会变成零。而只会前面这些值出效果,这样子的话我在算我的 output 的时候,我只用了 v 对应的 v1 一直到 vt-1 的结果就用上了它,而后面的东西我没有看,所以这个 mask 效果是在我训练的时候,我让你 t 个时间的 query,只看我对应的前面那一些的 key value pair,使得我在做预测的时候,我跟现在这个是能够一一对应上的。

大多数同学的问题是: 可不可以直接对 softmax 后的结果 直接 置0? 如果这么做,那么 加起来的权重 的和就不是 1 , 很可能加起来是一个很小的值,然后 该向量再和 v 相乘,就无法有效提取信息。

在讲完注意力机制的计算之后,我们来看一下 multi-head 是在干什么事情。 我们首先还是回到我们的文字那部分,他这里说与其我做一个单个的注意力函数,不如说我把整个 query 呀,key value呀投影到一个低维,投影 h 次,然后再做 h 次的,注意力函数,然后每一个函数的输出,我把它并在一起,然后再投影来会得到我的最终的输出。他说我们在图二给大家演示这个效果。

那我们就跳回 图二 看一眼它是怎么做的,然后他说这个是我们原始的 value key query,然后在这地方我们,进入一个线性层,线性层就是把你投影的比较低的维度。然后再做一个,scaled dot-product attention,就是这个东西了,全部放进来。然后我们这里做 h 次,会得到h的输出,我们再把自己这些向量全部,合并在一起,最后做一次线性的投影,会回到我们的multi-head attention。

所以为什么要做多头注意力机制? 如果我们回过头来看这个,dot-product 的注意力的话,你会发现里面没有什么可以学的参数。 你的 距离函数就是你的内积。 但有时候我为了识别不一样的那些模式,我希望你可能有一些不一样的计算像素的办法,如果你用的是加性 attention 的话,这里没有提到的,那里面其实还是有一个权重,你来学的,你也许可以学到这些东西。 他说那我不用那个,那我用这个的话,我的一个做法是我先让你,投影到一个低维,这个投影的 w 是可以学的。也就是说我给你 h 次会,希望你能学到不一样的投影的方法,使得在那个投影进去的那个度量空间里面,能够去匹配不同模式它需要的一些相似函数,然后最后把这些东西回来,最后再做一次投影。所以跟我们之前说到的有点点像,在卷积网络里面,你有多个输出通道的感觉。

然后我们看一下这个东西的具体的公式是怎么算的,那你会发现是在 Multi-Head 的情况下,你还是以前的 Q,K,V,但是你的输出已经是你不同的头的,那一个输出的做 concat 起来,再投影到一个 WO 里面的。 对每一个头,他就是把你的 qkv,然后通过一个不同的可以学习的WQ,WK,WV,投影到一个 低维 上面,在做我们之前提到过的注意力函数,然后再出来就行了。 这个地方你可以说每一个里面当时怎么算的。 他们在实际上来说,他用的 h 是等于 8 的,就是用 8 个头。 而且我们知道你的注意力的时候,因为有残差连接的存在,使得你的输入和输出的维度,至少是一样的。所以它的做法是说你投影的时候,它投影的就是你的输出的维度,除以 h,因为我们之前我的输出维度是 512,所以除以 8 之后,就是每一次我们把它投影到一个 64 维的一个维度。 然后在上面算你的注意力函数。然后再 并起来再投影回来。虽然这个地方你看到是非常多的小矩阵的乘法,实际上你在实现的时候也可以通过一次的矩阵乘法来实现,这个可以作为一个练习题,大家去看一下怎么样实现它。

在讲完多头注意力是如何实现了之后。在 3.2章 的最后个小节里面讲的是,在 Transformer 这个模型里面是如何使用注意力的。他这里讲了三种使用的情况,我们最简单的方法是回到我们之前那个架构图,看一看它到底是怎么被用的。我们回到我们的架构图,我们看到的是 黄色这个东西表示的是 注意力的层,这个地方一共有三种不一样的注意力层 。首先我们看一下我们的编码器的注意力是在干什么事情,然后我们分别来看一下每一个注意力层,它的输入和输出分别是什么,这个其实对应的是刚刚我们那一小节里边的三段话。

首先我们来看一下我们的 编码器的注意力 是在干什么事情。我们知道编码器的输入,这个地方,假设你的句子长度是 n 的话,它的输入其实是一个 n 个长为 d 的向量,假设我们的 bn 大小设成 1 了。 我们把它画出来,就是每一个输入它的词,对应的是一个长为 d 的向量,然后我们这里一共有 n 个这样子的东西。然后我们来看一下注意力层,它有三个输入,他分别表示的是 key,value 和query。然后这个地方是你一根线过来,然后他复制成了三下。意思就是说,同样一个东西,我既作为 key,也作为 value,也作为 query,所以这个东西叫做自注意力机制,就是说你的key, value和query其实就是一个东西,就是自己本身。 然后我们知道,那么这个地方我们输入了 n 个query,那么 每个 query 我会拿到一个输出,那么意味着我会有 n 个输出。 而且这个输出和 value,因为长度是一样的话,那么我的输出的维度其实也是那个 d,就是意味着我的输入和输出的大小其实是一个东西。但我们也可以把它画出来,画出来的话,其实你就是你的输出也是跟它长度一样长,长为 n 的一个东西。 对于每一个 query,我会计算一个这样子的输出。 因为我们知道这个输出,其实就是你的 value 的一个加权和,权重是来自于query和key的一些东西。 但它本身是一个东西,那么就意味着说,他的这个东西,实际上本身就是你的输入的一个加权的一个和。然后这个绿色线代表权重的话,因为这个权重其实本身就是这一个向量,这个向量跟每一个输入的别的向量计算相似度,那么他跟自己算肯定是最大的。就是说你这根线肯定是最粗的。 假设这个线跟你最这边这个向量也相似度比较高的话,那么这个权重也会稍微高一点。 假设我们不考虑多头和有投影的情况,你的输出其实就是你的输入的一个加权和,你的权重来自于你自己本身跟各个向量之间的一个相似度。 但如果我们说过有多头的话,因为有投影,其实我们在这个地方会学习 h 个不一样的距离空间出来,使得你出来的东西当然是会有一点点不一样了。 这个就是第一个,注意力层是如何用的。

然后你看到嗯,解码器解码器是一回事,它这个地方一样的,是一个东西过来,然后复制成了三次。 然后解码器的输入也是一样的,只是长度可能变成了一个长为 m 的样子,然后你的维度其实也是一样的,所以它跟编码器是一样的,自注意力。 唯一不一样的是,这里有个 masked 这个东西。我们之前有解释过,在解码器的时候,比如说你算这一个 query 它对应的输出的时候,它是不应该看后面那些东西。所以意味着是说在解码器的时候,你的这些后面的东西要设成零,我们用黄色的线表示一下,就是说后面这些东西这些权重你要设成零,在解码器的时候,这就是 masked 它的一个作用。

然后我们看 第三个注意力层,也就是在这个地方。这个地方你看到是他不再是自注意力了,而是,你的key和你的value来自于你的,编码器的输出,然后你的,query 是来自于你下解码器下一个 attention 的输入。我们知道你的 编码器最后一层的输出,就是 n 个长为d的向量。我们把它画在这个地方。 那么你的 解码器的 masked attention,就最下面那个attention它的输出是,m 个也是长为d的向量,我们也把它画在这个地方。 这里你的编码器的输出作为 value 和 key 进来,然后你的解码器上一层的输出作为 query 进来。 意味着是说对 解码器的每一个输出,作为 query 我要算一个我要的输出。 假设我用蓝色的表示的话,那么你的输出是我们知道是来自于 value 的一个加权和,那我就是来自于你的,编码器它的输出的加权和。我如果把它画过来,就是有一个这样子的东西过来,一些权重过来,这个权重它的粗细程度,就是取决于,我这个query,跟这个东西的相似度。假设我这个东西跟这个东西相似度比较高的话,那么我的权重这个地方就会大一点。如果相似度比较低的话,权重就会小一点。那意味着是说在这个 attention 干的事情,其实就是去有效的把你的,编码器里面的一些输出,根据我想要的东西给它拎出来。

举个具体的例子,假设你是在做英文翻译中文。我假设第一个词是 hello,对应的向量是这个东西,然后我第二个词是 hello world 的话。那么你的中文他就是第一个,当是你对吧,你好,所以你会知道说在算,好的时候,如果它作为query的时候,那么去看,hello的这个向量应该是会相近一点,给它一个比较大的权重,但是 world,这个是后面的次相关,我发现到word这个词跟我这个query,相关度没那么高,在计算你的相似度的时候,那么就是说在算好的时候,我会给它一个比较大的权重,在这一个上面,但是我在后面如果还有你好,世界的话,如果是个 世 的话,那么在这个query的时候,我再去算它的输出这个东西的时候,它那么就会给第二个向量,给一个比较大的一个权重出来。

意味着是说根据你在 解码器的时候,你的输入的不一样,那么我会去,根据你的当前的那一个向量,去在编码器的输出里面去挑我感兴趣的东西,也就是你,注意到你感兴趣的东西,那些没有跟你不那么感兴趣的东西,你就可以忽略掉它。这个也是说 attention 是如何在,编码器和解码器之间传递信息的时候起到的一个作用。这样我们就讲完了,Transformer里面三个attention,他到底是在干什么事情。

那么接下来我们要去讲蓝色这个 feed forward,是在干什么东西。3.3节 讲的就是这个名字很长的 point-wise feed forward network。他说他其实就是一个 fully connected feed-forward network,他就是一个 MLP ,但是他不一样的是说他是 applied to each position seperately and identically。 position 是什么东西,就是你输入的那一个序列,不是有很多很多个词吗,每个词它就是一个点,他就是那一个 position,然后他就是把一个MLP对每一个词作用一次,然后对每个词作用的是同样一个 MLP。所以这个就是 point wise 的意思。它说白了就是 MLP 只是作用在最后一个维度。具体来看一下它是怎么写的,这个东西大家认识对吧,就是一个线性层,max0到这个东西就是一个Relu的激活层,然后再有一个线性层。我们知道在我们的注意力层,它的输入,就每一个 query 它对应的那一个输出,它是长为 512。那么就是说这个 x 就是一个 512 的一个向量。 他说,w1 我会把 512 投影成 2048 这个维度。就等于是我把它的维度扩大了四倍,因为最后你有一个残差连接,你还得投影回去,所以 W2 又把2048投影回了512。所以这个东西说白了就是一个,单隐藏层的MLP,然后中间隐藏层,把你的输入扩大四倍,最后输出的时候也回到你输入的大小。说白了就是你用 pytorch 来实现的话,它其实就是把两个线性层放在一起,你都不需要改任何参数,因为 pytorch 去当你的输入是一个 3d 的时候,它默认就是在最后一个维度做计算。为了更好的理解,我们用图把它跟你的 attention 这个东西给大家画一下,以及说它跟我们之前的 rnn 它的区别在什么地方。

我们这里还是考虑一个最简单的情况,就是没有残差连接,也没有 layernorm,然后你的attention 也是一个单头,然后没有 投影。 我们知道 输入就是一个,长为 n 的一个一些向量,我们画在这个地方,在进入 attention 之后,我们就会得到同样长度的一些输出。在这个地方,在最简单的情况的 attention,其实说白了就是对你的输入,做一个加权的和。然后加权和之后我们进入我们的 MLP,就是那个 point wise 的 MLP。 我们把它画在这个地方,我们写个 MLP,虽然我们画了几个东西,但其实它就一个就是说每一个,红色的方块之间的权重是一样的。 然后每个 MLP 对每一个输入的点做运算,会得到一个输出,最后就得到了整个 Transformer块 的一个输出是这样子。虽然它的输入和输出都是大小都是一样的。所以这个地方你看到的是说, attention 起的作用是什么东西,他就是把整个序列里面的信息抓取出来,做一次汇聚aggregation。所以这个东西已经就有了我序列中感兴趣的东西,信息已经抓取出来了。以至于我在做投影,在做 MLP 的时候,映射成我更想要的那个语义空间的时候,因为这个东西已经含有了我的序列信息,所以每个 MLP 只要在对每个点独立做就行了。因为这个地方历史信息,因为这个地方序列信息已经被汇聚完成,所以这个地方是可以分开做的。也就是整个 Transformer 是如何抽取序列信息,然后把这些信息加工成我最后要的那个语义空间那个向量的过程。

作为对比,我们看一下 rnn 是怎么做的,我们知道 rnn 的输入跟你是一样,就是一些向量。 然后对于第一个点,说白了你也就是做一个线性层,我们做一个最简单的就是一个没有隐藏层的 MLP 就是一个纯线性的层,第一个点就是直接做出去就完事了。对于下一个点,我是怎么样利用我的序列信息的,我还是用之前这个 MLP 它的权重跟之前是一样的,但是我的时序信息,我用绿色表示,它就是把这个东西它的上一个时刻的输出放回来,作为跟输入一起并入进去。这样子我就完成了我信息的一个传递。然后用,绿色的线表示的是之前的信息,蓝色的线当时表示的是我当前的信息,这样子我会得到一个当前的一个输出,历史信息就是上一次的那个输出作为历史信息进来,然后得到我当前的一个输出。所以可以看到是说 rnn 跟 Transformer是一样的,都是用一个线性层,或者说一个 MLP,来做一个语义空间的一个转换。但是不一样的是你如何传递序列的信息, rnn 是把上一个时刻的信息,输出传入下一个时候做输入,但是在Transformer里面,它是通过一个 attention 层,然后再全局的去拉到整个序列里面信息,然后再,用 MLP 做语义的转换,这个是两个模式之间的区别。 但是它的关注点,都是在你怎么有效的去使用你的序列的信息。

好这样我们就只剩最后两个层了。第一个层是叫做 embedding,后面一个层叫做 positional encoding。 这 embedding 大家都知道一个。因为输入是一个个词,或者叫 词元、token。那我需要把它映射成一个向量,embedding 就是说给任何一个词,我学习一个长为 d 的一个向量来表示它。这个 d,当然这个地方你可以认为就是等于 512 了。他这里是说你的编码器要一个 embedding,你的解码器的输入也要有个 embedding,在 softmax 前面那个线性也需要一个 embedding。他说我 这三个是一样的权重,这样子我训练起来会简单一点。另外一个有意思的是说他把权重,乘了一个 根号d,d 就是512。为什么做这个事情是因为你在学embedding的时候,多多少少会把每一个向量,它的 L2 Norm 学成相对来说比较小的。比如说学成1吧,就不管你的维度多大的话,最后你的值都会等于 1,那就是说,你的维度一大,你学的一些权重值就会变小。但是你之后我要加上这个东西(positional encoding),加这个东西的时候,它不会随着你的长度变长,它把你的 Norm 固定住,所以它乘了 根号d 之后,使得这么两个(embedding 和 positional encoding)相加的时候,在一个 scale 上大家都差不多。所以乘以了 根号d。

下面一个东西叫做 positional encoding。你为什么有这个东西,是因为你发现,attention 这个东西是不会有时序信息的。你想一想你的输出是什么东西,你的输出是你的 value 的一个加权和,你这个权重,是 query 和 key 的之间的那个距离,它跟你的序列信息是无关的,就我根本就不会去看你那个 key value 对 在序列里面哪个地方。所以意味着说我给你一句话,我把顺序任何打乱之后,我 attention 出来结果都是一样的,顺序会变,但是值不会变。这个当然是有问题的对吧,在处理时序数据的时候,我给你一句话,假设我把里面的词给你完全打乱,那么在,你语义肯定会发生变化,但是你的 attention 不会处理这个情况,所以我需要把时序信息加进来。RNN 是怎么加,而是说上一个时刻的输出,作为下一个时刻的输入来传递我的历史的信息,所以它本来就是一个时序的一个东西。 但是要 attention 不是,他的做法是说我在我的输入里面加入时序信息。就是说你这一个词,他在一个位置 i,i 这个位置这个数字 12345 ,加到你的输入里边,所以这个东西叫做 positional encoding。具体来说他后面给了一个公式,它是具体是怎么算的,我就不给大家完全讲个公式给大家讲一下,大概的思路是什么样子。

在计算机里面我们怎么表示一个数字? 假设我用一个 32位的整数 来表示数字的话,那就是用 32 个 bit,每个bit上面有不同的值来表示,012345678。 你可以认为就是说我一个数字是用 一个长为32的一个向量 来表示的。 现在,我一个词在嵌入层,会表示成一个长为 512 的向量,同样我用一个长为 512 的向量来,表示一个数字,表示你这个位置012345678。 具体那些值是怎么算出来的? 是用周期不一样的 sin 和 cos 函数的值来算出来的,所以导致说,我任何一个值,可以用一个长为 512 的一个向量来表示它。 然后这个长为512的记录了时序信息的一个东西,跟你的嵌入层相加就会完成了把时序信息加进我的数据的一个做法。 如果我们回到之前的架构图的话,你可以看到我的输入进来,进入 embedding 层之后,那么对每个词都会拿到那个向量,长为512的一个向量,然后positional encoding,就是你这个词在我这个句子中的位置,告诉他,他返回给你一个长为 512 的一个向量,表示这个位置,让把这两个加起来就行了。这个东西因为是 cos 和 sin 的一个函数,它是在 正1 负1 之间抖动的,所以 input Embedding 乘以了一个 根号d,使得 input Embedding 后的每个数字也是在差不多的 正1 到 负1 之间,这个数值区间里面。然后进去之后,那么我就完成了在输入里面加入了位置信息。这是因为 编码器这一块是,顺序不变,就是说我的输入的序列,不管我怎么打乱我的顺序,进去之后,我的输出那些值是不变的。最多是,我的顺序发生了相应的变化。所以他就把顺序信息直接在数据里面那个值给加进去了。

好这样我们就讲完了,第三章也就是整个 Transformer 模型的架构。 你看一下第三章其实写的并不长,就那么几页,但是你真的要讲的话,其实你需要花比较长的时间,这也给大家带来了很多困扰。 如果你第一次读这篇文章,而且对这个模型,不那么了解的话,你可能读下来发现,很多细节你都没有搞明白是怎么回事。

第四章是大概解释一下为什么要用自注意力。你发现读到这里为止,整个这篇文章是告诉你我这个模型长什么样子,并没有告诉你说我为什么要这么做,以及我整个设计理念是怎么回事。但这个地方大概给你稍微讲了一下,我为什么要这么做,它主要说的是相对于你用循环层或者卷积层的时候,我用 自注意力有多么好。但是整个来说这篇文章呀,对整个模型的解释其实是比较欠缺的。然后整个这一段话,他解释的其实就是我们这一个表,我们就直接给大家讲一下这个表是在干什么。

他说我们比较了四种不一样的层,第一个当然是他们关注的 自注意力,然后是循环层,卷积层,另外一个是它构造出来一个受限的自注意力。它有三列做比较,第一列是说我的计算复杂度当然是越低越好,第二列是说我的顺序的计算,越少越好。顺序的计算,就是说你下一步计算必须要等前面多少步计算完成,在算一个Layer的时候,你越不要等那么你的并行度就越高。最后一列是说 ,一个信息从一个数据点走到另外一个数据点要走多远,这也是越短越好。我们分别来看一下每一个层它代表的数值,是什么意思。

首先我们来看自注意力层,n 是序列的长度,d 是向量的长度。我们知道整个自注意力的话,其实说白了就是几个矩阵做运算,其中一个矩阵是 query的矩阵 乘以 key的矩阵。 query 矩阵 有n行,n个query,列数是 d,就是维度是 d。key 也是一样的,也是 n 乘 d,所以两个矩阵一乘的话,那么算法复杂度就是 n 的平方乘以 d。另外当然还有一些别的矩阵运算,但是它的复杂度都是一样的。所以是 O(n^2^d) 。

然后 sequential operation,因为你就是那么几个矩阵乘法,矩阵里面它可以认为是并行度比较高的,所以这个地方是一个 O(1) 的。 最大的长度是说,你从一个点的信息想跳到另外一个点要走多少步,我们知道在 attention 里面,就是一个 query 可以跟 所有的key 去做运算,而且你的输出是你 所有value 的一个加权和,所以就是说任何query 跟任何一个很远的一个 key value pair,我只要一次就能过来,所以这个长度是比较短的。

然后看一下循环层,循环层是我们知道就是,如果你的序列是长为 n 的话,它就一个一个做运算。每个里面它的主要的计算就是一个 d 乘以 d 的一个矩阵, 就是一个dense layer,然后再乘以你一个长为 n 的一个输入,所以它是一个 d平方,然后要做n次 所以是 n乘d的平方。

然后你对比一下这两个东西,是有一定区别的,就真的取决于是 n 大还是 d 大。如果你 n 大的话,当然 自注意力 贵一点,你d大的话是 RNN 贵一点。实际上来说你的 d 这个地方是512,你的 n ,也差不多是几百的样子吧。现在当然是说,比较大的模型的话,d 可以做到 2048 呀,甚至更大,你的 n 相对来说也会做的比较,也是几千的样子。所以你其实现在看起来这两个东西都差不多,就是 n 和 d 的,其实在差不多的数据上面,所以这两个都差不多。但是在 RNN 的时候,因为你是要一步一步做运算,当前时间刻的那个词,需要等待前面那个东西完成,所以导致你是一个长为 n 的一个序列化的操作,在并行上是比较吃亏的。我们之前提到过。另外一个是 RNN 最初点的那个历史信息,需要到最后那一个点的话,需要走过 n 步才能过去,所以他这个地方的最长是O(n)。 所以大家会批评,RNN 说你对特别长的序列的时候做的不够好,因为你的信息一开始走走走 就走丢了,而不像attention一样,就可以直接一步就能过去。

然后看一下卷积,卷积我们,大家也没有特别解释卷积在序列上怎么做。具体做法是它用一个 1D 的卷积,所以 kernel 是 k, n是长度,然后 d 就是你的输入的通道数和输出的通道数,所以这个地方是 k 乘 n 乘 d的平方。 k一般也不大,k 一般就 3 、5 、几。 所以这个东西你也可以认为是常数,所以说 卷积的复杂度和 RNN 的复杂度其实是差不多的。但是卷积的好处是说,你就是一个卷积操作就完成了,里面的并行度很高,所以卷积做起来通常比 RNN 要快一点。 另外一个是说卷积每一次一个点是有,一个长为 k 的一个窗口来看的,所以他一次一个信息在, k 距离内是能够一次就能传递。如果你超过k了的话,他要传递信息的话,他要通过多层,一层一层上去,但是它是一个 log 的一个操作(最长路径长度),所以这个东西也不亏。

最后一个是说当我做注意力的时候,我的 query 只跟我 最近的 r 个邻居 去做运算。这样子的话我就不用去算 n平方 这个的东西了对吧。 但他的问题是 有两个比较长的远的一个点,需要走几步才能过来,所以他就损失了 O(n/r)。 一般来说在实际上来说 用attention主要是关心说,特别长的序列,你真的能够把整个信息揉的比较好一点。所以在实际过程中,这一块感觉上用的不是那么多,大家都是用最原始的版本,不用太做受限了。

所以基本上就是考虑这三个,所以基本上可以看起来是说就实际中,就是当你的序列的长度,和你的整个模型的宽度差不多的时候,而且大家深度都一样的话,基本上这三个模型的算法复杂都是差不多的。当然是说你的 attention 和卷积相对来说计算会好一点。另外一个是说 attention 在信息的糅合性上会好一点,所以你可能认为这个地方还是能赢下一些东西了。所以你看上去是说用了 self-attention 之后,是不是你的感觉上对长数据处理更好,而且可能算得也,不快就不会慢对吧。 但实际上其实也不是这样子的,实际上是 attention 对你整个模型的假设做了更少,导致说你需要更多的数据和更大的模型才能训练出来,跟你的RNN和CNN同样的效果,所以导致现在基于 Transformer 的模型 都是特别大特别贵。

好在讲完模型之后,我们接下来讲一下实验。 第五章是讲你训练的一些设置是怎么样的,首先他说我的训练数据集和我的 batching 怎么做的。他用了两个任务,一个是英语翻德语,他用的是标准的 WMT 2014 的数据,它这个里面有 4.5万 个句子的对,他说他用的是 byte-pair encoding,就是 bpe ,大概的思想是说,你不管是英语还是德语,其实一个词里面有很多种变化,就是什么加 ing ,加 es 呀,但是你如果直接把每一个词做成一个token的话,你会导致你的字典里面的东西会比较多,而且一个动词的可能有几种变化形式,你做成不一样的词的时候,他们之间的区别,模型是不知道了。bpe 相对来说就是把你那些词根给你提出来,这样好处是说它可以把整个字典这样的比较小,它这个地方用的是,37000 个 token 的一个字典,而且它是在英语和德语之间是共享的。就是说我们不再为英语构造一个字典,不再为德语构造一个字典,这样的好处是说,我整个编码器和解码器的一个embedding,就可以用一个东西了,而且整个模型变得更加简单,也就是他之前所谓的我的编码器,解码器那个 embedding 它是共享权重的。

另外一个英语到法语的话,他用了一个更大的一个数据集,在接下来的硬件和 schedule 部分。他说我训练使用了 8个 P100的 GPU,这个是比较有意思的,就是说在三年前,google的工作还是大量使用gpu的,但是,这个之后基本上就很少见了,因为在这个之后,google 让内部的员工尽量使用 tpu,限制你们使用gpu,这篇文章多多少少也是推动这个进展。 这是因为,Transformer里面我们知道基本就是一些比较大的矩阵,做乘法,tpu 这种东西,就是特别适合做大的矩阵乘法,以致于Google说,你们其实挪到 tpu 也没有太多问题,可能性能还更好一些。他说我们的 base 模型是用的一个小一点的参数,他每一个 batch 训练的时间是0.4秒,然后我们一共训练了 10万 步,一共就是在我的 8个gpu 上训练了12个小时,就基本上一台机器训练12个小时,也是不错的一个性能。 他说我一个大的模型,这样一个 batch 训练需要一秒钟,然后他一共训练了 30万步 ,最后是一台机器 3.5天。那其实也是一个可承受的范围,但是这个朋友之后的那些,之后的工作基本上是属于大家几乎不能承受的时间成本了。在训练器上面,他们用的是 Adam,这是它的参数,β2 我觉得可能不是最常用的,β2我记得应该是0.99还是0.999,所以他选了一个稍微少一点的值。然后他的 学习率 是用这个公式算出来的,有意思的是他的学习率是根据你的模型的那个宽度的 -0.5次方,就是说当你的模型越宽的时候,就是你学的那些向量越长的时候,你的学习率要低一点。另外一个是它有,他有一个 warmup,就是从一个小的值慢慢地爬到一个高的值,爬到之后,再根据你的步数,按照0.5次方 衰减,最后他说我的 warmup 是4000。有意思的是,基本可以看到说这个地方没有东西可以调的,就是你的学习率几乎是不用调的,取决于第一,adam 对学习率确实不那么敏感。第二个是说他这个地方也是把 d_{model}考虑进来了,这个这个schedule就已经也算是不错的schedule了,所以在学习率是不需要调的。

然后在 5.4 的时候讲的,他使用的正则化,他用了三个正则化呀。第一个是他用的是一个叫做 residual dropout,说白了就是说对每一个子层,子层就是包括了你的多头的注意力层,和你之后的 MLP,在每个层的输出上,在他进入残差连接之前和在进入 layerNorm 之前,他使用了一个dropout,他的dropout率是 0.1,也就是说,把这些输出的 10% 的那些元素置成 0。 另外 在 输入 加上 词嵌入,再加上 positional enconding 的时候,在它上面也用了一个 dropput,也就是把10%的元素值置成 0。有意思的是说你基本看到对每一个带权重的乘,他在输出上都使用的 dropout,用的是比较狠的。虽然这个,dropout 率并不是特别高,但是它使用了大量的dropout的层,来对它的模型做正则化。

然后另外一个使用 label smoothing,这个技术是在,inception v3 让大家官方的知道。意思是说我们用softmax,去学一个东西的时候,我的标号是,正确的是 1 错误的是0,就是我用,对于正确的那一个label的softmax的值,去逼近于 1 但我们知道softmax,是很难逼近于1的,因为它里面是一个指数,它是一个很 soft 的一个东西,就是说,它需要你的输出接近无限大的时候,才能逼近于1,这个使得训练比较难。一般的做法是说你不要让,搞成那么特别难的零和一,你可以把那个一的值往下降一点,比如说降成 0.9 。但这个地方他降得比较狠,他是降成了 0.1,就是说对于正确的那个词,我只需要我的 softmax 的输出,是到 0.1 就行了,叫置信度是0.1 就行了,不需要做的很高,剩下的那些值就可以是 0.9 除以你的字典的大小,他说这里会损失你的 perplexity,perplexity 你的 log loss 做指数,基本上可以认为是你的模型不确信度。因为你这个地方让你学说我正确答案,我也只要给个 10% 是对的就行了,所以当然你的不确信度会增加,所以你这个值会变高。但是他说我的模型会不那么确信,会提升我的精度和我的 BLUE 的分数,因为精度和 blue 的分数才是我们关心的重点。所以这个地方他说觉得没关系。

那我们接下来看第二个表,第二个表表示的是不同的超参数之间的一些对比。我们可以看一下有哪些超参数。就回忆一下 n 是什么,n 是要堆多少层,d 是你这个模型的宽度,就是一个token进来要表示成一个多长的向量,dff 表示的是 MLP中间那个隐藏层的输出的大小,h 是你的头的个数,就是你注意力层的头的个数,dk dv 分别是你一个头里面 key 和 value 那个维度,你的 P是你dropout 是你的丢弃的率,以及这个是说你最后 label smoothing的时候,你这个要学的那个label的真实值是等于多少,最后一个是说你要训练多少个batch。

然后可以看一下它的base模型,base模型我们知道是说他用了 6 个层,然后每一层的宽度是 512。 然后这个东西基本上就是它的四倍了,然后,头的数乘以你的维度数是等于他的,就是 8乘64 等于512,dropout率是0.1,然后这个是0.1,然后最后是训练了10万步。

另外一个是他的 big 模型,基本上可以看到是说,这个东西没有变,但是模型的宽度乘了两倍,然后这个东西也当然是跟着翻倍了,他也把这个头的个数乘了两倍,然后这个东西就不用变了,另外一个是你的模型更加复杂了,所以他用了一个比较大的,丢弃率 0.3,而且模型更加复杂了,收敛会慢一点,因为你的学习率是变低了的,这个地方他训练的 30万 个批量大小。所以基本上可以看到是,整个模型参数相对来说还是比较简单的,基本上你能调的就是一个要多少个层,然后你的模型有多宽,以及说你要多少个头,剩下的这些东西基本上都是可以按比例算过来的,这个也是Transformer架构的一个好处。虽然你看上去那个模型,比较复杂 ,但是也没有太多东西可以调,这个设计上来说让后面的人更加方便一点。

比如说 bert 它其实就是把这个架构拉过去,然后把这个把这几个参数改了一下,gpt也就是,基本上就是copy过去,然后稍微调一下就行了。 让后面的人的工作变得简单了很多。

最后一个表是另外一个,NLP任务上大家不熟的话,我们就可以跳过来。你放一个也挺好的,你不放其实也没关系,就是他是说我的结果还不错,就是不仅仅是在机器翻译上还行,我又找了一个另外一个任务,我说 哎 效果也还挺好的,就是这个要表达的一个观点。好这就是我们实验的部分,我们很快速的给大家过了一遍。

好最后我们来评价一下这篇文章,我们首先看一下它的写作。这篇文章的写作是非常简洁的,因为就是说,他每一句话就基本上在讲一件事情,你看那么短短的一篇文章,我们读下下来大概就花了一个半小时,也能理解是说你有那么多东西,你要塞到那么短的一个篇幅里面,你只能用这么写。另外一个是说他没有用太多的写作技巧,基本上就是说你看我提出一个什么东西,这个模型长什么样子,跟 CNN 和 RNN 比是什么样子,最后的结论是,最后的实验结果是什么东西。这个不是那么推荐的一种写法,因为对一篇文章来说,你需要在讲一个故事,让你的读者有代入感,能够 convince(说服) 读者。但是我也能理解说 你要在一篇文章里面发现那么多东西的话,也没那么多篇幅来讲一个很好的故事。一般的建议是说,假设你写篇文章的话,你可以选择把你的东西减少一点,甚至把一些东西 不那么重要的东西放到你的附录里面。但是在正文的时候,你还是最好讲个故事,说你为什么做这个事情,你的一些设计的理念是什么样子的,你的对整个文章的一些思考是什么样子的,这个东西让大家会觉得你的文章更加有深度一些。

接下来我们评论一下Transformer这个模型本身。我们现在当然可以看到 Transformer 模型不仅仅是用在机器翻译上面,它也能够用在几乎所有的 NLP 的任务上面。在后续的工作,bert gpt 让大家能够,训练很大的预训练的模型 能够极大的提升所有 NLP 里面的任务的性能,这个有点像 CNN 在对整个计算机视觉的改变。我们能够训练一个大的CNN的模型,使得别的任务也能够从中受益。另外一个是说,CNN 给整个计算机视觉的研究者,提供了一个同样的一个框架,使得我只要学会 CNN 就行了,而不需要去管以前跟任务相关的那么多的专业的知识,比如说做特征提取,对整个任务怎么建模。Transformer也是一样的,之前我们要做各种各样的数据文本的预处理,然后我要根据 NLP 的任务,给你设计不一样的架构,现在不需要了,我们用整个transform这个架构,就能够在各个任务上做得非常好的成绩。而且他预训练的模型也让大家的训练变得更加简单。当然我们还看到的是Transformer,现在不仅仅是用在 NLP 上面,也在图片上,语音上,video 上取得了很大的进展,这个是一个非常有影响力的一件事情。这是因为之前计算机视觉的研究者是用 CNN,而在 NLP 你用RNN,然后别的人用别的模型,现在是说大家发现同样一个模型能够在所有领域 都能用。就是让大家的语言变成一样了,就以前你用python,我用java,现在我说大家都用python了,那你任何一个领域的研究者做的一些突破,能够很快的在别的领域被使用,能够极大的减少一个新的技术在机器学习里面,各个领域被应用的那个时间。 另外一块的话我们知道人对世界的感知是多模态的,我们看见图片,我们读文字,我们听到语音。现在Transformer能够把这些所有的不同的数据给你融合起来,因为大家都用一个同样的架构抽取特征的话,那么那么它可以抽到一个同样的语义空间,使得我们可以用文本,图片,语音,视频能训练更好更大的模型,也可以看到这一块应该是现在和未来的一个研究重点。但反过来讲,虽然 Transformer 这些模型取得了非常好的实验性的结果,但是我们对它的理解还在比较初级的阶段。第一个是说这篇文章的标题叫做,你只需要 attention 就行了,但是最新的一些结果表明,attention 只是在 Transformer 里面起到一个作用,它的主要作用是把整个序列的信息给大家聚合起来,但是后面的 MLP 呀,以及你的残差连接是缺一不少了,如果你把这些东西去掉的话,attention 基本上什么东西的训练不出来,所以你这个模型attention,也不是说你只需要它就行了。第二个是说,attention 根本就不会去对你这个数据的那个顺序做建模,它为什么能够打赢 RNN ,RNN 能够显式的建模序列信息, 理论上应该比你的 NLP 效果更好。 现在大家觉得 attention 使用了一个更广泛的归纳偏置,使得它能处理一些更一般化的信息,这也是为什么说,而 attention 并没有做任何空间上的一些假设,它也能够甚至是比 RNN, CNN 取得更好的一些结果。但是它的代价是说因为它的假设更加一般,所以他对数据里面抓取信息的能力变差了,以至于说你需要,使用更多的数据,使用更大的模型,才能训练出你要的效果。这也是为什么现在 Transformer 模型一个比一个大训练,一个比一个贵。另外最后要提到一点是说,Attention 也给了研究者一些鼓励,说原来在 CNN 和 RNN 之外也会有新的模型能打败他们.现在有很多工作说我,就用 MLP 或者就用一些更简单的架构,也能够在,图片上面在文本上面取得很好的结果。 所以我觉得在未来肯定会有更多新的架构出现,让整个这个领域让更加有意思一些。

本文使用 Zhihu On VSCode 创作并发布



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3