中文tokenembedding(中文在线股吧)

http://www.itjxue.com  2023-02-15 00:27  来源:未知  点击次数: 

语言模型介绍

语言模型(LM)是很多自然语言处理(NLP)任务的基础。语言模型是指对于语言序列 ,计算该序列的概率,即 ,这里的语言序列是有序的语言序列,后续计算也会体现这一点。一般我们认为一个正常的语句,它出现的概率是大于非正常的语句。比如有如下三个语句:

那么应当会有 和 ,这是因为 正常词序的语句会比乱序的语句更常见,正常含义的语句会比无意义的语句更常见 。

计算一个语言序列的概率,我们可以使用链式法则去计算

但该计算方法有两个缺陷:

我们能够建立语言模型了,一般的我们在训练集上得到语言模型的参数,在测试集里面来测试模型的性能,那么如何去衡量一个语言模型的好坏呢?比较两个模型A,B好坏,一种外在的评价就是将AB放入具体的任务中,然后分别得到模型的准确率,这种方式当然是最好的方式,但这种方式的缺点是过于耗时,在实际情况中往往需要花费过多时间才能得到结果。另一种方式是使用下面要介绍的困惑度,但注意困惑度并不是上述外在评价的一个好的近似,所以一般使用在试点试验上,所谓试点试验就是一个小规模的初步研究,以评估一些性能。

困惑度的基本评价方式是对测试集赋予高概率值的模型更好,一个句子W的困惑度(PP)定义如下:

S代表sentence,N是句子长度, 是第i个词的概率。第一个词就是 ,而 是s,表示句子的起始,是个占位符,事实上,结尾应该也有一个占位符,但这里好像没有写。

这个式子可以这样理解,PPL越小, 则越大,一句我们期望的sentence出现的概率就越高。

为了解决参数空间过大的问题。人们引入了马尔科夫假设:随意一个词出现的概率只与它前面出现的有限的一个或者几个词有关。

如果一个词的出现与它周围的词是独立的,那么我们就称之为unigram,也就是一元语言模型:

如果一个词的出现仅依赖于它前面出现的一个词,那么我们就称之为bigram:

一般来说,N元模型就是假设当前词的出现概率只与它前面的n-1个词有关。而这些概率参数都是可以通过大规模语料库来计算:

在实践中用的最多的就是bigram和trigram了,高于四元的用的非常少,由于训练它须要更庞大的语料,并且数据稀疏严重,时间复杂度高,精度却提高的不多。

那在实际计算中,我们怎么计算一个句子的概率呢?

以一元模型为例,在一元语言模型中,我们的句子概率定义为:

那么这里面的每个因子 该怎么计算呢?

这里使用频率统计的办法,由于一元模型认为每个词是相互独立的,所以统计的时候,只需要统计语料库中每个词出现的频率作为概率就可以了。

这里的计算可以认为是根据极大似然估计得到的,假如词典里有V个词,每个词对应一个概率,考虑到所有词出现的概率和是1,那就有V-1个参数 。假设词典表中第i词在语料库中出现的数目为 ,并且 那么根据极大似然估计就有:

取对数之后:

这个可以用多元函数极值去求解。不过这样进行多元极值求解不容易计算,如果用条件极值会容易计算,用拉格朗日乘数法进行求解。

求解得:

有了每个词的出现概率,带入到式子 中就可以计算出对应句子的概率。

对于二元模型或者多元模型,其计算方式有些区别,因为假设有些不同,假如我们需要计算 的值,那么统计频率的方式是:

那对于bigram中的第一个词的概率,由于他之前没有词汇,那这时候我们一般会认为句子的开头和结尾分别有一个抽象符号s和/s,那么句子就变成了s, ,/s,因此 式可以变为:

其余的n-gram模型也是类似的计算方法,有的地方说的是不要用添加开始和结尾的符号,直接用unigram和bigram的方法去计算 即可,这里暂时以斯坦福的课程为准吧,但是应该对最终影响有限,毕竟本质上是在做最大似然估计。

在上述 计算过程中,由于分子是词对出现的次数,那很有可能在语料库中没有出现这样的词对,这时计算结果就是0,同时也会导致句子的出现概率也为0,那这样有些不合理,即使写错了字,也应当有一定概率出现,所以在计算 式的时候要做一下平滑处理

