BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT

前言

我在写上一篇博客《22下半年》时,有读者在文章下面评论道:“july大神,请问BERT的通俗理解还做吗?”,我当时给他发了张俊林老师的BERT文章,所以没太在意。

直到今天早上,刷到CSDN上一篇讲BERT的文章,号称一文读懂,我读下来之后,假定我是初学者,读不懂。

关于BERT的笔记,其实一两年前就想写了,迟迟没动笔的原因是国内外已经有很多不错的资料,比如国外作者Jay Alammar的一篇图解Word2vec、一篇图解Transformer:The Illustrated Transformer,再比如国内张俊林老师的这篇《说说NLP中的预训练技术发展史:从Word Embedding到Bert模型》。

本文基本上可以认为是对这几篇文章在内的学习笔记(配图也基本上都来自文末的参考文献),但本篇笔记准备面对没有任何NLP模型背景的文科生/初学者来写,不出现任何没有解释的概念。作为初学者的你,读本文之前,很多文章你看不懂看不下去,读本文之后,网上1/3值得看的文章你都看得懂、看得下去,本文的目的之一就达到了,因为我也是这么过来的!

毕竟据我观察,网上关于Transformer/BERT的文章无外乎是以下这几种情况:

  • 对经典外文做各种解读或翻译的,比如围绕上面那篇《The Illustrated Transformer》,当然,有的翻的不错 受益匪浅,有的还不如有道翻译来得通顺/靠谱(关键阅读量还不低,一想到大学期间不小心看了很多烂翻译的IT外版书,就恶心,劝君多说人话,没那水平别翻译),当然,本文已汲众各长、去其槽粕
  • 对BERT原始论文做各种解读的,有的号称一文读懂BERT,但读下来感觉像是在看机器翻译出来的文章,文章可读性取决于Google翻译的水平
  • 用自己语言介绍transformer和BERT的,但有些文章有个显而易见的缺点是,文章自身水平抬得过高,作者经常性的自以为、想当然,导致各种背景知识、各种概念有意无意的忽略

