全文共9517字,预计学习时长28分钟
来源:Pexels
今年,各种机器学习的应用程序纷纷涌现。其中OpenAI GPT-2能够创作出逻辑清晰且激情盎然的文章,远远超出了人们对当前语言模型创造力的预期,给公众留下了深刻的印象。GPT-2并不是专门写小说的语言架构——结构与仅含解码器的transformer相似,但它实则是基于transformer的语言模型,规模巨大,在海量的数据集上进行训练。本文将介绍能让模型生成结果的架构,深入到自注意力(self-attention)层,接着会讲一些仅含解码器的transformer在语言建模之外的应用。
我之前写过Illustrated Transformer一文,本文是对其的补充,将会更加直观地解释transformer的内部操作,以及是如何从初级阶段演变的。希望本文使用的可视化语言会让后文中基于transformer模型的解释更为简单,毕竟这些模型的内部操作会不断演变。
目录
· 第一部分:GPT2和语言模型
o 什么是语言模型
o 语言模型中的transformer
o 与BERT的区别
o Transformer模块的演变历程
o 速成课:揭开GPT-2的面纱
o 进一步深入了解
o 小结: GPT-2
· 第二部分:详解自注意力机制
o 不使用掩模的自注意力机制
o 1. 创建查询、键和值向量
o 2. 注意力得分
o 3. 求和
o 掩模自注意力机制
o GPT-2的掩模自注意力机制
o 超越语言模型
o 你已经完成了!
· 第三部分:超越语言模型
o 机器翻译
o 概述
o 迁移学习
o 音乐生成
第一部分:GPT2和语言模型
到底什么是语言模型?
什么是语言模型
通过Illustrated Word2vec,我们已经看了语言模型的样子——其实就是一种机器学习模型,在看到句子的某部分后可以预测后面的文字。最常见的语言模型就是在手机上打字的时候,系统会依据所输内容自动推荐接下来的文字。
在这个意义上,可以说GPT-2实则就是比手机内语言系统更庞大、更复杂的应用程序,其功能就是预测文字。OpenAI 的研究员为了实验研究,从网上收集了40GB大的数据集(名为WebText),对GPT-2进行了训练。就存储大小而言,笔者使用的键盘软件SwiftKey占手机内存的78MB。而训练的GPT-2中规模最小的也需500MB空间才能存储其所有参数,最大的所需空间是其13倍,那也就是说存储空间将不止6.5GB。
对GPT-2进行实验,有一个很妙的方式——使用AllenAI GPT-2文件资源管理器。通过GPT-2,将会显示10个可能的词组以及各自的概率。你可以选择一个单词,看看接下来的那些预测,然后继续编辑文章。
语言模型中的transformer
正如Illustrated Transformer一文中所介绍的,transformer模型最初由编码器(encoder)和解码器(decoder)组成——两个都是所谓的transformer模块的堆栈。该架构之所以合适是因为能够处理机器翻译,而机器翻译是编码-解码架构在过去取得成功的一处问题。
后续的很多调查都发现架构要么隐藏编码器,要么隐藏解码器,而且只用一个transformer模块的堆栈——堆得尽可能高,输入海量的训练文本,再进行大量运算。
这些模块能堆多高呢?事实证明这是能区分不同规模GPT2模型的主要因素:
如图所示,小规模的 GPT-2 模型堆叠了 12 层,中等规模的是24 层,大规模的是36 层,还有一个特大规模的堆叠了48 层。
与BERT的区别
机器人第一定律
机器人不得伤害人,也不得见人受到伤害而袖手旁观。
GPT-2是通过transformer解码器模块构建的,而BERT是通过transformer的编码器模块构建的。下文会探讨它们之间的差异,但其中最主要的一个就是GPT2会像传统的语言模型一样,每次输出一个词。我们可以试着激发一个训练过的GPT2,令其背诵机器人第一定律:
这些模型实际操作的流程就是每次生成词(token)后,将其添加到输入序列。新的序列就会作为模型下一步的输入内容。该理念称为“自回归(auto-regression)”,也是促成RNN高效的理念之一。
GPT2以及后来的一些模型比如TransformerXL和XLNet本质上讲都是自回归模型。但BERT是个例外,没有自回归,不过可以结合上下文来创作更好的文章。XLNet的出现让自回归重回公众的视野,与此同时,人们也找到了另一种可以结合上下文创作的途径。
Transformer模块的演变历程
最初的transformer论文介绍了两种transformer模块:
编码器模块
首先映入眼帘的是编码器模块:
如图所示,原始 transformer论文中的编码器模块可以接受长度不超过最大序列长度(如 512 个词)的输入。如果序列长度小于该限制,就在其后填入预先定义的空白词。
解码器模块
其次是解码器模块,带有来自编码器模块的一个小构架模型——该层允许关注编码器的特定片段:
两个模块的自注意力层之间的主要区别在于后者生成新词的过程——不是像BERT那样把单词改为[mask],而是干扰自注意力层的计算,阻止来自正在计算部分右侧词所传递的信息。
举个例子,从下图中可以看出如果要强调#4词的路径,只可以处理当前和先前的词:
BERT中的自注意力和GPT-2中的掩模自注意力(masked self-attention)的区别很明显,这点很关键。一般的自注意力模块允许某位置右侧的词计算时处于最大值。而掩模自注意力会阻止这种情况发生:
仅含解码器的模块
延续着先前的论文,一篇名为Generating Wikipedia bySummarizing Long Sequences的论文提出了另一种能够生成语言建模的transformer模块排序。该模型摒弃了transformer的编码器,所以,可以称其为“Transformer-Decoder”。它是早期基于transformer的语言模型,由6个transformer 解码器模块组建的堆栈构成:
解码器模块都相同。笔者对第一个展开了讲解,可以看出它的自注意力层是经过掩模处理的。要注意模型现在可以处理高达4000个词的序列,相较于最初只能处理512个的成绩来说是一个华丽的升级。
这些模块与原始的解码器模块极其相似,除了没有第二个自注意力层。在有深度自注意力层的字符级语言模型中检测到了相似的语言架构——可以创建一次预测一个字母或字符的语言模型。
而OpenAI GPT-2模型使用的就是这些仅含解码器模块。
速成课:揭开GPT-2的面纱
仔细观察你会发现,这些内容深深地印刻在我的大脑里。电闪雷鸣,让我感到痴狂。
——Budgie
用一个训练过的GPT-2试试手,看看它到底是怎么操作的。
该GPT-2可生成1024个词,每个词都会穿过路径中所有的解码器模块。
运行已训练过的GPT-2模型的最简单的办法就是让它自己动起来,用行话讲就是生成无条件样本或者给它一个提示符,令其就某一话题展开论述,也被称为生成交互式条件示例。前者可以轻松掌控词来创造单词(训练的模型使用<|endoftext|> 作为起始词,不如就叫它<s>吧)。
模型中只含有一个输入的词,所以激活的路径也就别无他选词会相继通过所有层,于是会有向量随之产生。该向量可根据模型的词汇进行评分,所有词汇也都是模型知道的,GPT-2中词汇量达5万。这种情况下,选择概率最高的词——the。但我们当然可以打乱这一切——要知道有时候一直点输入键盘推荐的第一个字很有可能就会陷入循环模式,而这时唯一能够打破局面的操作就是点击推荐的第二或第三个字,这里也一样。GPT-2中有一个参数被称为top-k,它能够让模型对首选单词以外的词汇进行采样,此时top-k=1。
下一步,将第一步的输出结果添加到输入序列,让模型进行下一轮预测:
注意,第二个路径是计算中唯一激活的。GPT-2每层都会保留对第一个词的解释,并将其用于处理第二个词。GPT-2不会根据第二个词再去解释第一个词。
进一步深入了解
输入编码
这有更多的细节以便更好地了解模型。从输入开始,正如之前讨论的其他自然语言处理模型,该模型会在嵌入矩阵中查找输入的词,嵌入矩阵是获取的训练模型一部分的组件之一。
每行都是一个词嵌入向量:这一串数字都代表一个单词或含义。不同大小的GPT2模型中的数字规模是存在差异的,最小模型嵌入的规格是768每单词或词。
所以一开始,我们就在嵌入矩阵中查看了起始词。先合并位置编码(该信号会指示transformer 模块序列中单词的顺序),再将词传递到第一个模块中。矩阵作为训练模型的一部分,是由输入的1024个位置上的位置编码向量组成的。
到目前,已经为大家讲解了输入的文字在传递到第一个transformer 模块之前是如何处理的,以及构成训练的GPT-2模型的权值矩阵。
给第一个transformer模块发送文字就意味着会查询其嵌入内容,并将位置编码向量添加到位置1处。
来一场堆栈之旅
现在,第一个模块可以让词先通过自注意力层,紧接着将其递交到神经网络层。第一个transformer 模块一旦接受处理了词,就会把处理的向量结果送去堆栈,等待下一个模块的加工。虽然每一个模块中的处理过程都不同,但其内部的自注意力层和神经网络子层都有着各自的权重。
回顾自注意力机制
语言离不开语境,比如这个第二定律:
机器人第二定律
机器人必须服从人给予它的命令,当命令与第一定律冲突时例外。
笔者已强调了句子中有三处单词与其他单词有联系。如果不结合语境是无法理解或者处理单词的,所以在模型处理这句话时,了解语境是必要的:
· 这句话是讲机器人的
· 这样的命令是定律早期的一部分,也即“人类发出的指令”
· 第一定律就是指整个第一定律
自注意力层的流程就是这样。如同烘培一样,它会在加工特定文字之前对其关联词进行预处理(再传递给神经网络层)。其方式就是对每个单词在语段中的相关度进行评分,然后把结果向量加起来。
例如,顶部的transformer模块的自注意力层在处理“it”时,将重点放在了“robot”上。那么传递给神经网络层的向量将是三个单词中每个单词的向量乘以其分数的总和。
自注意力机制处理
自注意力机制会贯穿于语段中每个词的处理路径。其中重要的组件是三个向量:
· Query查询):展示了当前的单词,该单词会使用键给其他单词评分。我们只需要注意目前正在处理的词的查询。
· Key(键):键向量就像段中所有单词的标签,是搜索相关词汇时的匹配项。
· Value(值):值向量是实际单词的体现,给每个单词的相关度评过分后,加起来的值就会用来表示当前的单词。
该过程可大致理解为在文件柜里进行搜索。查询就如同写有搜索主题的便利贴,键是里面的文件夹名称。对着便利贴寻找标签时,会提取文件夹中的内容,这些内容就是值向量。如果所找值不是一个,而是无数文件夹中的各个值,就要另当别论了。
将查询向量乘以每个键向量,得到的值即为每个文件夹对应的分数(从专业角度讲:乘指的是向量点乘,乘积会通过 softmax 函数处理)。
将值与其分数相乘,再求总和——得出自注意力层结果。
该加权向量值的结果,会让模型将50%的注意力都放在词语“robot”上,30%的注意力放在“a”上,还有19%会在“it”上。
模型输出
当顶层模块生成输出向量(内部自注意力层的结果,其后为神经网络层)时,模型会将该向量与嵌入矩阵相乘。
回想一下,嵌入矩阵中的每一行都会对应模型内的某个单词的嵌入。通过该乘法得到的最终结果可以视为模型中每个词相应的分数。
可以只选择分数最高(top_k = 1)的词,当然如果把其他单词也考虑在内结果会更好。因此,最稳妥的办法就是在整个单词表中以单词的分数为概率将其选出作为样本(自然而然,分数越高被选出的可能性越大)。最好是将top_k设置为40,让模型考虑40个分数最高的单词。
这样模型就完成了一次迭代,最后输出一个单词。直到整个文本(1024个词)生成或序列末词出现,迭代才算结束
小结:GPT-2
有关GPT2的运行讲到这里已经差不多了,如果你比较好奇它内部的自注意力层发生了什么,接下来这部分将是你的福利。笔者的创作之初是想介绍更多可视语言来描述自注意力层,这样在后期检查和描述对transformer模型的介绍时会更轻松一些(看看TransformerXL模型和XLNet)。
笔者在文章中做了一些简化处理,大家留意一下:
· 在此文中,“单词(word)”和“词(token)”两者都是指的同一个事物。但实际上,GPT2会使用字节对编码(BytePair Encoding)创建词汇表中的词(token),所以词(token)也是单词的一部分。
· 例子中运行的GPT2是处于干扰/评估模式的。这就是为什么一次只处理一个单词。在训练模型的时候,会选择更长序列的文本以及多项词实现一次性处理。同时,处理的批量(512)比评估时的更大。
· 为了更好的调整图像中的空间大小,笔者将自由旋转、转换向量。
· 会使用很多标准化层,这一点非常重要。其中一些已经在讲Illustrated Transformer时提到过,但本文会更侧重于自注意力的讲解。
· 例如:有很多次笔者都需要有更多的box来代表向量。笔者将这个比为“放大”,请看:
第二部分:详解自注意力层
文章开篇,已经呈现了这个图片,让大家了解自注意力层是如何应用并处理单词it的:
这一部分,我们将聚焦于它是如何运行的。注意,会尝试一种方式尽可能让大家理解每个单词都发生了什么。所以展示多个单个的向量原因也在此。实际的操作就是将大型矩阵相乘,但笔者想把重点放在感知上,从单词层面感知其中的变化。
自注意力层(不使用掩模)
先看在编码器模块中进行计算的原始自注意力层。可以看一次只能处理四个词的transformer模块的模拟模型。
会经历三个主要步骤:
-
为每个路径创建查询、键和值向量。
-
对每个输入词,用其查询向量给其他键向量评分。
-
在向量值与相关分数相乘后将所有数想加。
1. 创建查询、键和值向量
我们来看第一个路径。将取其查询与所有的键做对比。这样每个键都会得到一个分数。在自注意力层的第一步就是给每个词的路径计算三个向量:查询向量、键向量、值向量(不用管目前的头部注意力)。
2. 注意力得分
三个向量已具备,步骤2只用查询向量和键向量。因为关注的是第一个词,所以将其查询向量与其他所有的键向量相乘,最终结果就是四个词各自的分数。
3. 求和
现在可以将注意力得分与值向量相乘。分数高的值将占结果向量的大部分比重,在那之前会将所有相乘的结果相加求和。
分数越低,呈现的值向量的透明度越高。从中可以看出乘以小数量后会削减向量值。
假如每个路径的操作一致,最终会得到一个代表词的向量,包含合适的语境。这些都会传到下一层中(前馈神经网络):
详解掩模自注意力机制
看过了transformer的自注意力步骤,现在开始介绍掩模自注意力机制(masked self-attention)。除了第二步与自注意力不同,其余都相同。假设模型只输入了两个词,观察第二个词。这种情况下,最后两个词会经过掩模处理,所以模型在评分阶段会进行干预。基本上会经常给后面出现的词评分为0,如此一来模型的最高值就不会出现在这些后出现的词中:
通常会将其作为矩阵(称作注意力掩模)进行操作,设想四个单词的序列(如“robot must obey oders”),在语言模型的场景中,该序列会分为四步骤进行处理——一个单词对应一个步骤(假设每个单词都是一个词)。这些模型是批处理的,所以可以给这个模型提供四个为一批的单词序列,然后将整个序列当作一批来处理(四步走)。
在矩阵表格中,通过查询矩阵与键矩阵相乘计算得出了分数。下面是该过程的可视化,除了单词,不过会有与该单词相关联的查询(或键)向量:
相乘过后,将注意力都放在注意力掩模三角区域。我们想要进行掩模处理的数都变成了负无穷或者非常大的负数(比如GPT2中的-10亿)。
然后在每一行都应用softmax函数,生成用于自注意力层的实际注意力得分:
该分数表格传达的信息就是:
-
在模型处理数据集中的第一个例子(第一行,仅包含一个单词robot)时,100%的注意力都会在该单词上。
-
处理数据集中的第二个例子(第二行,含有单词“robot must”)的过程中,在遇到单词“robot”时,48%的注意力会分给它,而剩下52%的注意力将聚焦到“must”上。
-
等等
GPT-2 中的掩模自注意力机制
评估时间:一次处理一个词
GPT-2模型的操作也能像掩模自注意力那样。但是在评估期间,我们的模型只能在每次迭代之前增添一个新词,而对于那些已经处理过的词,再在之前的路径中重新计算自注意力层是无用的。
这种情况下,处理第一个词
(忽视<s>)
GPT-2模型会保存词a的键向量和值向量。每个自注意力层都会为此词保留各自的键向量和值向量:
到下一次迭代,在模型处理单词robot时,不需要生成词 a的查询向量、键向量和值向量,直接利用第一次迭代生成的就可以:
GPT-2 自注意力: 1- 创建查询、键和值
假如模型正在处理单词it,讨论最底层的模块,那么输入的就会是it的嵌入式+slot#9的位置编码:
Transformer中的每一个模块都有自己的权重(后面会有详解)。首先遇到的就是权重矩阵,这个将会用于创建查询向量、键向量和值向量。
自注意力层将输入的内容与权重矩阵相乘(并且添加了一个向量偏差,这里没有讲)
相乘得到的向量其实就是单词 it的查询向量、键向量和值向量的串联。
将输入的向量与注意权重相乘(以及添加向量偏差)得到该词的键向量、值向量和查询向量。
GPT-2自注意力: 1.5-细分 头部注意力
在先前的例子中,我们直接剖析了自注意力层,并没有考虑到“多头(multi-head)”。现在不着重看这一部分的概念是有好处的。自注意力层多次在查询(Q)、键(K)、值(V)向量的不同部分进行操作。“细分”头部注意力就是重新将长的向量塑造为矩阵。小的GPT2模型有12个头部,会构成重塑矩阵的第一维度:
之前已经探讨过一个注意层的运行过程了。可以这样思考多个头部注意力(如果我们只将12个头部注意力中的3个进行可视化):
GPT-2 自注意力: 2- 计算注意力得分
既然我们只关注一个头部注意力(其他的操作都一样),现在可以继续打分:
现在词可以针对其他词的所有键进行打分(这在之前迭代时的头部注意力 #1过程中计算过):
GPT-2 自注意力机制: 3- 总和
正如之前所见,现在将每个值与其分数相乘,然后相加,针对头部注意力 #1生成自注意力的结果:
GPT-2 自注意力: 3.5- 合并头部注意力
处理各种头部注意力的方式就是一开始将所有事物连结成一个向量:
但是向量还不足以传送到下一层。需要先将这个惹人厌的东西变为同质的事物。
GPT-2 自注意力: 4- 映射
让模型学会如何最大程度将自注意力层的结果绘制成向量,前馈神经网络可以处理。接下来出场的就是第二大权重矩阵,它会把头部注意力的结果映射到自注意力子层的输出向量中:
现在已经有了可以送到下一层向量了:
GPT-2全连接神经网络: 第一层
全连接神经网络中,模块在自注意力层呈现合适的语境之前会处理输入的词。它包含两层。第一层有模型大小的四倍(小的GPT2模型是768,那该网络将会达到768*4=3072)。为什么会是4倍呢?这其实就是原始transformer操作的(模型尺寸为512,图层1为2048)。这似乎给transformer模型创造了足够大的空间去处理截止到目前所需进行的任务。
(没有展示向量偏差)
GPT-2全连接神经网络: 第二层 – 映射到模型尺寸
第二层会将第一层得到的结果映射到模型尺寸上(小的GPT2模型尺寸大小为768)。相乘的结果就是transformer模块处理该词的结果。
(没有显示向量偏差)
你做到了!
这就是有关transformer模块的最详细的解读了!现在你已经差不多了解了transformer语言模型内部是如何运行的。概括一下,输入向量遇到了这些权重矩阵:
并且每个模块都有各自成套的权重。另一方面,模型只有一个词嵌入矩阵和一个位置编码矩阵:
想看所有的模型参数,笔者在这里帮大家汇总一下:
出于一些原因,这些加起来最多是1 亿 2,400 万个参数而非1 亿 1,700 万个。笔者也不知是为什么,不过这些似乎出现在了发布的代码中。(如果错了请及时纠正)
第三部分: 超越语言模型
仅含解码器的transformer一直发挥着胜于语言模型的作用。很多的应用程序都可以体现出其成功之处,类似于上述可视化的都可以对其进行描述。文章到了最后就以一些应用程序为结尾吧。
机器翻译
不需要编码器进行翻译。同一个任务,由仅含解码器的transformer即可完成:
概述
这个就是第一个仅含解码器的transformer的训练任务——读一篇维基百科的文章(在内容之前没有引言),然后进行概括总结。文章开篇的部分会当作训练数据集中的标签:
本文依据维基百科的文章对模型进行了训练,所以训练过的模型可以实现对文章的总结:
迁移学习
在论文「Sample Efficient TextSummarization Using a Single Pre-Trained Transformer」中,仅含解码器的transformer是第一个在语言模型上进行预训练的,然后才完成的概述任务。结果证明在遇到有限的数据集时,它比预训练的仅含编码器-解码器transformer效果要好。
GPT2的论文也展示了在语言模型中预训练后的模型的概括结果。
音乐生成
音乐transformer使用仅含解码器的transformer生成富有动感和韵律的音乐。“音乐模型”就像语言模型——让模型以一种无人监管的方式学习音乐,从而有自己的输出(我们将其称为“随机工作(rambling)”)。
你可能会好奇这种情况下音乐是如何产生的。记住一点,语言模型可以通过角色、单词、或者词的向量体现完成。有了音乐能力(现在就想钢琴),需要呈现笔记,也要求快速——测量钢琴键按下去有多难。
曲子的呈现只是曲子只是一系列独热向量之一,midi文件可以转换为下面这种格式。文章有下列输入顺序的例子:
独热向量对于该输入序列会呈现下面这种效果:
笔者喜欢把这些呈现出来,展示音乐transformer中自注意力层。
这段作品中出现了反复出现的三角轮廓。查询向量位于后一个峰值,它关注前面所有峰值的高音,直到乐曲的开头。本图显示了一个查询向量(所有注意力线来源)和正要处理的以前的记忆(突出了有更高 softmax函数概率的音符)。注意力线的颜色对应于不同的注意力头,而宽度对应于 softmax函数概率的权重。
想要进一步了解这种音符的表征,请观看以下视频:https://www.youtube.com/watch?v=ipzR9bhei_o
总结
这就是GPT2的学习之旅,也探索了它的母模型——仅含解码器的transformer。笔者希望通过这篇文章大家可以能够更好地了解自注意力机制。
编译组:孙梦琪
相关链接:
https://jalammar.github.io/illustrated-gpt2/
* 凡来源非注明“机器学习算法与Python学习原创”的所有作品均为转载稿件,其目的在于促进信息交流,并不代表本公众号赞同其观点或对其内容真实性负责。
推荐阅读
从工作的角度看,NLP/CV/RS/DS选哪个?
【工具】GitHub 2.3W星,Bash脚本代码块汇总
15 个相见恨晚的 Linux 神器,你可能一个都没见过
超生动图解LSTM和GPU,一文读懂循环神经网络!
NEZHA & TinyBERT预训练LM,华为诺亚方舟出品