其原理是保证每一个词对(对于bigram而言)都会出现一次,因此, 式可以修改为:

其中V是词的字典数目,这里分母加1是为了保证概率和为1,即 ,通俗理解为,我们往语料库中加入了 这V个词对,因此其分母语料库统计的数目也要加V。

Add-K平滑就是保证每个词对都出现K次,因此, 式可以修改为:

这里分母加KV和之前的模式是一样的。

这个估计是一个很重要的平滑方式,其原理就是对于没有看见的事件,我们不能认为它的发生概率就是零,因此我们从概率的总量(Probability mass)中,分配一个很小的比例给予这些没有看见的事件,这样一来,看见的那些事件的概率总和就要小于1,因此,需要将所有看见的事件概率小一点。至于小多少,要根据“越是不可信的统计折扣越多”的方法进行。

可以参考:

神经网络语言模型就是指利用神经网络进行语言建模,当前的一些预训练语言模型就是利用神经网络来建模的。

前向神经网络,又被称为全连接(Fully Connected Neural Network)神经网络,是最早被引入到语言建模中的神经网络结构。前向神经网络一般可表示为:

这里的 是一个residual connect的含义。

其中 ,是权重矩阵, 是输出层的节点数,在语言模型中等于词典的大小, 等于隐藏层大小,为用户自定义。 是输入的特征维度。

当我们用上述前馈神经网络来描述语言模型的时候,我们假设要预测词 ,那我们通常用 这前n-1个词作为输入。一般词的输入先要做一层embedding的映射,将每个词转为一个低维度的向量,这就需要一个look up table,假设字典数目是V,而embedding的长度是m,那么这个look up table就是一个 的矩阵。输入了n-1个词,经过embedding之后,就有n-1个m维度向量,我们将它们拼接起来作为前馈神经网络的输入,这时候前馈神经网络的输入维度 ,最终输出的 作为预测 的得分,然后再接一层softmax得出概率,计算交叉熵损失函数即可训练模型。

下图中的每个子单元是在对语句中的某一个词进行预测。

循环神经网络(Recurrent Neural Networks)是另一种可以用来进行语言模型建模的网络结构,之前提到的前向神经网络语言模型是以前n-1个词作为输入来预测当前词,这种处理方式是解决不了时序问题的,在预测当前词的时候,无法很好的依赖于上下文(主要是上文),而循环神经网络则可以解决上下文依赖问题。

循环神经网络引入了一个中间隐藏层 ,该隐藏层的状态可以沿着时间将信息传给下一次预测。直观来说,就是将第 时刻的隐藏层的状态 作为第 时刻模型预测或者训练的一个输入,这里的时刻也可以叫做时间步。

下图是一张对RNN进行时间铺开的展示图,每个时间单元都将自身的隐藏层作为下一个时间单元的输入,这张图上面并没有画出第一个时间单元接受的隐藏层的输入,事实上,第一个单元也接受了输入,一般是一个初始化的0向量。

上述的结构依然有局限性,就是它只能利用近期的信息去编码我们需要的格式,如果时间步的跨度过大,原先的信息会在传递中逐渐丢失。

假设现在有这样一个任务,考虑到下面这句话“I grew up in France… I speak fluent French.”,现在需要语言模型通过现有以前的文字信息预测该句话的最后一个字。通过以前文字语境可以预测出最后一个字是某种语言,但是要猜测出French,要根据之前的France语境。因为这次的有用信息与需要进行处理信息的地方之间的距离较远,这样容易导致RNN不能学习到有用的信息,最终推导的任务可能失败。

LSTMs也是循环神经网络的一种,它利用了cell状态将长期依赖的信息保留了下来,它也具有这种链式结构,但是它的重复单元不同于标准RNN网络里的单元只有一个网络层,它的内部有四个网络层。LSTMs的结构如下图所示。

LSTM的核心是细胞状态,用贯穿计算单元的水平线表示。这个状态区别于隐藏层的状态,它只是很少的参与信息交换,所以可以保存较远的时间步的信息。我们可以从下图看到,细胞状态在一个时间步里面只参与三次信息交互:两次接受信息,一次输出信息参与计算。这三个操作被称为门,分别称为忘记门、输入门和输出门。