我写博客从2010.10.11至今已经超过11年了,这11年还是写过很多通俗易懂的笔记,比如svm xgboost cnn rnn lstm这些,刚好总结一下把文章写通俗的方法,也算是对本文行文的指导思想:

  1. 行为逻辑/条理一定要清晰,这点是最基本的,没有逻辑就没有可读性,通俗是增加可读性
  2. 凡是背景知识得交待,不要想当然 不要自以为读者什么都懂,你自己变聪明了 不见得是聪明,你自己不聪明 让读者变聪明 才是真的聪明
  3. 爬楼梯逐级而上 有100级则爬100级,不出现断层,一出现断层,笔记就不完美了,所以本文准备从头开始写:NNLM → Word2Vec → Seq2Seq → Seq2Seq with Attention → Transformer → Elmo → GPT → BERT(从不懂到弄懂所有这些模型,我用了整整5个半天即2.5天,而有了本文,你从不懂到懂这些模型,可能只需要5个半小时即2.5h,这是本文的目的之二
  4. 公式能展开尽可能展开,不惜字如金,十句解释好过一句自以为是
  5. 多用图、多举例,有时一图胜千言,有时一个好的例子 可以避免绕来绕去

​当然 上面都是术,真正战略层面的则一句话:用初学者思维行文每一字每一句

最近我一直在看哲学相关的书,哲学里有一个很流行的观点是:反思一切司空见惯的事务、现象、常识,类比到大家写文章的话,则是你真的理解你行文的每一字每一句么还是想当然,你真的认为初学者能看懂你写的每一字每一句么还是自以为。

本文篇幅较长,没办法,一者 为了大一统BERT相关的所有概念/模型,篇幅不可能短,二者 不想因为所谓的篇幅有限而抹杀本文的通俗易懂性。另,行文过程中,得到了我司部分讲师的指点,以及杜助教的确认/讨论,有何问题 欢迎不吝指正,thanks。

本文导读

本文篇幅较长(本页面右侧有目录),为让大一新生都能更好的理解本篇笔记,有必要先强调三点:

  1. 想快速学懂任何一个模型,除了通俗易懂的资料外,最重要的一点就是理解透彻该模型的目的/目标是啥,比如本文篇幅很长、涉及的模型很多,但不论再多,都请把握住一点:它们在某种意义上都是预训练模型
  2. 为何会有本文等一系列预训练模型(注意,明白这点非常重要)?
    原因在于:很多机器学习任务都需要带标签的数据集作为输入完成。但是我们身边存在大量没有标注的数据,例如文本、图片、代码等等。标注这些数据需要花费大量的人力和时间,标注的速度远远不及数据产生的速度,所以带有标签的数据往往只占有总数据集很小的一部分。随着算力的不断提高,计算机能够处理的数据量逐渐增大,如果不能很好利用这些无标签的数据就显得很浪费。

    所以半监督学习和预训练+微调的二阶段模式就变得越来越受欢迎。最常见的二阶段方法就是Word2Vec,使用大量无标记的文本训练出带有一定语义信息的词向量,然后将这些词向量作为下游机器学习任务的输入,就能够大大提高下游模型的泛化能力。
  3. 如果你以前还从来没有接触过机器学习这一概念,没关系,我给你简单讲一下:
    所谓机器学习,一般人会和你说,所谓机器学习就是让机器通过对数据的处理具备像人一样的思考、选择、判断、预测等能力,这是第一层

    第二层,举三个例子,第一,让机器学习和让小孩学习是类似的,比如小孩是怎么辨别猫或狗的,无非是父母每次看到一只猫或一条狗,告诉小孩这是猫/狗,久而久之,小孩见到一支新猫/狗,不用父母告诉TA,也能知道是猫/狗了,机器也是一样的,给1000支猫的图片告诉机器是猫,等给它1001只不标注是猫的图片的时候,机器能立马认出来是猫,而前面这1000支猫的图片就是训练机器的过程,通过已知样本不断校准机器的判断能力、不断迭代降低误差(误差 = 实际数据与实验数据的差距)
    第二,这和哪天我们让机器自己编程实现求和函数sum一个意思(a +b = c),通过已知的c不断的判断函数sum的准确性..
    第三,本文中大量出现的让机器通过上文预测下一个单词,或者在句子中间抠掉一个词 让机器通过上下文预测中间抠掉的这个词,试想 是你 你咋预测,机器便差不多类似预测的..
    对机器的预测而言,关键在于通过样本训练提高预测的准确性(比如ELMO、GPT、BERT为何要预训练?训练就是学习,不训练不学习,模型没法天生自知,也就啥也不会了啊)!
    对你的预测而言,比如你在学生时代一直做的『完形填空 』如何提高准确性?老师通过批改作业不断给你对或错的反馈

    第三层,什么是AI?对于人类几千年来说,或社会生产生活当中,不可避免的每天都要做选择、判断、决策、预测,而AI刚好可以基于数据辅助人类做这些行为,所以本质上AI就是一生产生活的工具,而几年来的文明史不就是人类生产力、生产工具、生产关系的历史么?

第一部分 理解基本概念:从NNLM到Word2Vec

我博客内之前写过一篇word2vec笔记,如今再看 写的并不通俗易懂,巧的是,写本文开头那篇图解transformer文章的作者,他也有写一篇图解word2vec,本部分中的核心阐述和大部分配图均来自此文。

为了让每一个初学者可以从头看到尾,不至于因行文过程中的任何一句话而卡壳看不下去,我在原英文的基础上,加了大量自己的学习心得、说明解释(得益于看过很多文学和哲学书,且学过算法,所以文笔尚可、逻辑尚可)。

1.1 从向量表示到词嵌入

不知你可曾听说过华为对于求职人员的性格测试? 这个测试会问你一系列的问题,然后在很多维度给你打分,内向/外向就是其中之一,然后用0到100的范围来表示你是多么内向/外向(其中0是最内向的,100是最外向的)

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第1张图片

假设一个叫Jay的人,其内向/外向得分为38/100,则可以用下图表示这个得分:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第2张图片

为了更好的表达数据(比如归一化便是为了更好表达数据的例子),我们把范围收缩到-1到1:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第3张图片

考虑到人性复杂,对于一个人的描述只有一条信息显然是不够的,为此,我们添加另一测试的得分作为一个新的第二维度,而这两个维度均可以表现为图上的一个点(或称为从原点到该点的向量)

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第4张图片

然后可以说这个向量部分地代表了Jay的人格。当你想要将另外两个人与Jay进行比较时,这种表示法就有用了。假设Jay是一家公司的CEO,某天被公共汽车给撞住院了,住院期间需要一个性格相似的人代行Jay的CEO之责。那在下图中,这两个人中哪一个更像Jay呢,更适合做代理CEO呢?

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第5张图片

而计算两个向量之间相似度得分的常用方法是余弦相似度,可曾还记得夹角余弦的计算公式?

通过该计算公式可得

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第6张图片

从而可知,person 1在性格上与Jay更相似。其实,从坐标系里也可以看出,person1的向量指向与Jay的向量指向更相近(当然,长度也起作用),即他俩具有更高的余弦相似度。

更进一步,两个维度还不足以捕获有关不同人群的足够信息。可曾听说国内有七重人格,而国外心理学也研究出了五个主要人格特征(以及大量的子特征)。

为从简起见,就不用七个维度了,而用五个维度再度比较Jay与person1 2的相似性:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第7张图片

当使用五个维度时,我们没法在二维平面绘制向量小箭头了(毕竟你我都不曾见过五维坐标系)。而实际生活中,我们经常要在更高维度的空间中做研究(有的人把研究一词表达成思考,实际上,大部分人没法在高纬度空间思考,但科学研究人员经常干这事,故表达成研究更准确),好在余弦相似度仍然有效,它适用于任意维度:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第8张图片

这些得分比上次的得分看起来更准确(对,目前为止,咱只能说看起来更准确,最近学哲学给我最大的感悟是,凡事客观,不要轻易绝对化),毕竟它们是根据被比较事物的更高维度算出的。

小结一下,有两点

1.我们可以将人和事物表示为代数向量

2.我们可以很容易地计算出相似向量之间的相互关系。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第9张图片


行文至此,可能有同学要问了,为何要把词向量化表示呢,其背后的深意在哪?

众所周知,咱们居住在各个国家的人们通过各自的语言进行交流,但机器无法直接理解人类的语言,所以需要先把人类的语言“计算机化”,那如何变成计算机可以理解的语言呢?

对于这个问题,我们考虑一个很简单的问题,比如对于计算机,它是如何判断一个词的词性,是动词还是名词的呢?

假定我们有一系列样本(x,y),对于计算机技术机器学习而言,这里的 x 是词语,y 是它们的词性,我们要构建 f(x)->y 的映射:

  1. 首先,这个数学模型 f(比如神经网络、SVM)只接受数值型输入;
  2. 而 NLP 里的词语,是人类语言的抽象总结,是符号形式的(比如中文、英文、拉丁文等等);
  3. 如此一来,咱们便需要把NLP里的词语转换成数值形式,或者嵌入到一个数学空间里;
  4. 进一步,可以把文本分散嵌入到另一个离散空间,称作分布式表示,又称为词嵌入(word embedding)或词向量。
  5. 在各种词向量中,有一个简单的词向量是one-hot encoder。所谓 one-hot编码,其思想跟特征工程里处理类别变量的one-hot 一样,本质上是用一个只含一个 1、其他都是 0 的向量来唯一表示词语。当然,不是所有的编码都是01编码。

这就是所谓的词嵌入了。

再举一个例子,这是一个单词“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”与其它单词进行比较(注意,世间有相似之人,也有相似之词):

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第10张图片

你会发现“Man”这个词和“Woman”相比,比与“King”相比更相似,而这些向量图示很好的展现了这些单词的含义与关联。

这是另一个示例列表:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第11张图片

通过垂直扫描列来查找具有相似颜色的列,相信你可以看到以下这几点

  1. “woman”和“girl”在很多地方是相似的,“man”和“boy”也是一样
  2. 当然,“boy”和“girl”也有彼此相似的地方,但这些地方却与“woman”或“man”不同,为何呢,毕竟boy/girl特指青春年少,而woman/man更多指成人
  3. 此外,“king”和“queen”彼此之间相似,毕竟都是所谓的王室成员

1.2 从神经语言模型NNLMWord2Vec

现在我们已经看过训练好的词嵌入,接下来让我们更多地了解训练过程。 但在我们开始使用word2vec之前,我们需要看一下词嵌入的父概念:神经语言模型(NNLM)。

我们每天都会用手机或者电脑,比如我们经常会用到智能手机输入法中的下一单词预测功能,或者你在电脑上用Google搜索也会遇到类似的搜索智能提示(详见此文)。

比如当你输入thou shalt时,系统会预测/提示你想输入的下一个单词是不是not?

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第12张图片

在上面这个手机截屏中,该模型接收到两个绿色单词(thou shalt)后,推荐了一组单词(当然,“not” 是其中最有可能被选用的一个):

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第13张图片

我们可以把这个模型想象为这个黑盒:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第14张图片

但事实上,该模型不会只输出一个单词。实际上,它对所有它知道的单词(模型的词库,可能有几千到几百万个单词)的按可能性打分,输入法程序会选出其中分数最高的推荐给用户,比如not

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第15张图片

自然语言模型的输出就是模型所知单词的概率评分,我们通常把概率按百分比表示,但是实际上,40%这样的分数在输出向量组是表示为0.4

自然语言模型(请参考Bengio 2003)在完成训练后,会按下图中所示的三个步骤完成预测:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第16张图片

第一步与我们最相关,因为我们讨论的就是Embedding。模型在经过训练之后会生成一个映射单词表所有单词的矩阵,也称词嵌入矩阵。在进行预测的时候,我们的算法就是在这个映射矩阵(词嵌入矩阵)中查询输入的单词(即Look up embeddings),然后计算出预测值:

现在让我们将重点放到模型训练上,来学习一下如何构建这个映射矩阵(词嵌入矩阵)。

1.2.2 语言模型训练:N-Gram技术

我们通过找常出现在每个单词附近的词,就能获得它们的映射关系。机制如下:

  1. 先是获取大量文本数据(例如所有维基百科内容)
  2. 然后我们建立一个可以沿文本滑动的窗(例如一个窗里包含三个单词)
  3. 利用这样的滑动窗就能为训练模型生成大量样本数据。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第17张图片

当这个窗口沿着文本滑动时,我们就能(真实地)生成一套用于模型训练的数据集

不用多久,我们就能得到一个较大的数据集,从数据集中我们能看到在不同的单词组后面会出现的单词:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第18张图片

在实际应用中,模型往往在我们滑动窗口时就被训练的。而怎么训练模型呢?除了使用神经网络建模之外,大家还常用一项名为N-Gram的技术进行模型训练。

举个例子,请你根据下面这句话前面的信息进行填空:

在空白前面,我提供的背景是五个单词(如果事先提及到‘bus’),可以肯定,大多数人都会把bus填入空白中。但是如果我再给你一条信息——比如空白后的一个单词,那答案会有变吗?

这下空白处改填的内容完全变了。这时’red’这个词最有可能适合这个位置的词之一。从这个例子中我们可以看到,一个单词的前后词语其实都带着有价值的信息,而且要尽可能考虑两个方向的单词(目标单词的左侧单词与右侧单词)。

1.2.3 Word2Vec的两种架构:从CBOW到Skipgram模型

 事实上,我们不仅要考虑目标单词的前两个单词,还要考虑其后两个单词。

如果这么做,我们实际上构建并训练的模型就如下所示:

上述的这种『以上下文词汇预测当前词』架构被称为连续词袋(CBOW),在word2vec的原始论文中有阐述。

还有另一种架构,它不根据前后文(前后单词)来猜测目标单词,这种架构就是所谓的Skipgram架构:推测当前单词可能的前后单词

1.2.4 如果模型未经训练则将:误差练练、漏洞百出

还是回到这个例子:“Thou shalt not make a machine in the likeness of a human mind” 

当我们从现有的文本中获得了Skipgram模型的训练数据集后,接下来让我们看看如何使用它来训练一个能预测相邻词汇的自然语言模型。

如下图所示,左边是输入单词,右边是目标单词,基本可以理解为右边的单词是左边输入单词的前后两个单词,比如在这个例子里

  • not 的前后各两个单词分别是:Thou shalt 、make a
  • make 的前后各两个单词分别是:shalt not、a machine
  • a 的前后各两个单词分别是:not make、machine in
  • machine 的前后各两个单词分别是:make a machine in the
  • in 的前后各两个单词分别是:a machine、the likeness 

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第19张图片

从数据集中的第一个样本开始。我们将特征输入到未经训练的模型,让它预测一个可能的相邻单词。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第20张图片

该模型会执行三个步骤并输入预测向量(对应于单词表中每个单词的概率)。因为模型未经训练,该阶段的预测肯定是错误的。但是没关系,我们知道应该猜出的是哪个单词——这个词就是我训练集数据中的输出标签:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第21张图片

目标单词概率为1,其他所有单词概率为0(这是在数据集中引入的负样本,即不是邻居的单词样本。我们的模型需要为这些样本返回0),这样数值组成的向量就是“目标向量”。

模型的偏差有多少?将两个向量相减,就能得到偏差向量:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第22张图片

现在这一误差向量可以被用于更新模型了,所以在下一轮预测中,如果用not作为输入,我们更有可能得到thou作为输出了。

这其实就是训练的第一步了。我们接下来继续对数据集内下一份样本进行同样的操作,直到我们遍历所有的样本。这就是一轮(epoch)了。我们再多做几轮(epoch),得到训练过的模型,于是就可以从中提取嵌入矩阵来用于其他应用了。

顺带提一句,关于什么是负采样,可以参见网上相关资料,比如参考文献1。

1.3 Word2vec训练流程:不断缩小error(target - sigmoid_scores)

在训练过程开始之前,我们预先处理我们正在训练模型的文本。

在训练阶段的开始,我们创建两个矩阵:词嵌入Embedding矩阵、上下文Context矩阵,这两个矩阵在我们的词汇表中嵌入了每个单词,且两个矩阵都有这两个维度

  1. 第一个维度,词典大小即vocab_size,比如可能10000,代表一万个词
  2. 第二个维度,每个词其嵌入的长度即embedding_size,比如300是一个常见值(当然,我们在前文也看过50的例子,比如上文1.1节中最后关于单词“king”的词嵌入长度)

在训练过程开始时,我们先用随机值初始化这些矩阵,然后我们开始训练过程。在每个训练步骤中,我们采取一个相邻的例子及其相关的非相邻例子。

还是这个例子:“Thou shalt not make a machine in the likeness of a human mind”,我们来看看我们的第一组(对于not 的前后各两个邻居单词分别是:Thou shalt 、make a):

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第23张图片

现在我们有四个单词:输入单词not,和输出/上下文单词:thou(实际邻居词),aaron和taco(负面例子)。

我们继续查找它们的嵌入

  • 对于输入词not,我们查看Embedding矩阵
  • 对于上下文单词,我们查看Context矩阵

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第24张图片

然后,我们计算输入嵌入与每个上下文嵌入的点积。

还记得什么叫点积吧?所谓点积
两个向量a = [a1, a2,…, an]和b = [b1, b2,…, bn]的点积定义为:

a·b=a1b1+a2b2+……+anbn。

而这个点积的结果意味输入和上下文嵌入的相似性程度,结果越大代表越相似。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第25张图片

为了将将这些分数转化为看起来像概率的东西——我们需要它们都是正值,并且处于0到1之间,刚好可以用上sigmoid这一逻辑函数转换下。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第26张图片

从而我们可以将sigmoid操作的输出视为这些示例的模型输出。您可以看到taco得分最高,aaron最低,无论是sigmoid操作之前还是之后。

既然未经训练的模型已做出预测,而且我们确实拥有真实目标标签来作对比,那么让我们计算模型预测中的误差吧。为此我们只需从目标标签中减去sigmoid分数。

error = target - sigmoid_scores

这是“机器学习”的“学习”部分。现在,我们可以利用这个错误分数来调整not、thou、aaron和taco的嵌入,使我们下一次做出这一计算时,结果会更接近目标分数。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第27张图片

训练步骤到此结束。我们从中得到了这一步所使用词语更好一些的嵌入(not,thou,aaron和taco)。我们现在进行下一步(下一个相邻样本及其相关的非相邻样本),并再次执行相同的过程。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第28张图片

当我们循环遍历整个数据集多次时,嵌入会继续得到改进。然后我们就可以停止训练过程,丢弃Context矩阵,并使用Embeddings矩阵作为下一项任务的已被训练好的嵌入。

第二部分  从Seq2Seq到Seq2Seq with Attention

2.1 从Encoder-Decoder模型到Seq2Seq

2.1.1 什么是Encoder-Decoder模型:RNN/LSTM与GRU

从上文我们已经接触到编码的概念,有编码则自然有解码,而这种编码、解码的框架可以称之为Encoder-Decoder,中间一个向量C传递信息,且C的长度是固定的。

本节配图大半来源于参考文献2。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第29张图片

没太明白?可以这么理解,在上图中,我们可以根据不同的任务可以选择不同的编码器和解码器,具体化就是可以是一个 RNN

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第30张图片

当然 更多通常是其变种LSTM或者GRU

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第31张图片

这样你就明白了吧。

而在参考文献3里《如何从RNN起步,一步一步通俗理解LSTM》,我们已经详细了解了RNN和LSTM。
而只要是符合上面的框架,都可以统称为 Encoder-Decoder 模型。说到 Encoder-Decoder 模型就经常提到一个名词—— Seq2Seq。

2.1.2 什么是Seq2Seq:输入一个序列 输出一个序列

Seq2Seq(是 Sequence-to-sequence 的缩写),就如字面意思,输入一个序列,输出另一个序列,当然,其中输入序列和输出序列的长度是可变的。

比如我们翻译英国经验派哲学家弗兰西斯・培根的一句名言“知识就是力量”,如下图:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第32张图片

Seq2Seq(强调目的)不特指具体方法,满足「输入序列、输出序列」的目的,都可以统称为 Seq2Seq 模型。

与上面的Encoder-Decoder联系在于:Seq2Seq 属于 Encoder-Decoder 的大范畴,但Seq2Seq 更强调目的,Encoder-Decoder 更强调方法。

2.2 从Seq2Seq到Seq2Seq with Attention

2.2.1 Encoder-Decoder的缺陷:输入信息过长时丢掉信息

上文提到:Encoder(编码器)和 Decoder(解码器)之间只有一个「向量C」来传递信息,且C的长度固定。

这个会导致什么问题呢?比如拿破仑带军打仗时,其军队里炮兵、骑兵、步兵,而队伍的发号施令系统简言之可能是:上层制定、中层传达、下层执行,当从上层往下层发命令时,如果所有的命令甭管是什么命令都只通过一个中间传话人C来传达,那整个集团军的战略执行全系于此一人的觉悟与信息传达的准确性,你想想有多可怕。 

所以更好的做法是,根据面向不同兵种的命令,炮兵指挥C1、骑兵指挥C2、步兵指挥C3各自传达对应兵种的命令,如此,分工协作中不同的中层吸收到的各自命令更准确,从而下层在执行各个不同命令时,执行也更准确。

再比如,当我们Google翻译这句话时:Tom chase Jerry,Encoder-Decoder框架逐步生成中文单词:“汤姆”,“追逐”,“杰瑞”。

在把第三个英文单词Jerry翻译成“杰瑞”这个中文单词的时候,模型里面的每个英文单词对于翻译目标单词“杰瑞”贡献或倾向是相同的,很明显这里不太合理,但显然“Jerry”对于翻译成“杰瑞”比把“Jerry”对于翻译成“汤姆”更重要,但是模型是无法体现这一点的。

要翻译的句子短还好,句子一长呢?当输入句子比较长,此时所有语义完全通过一个中间语义向量C来表示,单词自身的信息已经消失,可想而知会丢失很多细节信息。

所以Encoder-Decoder是有缺陷的,其缺陷在于:当输入信息太长时,会丢失掉一些信息。

2.2.2 Attention应运而生:解决信息过长时信息丢失的问题

而为了解决「信息过长,信息丢失」的问题,Attention 机制就应运而生了。

Attention 模型的特点是 Eecoder 不再将整个输入序列编码为固定长度的「中间向量 C」 ,而是编码成一个向量的序列。引入了Attention的Encoder-Decoder 模型如下图:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第33张图片

Attention(注意力)机制如果浅层的理解,跟他的名字非常匹配。他的核心逻辑就是「从关注全部到关注重点」。

Attention 机制很像人类看图片的逻辑,当我们看一张图片的时候,我们并没有看清图片的全部内容,而是将注意力集中在了图片的焦点上,否则便会抓不住重点。

比如当你女朋友给你闪过一张美女图片(假定正在阅读此文的你是男生,若是女生自行脑补),你只先关注了哪几个点:是脸、胸、腿否?你重点先关注的脸/胸/腿就是你的“Attention”所在了。图片闪过之后,你女友突然发难于你:美女袖标上是个什么英文字母?

然后你心里便会很冤屈,毕竟一张图片信息那么多,只能把有限的注意力放在最重点的信息上。恩,你内心明白就行,可千万别和你女友去谈Attention的大道理,^_^。

2.2.3 通过翻译Tom chase Jerry揭示Attention的算法流程

再次回到这个机器翻译的例子(本猫追老鼠例子的配图和核心阐述均来源于参考文献4),即用Google翻译这句话:Tom chase Jerry

  1. 在翻译“杰瑞”的时候,带有注意力机制的模型会体现出英文单词对于翻译当前中文单词不同的影响程度,比如给出类似这样一个概率分布值:(Tom,0.3)(Chase,0.2) (Jerry,0.5),每个英文单词的概率代表了翻译当前单词“杰瑞”时,注意力分配模型分配给不同英文单词的注意力大小。

  2. 目标句子中的每个单词都应该学会其对应的源语句子中单词的注意力分配概率信息。这意味着在生成每个单词y_{i}的时候,原先都是相同的中间语义表示C会被替换成根据当前生成单词而不断变化的C_{i}(注:这里就是Attention模型的关键,即由固定的中间语义表示C换成了根据当前输出单词来调整成加入注意力模型的变化的C_{i})。

  3.  生成目标句子单词的过程成了下面的形式:

    而每个C_{i}可能对应着不同的源语句子单词的注意力分配概率分布,比如对于上面的英汉翻译来说,其对应的信息可能如下: 

    其中,f2函数代表Encoder对输入英文单词的某种变换函数,比如如果Encoder是用的RNN模型的话,这个f2函数的结果往往是某个时刻输入x_{i}后隐层节点的状态值;g代表Encoder根据单词的中间表示合成整个句子中间语义表示的变换函数(左边这句话太绕,待改),一般的做法中,g函数就是对构成元素加权求和,即下列公式: 

    其中,L_{x}代表输入句子Source的长度,a_{ij}代表在Target输出第i个单词时Source输入句子中第j个单词的注意力分配系数,而h_{j}则是Source输入句子中第j个单词的语义编码。

  4. 假设C_{i}下标i就是上面例子所说的“ 汤姆” ,那么L_{x}就是3,h1=f(“Tom”)、h2=f(“Chase”)、h3=f(“Jerry”)分别是输入句子每个单词的语义编码,对应的注意力模型权值则分别是0.6,0.2,0.2,所以g函数本质上就是个加权求和函数。如果形象表示的话,翻译中文单词“汤姆”的时候,数学公式对应的中间语义表示C_{i}​​​​​​​的形成过程类似下图。

这里有一个问题:生成目标句子某个单词,比如“汤姆”的时候,如何知道Attention模型所需要的输入句子单词注意力分配概率分布值呢?就是说“汤姆”对应的输入句子Source中各个单词的概率分布:(Tom,0.6)(Chase,0.2) (Jerry,0.2) 是如何得到的呢?为做说明,特引用参考文献4对应的内容,如下

为了便于说明,我们假设对非Attention模型的Encoder-Decoder框架进行细化,Encoder采用RNN模型,Decoder也采用RNN模型,这是比较常见的一种模型配置

那么用下图便可以较为便捷地说明注意力分配概率分布值的通用计算过程。

对于采用RNN的Decoder来说

  1. 在时刻i,如果要生成y_{i}单词,我们是可以知道Target在生成y_{i}之前的时刻i-1时,隐层节点i-1时刻的输出值H_{i-1}
  2. 而我们的目的是要计算生成y_{i}时输入句子中的单词“Tom”、“Chase”、“Jerry”对y_{i}来说的注意力分配概率分布,那么可以用Target输出句子i-1时刻的隐层节点状态H_{i-1}去一一和输入句子Source中每个单词对应的RNN隐层节点状态h_{j}​​​​​​​进行对比,即通过函数F(h_{j},H_{i-1})来获得目标单词y_{i}和每个输入单词对应的对齐可能性,这个F函数在不同论文里可能会采取不同的方法,然后函数F的输出经过Softmax进行归一化就得到了符合概率分布取值区间的注意力分配概率分布数值 

再解释一下,如我司杜助教所言:“ 这里举的例子是由 Tom chase Jerrry 得到 汤姆追逐杰瑞,现在我们假设要预测杰瑞(已经预测出来汤姆追逐),那么这个时候,i 就表示的是杰瑞这个时刻,i-1时刻的hidden就包含了汤姆追逐的信息,就是想计算i-1时刻的hidden和Tom、chase、Jerry的attention数值,进而更好地预测杰瑞这个词 


再比如,图书馆(source)里有很多书(value),为了方便查找,我们给书做了编号(key)。当我们想要了解漫威(query)的时候,我们就可以看看那些动漫、电影、甚至二战(美国队长)相关的书籍。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第34张图片

为了提高效率,并不是所有的书都会仔细看,针对漫威来说,动漫,电影相关的会看的仔细一些(权重高),但是二战的就只需要简单扫一下即可(权重低)。

当我们全部看完后就对漫威有一个全面的了解了。

 可以看到,将Source中的构成元素想象成是由一系列的数据对构成,此时给定Target中的某个元素Query,通过计算Query和各个Key的相似性或者相关性,得到每个Key对应Value的权重系数,然后对Value进行加权求和,即得到了最终的Attention数值。

所以本质上Attention机制是对Source中元素的Value值进行加权求和,而Query和Key用来计算对应Value的权重系数。即可以将其本质思想改写为如下公式:

整个过程具体如下图所示:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第35张图片

归纳整理一下,则为

  1. 第一步:代表漫威漫画的query 和 代表某本书的key 进行相似度计算(常见的方法包括:求两者的向量点积、求两者的向量Cosine相似性等),得到权值
  2. 第二步:将权值进行归一化(将原始计算分值整理成所有元素权重之和为1的概率分布,或者说通过SoftMax的内在机制更加突出重要元素的权重),得到直接可用的权重
  3. 第三步:将权重和 value 进行加权求和

值得一提的是,Attention 并不一定要在 Encoder-Decoder 框架下使用的,他是可以脱离 Encoder-Decoder 框架的。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第36张图片

了解了Attention的本质思想,理解所谓的Self-Attention就容易了,具体下文会详细阐述。这里先简单提一嘴。

在一般任务的Encoder-Decoder框架中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attention机制发生在Target的元素Query和Source中的所有元素之间。

而Self Attention顾名思义,指的不是Target和Source之间的Attention机制,而是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力计算机制。其具体计算过程是一样的,只是计算对象发生了变化而已。


第三部分 通俗理解Transformer:理解它就理解了BERT的一半

目前我所看到的文章里面,介绍Transformer比较好懂的还是开头所提的这篇《The Illustrated Transformer》本部分中的核心阐述和大部分配图均来自此文。

接下来,我在此文的基础上加以大量解释、说明,以让之成为全网最通俗易懂的Transformer导论。因为这些解释、说明是你在其他文章中看不到的,而正因为有了这些解释、说明,才足以真正让每一个初学者都能快速理解到底什么是Transformer。

3.1 Transformer之编码:位置编码/自注意力/求和与归一化

3.1.1 从机器翻译模型开始谈起

还是考虑上文中已经出现过的机器翻译的模型。当我们从外部现象来看的话,这个机器翻译技术就像是一个黑箱操作:就是输入一种语言,系统输出另一种语言:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第37张图片

当我们拆开这个黑箱后,我们可以看到它是由编码组件、解码组件和它们之间的连接组成。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第38张图片

其中

  • 编码组件部分由一堆编码器/encoder构成(具体个数可以调)
  • 解码组件部分也是由相同数量(与编码器对应)的解码器decoder组成的

当我们拆开这个编码器会发现,所有的编码器在结构上都是相同的,但是并不共享参数,每个解码器都可以分解成两个子层。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第39张图片

当我们把编码器和解码器联合起来看待整个流程就是

  1. 从编码器输入的句子首先会经过一个自注意力层(即self-attention,下文会具体阐述),它会帮助编码器在对每个单词编码时关注输入句子的其他单词。
  2. 自注意力层的输出会传递到前馈(feed-forward)神经网络中,每个位置的单词对应的前馈神经网络的结构都完全一样(注意:仅结构相同,但各自的参数不同)。
  3. 解码器中也有编码器的自注意力(self-attention)层和前馈(feed-forward)层,除此之外,这两个层之间还有一个注意力层,用来关注输入句子的相关部分(和seq2seq模型的注意力作用相似)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第40张图片

3.1.2 将张量引入图景

我们已经了解了模型的主要部分,接下来我们看一下各种向量或张量(译注:张量概念是矢量概念的推广,可以简单理解矢量是一阶张量、矩阵是二阶张量)是怎样在模型的不同部分中,将输入转化为输出的。

像大部分NLP应用一样,我们首先将每个输入单词通过词嵌入算法转换为词向量。

每个单词都被嵌入为512维的向量(512是transformer原作者设定的一个维度,类似编码器/解码器的数量一样,也是我们可以设置的超参数——一般是我们训练集中最长句子的长度),但为方便后续一系列的图示,这里用4个格子代表512维,即虽然你只看到4维,但你要明白实际背后代表着512维。

所有编码器接收一组向量作为输入,论文中的输入向量的维度是512。最底下的那个编码器接收的是嵌入向量,之后的编码器接收的是前一个编码器的输出。

当我们的输入序列经过词嵌入之后得到的向量,会依次通过编码器组件中的两个层。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第41张图片

接下来我们看看Transformer的一个核心特性

  • 在这里输入序列中每个位置的单词都各自单独的路径流入编码器。不知你发现没有,各个单词是同时流入编码器中的,不是排队进入..
  • 在自注意力self-attention层中,这些路径两两之间是相互依赖的,而前馈层(feed-forward)则没有这些依赖性,所以这些路径在流经前馈层(feed-forward)时可以并行计算

然后我们将以一个更短的句子(Thinking Machine)为例,看看编码器的每个子层中发生了什么。


3.1.3 开始“编码”:从自注意力机制到多头注意力

如上述已经提到的,一个编码器接收一组向量作为输入,输入向量先通过一个自注意力层,再经过一个前馈神经网络,最后将其输出给下一个编码器组件。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第42张图片

输入序列的每个单词都经过自编码过程。然后,他们各自通过前向传播神经网络——完全相同的网络,而每个向量都分别通过它。


3.1.3之0/7 什么是自注意力机制:从宏观视角看自注意力机制

咱们通过一个例子了解自注意力机制的工作原理。

例如,下列句子是我们想要翻译的输入句子:
“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吧?回想一下RNN对隐藏状态的处理:将之前的隐藏状态与当前位置的输入结合起来。在Transformer中,自注意力机制也可以将其他相关单词的“理解”融入到我们当前处理的单词中。

可能上面那段话稍微有点绕,为通俗起见,说的直白点就是,你如果要更好的理解句中某个特定单词的含义,你要把它放到整个语境之中去理解,比如通过对上下文的把握。那上下文哪些词对理解该特定词更重要呢?这个重要程度便用所谓的权重表示,所以才算出每个词对『该词』的权重,权重越大的单词代表对理解『该词』越重要,然后把该词编码为包括该词在内所有词的加权和。

所以,才会出现当我们在编码器#5(栈中最上层编码器)中编码“it”这个单词的时,因为线条颜色的深浅反映了权重的大小,从而可以看到编码“it”时,权重最大的两个词是“The Animal”,便意味着注意力机制的视角/关注度会更集中在“The Animal”,表示 it 跟这两个词关联最大,这就是attention的意义所在,输出跟哪个词关联比较强,就放比较多的注意力在上面,并最终将其考虑进it的编码中。


3.1.3之1/7 计算自注意力第一步:生成查询向量、键向量和值向量向量

首先我们了解一下如何使用向量来计算自注意力,然后来看它是如何以矩阵的方式来计算。

计算自注意力的第一步,就是从每个编码器的输入向量(即每个单词的词向量)一分为三,生成三个向量:查询向量query-vec、一个键向量key-vec和一个值向量value-vec。

至于它们的生成方法是把输入的向量分别乘以三个固定不变的权重矩阵(即所有输入向量都用的相同的权重矩阵Q、K、V),而这些权重矩阵是在模型训练阶段中训练出来的。

这里面有一个细节值得注意下,这些新向量的维度是64,在维度上比词嵌入向量更低,因为词嵌入和编码器的输入/输出向量的维度是512。

这三个向量也不是必须比编码器输入输出的维数小,这样做主要是为了让多头注意力的计算更稳定(在下文你会看到,transformer通过多头注意力机制multiheaded attention,对每个512维的输入向量都设置了8个头,不同的头关注每个输入向量不同的部分,而你发现没有:512/8 = 64,多说一句,也可以设置为2个头,不一定非得设置为8个头)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第43张图片

对于单词X1、X2分别而言

  • X1与WQ权重矩阵相乘得到与这个单词相关的查询向量q1、X1与WK权重矩阵相乘得到与这个单词相关的键向量k1、X1与WV权重矩阵相乘得到与这个单词相关的值向量v1
  • 对于单词X2而言,依上类推:X2分别于WQ权重矩阵、WK权重矩阵、WV权重矩阵相乘得到该单词的查询向量q2、键向量k2、值向量v2

最终使得输入序列的每个单词各自创建一个查询向量、一个键向量和一个值向量

可能有的读者有疑问了,设置这三个向量的用意何在或有何深意。实际上

  • 对于查询向量Query而言:query是当前单词的表示形式,用于对所有其他单词(key)进行评分,我们只需要关注当前正在处理的token的query。
  • 对于键向量Key而言: Key可以看做是序列中所有单词的标签,是在我们找相关单词时候的对照物。
  • 对于值向量Value而言:Value是单词的实际表示,一旦我们对每个单词的相关度打分之后,我们就要对value进行相加表示当前正在处理的单词的value。


3.1.3之2/7 计算自注意力第二步:计算得分

计算自注意力的第二步是计算得分。现在我们需要针对这个例子中的第一个单词“Thinking”(pos#1)计算attention分值,即计算每个词对“Thinking”的打分,这个分决定着编码“Thinking”时(某个固定位置时),应该对其他位置上的单词各自给予多少关注度。

这个得分通过“Thinking”所对应的查询向量query和所有词的键向量key,依次乘积得出来。所以如果我们是处理位置最靠前的词的attention分值的话,第一个分数是q1和k1的点积,第二个分数是q1和k2的点积。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第44张图片

3.1.3之3/7 计算自注意力第三、四步:分数除以8然后softmax下

第三步和第四步是将分数除以8(8是论文中使用的键向量的维数64的平方根,这会让梯度更稳定。这里也可以使用其它值,8只是默认值),然后通过softmax传递结果。softmax的作用是使所有单词的分数归一化,得到的分数都是正值且和为1。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第45张图片

这个softmax分数决定了在编码当下位置(“Thinking”)时,包括当下位置单词(“Thinking”)在内每个单词的所获得的关注度。显然,已经在这个位置上的单词将获得最高的softmax分数,但有时关注与当前单词相关的其他单词是有用的。别忘了,如前文所述,你在考虑某个特定单词的含义时,需要考虑整个语境、考虑上下文。


3.1.3之4/7 计算自注意力第五、六步:值向量乘以softmax分数后对加权值向量求和

第五步是将softmax分值乘以每个值向量(这是为了准备之后将它们求和)。这样操作的意义在于留下我们想要关注的单词的value,并把其他不相关的单词丢掉(例如,让它们乘以0.001这样的小数)。

第六步是对加权值向量求和,产生该位置的self-attention的输出结果(在我们的例子中是对于第一个单词)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第46张图片

通过这样一系列的计算,可以看到,现在每个词的输出向量z都包含了其他词的信息,每个词都不再是孤立的了。而且词与词的相关程度,可以通过softmax输出的权重进行分析。

这样自注意力的计算就完成了。得到的向量就可以传给前馈神经网络。然而实际中,这些计算是以矩阵形式完成的,以便算得更快。那我们接下来就看看如何用矩阵实现的。


3.1.3之5/7 通过矩阵运算实现自注意力机制

第一步是计算查询矩阵、键矩阵和值矩阵。为此,我们将输入词向量合并成输入矩阵X(矩阵的每一行代表输入句子中的一个单词,所以整个矩阵就代表整个句子),将其乘以我们训练的权重矩阵(WQ,WK,WV)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第47张图片

我们再次看到词嵌入向量 (512,或图中的4个格子)和q/k/v向量(64,或图中的3个格子)的大小差异。

最后,由于我们处理的是矩阵,我们可以将步骤2到步骤6合并为一个公式来计算自注意力层的输出,下图是自注意力的矩阵运算形式:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第48张图片


3.1.3之5/7 “multi-headed” attention:“大战多头怪”

通过增加一种叫做“多头”注意力(“multi-headed” attention)的机制,论文进一步完善了自注意力层,并在两方面提高了注意力层的性能:

  1. 它扩展了模型专注于不同位置的能力。编码“Thinking”的时候,虽然最后Z1或多或少包含了其他位置单词的信息,但是它实际编码中还是被“Thinking”单词本身所支配。如果我们翻译一个句子,比如“The animal didn’t cross the street because it was too tired”,我们会想知道“it”指的是哪个词,这时模型的“多头”注意机制会起到作用。
  2. 它给出了注意力层的多个“表示子空间”(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 也有着类似的思想。
简而言之,就是希望每个注意力头,只关注最终输出序列中一个子空间,互相独立。其核心思想在于,抽取到更加丰富的特征信息。不过,Multi-Head 的多个头并不一定就如我们所期望的那样去关注不同方面的信息。在 EMNLP 2018 的 Multi-Head Attention with Disagreement Regularization 一文中采用正则化手段来保证每个头关注不同的子空间。

ok,接下来,我们将看到对于“多头”注意机制,我们有多个查询/键/值权重矩阵集(Transformer使用八个注意力头,因此我们对于每个编码器/解码器有八个矩阵集合)。每一组都是随机初始化,经过训练之后,输入向量可以被映射到不同的子表达空间中。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第49张图片

在“多头”注意机制下,我们为每个头保持独立的查询/键/值权重矩阵,从而产生不同的查询/键/值矩阵。和之前一样,我们拿X乘以WQ/WK/WV矩阵来产生查询/键/值矩阵。

如果我们做与上述相同的自注意力计算,只需八次不同的权重矩阵运算,我们就会得到八个不同的Z矩阵。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第50张图片

这给我们带来了一点挑战。前馈层没法一下子接收8个矩阵,它需要一个单一的矩阵(最终这个单一矩阵类似输入矩阵X那样,矩阵中每个的行向量对应一个单词,比如矩阵的第一行对应单词Thinking、矩阵的第二行对应单词Machines)。所以我们需要一种方法把这8个矩阵合并成一个矩阵。那该怎么做?其实可以直接把这些矩阵拼接在一起,然后乘以一个附加的权重矩阵WO。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第51张图片

这几乎就是多头自注意力的全部。这确实有好多矩阵,我们试着把它们集中在一个图片中,这样可以一眼看清。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第52张图片

3.1.3之7/7 当我们重新用上多头注意力后

现在我们已经看过什么是多头注意力了,让我们回顾一下之前的一个例子,再看一下编码“it”的时候每个头的关注点都在哪里:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第53张图片

编码it,用两个head的时候:其中一个更关注the animal(注意看图中黄线的指向),另一个更关注tired(注意看图中绿线的指向)。
相当于此时该模型对it的编码,除了it本身的表达之外,同时也包含了the animal和tired的相关信息。
这个时候,可能有读者又有疑问了:两个头的时候,为何其中一个头更关注animal,另一个关注tired,那没『头』关注it 本身么?

好问题!如果我们把所有的头的注意力都可视化一下,就是下图这样。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第54张图片

3.2 Transformer结构相比RNN的三大优势

同样都是做NLP任务,我们来和RNN做个对比,下图是个最基本的RNN结构,还有计算公式。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第55张图片

如参考文献7所说,当计算隐向量h4时,用到了输入x4,和上一步算出来的隐向量h3,h3包含了前面所有节点的信息。

  1. h4中包含最多的信息是当前的输入x4,越往前的输入,随着距离的增加,信息衰减得越多。对于每一个输出隐向量h都是如此,包含信息最多得是当前的输入,随着距离拉远,包含前面输入的信息越来越少。但是Transformer这个结构就不存在这个问题,不管当前词和其他词的空间距离有多远,包含其他词的信息不取决于距离,而是取决于两者的相关性,这是Transformer的第一个优势。
  2. 第二个优势在于,对于Transformer来说,在对当前词进行计算的时候,不仅可以用到前面的词,也可以用到后面的词。而RNN只能用到前面的词,这并不是个严重的问题,因为这可以通过双向RNN来解决。
  3. 第三点,RNN是一个顺序的结构,必须要一步一步地计算,只有计算出h1,才能计算h2,再计算h3,隐向量无法同时并行计算,导致RNN的计算效率不高,这是RNN的固有结构所造成的,之前有一些工作就是在研究如何对RNN的计算并行化。通过前文的介绍,可以看到Transformer不存在这个问题。通过这里的比较,可以看到Transformer相对于RNN有巨大的优势。

不过有个细节值得注意下,不知有读者发现没有,即RNN的结构包含了序列的时序信息,而Transformer却完全把时序信息给丢掉了。

为了解决时序的问题,Transformer的作者用了一个绝妙的办法:位置编码(Positional Encoding)。位置编码是和word embedding同样维度的向量,将位置embedding和词embedding加在一起,作为输入embedding:

在这里插入图片描述

 关于『使用位置编码表示序列的位置』的细节请参看英文原文。

3.3 解码器与一些细节问题

3.3.1 求和与归一化

最后,在回顾整个Transformer架构之前,还需再提一下编码器中的一个细节:每个编码器中的每个子层(自注意力层、前馈神经网络)都有一个残差连接,之后是做了一个层归一化(layer-normalization)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第56张图片

将过程中的向量相加和layer-norm(即求和与归一化)的示意图如下所示:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第57张图片

当然在解码器子层中也是这样的,当我们现在画一个有两个编码器和解码器的Transformer,那就是下图这样的:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第58张图片

现在我们已经介绍了编码器的大部分概念,由于encoder的decoder组件差不多,所以也就基本知道了解码器的组件是如何工作的。如此,那让我们直接看看二者是如何协同工作的:

值得一提的是,解码器中的自注意力层和编码器中的不太一样:

  • Encoder中的Q、K、V全部来自于上一层单元的输出
  • 而Decoder只有Q来自于上一个Decoder单元的输出,K与V都来自于Encoder最后一层的输出。也就是说,Decoder是要通过当前状态与Encoder的输出算出权重后,将Encoder的编码加权得到下一层的状态。总之,在解码器中,自注意力层只允许关注已输出位置的信息。实现方法是在自注意力层的softmax之前进行mask,将未输出位置的信息设为极小值。

3.3.2 最后的线性层和softmax层

Decoder输出的是一个浮点型向量,如何把它变成一个词?这就是最后一个线性层和softmax要做的事情。

线性层就是一个简单的全连接神经网络,它将解码器生成的向量映射到logits向量中。
假设我们的模型词汇表是10000个英语单词,它们是从训练数据集中学习的。那logits向量维数也是10000,每一维对应一个单词的分数。

然后,softmax层将这些分数转化为概率(全部为正值,加起来等于1.0),选择其中概率最大的位置的词汇作为当前时间步的输出。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第59张图片

3.4 Transformer的整个训练过程:预处理与迭代

现在我们已经了解了Transformer的整个前向传播的过程,那我们继续看一下训练过程。
在训练期间,未经训练的模型会进行相同的前向传播过程。由于我们是在有标记的训练数据集上训练它,所以我们可以将其输出与实际的输出进行比较。

3.4.1 预处理阶段:创建词汇表

为了便于理解,我们假设预处理阶段得到的词汇表只包含六个单词(“a”, “am”, “i”, “thanks”, “student”, “”)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第60张图片

注意这个词汇表是在预处理阶段就创建的,在训练之前就已经得到了。
一旦我们定义好了词汇表,我们就可以使用长度相同的向量(独热码,one-hot 向量)来表示词汇表中的每个单词。例如,我们可以用以下向量表示单词“am”:

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第61张图片

接下来让我们讨论一下模型的损失函数,损失函数是我们在训练阶段优化模型的指标,通过损失函数,可以帮助我们获得一个准确的、我们想要的模型。

3.4.2 损失函数

假设现在是训练阶段的第一步,我们用一个简单的例子(一个句子就一个词)来训练模型:把法文单词“merci”翻译成英文单词“thanks”。
这意味着,我们希望输出是表示“thanks”的概率分布。但由于这个模型还没有经过训练,所以目前还不太可能实现。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第62张图片

由于模型的参数都是随机初始化的,未经训练的模型为每个单词生成任意的概率分布。
我们可以将其与实际输出进行比较,然后使用反向传播调整模型的权重(关于反向传播的解释,请查看此文:如何理解反向传播算法BackPropagation),使输出更接近我们所需要的值。
如何比较两种概率分布?在这个例子中我们只是将二者相减。实际应用中的损失函数请查看交叉熵损失和Kullback–Leibler散度。

3.4.3 一个示例:法文翻译成英文

上述只是最最简单的一个例子。现在我们来使用一个短句子(一个词的句子升级到三三个词的句子了),比如输入法文 “je suis étudiant”,预期的英文翻译结果为: “i am a student”。

所以我们希望模型不是一次输出一个词的概率分布了,能不能连续输出概率分布,最好满足下边要求:

  1. 每个概率分布向量长度都和词汇表长度一样。我们的例子中词汇表长度是6,实际操作中一般是30000或50000。
  2. 在我们的例子中第一个概率分布应该在与单词“i”相关的位置上具有最高的概率
  3. 第二个概率分布在与单词“am”相关的单元处具有最高的概率
  4. 以此类推,直到最后输出分布指示“”符号。除了单词本身之外,单词表中也应该包含诸如“”的信息,这样softmax之后指向“”位置,标志解码器输出结束。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第63张图片

对应上面的单词表,我们可以看出这里的one-hot向量是我们训练之后想要达到的目标。
在足够大的数据集上训练模型足够长的时间后,我们希望生成的概率分布如下所示:

这个是我们训练之后最终得到的结果。当然这个概率并不能表明某个词是否是训练集之中的词汇。
在这里你可以看到softmax的一个特性,就是即使其他单词并不是本时间步的输出,也会有一丁点的概率存在,这一特性有助于帮助模型进行训练。
模型一次产生一个输出,在这么多候选中我们如何获得我们想要的输出呢?现在有两种处理结果的方法:

  • 一种是贪心算法(greedy decoding):模型每次都选择分布概率最高的位置,输出其对应的单词
  • 另一种方法是束搜索(beam search):保留概率最高前两个单词(例如,“I”和“a”),然后在下一步继续选择两个概率最高的值,以此类推,在这里我们把束搜索的宽度设置为2,当然你也可以设置其他的束搜索宽度。

第四部分 通俗理解BERT: 从Elmo/GPT到集大成者BERT

本部分内容基本或参考或引用自本文开头所说的张俊林老师的文章,或文末参考文献6:《说说NLP中的预训练技术发展史:从Word Embedding到Bert模型》,此文逻辑清晰,核心要点都有。

4.1 从Word Embedding到ELMO

4.1.1 Word Embedding的缺陷:无法处理多义词问题

在本文的第一部分中,我们介绍了word2vec,但实际生产应用中,word2vec的效果并没有特别好。所以,Word Embedding存在什么问题?

这片在Word Embedding头上笼罩了好几年的乌云是什么?是多义词问题。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第64张图片

 如上图所示,比如多义词Bank,有两个常用含义,但是Word Embedding在对bank这个单词进行编码的时候,是区分不开这两个含义的,因为它们尽管上下文环境中出现的单词不同,但是在用语言模型训练的时候,不论什么上下文的句子经过word2vec,都是预测相同的单词bank,而同一个单词占的是同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间里去。

所以word embedding无法区分多义词的不同语义,问题的产生总是无情的推动技术的发展,这不ELMO就来了。

4.1.2 根据上下文对Word Embedding动态调整的ELMO:双层双向LSTM

ELMO是“Embedding from Language Models”的简称,提出ELMO的论文题目:“Deep contextualized word representation”更能体现其精髓,而精髓在哪里?在deep contextualized这个短语,一个是deep,一个是context,其中context更关键。

它与之前的wordvec有何本质区别?

  • 在此之前的Word Embedding本质上是个静态的方式,所谓静态指的是训练好之后每个单词的表达就固定住了,以后使用的时候,不论新句子上下文单词是什么,这个单词的Word Embedding不会跟着上下文场景的变化而改变,所以对于比如Bank这个词,它事先学好的Word Embedding中混合了几种语义 ,在应用中来了个新句子,即使从上下文中(比如句子包含money等词)明显可以看出它代表的是“银行”的含义,但是对应的Word Embedding内容也不会变,它还是混合了多种语义。这是为何说它是静态的,这也是问题所在。
  • 而ELMO的本质思想是:我事先用语言模型学好一个单词的Word Embedding,此时多义词无法区分,不过这没关系。在我实际使用Word Embedding的时候,单词已经具备了特定的上下文了,这个时候我可以根据上下文单词的语义去调整单词的Word Embedding表示,这样经过调整后的Word Embedding更能表达在这个上下文中的具体含义,自然也就解决了多义词的问题了。

所以ELMO本身是个根据当前上下文对Word Embedding动态调整的思路

ELMO采用了典型的两阶段过程

  1. 第一个阶段是利用语言模型进行预训练;
  2. 第二个阶段是在做下游任务时,从预训练网络中提取对应单词的网络各层的Word Embedding作为新特征补充到下游任务中。

上图展示的是其预训练过程,它的网络结构采用了双层双向LSTM(忘了LSTM长啥样的,可以通过参考文献3回顾下),双向LSTM可以干啥?可以根据单词的上下文去正确预测单词,说人话,就是做完形填空。之前的单词序列Context-before称为上文,之后的单词序列Context-after称为下文。

至于有同学有疑问了,ELMO咋采用双向结构不就能看到需要预测的单词了么,你都能看到参考答案了,那岂不影响最终模型训练的效率与准确性,因为哪有做题时看着参考答案做题的。不过,巧就巧在ELMO采用的双向结构是基于LSTM的,而各个LSTM之间是独立的,从而避免了这个问题!

上图左端的前向双层LSTM代表正方向编码器,输入的是从左到右顺序的除了预测单词外的上文Context-before;右端的逆向双层LSTM代表反方向编码器,输入的是从右到左的逆序的句子下文Context-after;每个编码器的深度都是两层LSTM叠加。

这个网络结构其实在NLP中是很常用的。使用这个网络结构利用大量语料做语言模型任务就能预先训练好这个网络,如果训练好这个网络后,输入一个新句子,句子中每个单词都能得到对应的三个Embedding:

  1. 最底层是单词的Word Embedding;
  2. 往上走是第一层双向LSTM中对应单词位置的Embedding,这层编码单词的句法信息更多一些;
  3. 再往上走是第二层LSTM中对应单词位置的Embedding,这层编码单词的语义信息更多一些。

也就是说,ELMO的预训练过程不仅仅学会单词的Word Embedding,还学会了一个双层双向的LSTM网络结构,而这两者后面都有用。

上面介绍的是ELMO的第一阶段:预训练阶段。

那么预训练好网络结构后,如何给下游任务使用呢?

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第65张图片

下图展示了下游任务的使用过程,比如我们的下游任务仍然是QA问题,此时对于问句X

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第66张图片

  1. 我们可以先将句子X作为预训练好的ELMO网络的输入
  2. 这样句子X中每个单词在ELMO网络中都能获得对应的三个Embedding
  3. 之后给予这三个Embedding中的每一个Embedding一个权重a,这个权重可以学习得来
  4. 根据各自权重累加求和,将三个Embedding整合成一个
  5. 然后将整合后的这个Embedding作为X句在自己任务的那个网络结构中对应单词的输入,以此作为补充的新特征给下游任务使用

对于上图所示下游任务QA中的回答句子Y来说也是如此处理。因为ELMO给下游提供的是每个单词的特征形式,所以这一类预训练的方法被称为“Feature-based Pre-Training”。


技术迭代的长河永不停歇,那么站在现在这个时间节点看,ELMO有什么值得改进的缺点呢?

  1. 首先,一个非常明显的缺点在特征抽取器选择方面,ELMO使用了LSTM而不是新贵Transformer,Transformer是谷歌在17年做机器翻译任务的“Attention is all you need”的论文中提出的,引起了相当大的反响,很多研究已经证明了Transformer提取特征的能力是要远强于LSTM的。如果ELMO采取Transformer作为特征提取器,那么估计BERT的反响远不如现在的这种火爆场面。
  2. 另外一点,ELMO采取双向拼接这种融合特征的能力可能比Bert一体化的融合特征方式弱,但是,这只是一种从道理推断产生的怀疑,目前并没有具体实验说明这一点。我们如果把ELMO这种预训练方法和图像领域的预训练方法对比,发现两者模式看上去还是有很大差异的。

除了以ELMO为代表的这种基于特征融合的预训练方法外,NLP里还有一种典型做法,这种做法和图像领域的方式就是看上去一致的了,一般将这种方法称为“基于Fine-tuning的模式”,而GPT就是这一模式的典型开创者。

4.2 从Word Embedding到GPT

4.2.1 生成式的预训练之GPT:预训练 + Fine-tuning + 单向Transformer

GPT是“Generative Pre-Training”的简称,从名字看其含义是指的生成式的预训练。
GPT也采用两阶段过程,第一个阶段是利用语言模型进行预训练,第二阶段通过Fine-tuning的模式解决下游任务。下图展示了GPT的预训练过程,其实和ELMO是类似的,主要不同在于两点:

  1. 首先,特征抽取器不是用的RNN,而是用的Transformer,上面提到过它的特征抽取能力要强于RNN,这个选择很明显是很明智的;
  2. 其次,GPT的预训练虽然仍然是以语言模型作为目标任务,但是采用的是单向的语言模型,所谓“单向”的含义是指:语言模型训练的任务目标是根据单词的上下文去正确预测单词,之前的单词序列Context-before称为上文,之后的单词序列Context-after称为下文。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第67张图片

ELMO在做语言模型预训练的时候,预测单词可以同时使用上文和下文做完形填空,用的双向LSTM结构,而GPT则只采用Context-before这个单词的上文来进行预测,而抛开了下文。说人话,就是让你根据提示造句,从左到右,是单向的

什么是单向Transformer?在Transformer的文章中,提到了Encoder与Decoder使用的Transformer Block是不同的。怎么个不同?通过本文第三部分对Transformer的介绍可得知:

  • Encoder因为要编码整个句子,所以每个词都需要考虑上下文的关系。所以每个词在计算的过程中都是可以看到句子中所有的词的;
  • 但是Decoder与Seq2Seq中的解码器类似,每个词都只能看到前面词的状态,所以是一个单向的Self-Attention结构

换言之,在解码Decoder Block中,使用了Masked Self-Attention,即句子中的每个词,都只能对包括自己在内的前面所有词进行Attention,这就是单向Transformer。

而GPT使用的Transformer结构就是将Encoder中的Self-Attention替换成了Masked Self-Attention,从而每个位置的词看不到后面的词,看不到好啊!


July写博客嘛,就喜欢抠细节 不抠清楚不罢休,故帮本文的读者发挥下想象。

比如有人可能立马就疑问了,为何看不到还好?因为我本来就要训练模型的预测能力,让模型通过大量样本慢慢的学会通过上文预测下一个单词,而如果你通过上文预测下一个单词的时候你都能看见下一个单词、看到答案了,还做个啥子预测?
这和咱们学生时代做题一样,你都有参考答案了,你还能好好做题不?你会分心、分神,最终你做题的能力会越来越差..

凡事都要辩证的看,凡事也都有利有弊。根据上文预测下个单词还好,本身就是根据提示造句嘛,只有上文。但如果训练模型做完形填空的能力呢?做完形填空需要结合上下文语境:从左到右 + 从右到左),是双向的。所以,如果预训练时候不把单词的下文嵌入到Word Embedding中,在面对完形填空类似任务时就显得力不从心了。

上面讲的是GPT如何进行第一阶段的预训练,那么假设预训练好了网络模型,后面下游任务怎么用?它有自己的个性,和ELMO的方式大有不同。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第68张图片

上图展示了GPT在第二阶段如何使用。

  • 首先,对于不同的下游任务来说,本来你可以任意设计自己的网络结构,现在不行了,你要向GPT的网络结构看齐,把任务的网络结构改造成和GPT的网络结构是一样的。
  • 然后,在做下游任务的时候,利用第一步预训练好的参数初始化GPT的网络结构,这样通过预训练学到的语言学知识就被引入到你手头的任务里来了,这是个非常好的事情。
  • 再次,你可以用手头的任务去训练这个网络,对网络参数进行Fine-tuning,使得这个网络更适合解决手头的问题。

4.2.2 示例:基于Transformer与自注意力机制的具体迭代

虽然GPT基本就用的Transformer的机构,但话虽如此,可光理论不举例则总是不够形象、不够具体。

接下来,举个例子。我们已经能理解语言很大程度上是依赖于语境的。例如,看看机器人第二定律:

Second Law of Robotics
A robot must obey the orders given it by human beings except where such orders would conflict with the First Law.

上面这个句子中有三个地方被标粗了,这些地方的单词是指代其他单词的。如果不结合它们所指的上下文,就没有办法理解或处理这些单词。当一个模型处理这个句子时,它必须能够知道:

  • it 指的是 robot
  • such orders前半部分的 the orders given it by human beings
  • First Law 指的是机器人第一定律

这就是自注意力要做的。在处理某个单词之前,它会先让模型理解相关单词,这些相关单词可以解释某个单词的上下文。注意力要做的就是给每个词进行相关度打分,并将它们的向量表示相加来。

如下图,处理“it”的时候,注意力机制会关注到“a robot”,注意力会计算三个词“it”、“a”、“robot”的向量及其attention分数的加权和。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第69张图片

上文已经说过了,自注意力机制沿着序列中每一个单词的路径进行处理,一分为三成三个向量:查询向量(Query 向量)、键向量(Key 向量)、值向量(Value 向量)。

一个简单的比喻是,就像在文件柜里找文件一样。query就像一张你拿着一张便签,上面写着要找的主题。key就像柜子里文件夹的标签,当你找到与便利贴上相关的标签的时候,我们取出该文件夹,文件夹中的内容就是value向量。当然你要找的不是一个文件,是一组相关文件。

将query向量乘以key向量生成文件夹的相关度分数(实现上就是两个向量点积之后softmax)。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第70张图片

我们把每一个key乘以一组value,然后相加,这就产生了最终自注意力层的结果。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第71张图片

这样将值向量加权混合得到的结果是一个向量,它将其 50% 的「注意力」放在了单词「robot」上,30% 的注意力放在了「a」上,还有 19% 的注意力放在「it」上。

接下来,我们继续向前探索 transformer 堆栈,看看模型的输出。

当最后一个decoder组件产生输出之后(即经过了它自注意力层和神经网络层的处理),模型会将输出的向量乘上embedding矩阵。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第72张图片

我们知道,嵌入矩阵的每一行都对应模型的词汇表中一个单词的嵌入向量。所以这个乘法操作得到的结果可以认为是词汇表中每个单词对应的注意力得分:即对整个词汇表的打分。

在这里插入图片描述

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第73张图片

我们简单地选取分数最高的单词作为输出结果(即 top-k = 1)。但其实如果模型考虑其他候选单词的话,效果通常会更好。因此一个更好的策略是使用这个分数进行随机采样,从中抽一个词出来,这样分数高的词汇被选中的概率也高。通常一个折中的方法是,将 top-k 设为 40,这样模型会考虑注意力得分排名前 40 位的单词。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第74张图片

这样,模型就完成了一次迭代,最终输出了一个单词。该模型继续迭代,直到生成整个上下文(GPT-2序列上限1024个token)或直到生成序列结束的token

4.3 集大成者之BERT:双向Transformer版的GPT

4.3.1 BERT模型的架构:预训练 + Fine-Tuning + 双向Transformer

我们经过跋山涉水,终于到了目的地Bert模型了。GPT-2是使用「Transformer单向解码器Decoder模块」构建的,而 BERT则是通过「Transformer双向编码器Encoder 模块」构建的

  1. Bert采用和GPT完全相同的两阶段模型,首先是语言模型预训练;其次是使用Fine-Tuning模式解决下游任务
  2. 和GPT的最主要不同在于在预训练阶段采用了类似ELMO的双向语言模型,当然另外一点是语言模型的数据规模要比GPT大

所以这里Bert的预训练过程不必多讲了。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第75张图片

第二阶段,Fine-Tuning阶段,这个阶段的做法和GPT是一样的。

4.3.3 BERT与GPT、ELMO、Word2Vec三者的异同

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第76张图片

到这里我们可以再梳理下几个模型之间的演进关系。从上图可见,Bert其实和ELMO及GPT存在千丝万缕的关系,

  • 比如如果我们把GPT预训练阶段换成双向语言模型,那么就得到了Bert;
  • 而如果我们把ELMO的特征抽取器换成Transformer,那么我们也会得到Bert。

所以你可以看出:Bert最关键两点

  • 一点是特征抽取器采用Transformer
  • 第二点是预训练的时候采用双向语言模型

4.3.4 BERT的两个创新点:Masked语言模型与Next Sentence Prediction

那么新问题来了:对于Transformer来说,怎么才能在这个结构上做双向语言模型任务呢?

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第77张图片

首先,它借鉴了Word2Vec的CBOW方法:根据需要预测单词的上文Context-Before和下文Context-after去预测单词。

问题又来了,如果我们要训练ELMO、GPT、BERT做完形填空的能力时

  • 如上文4.1.2节所说,ELMO采用双向LSTM结构,因为各个LSTM结构之间是互相独立的,所以可以根据上下文预测中间词,即做完形填空
  • 如上文4.2.1节所说,GPT在做另一个任务:根据上文预测下一个单词时,要求Pre-Training预测下一个词的时候只能够看见当前以及之前的词,这也是GPT放弃原本Transformer的双向结构转而采用单向结构的原因。从而此举也就决定了GPT适合根据上文预测下一个单词,但不适合做完形填空这样的任务..
  • BERT为了做完形填空,而不是像GPT一样完全放弃下文信息,采用了双向的Transformer。但是如果单纯用双向Transformer结构的话,不就导致每个Transformer的输出都可以看见整个句子的,无论你用这个输出去预测什么,都会“看见”参考答案,也就是“see itself”的问题

好问题啊!如果BERT只是一个大杂烩,没有任何创新,便只会注定BERT平凡的命运,也就不是我们目前所熟知的BERT了。但Bert强就强在除了集众所长之外,还做了两个创新:一个是论文中指出的Masked 语言模型,一个是Next Sentence Prediction。而Masked语言模型的本质思想其实就是CBOW,但是细节方面有改进。

实际上,“Masked Language Model(MLM)和核心思想取自Wilson Taylor在1953年发表的一篇论文。所谓MLM是指在训练的时候随即从输入预料上mask掉一些单词,然后通过的上下文预测该单词,该任务非常像我们在中学时期经常做的完形填空。正如传统的语言模型算法和RNN匹配那样,MLM的这个性质和Transformer的结构是非常匹配的。

也就是说,当BERT用到Transformer双向结构做完形填空时(即想要知道上文的信息,又想要知道下文的信息),为了让BERT尽快自行具备更准确的预测能力,就得让BERT不知道要预测词的信息,那么就干脆不要告诉模型这个词的信息就可以了。也就是说,BERT在输入的句子中,挖掉一些需要预测的词,然后通过上下文来分析句子,最终使用其相应位置的输出来预测被挖掉的词。

但是,直接将大量的词替换为标签可能会造成一些问题,模型可能会认为只需要预测相应的输出就行,其他位置的输出就无所谓。同时Fine-Tuning阶段的输入数据中并没有标签,也有数据分布不同的问题。为了减轻这样训练带来的影响,BERT采用了如下的方式:输入数据中随机选择15%的词用于预测,这15%的词中,

  • 80%的词向量输入时被替换为,比如my dog is hairy -> my dog is [mask]
  • 10%的词的词向量在输入时被替换为其他词的词向量,比如my dog is hairy -> my dog is apple
  • 另外10%保持不动,比如my dog is hairy -> my dog is hairy

这样一来就相当于告诉模型,我可能给你答案,也可能不给你答案,也可能给你错误的答案,有的地方我会检查你的答案,没的地方我也可能检查你的答案,所以标签对你来说没有什么特殊意义,所以无论如何,你都要好好预测所有位置的输出。

至于BERT的第二个创新点:“Next Sentence Prediction”,指的是做语言模型预训练的时候,分两种情况选择两个句子,一种是选择语料中真正顺序相连的两个句子;另外一种是第二个句子从语料库中抛色子,随机选择一个拼到第一个句子后面。我们要求模型除了做上述的Masked语言模型任务外,附带再做个句子关系预测,判断第二个句子是不是真的是第一个句子的后续句子。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第78张图片

之所以这么做,是考虑到很多NLP任务是句子关系判断任务,单词预测粒度的训练到不了句子关系这个层级,增加这个任务有助于下游句子关系判断任务。所以可以看到,它的预训练是个多任务过程。这也是Bert的一个创新。

July注6:「“换句话说,Next Sentence Prediction(NSP)的任务是判断句子B是否是句子A的下文。如果是的话输出’IsNext‘,否则输出’NotNext‘。训练数据的生成方式是从平行语料中随机抽取的连续两句话,其中50%保留抽取的两句话,它们符合IsNext关系,另外50%的第二句话是随机从预料中提取的,它们的关系是NotNext的。这个关系保存在BERT输入表示图中的[CLS]符号中。”」

上面这个图给出了一个我们此前利用微博数据和开源的Bert做预训练时随机抽出的一个中文训练实例,从中可以体会下上面讲的masked语言模型和下句预测任务。训练数据就长这种样子。

4.3.5 BERT对输入、输出部分的处理

顺带讲解下Bert的输入部分,也算是有些特色。它的输入部分是个线性序列,两个句子通过分隔符分割,最前面和最后增加两个标识符号。每个单词有三个embedding:

  1. 位置信息embedding,这是因为NLP中单词顺序是很重要的特征,需要在这里对位置信息进行编码;
  2. 单词embedding,这个就是我们之前一直提到的单词embedding;
  3. 第三个是句子embedding,因为前面提到训练数据都是由两个句子构成的,那么每个句子有个句子整体的embedding项对应给每个单词。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第79张图片

把单词对应的三个embedding叠加,就形成了Bert的输入。

July注:「“BERT的输入的编码向量(长度是512)是3个嵌入特征的单位和,这三个词嵌入特征是:
1) 位置嵌入(Position Embedding):位置嵌入是指将单词的位置信息编码成特征向量,位置嵌入是向模型中引入单词位置关系的至关重要的一环;
2) WordPiece 嵌入:WordPiece是指将单词划分成一组有限的公共子词单元,能在单词的有效性和字符的灵活性之间取得一个折中的平衡。例如上图的示例中‘playing’被拆分成了‘play’和‘ing’;
3) 分割嵌入(Segment Embedding):用于区分两个句子,例如B是否是A的下文(对话场景,问答场景等)。对于句子对,第一个句子的特征值是0,第二个句子的特征值是1。
对了,其中的[SEP]表示分句符号,用于断开输入语料中的两个句子。”」

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第80张图片

