做一名知识的搬运工
介绍
语言模型(LM)是很多自然语言处理(NLP)任务的基础。语言模型是指对于语言序列,计算该序列的概率,即 ,这里的语言序列是有序的语言序列,后续计算也会体现这一点。一般我们认为一个正常的语句,它出现的概率是大于非正常的语句。比如有如下三个语句:
- 树上 有 一颗 苹果
- 苹果 有 一颗 树上
- 树上 有 一颗 太阳
那么应当会有和,这是因为正常词序的语句会比乱序的语句更常见,正常含义的语句会比无意义的语句更常见。
计算一个语言序列的概率,我们可以使用链式法则去计算
但该计算方法有两个缺陷:
- 参数空间过大:条件概率的可能性太多,无法估算,不可能有用;
- 数据稀疏严重:许多词对的组合,在语料库中都没有出现,依据最大似然估计得到的概率将会是0;
语言模型评价指标
我们能够建立语言模型了,一般的我们在训练集上得到语言模型的参数,在测试集里面来测试模型的性能,那么如何去衡量一个语言模型的好坏呢?比较两个模型A,B好坏,一种外在的评价就是将AB放入具体的任务中,然后分别得到模型的准确率,这种方式当然是最好的方式,但这种方式的缺点是过于耗时,在实际情况中往往需要花费过多时间才能得到结果。另一种方式是使用下面要介绍的困惑度,但注意困惑度并不是上述外在评价的一个好的近似,所以一般使用在试点试验上,所谓试点试验就是一个小规模的初步研究,以评估一些性能。
困惑度的基本评价方式是对测试集赋予高概率值的模型更好,一个句子W的困惑度(PP)定义如下:
S代表sentence,N是句子长度,是第i个词的概率。第一个词就是,而是,表示句子的起始,是个占位符,事实上,结尾应该也有一个占位符,但这里好像没有写。
这个式子可以这样理解,PPL越小,则越大,一句我们期望的sentence出现的概率就越高。
n-gram模型
为了解决参数空间过大的问题。人们引入了马尔科夫假设:随意一个词出现的概率只与它前面出现的有限的一个或者几个词有关。
如果一个词的出现与它周围的词是独立的,那么我们就称之为unigram,也就是一元语言模型:
如果一个词的出现仅依赖于它前面出现的一个词,那么我们就称之为bigram:
一般来说,N元模型就是假设当前词的出现概率只与它前面的n-1个词有关。而这些概率参数都是可以通过大规模语料库来计算:
在实践中用的最多的就是bigram和trigram了,高于四元的用的非常少,由于训练它须要更庞大的语料,并且数据稀疏严重,时间复杂度高,精度却提高的不多。
计算n-gram模型
那在实际计算中,我们怎么计算一个句子的概率呢?
以一元模型为例,在一元语言模型中,我们的句子概率定义为:
那么这里面的每个因子该怎么计算呢?
这里使用频率统计的办法,由于一元模型认为每个词是相互独立的,所以统计的时候,只需要统计语料库中每个词出现的频率作为概率就可以了。
这里的计算可以认为是根据极大似然估计得到的,假如词典里有V个词,每个词对应一个概率,考虑到所有词出现的概率和是1,那就有V-1个参数。假设词典表中第i词在语料库中出现的数目为,并且那么根据极大似然估计就有:
取对数之后:
这个可以用多元函数极值去求解。不过这样进行多元极值求解不容易计算,如果用条件极值会容易计算,用拉格朗日乘数法进行求解。
求解得:
有了每个词的出现概率,带入到式子中就可以计算出对应句子的概率。
对于二元模型或者多元模型,其计算方式有些区别,因为假设有些不同,假如我们需要计算的值,那么统计频率的方式是:
那对于bigram中的第一个词的概率,由于他之前没有词汇,那这时候我们一般会认为句子的开头和结尾分别有一个抽象符号和,那么句子就变成了,,,因此式可以变为:
其余的n-gram模型也是类似的计算方法,有的地方说的是不要用添加开始和结尾的符号,直接用unigram和bigram的方法去计算即可,这里暂时以斯坦福的课程为准吧,但是应该对最终影响有限,毕竟本质上是在做最大似然估计。
平滑方法
在上述计算过程中,由于分子是词对出现的次数,那很有可能在语料库中没有出现这样的词对,这时计算结果就是0,同时也会导致句子的出现概率也为0,那这样有些不合理,即使写错了字,也应当有一定概率出现,所以在计算式的时候要做一下平滑处理
Add-One 平滑(又称Laplace平滑)
其原理是保证每一个词对(对于bigram而言)都会出现一次,因此,式可以修改为:
其中V是词的字典数目,这里分母加1是为了保证概率和为1,即,通俗理解为,我们往语料库中加入了这V个词对,因此其分母语料库统计的数目也要加V。
Add-K 平滑
Add-K平滑就是保证每个词对都出现K次,因此,式可以修改为:
这里分母加KV和之前的模式是一样的。
good-turning平滑
这个估计是一个很重要的平滑方式,其原理就是对于没有看见的事件,我们不能认为它的发生概率就是零,因此我们从概率的总量(Probability mass)中,分配一个很小的比例给予这些没有看见的事件,这样一来,看见的那些事件的概率总和就要小于1,因此,需要将所有看见的事件概率小一点。至于小多少,要根据“越是不可信的统计折扣越多”的方法进行。
可以参考:https://zhuanlan.zhihu.com/p/53636976
神经网络语言模型
神经网络语言模型就是指利用神经网络进行语言建模,当前的一些预训练语言模型就是利用神经网络来建模的。
前向神经网络模型
前向神经网络,又被称为全连接(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就可以得到下一个词输出的概率
一般可以用交叉熵损失函数的输出来计算下一个词是目标词的概率,可以从交叉熵的损失函数的计算过程来证明。只有目标词所在的才是1,其余都是0,所以loss就是目标词的
Bi-LM
上述的都是单向语言模型,但是实际上,我们在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 的参数按照方向取不同值。
Masked Language Model
传统的语言模型是从左到右或者从右到左的利用上文或者下文去预测当前词,但实际上,当前词出现不只是单单依靠上文或者下文,其实应该是同时依赖于上下文,在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
从经典结构到改进方法,神经网络语言模型综述
深入浅出讲解语言模型
神经网络语言建模系列之一:基础模型