本文在前辈的帮助下,对从one-hot到bert的一系列wordEmbedding技术进行整理。能力有限,期待得到大佬们的指正。
目录
One-Hot Encoding
Neural Network Language Model
NNLM如何解决泛化能力的问题?
word2vec
Glove
RNN(LSTM/GRU)
seq2seq
Attention机制
如何解决单向信息流问题?
Self-Attention
MultiHead
Transformer
Transformer位置编码
ELMO
OpenAI GPT
Bert
1. Masked LM (MLM)
2. Next Sentence Prediction (NSP)
wordEmbedding:将一个词映射到语义空间(低维稠密空间)中的一个点,使得语义上比较相似的词,在空间上有比较相近的距离。
比如word2vec方法可以学到一些词与词之间的关系,如人物关系,时态关系,国家首都之间的关系等。
wordEmbedding学到的词向量可以用于下游任务。(如作为特征或作为初始的词向量Fine-Tuning)
我们知道一个词的语义是上下文相关的,脱离上下文而编码某个词的语义会显得很片面。
假设某个词在语料库内有多种语义,这时候强行用一个向量来编码某个词语义的话,我们只能将多种语义都编码在一个向量里面,但是很显然,一个句子里,某个词只会存在一种语义。
最开始我们用one-hot,N-gram,后来用word2vec,glove等来生成词向量。这些方法或多或少都没有考虑到上下文的关系。
对于上下文的语义编码,我们借助RNN/LSTM/GRU来进行,这些序列的网络能够记忆之前的历史信息,记录一些相关的语义,但也存在某些问题(比如梯度消失等)。
我们可以用两个RNN组成seq2seq模型,用来实践翻译,摘要,问答和对话系统等挑战。seq2seq有encoder模型负责将数据进行编码,生成context向量,接着decoder模型将context向量进行解码,输出对应的目标。同时对于seq2seq模型的改进我们还有attention机制和transformer等。但是以上这些监督学习的模型,都存在这数据量不足等问题,无法学到完整的复杂的上下文表示。于是无监督(或半监督)的 contextual word embedding 应运而生。比如ELMO,OpenAI GPT,BERT等,用以生成词的向量表示,并同时考虑上下文信息。
one-hot词向量是一个高维稀疏的表示,由于具有正交性,one-hot词向量无法表达不同词之间的相似度。例如,任何一对词的one-hot向量的余弦相似度都为0。
比如一个具有四个词的句子,讲词向量用one-hot表示如图所示:
神经网络语言模型做这样的工作:
给定一个句子S,包含个词,可以按如下方式计算句子的概率:
在神经网络语言模型之前,主流的统计语言模型是N-gram,该方法基于词的历史()来预测当前的词。关于N-gram可以参考这里。
但是N-gram既不能解决长距离依赖的问题,也不能共享上下文(如果训练预料里面未出现某词,或出现很少,尽管该词在现领域中很重要,他的weight也会很低)。比如我们的语料是:1、我要去北京。2、我要去北京。3、北京和天津是中国的城市。4、北京和天津是大城市。那么训练出来后,返回我要去北京的概率就很大,我要去上海的概率就会很小,甚至没有。
神经网络模型基于当前的词(比如说["我","要","去"]),先将当前的词变成词向量,做向量拼接,接tanh层 和一些线性层,最后通过softmax来判断出现第四个词(比如说["北京"])的概率。
假设我们的语料是:1、我要去北京。2、我要去北京。3、北京和天津是中国的城市。4、北京和天津是大城市。假设我们的训练数据是["我","要","去"],output是北京。因为别的语料(3、4、5)上下文中有北京和天津类似的出现,那么在预测时,输入["我","要","去"],output是天津的概率也会比较大。
神经网络训练好之后,我们随之也得到了他的副产品,即我们的词向量。(刚开始训练的时候,神经网络的输入可以是随机的词向量,也可以是one-hot)
2013年,Google团队发表了word2vec工具。算法基于两个词的上下文相似,则他们的语义也相似-------这一假设(分布式假设)。
word2vec工具主要包含两个模型:跳字模型(skip-gram)和连续词袋模型(continuous bag of words,简称CBOW),以及两种高效训练的方法:负采样(negative sampling)和层序softmax(hierarchical softmax)。值得一提的是,word2vec词向量可以较好地表达不同词之间的相似和类比关系。关于word2vec可以参考这里。
Glove模型首先基于语料库构建词的共现矩阵,然后基于共现矩阵和GloVe模型学习词向量。关于Glove可以参考这里。
正如前面提到的,词向量编码,会将词的所有语义编码进向量。而一个句子中,词的语义往往只有一个。这时候,有记忆能力的RNN模型大有可为。我们知道RNN容易造成梯度消失,由于梯度消失,很难学到长距离的依赖关系,LSTM通过门的机制来避免梯度消失。GRU在LSTM上作一些改进来提高效率。(把遗忘门和输入门合并成一个更新门)关于RNN可以参考这里。
很多时候我们面对的是句子输入输出长度不一致的情况,这时一个RNN已经无法满足要求,我们需要seq2seq来完成某些任务。比如机器翻译,文本摘要等。
然而试图用一个Encoder state向量来编码整个输入语义是很困难的。具体的细节,我们往往需要参考原来的信息,所以我们引入attention机制。关于attention可以参考这里。
在Decoder进行t时刻计算的时候,除了t-1时刻的隐状态,当前时刻的输入,注意力机制还可以参考Encoder所有时刻的输入。拿机器翻译来说,我们在翻译以句子的第t个词的时候会把注意力机制在某个词上。当然常见的注意力是一种soft的注意力,假设输入有5个词,注意力可能是一个概率,比如(0.6,0.1,0.1,0.1,0.1),表示当前最关注的是输入的第一个词。同时我们之前也计算出每个时刻的输出向量,假设5个时刻分别是,那么我们可以用attention概率加权得到当前时刻的context向量。
注意力有很多方法计算,我们这里介绍Luong等人在论文提出的方法。它是用当前时刻的GRU计算出的新的隐状态来计算注意力得分,首先它用一个score函数计算这个隐状态和Encoder的输出的相似度得分,得分越大,说明越应该注意这个词。然后再用softmax函数把score变成概率。那机器翻译为例,在t时刻,表示t时刻的GRU输出的新的隐状态,我们可以认为 表示当前需要翻译的语义。通过计算 与的得分,如果与的得分很高,那么我们可以认为当前主要翻译词x1x1的语义。有很多中score函数的计算方法,如下图所示:
上式中表示t时刻的隐状态,比如第一种计算score的方法,直接计算与的内积,内积越大,说明这两个向量越相似,因此注意力也更多的放到这个词上。第二种方法也类似,只是引入了一个可以学习的矩阵,我们可以认为它先对做一个线性变换,然后在与计算内积。而第三种方法把它们拼接起来然后用一个全连接网络来计算score。
注意,我们前面介绍的是分别计算和的内积、和的内积,…。但是为了效率,可以一次计算与的乘积。 计算过程如下图所示。
RNN有时序依赖,后面一个时刻必须基于前一个时刻进行训练,无法并行。所以一般训练都比较耗时。
另外我们编码一个词的语义的时候,其实是要考虑整个句子的上下文,光看前面(RNN)或者光看后面(逆向RNN)都是不全面的。
这个时候我们需要self-attention 和 Transformer。
考虑这样一个问题,对于下面这个句子:
”The animal didn't cross the street because it was too tired”
其中"it"指代什么?是animal ?还是street ?
上文指出,self-attention根据句子的位置信息找出对该单词更好的编码的线索。
self-attention向量的计算:
假设我们的输入是两个单词组成的句子:
当我们计算"Thingking"的self-attention时,我们考虑的是整个句子的所有其他词相较于"Thingking"的attention score。这个score决定了在编码该词的时候,对句子的其他词的关注程度。
显然,以上操作中,本位置的单词将具有最好的softmax值,但是,这样的操作依旧是有意义的,他有利于引导关注其他与当前单词更加相近的词。
当然我们可以利用矩阵的运算,更快的得到self-attention layer。
如果我们的值不一样,输出得到的z当然也就不一样,实际上,transfromer输入的self-attention layer 就是多个这样的“multi-headed” attention。假设我们生成8个heads.如是我们按照以上的方式计算8次,得到8个矩阵。
这里会有一个问题,在作Transformer时(ps:接下来我们会讲到Transformer的结构),我们的 feed-forward layer(FFNN) 只能接收一个矩阵(每个词对应的self-attention 向量集合),于是我们将得到的多个矩阵拼接起来,得到一个总矩阵(从本例看,我们最后得到的是一个2 * 24的矩阵),由于信息会有冗余,我们最后用一个(24 * 4)进行压缩,最后生成一个2 * 4 的矩阵z,然后喂给 feed-forward layer。
回顾整个计算过程如下图:
摘录一段官网上的原文:
The Transformer – a model that uses attention to boost the speed with which these models can be trained.
The Transformers outperforms the Google Neural Machine Translation model in specific tasks.
The biggest benefit, however, comes from how The Transformer lends itself to parallelization.
上文可以看出,transfromer的最大好处是实现了seq2seq的并行计算。文章请参考这里。
假设翻译一个句子,transfrom的结构如下:
在Transformer 中encoder是独立的,每一个encoder结构如下,数据先流入self-attention layer计算出self-attention vector,然后再流入FFNN layer。
我们举回"Thinking Machines"的例子。
输入首先Embedding 经过的是 self-attention ,得到self-Attention 向量喂给全连接网络(FFNN)。
值得一提的是,计算z的时候,整个句子的词Embedding都要参与计算,输入FFNN进行计算时,只需要输入单个的
经过底层的encoder输出的向量r直接给上一层的encoder使用。
考虑下面两个句子:
北京 到 上海 的机票
上海 到 北京 的机票
当参数固定下来以后,transformer 对“北京”的编码是固定的(当然对“上海”也是一样)。但实际上,上面两个句子的语义是不一样的(一个是出发城市,一个是到达城市)。有时候,顺序关系比较重要。于是,transformer引入位置编码,如下图,数据在进入encoder之前,先结合一次位置编码。
位置编码(positional encodings)有很多种方式,其中一种就是绝对的位置编码,一种是相对位置编码。
Positional Encoding 是一种考虑输入序列中单词顺序的方法。encoder 为每个输入 embedding 添加了一个向量,这些向量符合一种特定模式,可以确定每个单词的位置,或者序列中不同单词之间的距离。
例如,input embedding 的维度为4,那么实际的positional encodings如下所示:
在下图中,是20个单词的 positional encoding,每行代表一个单词的位置编码,即第一行是加在输入序列中第一个词嵌入的,每行包含 512 个值, 每个值介于 -1 和 1 之间,用颜色表示出来。
可以看到在中心位置分成了两半,因为左半部分的值由一个正弦函数生成,右半部分由余弦函数生成,然后将它们连接起来形成了每个位置的编码向量。
当然这并不是位置编码的唯一方法,只是这个方法能够扩展到看不见的序列长度处,例如当我们要翻译一个句子,这个句子的长度比我们训练集中的任何一个句子都长时。
最后,整个Transformer结构就明朗了。
由于获取数据的代价往往比较大,通过无监督方法学习wordEmbedding,并考虑上下文信息越来越受到关注。
ELMo是一种是基于特征的语言模型,用预训练好的语言模型,生成更好的特征。
word2vec中我们取出了最后一步的输出作为词向量,ELMo方法认为应该把所有隐藏层的输出取出来,然后整体作为词向量在接下来的文本处理任务中继续训练,相当于给不同的输出层不同的权重。
从图的结构上来看,我们可以很清晰的看到ELMO模型的主要结构就是L层的双向LSTM,对于L层的双向lstm语言模型,一共会有有2L+1个representations。在多层模型中,浅层往往蕴含的是句法,语法信息,而高层蕴含的是语义信息,因此你可以选择ELMO中各层的输出作为最后的输出,也可以将各层的输出进行综合作为最后的输出。
关于ELMO可以参考这里,或者这里。
github link:https://github.com/allenai/bilm-tf
paper : Deep contextualized word representations
ELMo是通过大量的无监督的语料学习得到的,真实的任务中与无监督的语料还是会有差异。另外lstm是串行机制,训练时间长。于是OpenAI GPT出现。
OpenAI GPT根据Transformer训练出来语言模型,但是该语言模型不是固定的,他根据任务的不同,进行Fine-Tuning.由于Transformer 替代了LSTM在效率上也有所提升。
GPT的核心思想是先通过无标签的文本去训练生成语言模型,再根据具体的NLP任务(如文本蕴涵、QA、文本分类等),来通过有标签的数据对模型进行fine-tuning。
具体来说,在这篇论文中提出了半监督的方法,即结合了无监督的预训练和有监督的fine-tuning。论文采用两阶段训练。首先,在未标记数据集上训练语言模型来学习神经网络模型的初始参数。随后,使用相应NLP任务中的有标签的数据地将这些参数微调,来适应当前任务。
如上图所描述,OpenAI GPT相当于没有encoder,只有12层decoder的Transformer。
我们训练语言模型的时候,是用一个句子来进行训练的,但很多任务中,输入并不是一个句子(比如相似度计算,问答等)。对于这样的问题,上图展示了OpenAI GPT使用的Trick,其实就是将所有句子拼起来,为了区分句子的前后时序关系,插入Start,Delim,Extract分别表示开始,分隔,结束。然后将句子embedding 输入OpenAI GPT 定义的Transformer 后面再接入一个Linear Layer,然后softmax(或根据任务的不同使用其他方式)输出,并用输出的监督数据来Fine-tuning Transformer 包括 linear 层的参数,
OpenAI GPT 依旧有单向信息流的问题。让我们再次回到这个句子:
”The animal didn't cross the street because it was too tired”
我们观察到,OpenAI GPT 不论是pretraining 还是Fine-tuning ,都是句子从左往右或同时从右往左的进行。尽管有带mask的Self-attention,依旧无法解决单向信息流的问题。
另外,由于pretraining 输入的是一个句子,Fine-tuning的时候,根据任务的不同,会有两个或多个句子,会存在不匹配问题。bert使用Masked LM解决单向信息流的问题,使用NSP Muti-task Learning 使得pretraining 的时候也是两个句子,解决了不匹配的问题。
部分内容转自:https://www.jianshu.com/p/d110d0c13063,该部分图片 by Rani Horev
BERT 的目标是生成语言模型,利用了 Transformer 的 encoder 部分。
Transformer 的 encoder 是一次性读取整个文本序列,而不是从左到右或从右到左地按顺序读取,
这个特征使得模型能够基于单词的两侧学习,相当于是一个双向的功能,解决了单向信息流的问题。实验的结果表明,双向训练的语言模型对语境的理解会比单向的语言模型更深刻.
下图是 Transformer 的 encoder 部分,输入是一个 token 序列,先对其进行 embedding 称为向量,然后输入给神经网络,输出是大小为 H 的向量序列,每个向量对应着具有相同索引的 token。
当我们在训练语言模型时,有一个挑战就是要定义一个预测目标,很多模型在一个序列中预测下一个单词,
“The child came home from ___”
双向的方法在这样的任务中是有限制的,为了克服这个问题,BERT 使用两个策略:
在将单词序列输入给 BERT 之前,每个序列中有 15% 的单词被 [MASK] token 替换。 然后模型尝试基于序列中其他未被 mask 的单词的上下文来预测被掩盖的原单词。(遮掉部分词,类似与完形填空,根据上下文猜测被遮掉的词)这样就考虑了双向的信息流。
这样就需要:
BERT 的损失函数只考虑了 mask 的预测值,忽略了没有掩蔽的字的预测。这样的话,模型要比单向模型收敛得慢,不过结果的情境意识增加了。
在 BERT 的训练过程中,模型接收成对的句子作为输入,并且预测其中第二个句子是否在原始文档中也是后续句子。
在训练期间,50% 的输入对在原始文档中是前后关系,另外 50% 中是从语料库中随机组成的,并且是与第一句断开的。两个句子的关系在问答等任务中都很有作用。
为了帮助模型区分开训练中的两个句子,输入在进入模型之前要按以下方式进行处理:
为了预测第二个句子是否是第一个句子的后续句子,用下面几个步骤来预测:
在训练 BERT 模型时,Masked LM 和 Next Sentence Prediction 是一起训练的,目标就是要最小化两种策略的组合损失函数。
BERT 可以用于各种NLP任务,只需在核心模型中添加一个层,例如:
在 fine-tuning 中,大多数超参数可以保持与 BERT 相同,在论文中还给出了需要调整的超参数的具体指导。
文章中给出了BERT相对于另外两种方法的异同。
BERT 的代码也已经开源:
https://github.com/google-research/bert