至于Bert在预训练的输出部分如何组织,可以参考上图的注释。

BERT通俗笔记:从Word2Vec/Transformer逐步理解到BERT_第81张图片

我们说过Bert效果特别好,那么到底是什么因素起作用呢?如上图所示,对比试验可以证明,跟GPT相比,双向语言模型起到了最主要的作用,对于那些需要看到下文的任务来说尤其如此。而预测下个句子来说对整体性能来说影响不算太大,跟具体任务关联度比较高。

4.3.6 BERT总结与评价:借鉴多个模型设计的集大成者

BERT的横空出世基本也是NLP模型不断发展、演变的结果,如参考文献7所总结的:

  1. 早期的NLP模型基于规则解决问题,比如专家系统,这种方式扩展性差,因为无法通过人来书写所有规则。
  2. 之后提出了基于统计学的自然语言处理,早期主要是基于浅层机器学习解决NLP问题。例如,通过马尔科夫模型获得语言模型,通过条件随机场CRF进行词性标注。如果你去看StandFord的NLP工具,里面处理问题的早期方法,都是这类方法。
  3. 当深度学习在图像识别领域得到快速发展时,人们也开始将深度学习应用于NLP领域。

    首先是Word Embedding。它可以看成是对单词特征提取得到的产物,它也是深度学习的副产物。随后,人们又提出了Word2Vec,GloVe等类似预训练的词向量,作为对单词的特征抽象,输入深度学习模型。

    其次是RNN。RNN使得神经网络具有时序的特性,这种特性非常适合解决NLP这种词与词之间有顺序的问题。但是,深度学习存在梯度消失问题,这在RNN中尤其明显,于是人们提出了LSTM/GRU等技术,以解决这种梯度消失问题。在2018年以前,LSTM和GRU在NLP技术中占据了绝对统治地位。

    当时RNN有个致命的问题,就是训练慢,无法并行处理,这限制了其发展。于是人们想到了是否可以用CNN替代RNN,以解决这个问题。于是人们提出了用1D CNN来解决NLP问题。但是这种方式也有个明显问题,就是丢掉了RNN的时序优势。

    除了时序问题,我们还不得不提另外一个关键问题,即注意力Attention。Attention最早用于图像识别领域,然后再被用于NLP领域。

    有了Attention技术,我们急需新技术既可以保证并行处理,又可以解决时序问题。于是Transformer腾空出世。它也是BERT的基础之一。

    除此之外,ELMO提出的预训练双向语言模型以及GPT提出的单向Tranformer也是最新双向Transformer发展的基础,在《Attention Is All You Need》一文,Transformer上升到另外一个高度。

    BERT正是综合以上这些优势提出的方法,可以解决NLP中大部分问题。

