赞
踩
在本节中,我们将学习如何对BERT模型进行预训练。假设我们有一个模型 m m m。首先,我们使用一个大型数据集针对某个具体的任务来训练模型 m m m,并保存训练后的模型。然后,对于一个新任务,我们不再使用随机的权重来初始化模型,而是用已经训练过的模型的权重来初始化 m m m(预训练过的模型)。也就是说,由于模型 m m m已经在一个大型数据集上训练过了,因此我们不用为一个新任务从头开始训练模型,而是使用预训练的模型 m m m,并根据新任务调整(微调)其权重。这是迁移学习的一种类型。
BERT模型在一个巨大的语料库上针对两种特定的任务进行预训练。这两种任务是掩码语言模型构建和下句预测。在预训练完成之后,我们保存预训练好的BERT模型。对于一个新任务,比如问答任务,我们将使用预训练的BERT模型,而无须从头开始训练。然后,我们为新任务调整(微调)其权重。
下面,我们将详细了解BERT模型是如何进行预训练的。在深入研究预训练之前,先让我们看看BERT接受的输入格式。
在将数据输入BERT之前,首先使用如下3个嵌入层将输入转换为嵌入。
让我们逐一了解这些嵌入层是如何工作的。
首先,我们学习标记嵌入层。让我们通过一个示例来理解标记嵌入过程。请看下面的两个句子。
我们先对这两个句子进行分词标记,如下所示。在本例中,我们没有将字母小写化。
tokens = [Paris, is, a, beautiful, city, I, love, Paris]
接下来,我们添加一个新标记,即[CLS]标记,并将它放在第一句的开头。
字符[CLS]表达的意思很简单 - Classification (分类)
tokens = [ [CLS], Paris, is, a, beautiful, city, I, love, Paris]
然后,我们在每个句子的末尾添加一个新标记,即[SEP]。
[SEP]代表分隔符
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP]]
请注意,[CLS]只在第一句的开头添加,而[SEP]在每一句的结尾都要添加。[CLS]用于分类任务,而[SEP]用于表示每个句子的结束。在本章的后面,你将进一步了解[CLS]和[SEP]的作用。
在将所有标记送入BERT之前,我们使用标记嵌入层将标记转换成嵌入。请注意,标记嵌入的值将通过训练学习。如下图所示,我们计算所有标记的嵌入,如 E C L S E_{CLS} ECLS表示[CLS]的嵌入, E P a r i s E_{Paris} EParis表示Paris的嵌入,以此类推。
分段嵌入层用来区分两个给定的句子。让我们通过前面所列举的两个句子来理解分段嵌入。
在对这两个句子进行分词后,我们得到以下内容。
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP]]
现在,除了[SEP],我们需要给模型提供某种标记来区分两个句子。因此,我们将输入的标记送入分段嵌入层。
分段嵌入层只输出嵌入 E A E_A EA或 E B E_B EB。也就是说,如果输入的标记属于句子A,那么该标记将被映射到嵌入 E A E_A EA;如果该标记属于句子B,那么它将被映射到嵌入 E B E_B EB。
如图所示,句子A的所有标记都被映射到嵌入 E A E_A EA,句子B的所有标记则都被映射到嵌入 E B E_B EB。
如果我们只有一个句子,还需要分段嵌入吗?假设我们只有句子Paris is a beautiful city,在这种情况下,句子的所有标记都将被映射到嵌入
E
A
E_A
EA,如图所示。
最后,让我们了解一下位置嵌入层。我们在前文中了解到,由于Transformer没有任何循环机制,而且是以并行方式处理所有词的,因此需要一些与词序有关的信息。有鉴于此,我们使用了位置编码。
BERT本质上是Transformer的编码器,因此在直接向BERT输入词之前,需要给出单词(标记)在句子中的位置信息。位置嵌入层正是用来获得句子中每个标记的位置嵌入的。
如图所示,
E
0
E_0
E0表示[CLS]的位置嵌入,
E
P
a
r
i
s
E_{Paris}
EParis表示Paris的位置嵌入,以此类推。
现在,让我们看看最终的输入数据特征。如图所示,首先将给定的输入句子转换为标记,然后将这些标记依次送入标记嵌入层、分段嵌入层和位置嵌入层,并获得嵌入结果。接下来,将所有的嵌入值相加,并输入给BERT。
BERT使用一种特殊的词元分析器,即WordPiece。WordPiece遵循子词词元化规律。
让我们通过下面的示例来了解WordPiece的工作原理。
例句:Let us start pretraining the model(让我们开始预训练模型)
使用WordPiece对该句进行标记,得到如下结果
tokens = [let, us, start, pre, ##train, ##ing, the, model]
可以看出,在使用WordPiece对句子进行分词时,pretraining这个词被分成了以下几个部分:pre、##train、##ing。这意味着什么呢?
当使用WordPiece进行分词时,我们首先会检查该词是否存在于词表中。如果该词已经在词表中了,那么就把它作为一个标记。如果该词不在词表中,那么就继续将该词分成子词,检查子词是否在词表中。如果该子词在词表中,那么就把它作为一个标记。但如果子词还是不在词表中,那么继续分割子词。
我们通过这种方式不断地进行拆分,检查子词是否在词表中,直到字母级别(无法再分)。
这在处理未登录词
(out-of-vocabulary word, OOV word)时是有效的。
BERT词表的大小为3万个标记。如果一个词存在于BERT词表中,那么就把它作为一个标记。否则,我们将该词拆分为子词,并检查该子词是否存在于BERT词表中。我们不断地拆分并检查子词是否在词表中,直到单词被拆分为单个字母为止。
在本例中,BERT的词表中没有pretraining这个词,所以它将pretraining这个词拆分为pre、##train和##ing。符号##表示该词是一个子词,而且前面有其他的词。然后继续检查子词##train和##ing是否存在于词表中。因为它们在词表中,所以就不再拆分,将它们作为标记使用。
通过使用WordPiece,我们得到了以下标记。
tokens = [let, us, start, pre, ##train, ##ing, the, model]
接下来,在句首添加一个[CLS]标记,在句尾添加一个[SEP]标记。
tokens = [ [CLS], let, us, start, pre, ##train, ##ing, the, model, [SEP] ]
最后,正如前文所述,将输入标记送入标记嵌入层、分段嵌入层和位置嵌入层。获得嵌入值之后,将嵌入值相加,然后将结果送入BERT。
BERT模型在以下两个自然语言处理任务上进行预训练。
让我们依次了解上述两种预训练策略的工作原理。在执行掩码语言模型构建任务之前,先让我们了解一下语言模型构建任务。
语言模型构建任务是指通过训练模型来预测一连串单词的下一个单词。我们可以把语言模型分为两类。
自动回归式语言模型
自动回归式语言模型有以下两种方法。
下面,让我们通过一个示例来了解这两种方法的原理。
例句:Paris is a beautiful city. I love Paris。让我们掩盖单词city,并以空白代替,如下所示。
Paris is a beautiful __. I love Paris
然后,我们让模型预测空白处的词。如果使用正向预测
,那么模型就会从左到右读取所有的单词,直到空白处,然后进行预测,如下所示。
Paris is a beautiful __.
如果使用反向预测
,那么模型就会从右到左读取所有的单词,直到空白处,然后进行预测,如下所示。
__. I love Paris
自动回归式语言模型在本质上是单向的,也就是说,它只沿着一个方向阅读句子。
自动编码式语言模型
与自动回归式语言模型不同,自动编码式语言模型同时利用了正向预测和反向预测的优势。在进行预测时,它会同时从两个方向阅读句子,所以自动编码式语言模型是双向的。如下所示,为了预测单词city,自动编码式语言模型从两个方向阅读句子,即从左到右和从右到左。
Paris is a beautiful __. I love Paris
双向模型能够给出更好的结果,因为如果从两个方向阅读句子,模型就能够更加清晰地理解句子。
现在,我们已经了解了语言模型的基本工作原理。下面,我们将学习BERT的预训练策略之一——掩码语言模型构建。
BERT是自动编码式语言模型,也就是说,它从两个方向阅读句子,然后进行预测。在掩码语言模型构建任务中,给定一个输入句,我们随机掩盖其中15%的单词,并训练模型来预测被掩盖的单词。为了预测被掩盖的单词,模型从两个方向阅读该句并进行预测。
让我们通过一个示例来了解掩码语言模型的工作原理。我们继续使用之前的例句。首先,对句子进行分词,得到如下标记。
tokens = [Paris, is, a, beautiful, city, I, love, Paris]
然后,在第一句的开头添加[CLS],在每句的结尾添加[SEP],如下所示。
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP] ]
接下来,在标记列表中随机掩盖15%的标记(单词)。假设我们掩盖了单词city,那么就用一个[MASK]标记替换city这个词,如下所示。
tokens = [ [CLS], Paris, is, a, beautiful, [MASK], [SEP], I, love, Paris, [SEP] ]
从上面的标记列表中可以看到,我们用[MASK]替换了city。现在就可以开始训练BERT模型来预测被掩盖的词。
不过,这里有一个小问题。以这种方式掩盖标记会造成预训练和微调之间的差异。也就是说,我们通过预测[MASK]来训练BERT。经过训练后,可以对预训练的BERT模型进行微调,用于执行下游任务,比如情感分析。但在微调期间,我们的输入中不会有任何[MASK]标记。因此,这将导致BERT的预训练方式和用于微调的方式不匹配。
为了解决这个问题,我们可以使用80-10-10规则。我们已经随机掩盖了句子中15%的标记。现在,对于这些标记,我们做以下处理。
tokens = [ [CLS], Paris, is, a, beautiful, [MASK], [SEP], I, love, Paris, [SEP] ]`
tokens = [ [CLS], Paris, is, a, beautiful, love, [SEP], I, love, Paris, [SEP] ]
tokens = [ [CLS], Paris, is, a, beautiful, city, [SEP], I, love, Paris, [SEP] ]
在分词和掩码后,将标记列表送入标记嵌入层、分段嵌入层和位置嵌入层,得到嵌入向量。
然后,将嵌入向量送入BERT。如下图所示,BERT将输出每个标记的特征向量。
R
C
L
S
R_{CLS}
RCLS表示[CLS]的特征向量,
R
P
a
r
i
s
R_{Paris}
RParis表示Paris的特征向量,以此类推。
本例使用了BERT-base配置。BERT-base有12层编码器、12个注意力头和768个隐藏神经元。由于使用的是BERT-base模型,因此每个标记的特征向量大小是768。
我们得到了每个标记的特征向量R。但如何使用这些特征向量来预测被掩盖的词呢?
为了预测被掩盖的词,我们将BERT计算的被掩盖的词的特征向量 R M A S K R_{MASK} RMASK送入使用softmax激活函数的前馈网络层。然后,前馈网络层将 R M A S K R_{MASK} RMASK作为输入,并返回词表中所有单词为被掩盖单词的概率,如图所示。为减少重复,图中没有展示嵌入层。
从上图可以看出,被掩盖的词大概率是city。在这种情况下,被掩盖的词将被预测为city。
请注意,在刚开始的迭代[插图]中,模型将不会返回正确的概率,因为BERT的前馈网络层和编码器层的权重并不是最优的。然而,在多次迭代后,通过反向传播,我们更新并优化了BERT的前馈网络层和编码器层的权重。
掩码语言模型构建任务也被称为完形填空任务。我们学习了掩码语言模型的原理,以及如何使用它训练BERT模型。除了对输入标记进行掩码处理,我们还可以使用另一种略有不同的方法,即全词掩码。下面,我们了解一下全词掩码。
让我们借助例句来了解全词掩码(whole word masking)的工作原理。请看例句:Let us start pretraining the model。由于BERT使用WordPiece,因此在使用WordPiece对句子进行标记后,我们得到如下标记。
tokens = [let, us, start, pre, ##train, ##ing, the, model]
接下来,在句首添加一个[CLS]标记,在句尾添加一个[SEP]标记。
tokens = [[CLS], let, us, start, pre, ##train, ##ing, the, model, [SEP]]
现在,我们随机掩盖句中15% 的单词。假设结果如下所示(掩码单词标记为[MASK])。
tokens = [[CLS], [MASK], us, start, pre, [MASK], ##ing, the, model, [SEP]]
从以上结果可以看出,let和##train这两个词被随机掩盖了。需要注意的是,##train实际上是一个子词,它是pretraining这个词的一部分。在全词掩码方法中,如果子词被掩盖,那么该子词对应的单词也将被掩盖。所以,我们得到如下标记。
tokens = [[CLS], [MASK], us, start, [MASK], [MASK], [MASK], the, model, [SEP]]
可以看到,子词##train所对应的标记都被掩盖了。同时要注意,必须保持15%的掩码率。因此,在掩盖所有对应子词的同时,如果掩码率超过15%,那么可以忽略掩盖其他词。如下所示,为了保持15%的掩码率,我们忽略了对let的掩盖。
tokens = [[CLS], let, us, start, [MASK], [MASK], [MASK], the, model, [SEP]]
这就是全词掩码方法。我们将生成的标记送入BERT模型,并训练模型来预测被掩盖的标记。
现在,我们学会了使用全词掩码方法来训练BERT模型。下面,我们将学习与训练BERT模型有关的另一个任务。
下句预测(next sentence prediction)是一个用于训练BERT模型的策略,它是一个二分类任务。在下句预测任务中,我们向BERT模型提供两个句子,它必须预测第二个句子是否是第一个句子的下一句。让我们通过一个示例来理解下句预测任务。
在上面的这对句子中,句子B是句子A的下一句。所以,我们把这一句子对标记为isNext,表示句子B紧接着句子A。
再看以下两个句子。
在这个例子中,句子B不是后续句,也就是说,它并不在句子A的后面。此时,我们会把这一句子对标记为notNext,表示句子B不在句子A之后。
在下句预测任务中,BERT模型的目标是预测句子对是属于isNext类别,还是属于notNext类别。我们将句子对(句子A和句子B)送入BERT模型,训练它预测句子B与句子A的关系。如果句子B紧跟句子A,则模型返回isNext,否则返回notNext。可见,下句预测本质上是二分类任务。
下句预测任务的作用是什么呢?通过执行下句预测任务,BERT模型可以理解两个句子之间的关系。这在许多应用场景中是有用的,比如问答场景和文本生成场景。
但是,我们怎样才能获得下句预测任务的训练数据集呢?我们可以从任何一个单语言语料库中生成数据集。比如,我们有几份文档,对于isNext类别,我们从一个文档中抽取任意两个连续的句子,将其标记为isNext。对于notNext类别,我们从一个文档中抽取一个句子,并从一个随机文档中抽取另一个句子,将其标记为notNext。需要注意,我们要保证isNext类别和notNext类别的数据各占50%。
现在,我们已经了解了什么是下句预测任务,让我们通过执行下句预测任务来训练BERT模型。假设数据集如图
先看一下样本数据集中的第一个句子对,即She cooked pasta和It was delicious。首先,我们对句子对进行分词,如下所示。
tokens = [She, cooked, pasta, It, was, delicious]
接下来,我们在第一句的开头添加一个[CLS]标记,并在每一句的结尾添加一个[SEP]标记,如下所示。
tokens = [[CLS], She, cooked, pasta, [SEP], It, was, delicious, [SEP]]
然后,将以上输入标记送入标记嵌入层、分段嵌入层和位置嵌入层,得到嵌入值。再将嵌入值送入BERT模型,得到每个标记的特征值。如图2-16所示, R C L S R_{CLS} RCLS表示标记[CLS]的特征值, R s h e R_{she} Rshe表示She的特征值,以此类推。
我们已经知道,下句预测是二分类任务。但是,现在只有每个句子对的标记特征值,我们如何根据这些特征值对句子对进行分类呢?
为了进行分类,只需将[CLS]标记的特征值通过softmax激活函数将其送入前馈网络层,然后返回句子对分别是isNext和notNext的概率。但为什么只需要取[CLS]标记的嵌入,而不是其他标记的嵌入?
因为[CLS]标记基本上汇总了所有标记的特征,所以它可以表示句子的总特征。我们可以忽略所有其他标记的特征值,只取[CLS]标记的特征值 R C L S R_{CLS} RCLS,并将其送入使用softmax激活函数的前馈网络层,以得到分类概率,如图所示。注意,为减少重复,图中没有显示嵌入层。
如上图所示,前馈网络层的返回结果为,输入句子对属于isNext类别的概率较高。
需要注意,在刚开始的迭代中,模型并不会返回正确的概率,因为BERT的前馈网络层和编码器层的权重并未优化。经过一系列的迭代,并通过反向传播,我们可以更新前馈网络层和编码器层的权重,并从中习得最佳权重。
以上述方式,我们通过下句预测任务训练了BERT模型。本节讲解了如何使用全词掩码和下句预测对BERT模型进行预训练。下面,我们将学习预训练过程。
BERT使用多伦多图书语料库(Toronto BookCorpus)和维基百科数据集进行预训练。我们已经了解BERT是使用掩码语言模型(完形填空)和下句预测任务进行预训练的。现在,我们需要准备数据集,并通过这两个任务来训练BERT模型。
我们从语料库中抽取两个句子(两个文本跨度)。假设抽取了句子A和句子B,这两个句子的标记数之和应该小于或等于512。在对两个句子(两个文本跨度)进行采样时,我们需要保证句子B作为句子A的下一句和句子B不作为句子A的下一句的比例是1∶1。
假设我们抽取了以下两个句子。
首先,使用WordPiece对句子进行标记,将[CLS]标记添加到第一句的开头,并将[SEP]标记添加到每句的结尾,结果如下。
tokens = [[CLS], we, enjoyed, the, game, [SEP], turn, the, radio, on, [SEP]]
接下来,我们根据80-10-10规则,随机掩盖15%的标记。假设掩盖了标记game,标记内容如下所示。
tokens = [[CLS], we, enjoyed, the, [MASK], [SEP], turn, the, radio, on, [SEP]]
然后,将这些标记送入BERT模型,并训练BERT模型预测被掩盖的标记,同时对句子B是否是句子A的下一句进行分类。
BERT使用256个序列的批量大小进行1000000步的训练。我们使用Adam优化器将学习率设置为 l r = l e − 4 , β 1 = 0.9 , β 2 = 0.999 l_r = le - 4,\beta_1 = 0.9, \beta_2=0.999 lr=le−4,β1=0.9,β2=0.999,且将预热步骤设置为10000。但什么是预热步骤呢?
在训练过程的初始阶段,我们可以设置高学习率,使最初的迭代更快地接近最优点。在后续的迭代中,调低学习率可以使结果更加精确。因为在最初的迭代中,权值远离收敛值,所以较大幅度的变化是可以接受的。但在后来的迭代中,由于已经接近收敛值,因此如果仍采用同样的变化幅度,就容易错过收敛值。在开始时设置高学习率,在后续的训练中逐步降低学习率,这就是学习率调整策略。
学习率调整策略需要有预热步骤。假设我们的学习率是 l r = l e − 4 l_r = le - 4 lr=le−4,预热步骤为10000次迭代。这表示在最初的10000次迭代中,学习率将从0线性地提高到[ l r = l e − 4 l_r = le - 4 lr=le−4。在这10000次迭代之后,随着误差接近收敛,我们再线性地降低学习率。
在训练中,我们还对所有层使用了随机节点关闭(dropout),每层关闭节点的概率为0.1。BERT的激活函数是GeLU,即高斯误差线性单元(Gaussian Error Linear Unit)。
GeLU激活函数如下所示。
G e L U ( x ) = x ϕ ( x ) GeLU(x) = x\phi(x) GeLU(x)=xϕ(x)
ϕ ( x ) \phi(x) ϕ(x)是标准的高斯累积分布函数,GeLU激活函数与其类似,如下所示。
G e L U ( x ) = 0.5 x ( 1 + t a n h [ 2 π ( x + 0.044715 x 3 ) ] ) GeLU(x) = 0.5 x(1+tanh[\sqrt{\frac{2}{\pi}}(x + 0.044715x^3)]) GeLU(x)=0.5x(1+tanh[π2 (x+0.044715x3)])
下图为GeLU激活函数的图示
通过这种方式,我们可以使用掩码语言模型和下句预测对BERT模型进行预训练。预训练后,BERT模型可以应用于各种任务。我们将在第3章中详细学习如何使用BERT模型。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。