最后回到语言模型上面,使用RNN进行语言模型建模,那么输入的 就是经过embedding的结果,并且最后对于每个 的输出上再接一层全连接层,输出词典数目的维度,最后再加一层softmax就可以得到下一个词输出的概率

上述的都是单向语言模型,但是实际上,我们在t时刻的词的概率不只会依赖于之前的词,有时候还会和后面的词有关,比如说there is a tree,这里的is就是单数形式,依赖于后面的a tree来确定。所以就有了两个单向的LSTM(并不是Bi-LSTM)去更好的进行语言建模。

给定N个token的序列 ,前向语言模型的表示方法为:

同样的,后向语言模型的表示方法为:

前向模型和后向模型都是用同样的方式去预测下一个词,只是方向不同,而且ELMo不光是两个反方向的LSTM模型叠加,还可以是多层的两个反方向LSTM叠加,因此会有多个细胞状态和隐藏层,但其实和单层的是一样的,只是上层的LSTM接受的输入是下层的隐藏层(也可以加上残差连接),两个不同方向的LSTM模型是互不干扰的,他们的联系就只有输入的token的embedding是共用的,以及最后的全连接加softmax是通用的。ELMo训练的时候,比如预测词 ,输入 左边的词汇,经过正向LSTM得到一个 ,同时输入 右边的词汇,经过反向LSTM也得到了一个 ,然后将这两个hidden拼接到一起之后接一个全连接softmax,就可以得到当前输出为词汇表中各词的概率了,同前文中描述的一样,交叉熵损失函数就是当前预测词为实际词的概率,也就是我们要求的语言模型的概率。

最后的极大似然估计则为:

其中token 的embedding表示的参数 以及softmax 层(全连接加softmax转成字典的向量维度)参数 前后向是通用的,LSTM 的参数按照方向取不同值。

传统的语言模型是从左到右或者从右到左的利用上文或者下文去预测当前词,但实际上,当前词出现不只是单单依靠上文或者下文,其实应该是同时依赖于上下文,在ELMo里面,就是用了双向语言模型的结构,但是这种双向语言模型只是两个独立的前向和后向模型合并起来的,并不是一种完美的结合上下文。因此谷歌在Bidirectional Encoder Representation from Transformers一文中,提出了一种Masked Language Model,该语言模型结构是在一个句子中随机挑选一部分词汇,用一个MASK标记替换掉该词汇,然后在模型训练的时候去预测该词汇,完成训练过程

Masked的具体过程是随机选择语料中15%的token,然后再将这些token以80%的概率用[MASK]替换掉,10%的概率用词汇表中的其余词汇替换,还有10%的概率保持不变,然后将这15%的token的位置记录下来。Masked language model需要将包含了MASK的token输入到transformer的encoder的结构里面,encoder会针对每一个输入的token进行self-attention,这样就可以让某个词的信息编码到全局信息。最后根据之前MASK的token位置,取出这些token各自对应的hidden,然后再接一个全连接softmax得到预测值(这里的全连接softmax并不加入到语言模型的词语表征里面,只在训练时候使用),最后再根据实际值去计算mask token的损失函数值。在Bert里面除了mask token的损失值,还有一个next sentence的损失值,对于两个句子组成的句子对,bert在构造样本的时候,会将后一个句子以一定概率替换成其余的句子,并且要记录下构造样本是随机生成的句子对还是真实的句子对,损失值的计算需要用到[CLS]的表征结果,我们对[CLS]的表征结果经过一层全连接softmax,然后去判断这个句子对是随机生成的还是真实的。最后,这两个损失值是直接相加作为最终损失值。

参考链接:

Statistical language model 统计语言模型

深入理解语言模型 Language Model

从经典结构到改进方法,神经网络语言模型综述

深入浅出讲解语言模型

神经网络语言建模系列之一:基础模型

05-ELMo/BERT/GPT-NLP预训练模型

这里可以参考CSDN上的文章-BERT原理和实践:

在解释BERT,ELMO这些预训练模型之前,我们先看一下很久之前的计算机是如何读懂文字的?

每个字都有自己的独特的编码。但是这样是有弊端的,字和字之间的关联关系是无法得知的,比如计算机无法知道dog和cat都是动物,它反而会觉得bag和dog是比较相近的。

