ELECTRA(Efficiently Learning an Encoder that Classifies Token Replacements Accurately,高效训练编码器准确分类替换标记)是BERT的另一个变体。我们已知要使用掩码语言模型构建任务和下句预测任务对BERT进行预训练。在掩码语言模型构建任务中,我们随机掩盖15%的标记,并训练BERT来预测被掩盖的标记。但是,ELECTRA没有使用掩码语言模型构建任务作为预训练目标,而是使用一个叫作替换标记检测的任务进行预训练。
替换标记检测任务与掩码语言模型构建任务非常相似,但它不是用[MASK]标记来掩盖标记,而是用另一个标记来替换,并训练模型判断标记是实际标记还是替换后的标记。
但为什么使用替换标记检测任务,而不使用掩码语言模型构建任务?这是因为掩码语言模型构建任务有一个问题。它在预训练时使用了[MASK]标记,但在针对下游任务的微调过程中,[MASK]标记并不存在,这导致了预训练和微调之间的不匹配。在替换标记检测任务中,我们不使用[MASK]来掩盖标记,而是用不同的标记替换另一个标记,并训练模型来判断给定的标记是实际标记还是替换后的标记。这就解决了预训练和微调之间不匹配的问题。
与使用掩码语言模型构建任务和下句预测任务进行预训练的BERT不同,ELECTRA仅使用替换标记检测任务进行预训练。但替换标记检测任务是如何工作的?需要替换什么标记?如何训练模型来执行这一任务?下面,就让我们找出这些问题的答案。
1 了解替换标记检测任务
让我们通过一个例子来了解替换标记检测任务究竟是如何工作的。
首先,我们为句子The chef cooked the meal进行分词,如下所示。
python
tokens = [ the, chef, cooked, the, meal ]
然后,把第一个标记the换成a,把第三个标记cooked换成ate,如下所示。
python
tokens = [ a, chef, ate, the, meal ]
我们替换了两个标记,现在开始训练模型,来判断标记是实际标记还是替换标记。我们把这个模型叫作判别器,因为它只是对标记是实际标记还是替换标记进行分类。
如图所示,将标记送入判别器,它将给出分类结果。
我们已知需要在句子中替换一些标记,并将其送入判别器,以判断这些标记是实际标记还是替换标记。但问题是,在将标记送入判别器之前,究竟该如何替换它们?为了替换标记,需要使用掩码语言模型。在为句子The chef cooked the meal分词后,我们得到的结果如下所示。
python
tokens = [ the, chef, cooked, the, meal ]
现在,用[MASK]标记随机地替换两个标记,结果如下所示。
python
tokens = [ [MASK], chef, [MASK], the, meal ]
接下来,将这些标记送入另一个BERT模型,并预测被掩盖的标记。我们把这个BERT模型称为生成器,因为它会返回标记的概率分布。从图中可以看到,我们将[MASK]标记送入生成器,它会预测出被[MASK]掩盖的最有可能的标记。
从图中可以看出,生成器将the预测为a,将cooked预测为ate。就这样,将生成器生成的标记拿出来用于替换。也就是说,我们用生成器生成的标记替换给定句子中的标记。还是以句子The chef cooked the meal为例,在分词后,我们得到如下结果。
python
tokens = [ the, chef, cooked, the, meal ]
现在,用生成器生成的标记替换这些标记,标记列表将变成如下内容。
python
tokens = [ a, chef, ate, the, meal ]
可以看到,我们将the和cooked替换为生成器生成的a和ate。然后,我们将标记送入判别器,并训练它对标记是实际标记还是替换标记进行分类。
如下图所示,我们首先随机地将标记替换为[MASK],并将其送入生成器。生成器预测[MASK]标记。接下来,我们用生成器生成的标记替换原先的标记,并将其送入判别器。判别器对输入的标记是实际标记还是替换标记进行分类。
2 ELECTRA的生成器和判别器
我们知道生成器执行的是掩码语言模型构建任务,它以15%的概率随机将一些标记替换为[MASK],并预测[MASK]处的标记。假设用 X = [ x 1 , x 2 , ... ... , x n ] X = [x_1,x_2,......,x_n] X=[x1,x2,......,xn]来表示输入的标记。我们随机地用[MASK]替换一些标记,并将它们作为输入送入生成器。 h G ( X ) = [ h 1 , h 2 , ... ... , h n ] h_G(X) = [h_1,h_2,......,h_n] hG(X)=[h1,h2,......,hn]是生成器返回的每个标记的特征。 h 1 h_1 h1为第一个标记 x 1 x_1 x1的特征, h 2 h_2 h2为第二个标记 x 2 x_2 x2的特征,以此类推。
现在,将标记的特征送入分类器。分类器是使用softmax函数的前馈网络层,它将返回标记的概率分布,即词表中每个单词是[MASK]的概率。
还是以句子The chef cooked the meal为例,我们随机地将一些标记替换为[MASK],并将其送入生成器。之后,由分类器返回词表中每个单词是[MASK]的概率,如下图所示。
用 x t x_t xt表示位置 t t t被[MASK]替换的单词。然后,生成器返回词表中每个单词是 x t x_t xt的概率。计算概率的公式如下所示。
P G ( x t ∣ x ) = e x p ( e ( x t ) T h G ( X ) t ) ∑ x ' e x p ( e ( x t ) T h G ( X ) t ) P_G(x_t|x) = \frac{exp(e(x_t)^Th_G(X)t)}{\sum{x^`}exp(e(x_t)^Th_G(X)_t)} PG(xt∣x)=∑x'exp(e(xt)ThG(X)t)exp(e(xt)ThG(X)t)
在上面的公式中, e ( ⋅ ) e(·) e(⋅)为标记嵌入。通过分类器返回的概率分布,可以选择高概率的单词作为[MASK]标记所掩盖的实际单词。根据上图所示的概率分布,掩码标记 x 1 x_1 x1被预测为a,而掩码标记 x 3 x_3 x3被预测为ate。接下来,用生成器生成的标记替换输入的标记,并将其送入判别器。
判别器的目标是判断给定的标记是由生成器生成的还是实际标记。首先,将标记送入判别器,判别器返回每个标记的特征。我们用 h D ( X ) = [ h 1 , h 2 , ... ... , h n ] h_D(X)=[h_1,h_2,......,h_n] hD(X)=[h1,h2,......,hn]表示判别器返回的每个标记的特征。接下来,将每个标记的特征送入分类器,已知它是使用sigmoid函数的前馈网络层。分类器返回给定的标记是实际标记还是替换标记。
如下图所示,向判别器输入标记。
x t x_t xt代表位置 t t t的标记。判别器使用sigmoid函数返回该标记是实际标记还是替换标记,如下所示。
D ( X , t ) = s i g m o i d ( w T h D ( X ) t ) D(X,t) = sigmoid(w^Th_D(X)_t) D(X,t)=sigmoid(wThD(X)t)
小结一下,将[MASK]标记送入生成器,生成器则预测被[MASK]替换的标记。然后,用生成器生成的标记替换输入的标记,将其再送入判别器。判别器对输入的标记是实际标记还是替换标记进行分类,如图所示。
判别器就是ELECTRA模型,它要训练BERT对给定的标记是实际标记还是替换标记进行分类,因此它被称为ELECTRA,即高效训练编码器准确分类替换标记。
与BERT相比,ELECTRA有自己的优点。在BERT中,我们使用掩码语言模型构建任务作为训练目标,只替换15%的固定标记,所以模型的训练信号只有这15%的标记,它只预测那些被替换为[MASK]的标记。但是在ELECTRA中,训练信号是所有的标记,因为模型会对所有给定的标记是实际标记还是替换标记进行分类。
3 训练ELECTRA模型
我们使用掩码语言模型构建任务对生成器进行训练。对于一个给定的输入 X = [ x 1 , x 2 , ... ... , x n ] X = [x_1,x_2,......,x_n] X=[x1,x2,......,xn],我们随机选择几个位置替换为[MASK]标记。 M = [ m 1 , m 2 , ... ... , m n ] M = [m_1,m_2,......,m_n] M=[m1,m2,......,mn]表示选定的[MASK]位置。然后,我们用[MASK]标记替换所选位置的标记,公式如下所示。
X m a s k e d = R e p l a c e ( X , M , [ M A S K ] ) X^{masked} = Replace(X,M,[MASK]) Xmasked=Replace(X,M,[MASK])
将 X m a s k e d X^{masked} Xmasked标记送入生成器,让生成器预测被[MASK]替换的标记。
将输入中的一些标记用由生成器生成的标记替换,并将其标记为 X c o r r u p t X^{corrupt} Xcorrupt,它是由被生成器替换的标记组成的。
生成器的损失函数如下所示。
我们将被替换的标记 X c o r r u p t X^{corrupt} Xcorrupt送入判别器,对给定的标记是实际标记还是替换标记进行分类。判别器的损失函数如下所示。
我们通过最小化生成器和判别器的综合损失来训练ELECTRA模型,其公式如下所示。
在公式中, θ G \theta_G θG和 θ D \theta_D θD分别表示生成器和判别器的参数,表示一个大型文本语料库。下面,让我们学习如何高效地训练ELECTRA模型。
4 高效的训练方法
为了高效地训练ELECTRA模型,可以在生成器和判别器之间共享权重。也就是说,如果生成器和判别器的大小相同,那么编码器的权重就可以共享。
但问题是,如果生成器和判别器的大小相同,就会增加训练时间。为了避免这种情况,可以使用较小的生成器。当生成器较小时,可以仅共享生成器和判别器之间的嵌入层(标记嵌入和位置嵌入)。这种生成器和判别器之间的共享式嵌入可使训练时间最小化。
预训练的ELECTRA模型可以从GitHub上下载。它有以下3种配置。
- ELECTRA-small:有12层编码器,隐藏层大小为256。
- ELECTRA-base:有12层编码器,隐藏层大小为768。
- ELECTRA-large:有24层编码器,隐藏层大小为1024。
我们可以像使用其他BERT模型一样,将ELECTRA模型与Transformers库一起使用。
首先,导入必要的模块。
python
from transformers import ElectraTokenizer, ElectraModel
假设使用ELECTRA-small判别器,下载并加载预训练的ELECTRA-small判别器,如下所示。
python
model = ElectraModel.from_pretrained('google/electra-small-discriminator')
通过同样的方式,我们也可以加载ELECTRA的其他配置。