总之,Bert借鉴了ELMO,GPT及CBOW,主要提出了Masked 语言模型及Next Sentence Prediction,但是这里Next Sentence Prediction基本不影响大局,而Masked LM明显借鉴了CBOW的思想。所以说Bert的模型没什么大的创新,更像最近几年NLP重要进展的集大成者,这点如果你看懂了上文估计也没有太大异议,如果你有大的异议,杠精这个大帽子我随时准备戴给你。

如果归纳一下这些进展就是:

  • 首先是两阶段模型,第一阶段双向语言模型预训练,这里注意要用双向而不是单向,第二阶段采用具体任务Fine-tuning或者做特征集成;
  • 第二是特征抽取要用Transformer作为特征提取器而不是RNN或者CNN;
  • 第三,双向语言模型可以采取CBOW的方法去做(当然我觉得这个是个细节问题,不算太关键,前两个因素比较关键)。

Bert最大的亮点在于效果好及普适性强,几乎所有NLP任务都可以套用Bert这种两阶段解决思路,而且效果应该会有明显提升。可以预见的是,未来一段时间在NLP应用领域,Transformer将占据主导地位,而且这种两阶段预训练方法也会主导各种应用。

参考文献

  1. 国外一牛人Jay Alammar写的 图解Word2Vec(如果你打不开英文原文,可看此翻译版)..
  2. Encoder-Decoder 和 Seq2Seq
  3. 《如何从RNN起步,一步一步通俗理解LSTM》
  4. 《深度学习中的注意力机制(2017版)》,张俊林
  5. 还是Jay Alammar写的图解transformer(如果你打不开英文原文,可看此:翻译版1)
  6. 《说说NLP中的预训练技术发展史:从Word Embedding到Bert模型》,张俊林

  7. 深度学习:前沿技术-从Attention,Transformer,ELMO,GPT到BERT

  8. 自然语言处理中的Transformer和BERT

  9. 超细节的BERT/Transformer知识点

  10. 《The Illustrated GPT-2 (Visualizing Transformer Language Models)》(翻译版1、翻译版2)

  11. Transformer结构及其应用详解--GPT、BERT、MT-DNN、GPT-2