所以后来就有了Word Class,将一系列的词进行分类然后让一类词语和一类词语之间更有关联,但是这样的方法太过于粗糙,比如dog,cat,bird是一类,看不出哺乳动物鸟类的区别。

在这个基础之上,我们有了Word Embedding,Word Embedding我们可以想象成是一种soft的word class,每个词都用向量来表示,它的向量维度可能表示这个词汇的某种意思,如图中dog,cat,rabbit的距离相比其他更近。那么word embendding是如何训练出来的,是根据每个词汇的上下文所训练的。

每个句子都有bank的词汇,四个bank是不同的token,但是同样的type。(注:token-词例, type-词型, class-词类 or token是出现的总次数(还有种理解是token是具有一定的句法语义且独立的最小文本成分。 ),type是出现的不同事物的个数。)

对于典型的Word Embedding认为,每个词type有一个embedding,所以就算是不同的token只要是一样的type那么word embedding就是一样的,语义也就是一样的。

而事实上并非如此,1,2句bank指的是银行,3,4为水库。所以我们希望让机器给不同意思的token而且type还一致,给予不同的embedding。在这个问题上,之前的做法是从字典中去查找这个词包含几种意思,但是这样的做法显然跟不上现实中词语的一些隐含的含义。比如bank有银行的意思,与money一起是银行的意思,而与blood一起却是血库的意思。

所以我们想让机器今天进一步做到每一个word token都可以有自己的embedding(之前是每个type有一个embedding或者有固定的一个或多个embedding),那么怎么知道一个word应该有怎样的embedding呢?我们可以取决于该词的上下文,上下文越相近的token它们就会越相近的embedding。比如之前提到的bank,下面两个句子它们的word token的embedding可能是相近的,而和上面的word token的embedding是相远的。

所以我们想使用一种能够基于上下文的Contextual word Embedding来解决一词多义的问题。

这里使用ELMO可以做到这件事情,即每个word token拥有不同的word embedding。(右上角动物是芝麻街(美国公共广播协会(PBS)制作播出的儿童教育电视节目)里的角色)。

它是基于RNN的预训练模型,它只需要搜集大量语料(句子)且不需要做任何标注,就可以训练这个基于RNN的语言模型,预测下一个token是什么,学习完了之后就得到了上下文的embedding。因为我们可以将RNN的隐藏层中的某一节点拿出来(图中橙蓝色节点),它就是输入当前结点的词汇的word embedding。

从当计算识别到BOS,模型训练开始。首先输入"潮水",然后当作输入输出"退了",退了当做输入输出"就"。

假设当前要得到”退了”这个词的上下文embedding,首先,因为前边的RNN只考虑到了前文而没有考虑到后文,所以这里就使用了同前文一样的反向的RNN。然后,它从句尾开始进行,比如给它喂”知道”,它就要预测”就”,给它喂”就”,它就要预测”退了”。这时候就不仅考虑每个词汇的前文,还会考虑每个词的后文。最后将正向和逆向得到的两个不同的上下文embedding(因为方向不同训练结果也不一样)拼接起来。

现在我们训练的程度都会越来越深度,当层数增加,这样就会产生Deep的RNN,因为很多层,而且每一层都会产生上下文Embedding,那么我们到底应该使用哪一层?每一层这种深度LSTM中的每个层都可以生成潜在表示(方框处)。同一个词在不同的层上会产生不同的Embedding,那么我们应该使用哪一层呢?ELMo的策略是每一层得到的上下文embedding都要。

在上下文embedding的训练模型中,每个词输入进去都会有一个embedding输出来。但是在ELMo中,每个词汇输入进去,都会得到不止一个embedding,因为每层的RNN都会给到一个embedding,ELMo将它们统统加起来一起使用。

以图中为例,这里假设ELMo有两层RNN,这里是将α1(黄色,第一层得到的embedding)和α2(绿色,第二层得到embedding)加起来得到蓝色的embedding,并做为接下来要进行不同任务的输入。

但是这里存在一些问题,α1和α2是学习得到的,而且它是根据当前要进行的任务(如QA,POS of tagging ),然后根据接下来要进行的这些任务一起被学习出来。所以就导致不同任务导向下的α1和α2也不一样。

