注:本文主要是对 PPT 部分内容的补充与拓展,建议结合使用(当然也完全可以单看)。
一、基础知识
1、从向量表示到词嵌入
这部分主要是参考如下文章,大部分图片均来自此文:The Illustrated Word2vec – Jay Alammar – Visualizing machine learning one concept at a time. (jalammar.github.io)
不知道大家有没有做过 MBTI 人格测试?这个测试会问你一系列的问题,然后在很多维度给你打分,内向/外向就是其中之一,然后用0到100的范围来表示你是多么内向/外向(其中0是最内向的,100是最外向的)
假设我在测试中,内向/外向得分为38/100,则可以用下图表示这个得分:
为了更好的表达 “程度”,我们把范围收缩到 -1 到 1 :
考虑到人性复杂,对于一个人的描述只有一条信息显然是不够的,为此,我们添加另一测试的得分作为一个新的第二维度,而这两个维度均可以表现为图上的一个点(或称为从原点到该点的向量)
然后可以说这个向量部分地代表了我的人格。当你想要将另外两个人与我进行比较时,这种表示法就有用了。
假设我某天被公交给撞住院了,住院期间需要一个与我性格相似的人来代我行事。那在下图中,这两个人中哪一个更像我呢?
而计算两个向量之间相似度得分的常用方法是余弦相似度:
通过该公式,计算可得:
从而可知,person1 在性格上与我更相似。其实,从坐标系里也可以看出,person1 的向量指向与我的向量指向更相近,即他俩具有更高的余弦相似度。
更进一步,两个维度还不足以捕获有关不同人群的足够信息。国外心理学较为主流的是五大人格特征(以及大量的子特征)。
当使用五个维度时,我们没法在二维平面绘制向量小箭头了(毕竟三维空间下无法使五维空间可视化)。而实际生活中,我们经常要在更高维度的空间中做研究(有的人把研究一词表达成思考,实际上,大部分人没法在高维度空间思考,但科学研究人员经常干这事,故表达成研究更准确),好在余弦相似度仍然有效,它适用于任意维度:
这些得分比上次的得分看起来更准确,毕竟它们是根据被比较事物的更高维度算出的。
小结一下,有两点
- 我们可以将人和事物表示为代数向量
- 我们可以很容易地计算出相似向量之间的相互关系。
众所周知,机器无法直接理解人类的语言,所以需要先把人类的语言“计算机化”,那如何变成计算机可以理解的语言呢?
对于这个问题,机器翻译已经给出了答案:
比如对于计算机,它是如何判断一个词的词性,是动词还是名词的呢?
假定我们有一系列样本(x,y),其中的 x 是词语,y 是它们的词性,我们要构建的映射:
- 首先,这个数学模型 f(比如神经网络、SVM)只接受数值型输入;
- 而 NLP 里的词语是人类语言的抽象总结,是符号形式的(比如中文、英文、拉丁文等等);
- 如此一来,咱们便需要把NLP里的词语转换成数值形式,或者嵌入到一个数学空间里;
- 进一步,可以把文本分散嵌入到另一个离散空间,称作分布式表示,又称为词嵌入(word embedding)或词向量
- 在各种词向量中,有一个简单的词向量是one-hot encoder。所谓one-hot编码,本质上是用一个只含一个 1、其他都是 0 的向量来唯一表示词语
当然,不是所有的编码都是01编码,且one-hot编码无法反应词与词之间的语义相似度。
简单来说,就是用一系列二进制代码来表示词语的各种性质。
这就是所谓的词嵌入了,而一个单词表达成Word Embedding后,便很容易找出语义相近的其它词汇
再举一个例子,这是一个单词“king”的词嵌入(在维基百科上训练的GloVe向量):
[ 0.50451 , 0.68607 , -0.59517 , -0.022801, 0.60046 , -0.13498 , -0.08813 , 0.47377 , -0.61798 , -0.31012 , -0.076666, 1.493 , -0.034189, -0.98173 , 0.68229 , 0.81722 , -0.51874 , -0.31503 , -0.55809 , 0.66421 , 0.1961 , -0.13495 , -0.11476 , -0.30344 , 0.41177 , -2.223 , -1.0756 , -1.0783 , -0.34354 , 0.33505 , 1.9927 , -0.04234 , -0.64319 , 0.71125 , 0.49159 , 0.16754 , 0.34344 , -0.25663 , -0.8523 , 0.1661 , 0.40102 , 1.1685 , -1.0137 , -0.21585 , -0.15155 , 0.78321 , -0.91241 , -1.6106 , -0.64426 , -0.51042 ]
这是一个包含50个数字的列表。通过观察数值看不出什么,但是让我们稍微给它可视化,以便比较其它词向量。故我们把所有这些数字放在一行:
让我们根据它们的值对单元格进行颜色编码(如果它们接近2则为红色,接近0则为白色,接近-2则为蓝色):
我们将忽略数字并仅查看颜色以指示单元格的值。现在让我们将“king”与其它单词进行比较:
你会发现“Man”这个词和“Woman”相比,比与“King”相比更相似,而这些向量图示很好的展现了这些单词的含义与关联
这是另一个示例列表:
通过垂直扫描列来查找具有相似颜色的列,相信你可以看到以下这几点
- “woman”和“girl”在很多地方是相似的,“man”和“boy”也是一样
- 当然,“boy”和“girl”也有彼此相似的地方,但这些地方却与“woman”或“man”不同,为何呢,毕竟boy/girl特指青春年少,而woman/man更多指成人
- 此外,“king”和“queen”彼此之间相似,毕竟都是所谓的王室成员
2、从 Seq2Seq 到 Seq2Seq with Attention
本文主要参考以下文章:一文看懂 NLP 里的模型框架 Encoder-Decoder 和 Seq2Seq (easyai.tech)
(1)Encoder-Decoder
Encoder-Decoder 是一个模型构架,是一类算法统称,并不是特指某一个具体的算法,在这个框架下可以使用不同的算法来解决不同的任务。首先,编码(encode)由一个编码器将输入序列转化成一个固定维度的稠密向量,解码(decode)阶段将这个激活状态生成目标译文。
回顾一下,算法设计的基本思路:将现实问题转化为一类可优化或者可求解的数学问题,利用相应的算法来实现这一数学问题的求解,然后再应用到现实问题中,从而解决了现实问题。(比如,我们想解决一个词性标注的任务(现实问题),我们转化成一个BIO序列标注问题(数学模型),然后设计一系列的算法进行求解,如果解决了这个数学模型,从而也就解决了词性标注的任务)。
Encoder :编码器,如下。
Decoder:解码器,如下。
合并起来,如下:
更具体一点的表达如下所示:
几点说明:
- 不论输入和输出的长度是什么,中间的“向量c”长度都是固定的(这是它的缺陷所在)。
- 根据不同的任务可以选择不同的编码器和解码器(例如,CNN、RNN、LSTM、GRU等)
- Encoder-Decoder的一个显著特征就是:它是一个end-to-end的学习算法。
- 只要符合这种框架结构的模型都可以统称为Encoder-Decoder模型。
(2)Seq2Seq
Seq2Seq(是 Sequence-to-sequence 的缩写),就如字面意思,输入一个序列,输出另一个序列。这种结构最重要的地方在于输入序列和输出序列的长度是可变的。例如下图:输入了 6 个汉字,输出了 3 个英文单词。输入和输出的长度不同。
Seq2Seq 的由来
在 Seq2Seq 框架提出之前,深度神经网络在图像分类等问题上取得了非常好的效果。在其擅长解决的问题中,输入和输出通常都可以表示为固定长度的向量,如果长度稍有变化,会使用补零等操作。
然而许多重要的问题,例如机器翻译、语音识别、自动对话等,表示成序列后,其长度事先并不知道。因此如何突破先前深度神经网络的局限,使其可以适应这些场景,成为了13年以来的研究热点,Seq2Seq框架应运而生。
「Seq2Seq」和「Encoder-Decoder」的关系
Seq2Seq(强调目的)不特指具体方法,满足「输入序列、输出序列」的目的,都可以统称为 Seq2Seq 模型。
而 Seq2Seq 使用的具体方法基本都属于Encoder-Decoder 模型(强调方法)的范畴。
总结一下的话:
- Seq2Seq 属于 Encoder-Decoder 的大范畴
- Seq2Seq 更强调目的,Encoder-Decoder 更强调方法
(3)信息丢失的问题—— Attention 机制的引入
上文提到:Encoder(编码器)和 Decoder(解码器)之间只有一个「向量 c」来传递信息,且 c 的长度固定。
为了便于理解,我们类比为「压缩-解压」的过程:
将一张 800X800 像素的图片压缩成 100KB,看上去还比较清晰。再将一张 3000X3000 像素的图片也压缩到 100KB,看上去就模糊了。
Encoder-Decoder 就是类似的问题:当输入信息太长时,会丢失掉一些信息。
Attention 机制就是为了解决「信息过长,信息丢失」的问题。
Attention 模型的特点是 Eecoder 不再将整个输入序列编码为固定长度的「中间向量 C」 ,而是编码成一个向量的序列。引入了 Attention 的 Encoder-Decoder 模型如下图:
- 从输出的角度讲
每个输出的词Y会受到每个输入X1、X2、X3、X4的整体影响,不是只受某一个词的影响,毕竟整个输入语句是整体而连贯的,但同时每个输入词对每个输出的影响又是不一样的,即每个输出Y受输入X1、X2、X3、X4的影响权重不一样,而这个权重便是由Attention计算,也就是所谓的注意力分配系数,计算每一项输入对输出权重的影响大小 - 从编码的角度讲
在根据给到的信息进行编码时(或称特征提取),不同信息的重要程度是不一样的(可用权重表示),即有些信息是无关紧要的,比如一些语气助词之类的,所以这个时候在编码时,就可以有的放矢,根据不同的重要程度针对性汲取相关信息
(4)通过翻译 “Tom chase Jerry” 理解注意力 Attention 机制
该部分主要参考以下文献:深度学习中的注意力机制(2017版)_深度学习中的注意力机制 四万字-CSDN博客
再举一个机器翻译的例子(本猫追老鼠例子的配图和核心阐述均来源于参考文献4),即用Google翻译这句话:Tom chase Jerry
1. 在翻译“杰瑞”的时候,带有注意力机制的模型会体现出英文单词对于翻译当前中文单词不同的影响程度,比如给出类似这样一个概率分布值:(Tom,0.3)(Chase,0.2) (Jerry,0.5),每个英文单词的概率代表了翻译当前单词“杰瑞”时,注意力分配模型分配给不同英文单词的注意力大小(类似公司在线开董事会,虽然每个人都有发言权,但对不同议题进行决策时,很明显对具体议题更擅长的人拥有更大的发言权,而这个发言权就像权重一样,不同的人对最终决策结果的产生有着不同大小的影响)
2.目标句子中的每个单词都应该学会其对应的源语句子中单词的注意力分配概率信息。这意味着在生成每个单词的时候,原先都是相同的中间语义表示C会被替换成根据当前生成单词而不断变化的 (注:这里就是Attention模型的关键,即由固定的中间语义表示C换成了根据当前输出单词来调整成加入注意力模型的变化的 )。
3. 生成目标句子单词的过程成了下面的形式:
而每个 可能对应着不同的源语句子单词的注意力分配概率分布,比如对于上面的英汉翻译来说,其对应的信息可能如下:
其中,函数 代表Encoder对输入英文单词的某种变换函数,比如如果Encoder是用的RNN模型的话,这个 函数的结果往往是某个时刻输入 后隐层节点的状态值;g代表Encoder根据单词的中间表示合成整个句子中间语义表示的变换函数,一般的做法中,g函数就是对构成元素加权求和,即下列公式:
其中, 代表输入句子Source的长度, 代表在Target输出第i个单词时Source输入句子中第j个单词的注意力分配系数,而 则是Source输入句子中第j个单词的语义编码。
4. 假设 下标i就是上面例子所说的“ 汤姆” ,那么 就是3,h1=f(“Tom”)、h2=f(“Chase”)、h3=f(“Jerry”)分别是输入句子每个单词的语义编码,对应的注意力模型权值则分别是0.6,0.2,0.2,所以g函数本质上就是个加权求和函数。如果形象表示的话,翻译中文单词“汤姆”的时候,数学公式对应的中间语义表示 的形成过程类似下图。
这里有一个问题:生成目标句子某个单词,比如“汤姆”的时候,如何知道Attention模型所需要的输入句子单词注意力分配概率分布值呢?就是说“汤姆”对应的输入句子Source中各个单词的概率分布:(Tom,0.6) (Chase,0.2) (Jerry,0.2) 是如何得到的呢?
这里给出参考文献链接,在 PPT 和辅助笔记中不做更细致的讲解:Attention? Attention! | Lil'Log (lilianweng.github.io)
简单来说:
- 即当我们在翻译 Tom chase Jerry 为 汤姆追逐杰瑞
- 其实可以简单粗暴的认为计算“汤姆”与“Tom chase Jerry”中各个词的相似度
- 从而得到了“汤姆”对应的输入句子Source中各个单词的概率分布:(Tom,0.6) (Chase,0.2) (Jerry,0.2)
- 这个概率分布意味着“汤姆”与“Tom”更相似,从而给“Tom”更大的权重
- 使得最终把“汤姆”对应到“Tom”,达到的效果就是“Tom”翻译为“汤姆”
(5)Attention 算法机制总结:通过计算相似性得出权重最后加权求和
再比如,图书馆(source)里有很多书(value),为了方便查找,我们给书做了编号(key)。当我们想要了解漫威(query)的时候,我们就可以看看那些动漫、电影、甚至二战(美国队长)相关的书籍。
为了提高效率,并不是所有的书都会仔细看,针对漫威来说,动漫,电影相关的会看的仔细一些(权重高),但是二战的就只需要简单扫一下即可(权重低)。
当我们全部看完后就对漫威有一个全面的了解了。
可以看到,将Source中的构成元素想象成是由一系列的<Key,Value>数据对构成,此时给定Target中的某个元素Query,通过计算Query和各个Key的相似性或者相关性,得到每个Key对应Value的权重系数,然后对Value进行加权求和,即得到了最终的Attention数值。
所以本质上Attention机制是对Source中元素的Value值进行加权求和,而Query和Key用来计算对应Value的权重系数。即可以将其本质思想改写为如下公式::
Attention 原理的3步分解:
归纳整理一下,则为
- 第一步:代表漫威漫画的query 和 代表某本书的key 进行相似度计算(常见的方法包括:求两者的向量点积、求两者的向量Cosine相似性等),得到权值
- 第二步:将权值进行归一化(将原始计算分值整理成所有元素权重之和为1的概率分布,或者说通过SoftMax的内在机制更加突出重要元素的权重),得到直接可用的权重
- 第三步:将权重和 value 进行加权求和
值得一提的是,Attention 并不一定要在 Encoder-Decoder 框架下使用的,他是可以脱离 Encoder-Decoder 框架的。
解了Attention的本质思想,理解所谓的Self-Attention就容易了,具体下文会详细阐述,这里先简单提一嘴:
在一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attention机制发生在Target的元素Query和Source中的所有元素之间。
而Self Attention顾名思义,指的不是Target和Source之间的Attention机制,而是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力计算机制。其具体计算过程是一样的,只是计算对象发生了变化而已。
二、Attention is All You Need
原论文链接:https://arxiv.org/abs/1706.03762
这部分的辅助笔记是翻译自以下文章,由于翻译水平有限,建议看原文,链接如下:The Illustrated Transformer – Jay Alammar – Visualizing machine learning one concept at a time. (jalammar.github.io)
需要说明的是,由于需要更加侧重的是后续的项目复现以及应用,所以在 PPT 中对 Transformer 的讲解更加偏向于概念、原理上的理解,细致的算法及相关分析可以参考该辅助笔记。
1. A High-Level Look
首先将这个模型看成是一个黑箱操作。在机器翻译中,就是输入一种语言,输出另一种语言。
那么拆开这个黑箱,我们可以看到它是由编码组件、解码组件和它们之间的连接组成。
编码组件部分由一堆编码器(encoder)构成(论文中是将6个编码器叠在一起——数字6没有什么神奇之处,你也可以尝试其他数字)。解码组件部分也是由相同数量(与编码器对应)的解码器(decoder)组成的。
所有的编码器在结构上都是相同的,但它们没有共享参数。每个解码器都可以分解成两个子层。
从编码器输入的句子首先会经过一个自注意力(self-attention)层,这层帮助编码器在对每个单词编码时关注输入句子的其他单词。我们将在稍后的文章中更深入地研究自注意力。
自注意力层的输出会传递到前馈(feed-forward)神经网络中。每个位置的单词对应的前馈神经网络都完全一样(译注:另一种解读就是一层窗口为一个单词的一维卷积神经网络)。
解码器中也有编码器的自注意力(self-attention)层和前馈(feed-forward)层。除此之外,这两个层之间还有一个注意力层,用来关注输入句子的相关部分(和seq2seq模型的注意力作用相似)。
2. Bringing The Tensors Into The Picture
我们已经了解了模型的主要部分,接下来我们看一下各种向量或张量(译注:张量概念是矢量概念的推广,可以简单理解矢量是一阶张量、矩阵是二阶张量。)是怎样在模型的不同部分中,将输入转化为输出的。
像大部分NLP应用一样,我们首先将每个输入单词通过词嵌入算法转换为词向量。
每个单词都被嵌入为512维的向量,我们用这些简单的方框来表示这些向量。
词嵌入过程只发生在最底层的编码器中。所有的编码器都有一个相同的特点,即它们接收一个向量列表,列表中的每个向量大小为512维。在底层(最开始)编码器中它就是词向量,但是在其他编码器中,它就是下一层编码器的输出(也是一个向量列表)。向量列表大小是我们可以设置的超参数——一般是我们训练集中最长句子的长度。
将输入序列进行词嵌入之后,每个单词都会流经编码器中的两个子层。
3. Now We’re Encoding!
如上述已经提到的,一个编码器接收向量列表作为输入,接着将向量列表中的向量传递到自注意力层进行处理,然后传递到前馈神经网络层中,将输出结果传递到下一个编码器中。
输入序列的每个单词都经过自编码过程。然后,他们各自通过前向传播神经网络——完全相同的网络,而每个向量都分别通过它。
4. Self-Attention at a High Level
不要被我用自注意力这个词弄迷糊了,好像每个人都应该熟悉这个概念。其实我之也没有见过这个概念,直到读到Attention is All You Need 这篇论文时才恍然大悟。让我们精炼一下它的工作原理。
例如,下列句子是我们想要翻译的输入句子:
The animal didn’t cross the street because it was too tired
这个“it”在这个句子是指什么呢?它指的是street还是这个animal呢?这对于人类来说是一个简单的问题,但是对于算法则不是。
当模型处理这个单词“it”的时候,自注意力机制会允许“it”与“animal”建立联系。
随着模型处理输入序列的每个单词,自注意力会关注整个输入序列的所有单词,帮助模型对本单词更好地进行编码。
如果你熟悉RNN(循环神经网络),回忆一下它是如何维持隐藏层的。RNN会将它已经处理过的前面的所有单词/向量的表示与它正在处理的当前单词/向量结合起来。而自注意力机制会将所有相关单词的理解融入到我们正在处理的单词中。
当我们在编码器#5(栈中最上层编码器)中编码“it”这个单词的时,注意力机制的部分会去关注“The Animal”,将它的表示的一部分编入“it”的编码中。
请务必检查Tensor2Tensor notebook ,在里面你可以下载一个Transformer模型,并用交互式可视化的方式来检验。
5. Self-Attention in Detail
首先我们了解一下如何使用向量来计算自注意力,然后来看它实怎样用矩阵来实现。
计算自注意力的第一步就是从每个编码器的输入向量(每个单词的词向量)中生成三个向量。也就是说对于每个单词,我们创造一个查询向量、一个键向量和一个值向量。这三个向量是通过词嵌入与三个权重矩阵后相乘创建的。
可以发现这些新向量在维度上比词嵌入向量更低。他们的维度是64,而词嵌入和编码器的输入/输出向量的维度是512. 但实际上不强求维度更小,这只是一种基于架构上的选择,它可以使多头注意力(multiheaded attention)的大部分计算保持不变。
X1与WQ权重矩阵相乘得到q1, 就是与这个单词相关的查询向量。最终使得输入序列的每个单词的创建一个查询向量、一个键向量和一个值向量。
什么是查询向量、键向量和值向量向量?
它们都是有助于计算和理解注意力机制的抽象概念。请继续阅读下文的内容,你就会知道每个向量在计算注意力机制中到底扮演什么样的角色。
计算自注意力的第二步是计算得分。假设我们在为这个例子中的第一个词“Thinking”计算自注意力向量,我们需要拿输入句子中的每个单词对“Thinking”打分。这些分数决定了在编码单词“Thinking”的过程中有多重视句子的其它部分。
这些分数是通过打分单词(所有输入句子的单词)的键向量与“Thinking”的查询向量相点积来计算的。所以如果我们是处理位置最靠前的词的自注意力的话,第一个分数是q1和k1的点积,第二个分数是q1和k2的点积。
第三步和第四步是将分数除以8(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定。这里也可以使用其它值,8只是默认值),然后通过softmax传递结果。softmax的作用是使所有单词的分数归一化,得到的分数都是正值且和为1。
这个softmax分数决定了每个单词对编码当下位置(“Thinking”)的贡献。显然,已经在这个位置上的单词将获得最高的softmax分数,但有时关注另一个与当前单词相关的单词也会有帮助。
第五步是将每个值向量乘以softmax分数(这是为了准备之后将它们求和)。这里的直觉是希望关注语义上相关的单词,并弱化不相关的单词(例如,让它们乘以0.001这样的小数)。
第六步是对加权值向量求和(译注:自注意力的另一种解释就是在编码某个单词时,就是将所有单词的表示(值向量)进行加权求和,而权重是通过该词的表示(键向量)与被编码词表示(查询向量)的点积并通过softmax得到。),然后即得到自注意力层在该位置的输出(在我们的例子中是对于第一个单词)。
这样自自注意力的计算就完成了。得到的向量就可以传给前馈神经网络。然而实际中,这些计算是以矩阵形式完成的,以便算得更快。那我们接下来就看看如何用矩阵实现的。
6. Matrix Calculation of Self-Attention
第一步是计算查询矩阵、键矩阵和值矩阵。为此,我们将将输入句子的词嵌入装进矩阵X中,将其乘以我们训练的权重矩阵(WQ,WK,WV)。
x矩阵中的每一行对应于输入句子中的一个单词。我们再次看到词嵌入向量 (512,或图中的4个格子)和q/k/v向量(64,或图中的3个格子)的大小差异。
最后,由于我们处理的是矩阵,我们可以将步骤2到步骤6合并为一个公式来计算自注意力层的输出。
7. The Beast With Many Heads
通过增加一种叫做“多头”注意力(“multi-headed” attention)的机制,论文进一步完善了自注意力层,并在两方面提高了注意力层的性能:
- 它扩展了模型专注于不同位置的能力。在上面的例子中,虽然每个编码都在z1中有或多或少的体现,但是它可能被实际的单词本身所支配。如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时模型的“多头”注意机制会起到作用。
- 它给出了注意力层的多个“表示子空间”(representation subspaces)。接下来我们将看到,对于“多头”注意机制,我们有多个查询/键/值权重矩阵集(Transformer使用八个注意力头,因此我们对于每个编码器/解码器有八个矩阵集合)。这些集合中的每一个都是随机初始化的,在训练之后,每个集合都被用来将输入词嵌入(或来自较低编码器/解码器的向量)投影到不同的表示子空间中。
在“多头”注意机制下,我们为每个头保持独立的查询/键/值权重矩阵,从而产生不同的查询/键/值矩阵。和之前一样,我们拿X乘以WQ/WK/WV矩阵来产生查询/键/值矩阵。
如果我们做与上述相同的自注意力计算,只需八次不同的权重矩阵运算,我们就会得到八个不同的Z矩阵。
这给我们带来了一点挑战。前馈层不需要8个矩阵,它只需要一个矩阵(由每一个单词的表示向量组成)。所以我们需要一种方法把这八个矩阵压缩成一个矩阵。那该怎么做?其实可以直接把这些矩阵拼接在一起,然后用一个附加的权重矩阵WO与它们相乘。
这几乎就是多头自注意力的全部。这确实有好多矩阵,我们试着把它们集中在一个图片中,这样可以一眼看清。
既然我们已经摸到了注意力机制的这么多“头”,那么让我们重温之前的例子,看看我们在例句中编码“it”一词时,不同的注意力“头”集中在哪里:
当我们编码“it”一词时,一个注意力头集中在“animal”上,而另一个则集中在“tired”上,从某种意义上说,模型对“it”一词的表达在某种程度上是“animal”和“tired”的代表。
然而,如果我们把所有的attention都加到图示里,事情就更难解释了:
8. Representing The Order of The Sequence Using Positional Encoding
到目前为止,我们对模型的描述缺少了一种理解输入单词顺序的方法。
为了解决这个问题,Transformer为每个输入的词嵌入添加了一个向量。这些向量遵循模型学习到的特定模式,这有助于确定每个单词的位置,或序列中不同单词之间的距离。这里的直觉是,将位置向量添加到词嵌入中使得它们在接下来的运算中,能够更好地表达的词与词之间的距离。
为了让模型理解单词的顺序,我们添加了位置编码向量,这些向量的值遵循特定的模式。
如果我们假设词嵌入的维数为4,则实际的位置编码如下:
尺寸为4的迷你词嵌入位置编码实例
这个模式会是什么样子?
在下图中,每一行对应一个词向量的位置编码,所以第一行对应着输入序列的第一个词。每行包含512个值,每个值介于1和-1之间。我们已经对它们进行了颜色编码,所以图案是可见的。
20字(行)的位置编码实例,词嵌入大小为512(列)。你可以看到它从中间分裂成两半。这是因为左半部分的值由一个函数(使用正弦)生成,而右半部分由另一个函数(使用余弦)生成。然后将它们拼在一起而得到每一个位置编码向量。
原始论文里描述了位置编码的公式(第3.5节)。你可以在 get_timing_signal_1d()中看到生成位置编码的代码。这不是唯一可能的位置编码方法。然而,它的优点是能够扩展到未知的序列长度(例如,当我们训练出的模型需要翻译远比训练集里的句子更长的句子时)。
9. The Residuals
在继续进行下去之前,我们需要提到一个编码器架构中的细节:在每个编码器中的每个子层(自注意力、前馈网络)的周围都有一个残差连接,并且都跟随着一个“层-归一化”步骤。
层-归一化步骤:https://arxiv.org/abs/1607.06450
如果我们去可视化这些向量以及这个和自注意力相关联的层-归一化操作,那么看起来就像下面这张图描述一样:
解码器的子层也是这样样的。如果我们想象一个2 层编码-解码结构的transformer,它看起来会像下面这张图一样:
10. The Decoder Side
既然我们已经谈到了大部分编码器的概念,那么我们基本上也就知道解码器是如何工作的了。但最好还是看看解码器的细节。
编码器通过处理输入序列开启工作。顶端编码器的输出之后会变转化为一个包含向量K(键向量)和V(值向量)的注意力向量集 。这些向量将被每个解码器用于自身的“编码-解码注意力层”,而这些层可以帮助解码器关注输入序列哪些位置合适:
在完成编码阶段后,则开始解码阶段。解码阶段的每个步骤都会输出一个输出序列(在这个例子里,是英语翻译的句子)的元素
接下来的步骤重复了这个过程,直到到达一个特殊的终止符号,它表示transformer的解码器已经完成了它的输出。每个步骤的输出在下一个时间步被提供给底端解码器,并且就像编码器之前做的那样,这些解码器会输出它们的解码结果 。另外,就像我们对编码器的输入所做的那样,我们会嵌入并添加位置编码给那些解码器,来表示每个单词的位置。
(这儿有个动图缺失了,但是十分生动形象,建议回到原文观看)
而那些解码器中的自注意力层表现的模式与编码器不同:在解码器中,自注意力层只被允许处理输出序列中更靠前的那些位置。在softmax步骤前,它会把后面的位置给隐去(把它们设为-inf)。
这个“编码-解码注意力层”工作方式基本就像多头自注意力层一样,只不过它是通过在它下面的层来创造查询矩阵,并且从编码器的输出中取得键/值矩阵。
11. The Final Linear and Softmax Layer
解码组件最后会输出一个实数向量。我们如何把浮点数变成一个单词?这便是线性变换层要做的工作,它之后就是Softmax层。
线性变换层是一个简单的全连接神经网络,它可以把解码组件产生的向量投射到一个比它大得多的、被称作对数几率(logits)的向量里。
不妨假设我们的模型从训练集中学习一万个不同的英语单词(我们模型的“输出词表”)。因此对数几率向量为一万个单元格长度的向量——每个单元格对应某一个单词的分数。
接下来的Softmax 层便会把那些分数变成概率(都为正数、上限1.0)。概率最高的单元格被选中,并且它对应的单词被作为这个时间步的输出。
这张图片从底部以解码器组件产生的输出向量开始。之后它会转化出一个输出单词
12. Recap Of Training
既然我们已经过了一遍完整的transformer的前向传播过程,那我们就可以直观感受一下它的训练过程。
在训练过程中,一个未经训练的模型会通过一个完全一样的前向传播。但因为我们用有标记的训练集来训练它,所以我们可以用它的输出去与真实的输出做比较。
为了把这个流程可视化,不妨假设我们的输出词汇仅仅包含六个单词:“a”, “am”, “i”, “thanks”, “student”以及 “”(end of sentence的缩写形式)。
我们模型的输出词表在我们训练之前的预处理流程中就被设定好。
一旦我们定义了我们的输出词表,我们可以使用一个相同宽度的向量来表示我们词汇表中的每一个单词。这也被认为是一个one-hot 编码。所以,我们可以用下面这个向量来表示单词“am”:
例子:对我们输出词表的one-hot 编码
接下来我们讨论模型的损失函数——这是我们用来在训练过程中优化的标准。通过它可以训练得到一个结果尽量准确的模型。
13. The Loss Function
比如说我们正在训练模型,现在是第一步,一个简单的例子——把“merci”翻译为“thanks”。
这意味着我们想要一个表示单词“thanks”概率分布的输出。但是因为这个模型还没被训练好,所以不太可能现在就出现这个结果。
因为模型的参数(权重)都被随机的生成,(未经训练的)模型产生的概率分布在每个单元格/单词里都赋予了随机的数值。我们可以用真实的输出来比较它,然后用反向传播算法来略微调整所有模型的权重,生成更接近结果的输出。
你会如何比较两个概率分布呢?我们可以简单地用其中一个减去另一个。更多细节请参考交叉熵和KL散度。
交叉熵:Visual Information Theory -- colah's blog
KL散度:Kullback-Leibler Divergence Explained — Count Bayesie
但注意到这是一个过于简化的例子。更现实的情况是处理一个句子。例如,输入“je suis étudiant”并期望输出是“i am a student”。那我们就希望我们的模型能够成功地在这些情况下输出概率分布:
每个概率分布被一个以词表大小(我们的例子里是6,但现实情况通常是3000或10000)为宽度的向量所代表。
第一个概率分布在与“i”关联的单元格有最高的概率
第二个概率分布在与“am”关联的单元格有最高的概率
以此类推,第五个输出的分布表示“”关联的单元格有最高的概率
依据例子训练模型得到的目标概率分布
在一个足够大的数据集上充分训练后,我们希望模型输出的概率分布看起来像这个样子:
我们期望训练过后,模型会输出正确的翻译。当然如果这段话完全来自训练集,它并不是一个很好的评估指标(参考:交叉验证,链接https://www.youtube.com/watch?v=TIgfjmp-4BA)。注意到每个位置(词)都得到了一点概率,即使它不太可能成为那个时间步的输出——这是softmax的一个很有用的性质,它可以帮助模型训练。
因为这个模型一次只产生一个输出,不妨假设这个模型只选择概率最高的单词,并把剩下的词抛弃。这是其中一种方法(叫贪心解码)。另一个完成这个任务的方法是留住概率最靠高的两个单词(例如I和a),那么在下一步里,跑模型两次:其中一次假设第一个位置输出是单词“I”,而另一次假设第一个位置输出是单词“me”,并且无论哪个版本产生更少的误差,都保留概率最高的两个翻译结果。然后我们为第二和第三个位置重复这一步骤。这个方法被称作集束搜索(beam search)。在我们的例子中,集束宽度是2(因为保留了2个集束的结果,如第一和第二个位置),并且最终也返回两个集束的结果(top_beams也是2)。这些都是可以提前设定的参数。
三、Vision Transformer
原论文链接:[2010.11929] An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale (arxiv.org)
同样需要说明的是,由于需要更加侧重的是后续的项目复现以及应用,所以在 PPT 中对 Vision Transformer 的讲解更加偏向于概念、原理上的理解,细致的算法及相关分析可以参考该辅助笔记。
目前博主自己对 Vision Transformer 的理解也仍然十分不到位,细节上的东西还需要更加精进后可能才有更多自己的理解。这里给出一篇个人认为理解起来很不错的文章,用于参考:
Vision Transformer详解-CSDN博客
【Transformer系列】深入浅出理解ViT(Vision Transformer)模型_vit模型-CSDN博客
四、ViT 工作的简单介绍
图像处理无非是三大任务:分类 检测 分割。将 Transformer 中这一注意力机制运用到各个方面,也是一种创新。
这里不做更多的介绍,感兴趣可以去查账相关论文以及复现。以下给出相关论文链接
1、DETR: End-to-End Object Detection with Transformers
End-to-End Object Detection with Transformers | SpringerLink
2、MaskFormer: Per-Pixel Classification is Not All You Need for Semantic Segmentation
Per-Pixel Classification is Not All You Need for Semantic Segmentation
3、Mask2Former: Masked-attention Mask Transformer for Universal Image Segmentation
CVPR 2022 Open Access Repository
4、Transformer: Occlusion-Aware Instance Segmentation Via BiLayer Network Architectures
Occlusion-Aware Instance Segmentation Via BiLayer Network Architectures | IEEE Journals & Magazine | IEEE Xplore
5、Segment Anything
ICCV 2023 Open Access Repository
五、项目复现
从宏观来讲,其实抛开诸多 debug 上的细节,复现论文的最大关键就是照着别人给的指示走(再复现多个论文后目前最大的感受)。而指示在哪儿?请参考代码出处。
原论文匹配代码:GitHub - google-research/vision_transformer
考虑后续是否需要在这里补足对应的中文指示。
Reference:
注:包括但不限于文中已经提及过的
Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT-CSDN博客
图解Transformer(完整版)-CSDN博客
Transformer模型详解(图解最完整版) - 知乎 (zhihu.com)
【Transformer系列(2)】注意力机制、自注意力机制、多头注意力机制、通道注意力机制、空间注意力机制超详细讲解-CSDN博客
Vision Transformer详解-CSDN博客
【深度学习】详解 Vision Transformer (ViT)-CSDN博客
保姆级教学 —— 手把手教你复现Vision Transformer_transformer输出特征图大小-CSDN博客
地表最用心!Vision Transformer(ViT)数据流解析(代码同步)_rearrange层-CSDN博客
The Illustrated Transformer – Jay Alammar – Visualizing machine learning one concept at a time.
The Illustrated Word2vec – Jay Alammar – Visualizing machine learning one concept at a time.
一文看懂 NLP 里的模型框架 Encoder-Decoder 和 Seq2Seq
如何从RNN起步,一步一步通俗理解LSTM_rnn lstm-CSDN博客
深度学习中的注意力机制(2017版)_深度学习中的注意力机制 四万字-CSDN博客
a_journey_into_math_of_ml/03_transformer_tutorial_1st_part/transformer_1.ipynb at master · aespresso/a_journey_into_math_of_ml · GitHub
深度学习:前沿技术-从Attention,Transformer,ELMO,GPT到BERT – Ling之博客
https://zhuanlan.zhihu.com/p/53099098
https://zhuanlan.zhihu.com/p/132554155
CNN笔记:通俗理解卷积神经网络_cnn卷积神经网络-CSDN博客
如何通俗理解Word2Vec (23年修订版)-CSDN博客
《Attention is All You Need》浅读(简介+代码) - 科学空间|Scientific Spaces
The Annotated Transformer
Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT-CSDN博客
Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT-CSDN博客
https://www.zhihu.com/question/580810624/answer/2979260071
从零实现Transformer的简易版与强大版:从300多行到3000多行_transformer实现-CSDN博客
地表最用心!Vision Transformer(ViT)数据流解析(代码同步)_rearrange层-CSDN博客
保姆级教学 —— 手把手教你复现Vision Transformer_transformer输出特征图大小-CSDN博客
【论文阅读】Vision Transformer-CSDN博客
【深度学习】详解 Vision Transformer (ViT)-CSDN博客
【Transformer系列】深入浅出理解ViT(Vision Transformer)模型_vit模型-CSDN博客
Vision Transformer详解-CSDN博客
神经网络学习小记录67——Pytorch版 Vision Transformer(VIT)模型的复现详解_vit复现代码-CSDN博客