后记

从10.19日起,除了日常工作或去高校谈合作外,每天1/3的时间都在写这篇BERT笔记(晚上+白天一两个小时,每天如此)。行文过程中,因为各种查阅资料,自己之前一些比较模糊的细节逐渐清晰,而本文初稿差不多后,很多以前不想看的文章仔细一看,其中有1/3至少还是可以看的,核心原因自然是自己通过写本文对BERT相关的模型、技术、概念相较之前理解更深了,即便有的文章在表达上不是特别好,但基本能看出作者想表达啥。

另外1/3只要把文中的某些细节补充下,或者某些表达再通俗化下,或者再举几个例子就会好很多,至于最后1/3..

其次,我也在想,为何BERT出来至今已经4年了,但真正面向初学者入门且通俗易懂的BERT笔记为何并不多见(即便是显得水平高的文章其中很多离我司的BERT课程亦相差甚远),仔细想来应该无外乎缘由如下,姑且逐步深入探讨下

  1. 大部分人是在理解且入门了BERT之后,才对外写博客或发文章的,啥意思呢?意思是,你看到的已经是他已写好的这个文章、这个结果了,而他们在理解且入门BERT的这个过程中所经历的不解、困惑,以及怎么从不解/困惑到豁然开朗的步骤,大部分人不会在文中写出来
  2. 为何这个理解的过程比较少的人会写出来呢?毕竟就像我们学习数理统计这门课一样,如果我们在学这门课之前,先理解了各个公式、定理的发明过程与来龙去脉,那我们岂不可以更好的理解公式/定理本身么?
  3. 这就归结到一个定位的问题了。大部分作者写文章时,他是为了表达一个核心观点或核心感悟。类比教材也一样,它更多是为了告诉你这个定理是什么、解决什么问题,至于背后怎样一个发明过程,那就说来话长,毕竟篇幅有限,而恰恰是这个篇幅有限,自然就抹掉了理解过程、发明过程,从而也就一上来就给你结论,让一众初学者乍一看:一脸懵逼
  4. 当然,这里面也有个别怪现象,一是有炫耀之嫌,比如有的作者 正如有些学霸,他们喜欢直接展示他的学业成就,而不会轻易展示人前骄傲背后的各种努力思考(当然 他们的领悟能力相对较强 理解更快),从而向某些学渣炫耀,你看你这么努力 还是不如我考的分高;二是没那个意愿,即有的人写文章/博客压根就没打算给还没入门的初学者看,毕竟有时初学者这不懂、那不懂,一问起难得解释
  5. 于我而言,我是一个教育领域的创业者,教书育人者,传道授业解惑只是最基本的,更重要的是授人以鱼不如授人以渔,说的直白点,知识随处可见,而效果呢?屈指可数!所以虽然网上关于BERT的文章/资料一大堆,但真正对你帮助大的文章能有几篇呢?于我而言,BERT文章千千万,对我真正有大帮助的还是上面所列的那几个参考文献
  6. 好,即便依然有很多文章/博客作者原意展示背后的思考过程,想把文章写通俗,但简单粗暴展示容易,写通俗则不易了,比如你是否耐得住性子费尽心思、罗里吧嗦,是否能以初学者思维行文全文每一字一句,是否愿意多用图、多举例等等..

在此之前 作为初学者的你可能看过非常多关于BERT的文章(或者word2vec、注意力机制、transformer的),读完此文后,相信之前很多你读不下去、读不完的文章有1/3可以读下去、读得懂了,至于剩下的2/3,帮助有限,可以先不看。

截止到今天10.24,本文还只是初稿,未来一两周,本文会大修;未来一两月,本文会中修,未来一两年,本文会小修。还是那句话,竭尽全力,让本文成为每一个初学者都能看下去且完全看懂的通俗笔记,不至于各种卡壳而中断阅读。有啥问题,欢迎随时留言或指正,thanks。

你可能感兴趣的:(机器学习十大算法系列,bert,transformer,深度学习,word2vec,GPT,1024程序员节)