ELMo的论文中提到,在不同任务下(SRL,Coref,SNLI,SQuAD,SST-5)。蓝色的上下文embedding在经过token(这里为没有经过上下文的embedding),LSTM1,LSTM2后,它在不同阶段需要的weight也不一样。

BERT相当于是Transformer的Encoder部分,它只需要搜集大量的语料去从中学习而不经过标注(不需要label),就可以将Encoder训练完成。如果之前要训练Encoder,我们需要通过一些任务来驱动学习(如机器翻译)。

BERT就是句子给进去,每个句子给一个embedding。

这里可以回忆下,Transformer的Enoder中有self-attention layer,就是给进去一个sequence,输出也得到一个sequence。

虽然图中使用是用词作为单元进行输入,但是在使用BERT进行中文的训练时,字会是一个更好的选择。比如,我们在给BERT进行输入时,用one-hot给词进行编码,但是词在中文中数量庞大,会导致维度过高。但是,字的话相对会少很多,特别是中文(大约几千个,可以穷举)。这样以字为单位进行输入会占很大优势。

共有两种方法,一种是Mask LM遮盖语言模型,另一种是Next Sentence Prediction下一句预测。

下面用上图的例子来理解BERT是怎么样来进行填空的:

1)这里假设在所有句子中的词汇的第2个位置上设置一个MASK;

2)接下来把所有的词汇输入BERT,然后每个输入的token都会得到一个embedding;

3)接下来将设置为MASK的embedding输入到Linear Multi-class Classifier中中,要求它预测被MASK的词汇是哪个词汇?

但是这个Linear Multi-class Classifier它仅仅是一个线性分类器,所以它的能力十分弱,这也就需要在之前的BERT模型中需要将它的层数等参数设计的相当好,然后得到非常出色的representation,便于线性分类器去训练。

那么我们怎么知道最后得到的embedding是什么样的呢?如果两个MASK下的词汇(输入时设置的MASK和最后预测的MASK)都放回原来的位置而且没有违和感(就是语句还算通顺),那它们就有类似的embedding(比如退下和落下)。

如图中,给定两个句子1)醒醒吧 和 2)你没有妹妹。其中特殊符号[SEP]是告诉BERT两个句子的分隔点在哪里。

特殊符号[CLS]一般放在句子的开头,它用来告诉BERT从这开始分类任务,[CLS]输入BERT后得到embedding然后通过Linear Binary Classifier得出结果说明:经过BERT预测后现在我们要预测的两个句子是接在一起 or 不应该被接在一起。

这里可能会有疑问,为什么不将[CLS]放在句尾,等BERT训练完两个句子再输出结果?

对于上图中的任务,BERT现在要做的事情就是给定两个句子,让BERT输出结果这两个句子是不是应该接在一起?

所以在语料库的大量句子中,我们是知道哪些句子是可以接在一起的,所以也需要我们告诉BERT哪些句子是接在一起的。

Linear Binary Classifier和BERT是一起被训练的,通过预测下一句这个任务,我们就可以把将BERT部分的最优参数训练出来。

现在我们知道了任务一和任务二,在原论文中两种任务是要同时进行的,这样才能将BERT的性能发挥到最佳。

现在我们知道了BERT要做什么事情,那么我们要如何去使用它?共有四种方法。论文中是将【BERT模型和接下来你要进行的任务】结合在一起做训练。

第一种,假设当前任务是Input一个sentence,out一个class,举例来说输入一句话来判断分类。

训练流程:1)将做要分类的句子丢给BERT;

2)需要在句子开始加上分类的特殊符号,这个特殊符号经过BERT输出的embedding经过线性分类器,输出结果为当前的句子属于的类别是真还是假。BERT和Linear Classifier的参数一起进行学习;

3)这里的Linear Classifier是Trained from Scratch是白手起家从头开始,即它的参数随机初始化设置,然后开始训练;

4)而BERT则是加上Fine-tune微调策略(一种迁移学习方式*),例如Generative Pre-trained Transformer(OpenAI GPT生成型预训练变换器)(Radford等,2018),引入了最小的任务特定参数,并通过简单地微调预训练参数在下游任务中进行训练。

*这里不得不提一下迁移学习中的Fine-tune,这里可以参考csdn的一篇文章:

( )

第二种,假设当前任务是input一个sentence,输出这个句子中的每个词汇属于正例还是负例。举例现在的任务是slot filling填槽任务(填槽指的是为了让用户意图转化为用户明确的指令而补全信息的过程)(另一种解释是从大规模的语料库中抽取给定实体(query)的被明确定义的属性(slot types)的值(slot fillers))(槽可以理解为实体已明确定义的属性),输入的句子是 arrive Taipei on November 2nd输出的槽是other dest on time time

训练流程:

1)将句子输入BERT,句子中的每个词汇都会映射出一个embedding;

2)每个词汇的embedding输入Linear Classifier,输出结果;

3)Linear Classifier 白手起家和Bert微调的方式一起去做学习。

第三种,假设当前任务是input输入两个句子,输出class。举例现在要进行自然语言预测,让机器根据premise前提,预测这个hypothesis假设是True还是False还是unknown不知道。实际上,我们可以把这个任务当成三分类问题。

训练过程:

1)在一个sentence前设置特殊符号[CLS],然后在要输入的两个sentence中间设置[SEP]分隔符号;

2)将两个sentence连同特殊符号一起输入到BERT中;

3)将[CLS]输入BERT后得到的embedding,再把它输入linear Classifier中,得到class。

如图所示,假设gravity的token序号是17,即 ,我们现在有一个问题通过QA Model后得到的s=17,e=17,那么答案就是 为gravity;

同理,假设within a cloud的序号顺序是77到79,即 到 ,我们现在有一个问题通过QA Model后得到的s=77,e=79,那么答案就是 为within a cloud。

这张图显示了BERT从0-24层的层数在针对不同的NLP任务上的表现。

而所谓的GPT,它其实就是Transformer的Decoder。

我们简单的描述下GPT的训练过程:这里我们inputBOS这个token和潮水,想要GPT预测输出“退了”这个词汇。

1)首先输入[BOS](begin of sentence)和潮水,通过Word Embedding再乘上matrix W变成a 1到a 4,然后把它们丢进self-attention 层中,这时候每一个input都分别乘上3个不同的matrix产生3个不同的vector,分别把它们命名为q,k,v。

q代表的是query (to match others用来去匹配其它的向量)

k代表的是key (to be matched用来去被query匹配的向量)

v代表的是value(information to be extracted用来被抽取的信息的向量)

2)现在要做的工作就是用每个query q 去对每个 key k做attention(吃2个向量,输出就是告诉你这2个向量有多么匹配或者可以说输入两个向量输出一个分数\alpha(而怎么去吃2个向量output一个分数,有很多不同的做法))。这里要预测潮水的下一个词,所以乘 , 乘上 , 乘上 再经过soft-max分别得到 到 。

3)我们用 和每一个v相乘, 和 相乘加上 和 相乘。以此类推并相加,最终得到 。

4)然后经过很多层的self-attention,预测得到”退了”这个词汇。

同理,现在要预测”退了”的下一个词汇,按照前面的流程可以得到 ,然后经过很多层的self-attention层,得到”就”这个词汇。

GPT的神奇之处在于它可以在完全没有训练数据的情况下,就可以做到阅读理解,摘要,翻译。折线图中显示了它在参数量上升的情况下,F1的值的效果。

1.Transformer的问题:

word Embedding 无上下文

监督数据太少

解决方法:

Contextual Word Embedding

2.ELMo( E mbeddings from L anguages Mo del)

- 多层双向的LSTM的NNLM

- RNN-based language models(trained from lots of sentences)

ELMo的问题:

Contextual Word Embedding作为特征

不适合特定任务

3.OpenAI GPT的改进

根据任务Fine-Tuning

使用Transformer替代RNN/LSTM

OpenAI GPT的问题:

单向信息流的问题

Pretraining(1)和Fine-Tuning(2)不匹配

解决办法:

Masked LM

NSP Multi-task Learning

Encoder again

Tips

- 使用中文模型

- max_seq_length可以小一点,提高效率

- 内存不够,需要调整train_batch_size

- 有足够多的领域数据,可以尝试Pretraining

BERT 结构与原理(1)--Embedding

??BERT主要是多个Transformer的Encoder作为主题,主要包含Embedding层,Encoder层。

??BERT中的Embedding主要有3种:

??Token Embedding 是对词向量进行编码。原始的输入是[batch,seq_len]。经过 Token Embedding 后数据的维度为[batch,seq_len,d_model]。

??在BERT中Token Embedding的内部计算流程是初始化一个二维数组,大小为[vocab_size,d_model],然后将输入的数据进行one-hot编码,维度为[batch,seq_len,vocab_size],进行tensor的乘法。验证如下:

可以看见两者的结果是一样的,所以猜测embedding内部就是先将句子中每个词的索引表示转化为one-hot表示,然后对编码后的数据进行矩阵的变换,其中参数开始是输出化的,后面训练的时候可以用来学习。编码后的输出为[batch,seq_len,d_model]

??BERT中的Position Embedding和Transformer不一样,transormer中式直接利用公式,计算出对用维度的值。在BERT中是要学习的。比如说d_model的大小为512,那么每个句子就会生成一个[0,1,2,...511]的一维数组,然后重复batch次,因此实际的输入为[batch,d_model],将其送到one_hot中进行编码,具体的编码过程和Token Embedding一样,然后最后的输出为[batch,seq_len,d_model]。和Token Embedding输出的维度一样。

一般是不用的,只在句子对的时候采用。其编码后的维度也是[batch,seq_len,d_model]。

bert原码解析(embedding)

? ? ? ?写这篇文章的起因是看ALBERT的时候,对其中参数因式分解,减少参数的方式不理解,后来通过原码来了解原理。后来想到虽然平时基于bert的nlp任务做的挺多的,但对原理还是一知半解的,所以在此记录。后续有时间的话,将常见的,看过的论文做个总结,不然容易忘记。(attention is all your need,bert,albert,roberta,sentence -bert,simcse,consert,simbert,nezha,ernie,spanbert,gpt,xlnet,tinybert,distillbert)

从图一可以明显看出,bert主要分为三块。embedding层,encoder层,以及pooler层,本章为embedding层的原码分析。

可以看出,输入的input,会先经过tokernizer,会补上cls,sep等特殊字符。然后embedding层会获取句子的token embeddings+segment embeddings+position embeddings作为最终的句子embedding。

1 token embedding:

token embedding有两种初始化方式。如果是训练预训练,随机出初始化一个30522*768的lookup table(根据wordpiece算法,英文一共有30522个sub-word就可以代表所有词汇,每个sub-word 768纬)。如果是在预训练模型的基础上finetune,读取预训练模型训练好的lookup table。假设输入的句子经过tokernized长度为16。经过lookup table就是16*768维的句子表示。

2 position embedding:

position embedding的lookup table 大小512*768,说明bert最长处理长度为512的句子。长于512有几种截断获取的方式。position embedding的生成方式有两种:1 根据公式直接生成 2 根据反向传播计算梯度更新。其中,transformer使用公式直接生成,公式为:

其中,pos指的是这个word在这个句子中的位置;2i指的是embedding词向量的偶数维度,2i+1指的是embedding词向量的奇数维度。为什么这个公式能代表单词在句子中的位置信息呢?因为位置编码基于不同位置添加了正弦波,对于每个维度,波的频率和偏移都有不同。也就是说对于序列中不同位置的单词,对应不同的正余弦波,可以认为他们有相对关系。优点在于减少计算量了,只需要一次初始化不需要后续更新。

其中, bert使用的是根据反向传播计算梯度更新。

3 segment embedding:

bert输入可以为两句话。[cls]....[seq]....[seq]。每句话结尾以seq分割。从embedding的大小可以看出,lookup table由两个768组成,对应第一句和第二句。该参数也由训练得到。

4 LN以及dropout:

embeddings = dropout(layernorm(token embeddings+segment embeddings+position embeddings))。Normalization 有很多种,但是它们都有一个共同的目的,那就是把输入转化成均值为 0 方差为1的数据。我们在把数据送入激活函数之前进行normalization(归一化),因为我们不希望输入数据落在激活函数的饱和区,发生梯度消失的问题,使得我们的模型训练变得困难。这里不使用bn可以去除batch size对模型的影响。

下一篇为bert核心encoder模块的解析。

(责任编辑:IT教学网)

更多

推荐Oracle认证文章