NLP

自然语言处理的发展

Natural Language Processing

Posted by Jiayue Cai on December 10, 2018

Last updated on 2019-5-19…

原文链接,本文仅挑重点进行了梳理,并在关键部分增加了个人理解。

早期的词袋 -> 13年的word2vec -> 18年的BERT

任务与框架

本质上,自然语言理解NLU的核心问题其实就是如何从语言文字的表象符号中抽取出来蕴含在文字背后的真实意义,并将其用计算机能够读懂的方式表征出来——当然这通常对应的是数学语言。

表征是如此重要,以至于2012年的时候Yoshua Bengio自己作为第一作者发表了一篇表征学习的综述,并随后在2013年和深度学习三大巨头的另一位巨头Yann LeCun牵头创办ICLR,这一会议至今才过去5年时间,如今已是AI领域最负盛名的顶级会议之一。可以说,探究NLP或NLU的历史,也可以说同样也是探究文本如何更有效表征的历史。

通常来说,NLP中监督任务的基本套路如下:

  • 文本数据搜集和预处理
  • 将文本进行编码和表征
  • 设计模型解决具体任务

其中数据处理阶段自不用说,各个任务按照各自的逻辑去处理和得到相应的输入。而其中的第二阶段Encoder模块与第三阶段的Task-specific Model模块,通常来说,界限并不是特别清晰,二者之间互有渗透。

回顾过去基于深度学习的NLP任务可以发现,几乎绝大多数都比较符合这三层概念。

  • 比如很多生成任务的Seq2Seq框架中不外乎都有一个Encoder和一个Decoder,对应到这里的话Decoder更像是一个Task-specific Model,然后相应的将Encoder做一些细微的调整,比如引入Attention机制等等,
  • 对于一些文本分类任务的结构,则Encoder模块与Task-specific Model模块的区分更为明显和清晰,Encoder负责提取文本的特征,最后接上一些全连接层和Softmax层便可以当做Task-specific Model模块,如此便完成了一个文本分类任务。

早期模型发展

在word2vec诞生之前,NLP中并没有一个统一的方法去表示一段文本,各位前辈和大师们发明了许多的方法:

  • one-hot表示一个词
  • bag-of-words来表示一段文本
  • k-shingles把一段文本切分成一些文字片段到汉语中用各种序列标注方法将文本按语义进行分割
  • tf-idf中用频率的手段来表征词语的重要性到text-rank中借鉴了page-rank的方法来表征词语的权重
  • 基于SVD纯数学分解词文档矩阵的LSA
  • pLSA中用概率手段来表征文档形成过程并将词文档矩阵的求解结果赋予概率含义
  • LDA中引入两个共轭分布从而完美引入先验
  • ……

语言模型

实际上,语言模型的本质是对一段自然语言的文本进行预测概率的大小,即如果文本用Si来表示,那么语言模型就是要求P(Si)的大小。

如果按照大数定律中频率对于概率无限逼近的思想,求这个概率大小,自然要用这个文本在所有人类历史上产生过的所有文本集合中,先求这个文本的频率 P(Si),然后便可以通过如下公式来求得:

这个公式足够简单,但问题是全人类所有历史的语料这种统计显然无法实现,因此为了将这个不可能的统计任务变得可能,首先有人将文本不当做一个整体,而是把它拆散成一个个的词,通过每个词之间的概率关系,从而求得整个文本的概率大小。假定句子长度为T,词用x表示,即

然而,这个式子的计算依然过于复杂,我们一般都会引入马尔科夫假设:假定一个句子中的词只与它前面的n个词相关,特别地,当n=1的时候,句子的概率计算公式最为简洁:

并且把词频的统计用来估计这个语言模型中的条件概率,如下

这样一来,语言模型的计算终于变得可行。然而,这种基于统计的语言模型却存在很多问题:

  • 很多情况下 c(xi+1,xi) 的计算会遇到特别多零值,尤其是在n取值比较大的情况下,这种数据稀疏导致的计算为0的现象变得特别严重。所以统计语言模型中一个很重要的方向便是设计各种平滑方法来处理这种情况。
  • 另一个更为严重的问题是,基于统计的语言模型无法把n取得很大,一般来说在3-gram比较常见,再大的话,计算复杂度会指数上升。这个问题的存在导致统计语言模型无法建模语言中上下文较长的依赖关系。
  • 统计语言模型无法表征词语之间的相似性。

NNLM

输入一个单词的上文,去预测这个单词

这些缺点的存在,迫使2003年Bengio在他的经典论文《A Neural Probabilistic Language Model》中,首次将深度学习的思想融入到语言模型中,并发现将训练得到的NNLM(Neural Net Language Model, 神经网络语言模型)模型的第一层参数当做词的分布式表征时,能够很好的获取词语之间的相似度。

撇去正则化项,NNLM的极大目标函数对数似然函数,其本质上是个N-Gram的语言模型,如下所示

其中,归一化之前的概率大小(也就是logits)为

x实际上就是将每个词映射为m维的向量,然后将这n-1个词的向量concat起来,组合成一个 (n-1)*m维的向量。这里可以将NNLM的网络结构拆分为三个部分:

  • 第一部分:从词到词向量的映射,通过C矩阵完成映射,参数个数为 |V| * m;
  • 第二部分:从x到隐藏层的映射,通过矩阵H,这里的参数个数为 |H| * m * (n-1);
  • 第三部分:从隐藏层到输出层的映射,通过矩阵U,参数个数为|V| * |H|
  • 第四部分:从x到输出层的映射,通过矩阵W,参数个数为|V| * m * (n-1);

因此,如果算上偏置项的参数个数(其中输出层为|V|,输入层到隐藏层为|H|)的话,NNLM的参数个数为:

可见NNLM的参数个数是所取窗口大小n的线性函数,这便可以让NNLM能够对更长的依赖关系进行建模。不过NNLM的最主要贡献是非常有创见性的将模型的第一层特征映射矩阵当做词的分布式表示,从而可以将一个词表征为一个向量形式,这直接启发了后来的word2vec的工作。

自NNLM于2003年被提出后,后面又出现了很多类似和改进的工作,诸如LBL, C&W和RNNLM模型等等,这些方法主要从两个方面去优化NNLM的思想:

  • 其一是NNLM只用了左边的n-1个词,如何利用更多的上下文信息便成为了很重要的一个优化思路(如Mikolov等人提出的RNNLM);
  • 其二是NNLM的一个非常大的缺点是输出层计算量太大,如何减小计算量使得大规模语料上的训练变得可行,这也是工程应用上至关重要的优化方向(如Mnih和Hinton提出的LBL以及后续的一系列模型)。

2007年Mnih和Hinton提出的LBL以及后续的一系列相关模型,省去了NNLM中的激活函数,直接把模型变成了一个线性变换,尤其是后来将Hierarchical Softmax引入到LBL后,训练效率进一步增强,但是表达能力不如NNLM这种神经网络的结构

2008年Collobert和Weston 提出的C&W模型不再利用语言模型的结构,而是将目标文本片段整体当做输入,然后预测这个片段是真实文本的概率,所以它的工作主要是改变了目标输出,由于输出只是一个概率大小,不再是词典大小,因此训练效率大大提升,但由于使用了这种比较“别致”的目标输出,使得它的词向量表征能力有限

2010年Mikolov(对,还是同一个Mikolov)提出的RNNLM主要是为了解决长程依赖关系,时间复杂度问题依然存在。

新一阶段的模型发展

Word2Vec = CBOW + Skip-gram

Word2Vec包含了两种词训练模型:CBOW模型和Skip-gram模型。

  • CBOW模型根据中心词W(t)周围的词来预测中心词,即从一个句子里面把一个词抠掉,用这个词的上文和下文去预测被抠掉的这个词
  • Skip-gram模型则根据中心词W(t)来预测周围词,和CBOW正好反过来,输入某个单词,要求网络预测它的上下文单词

观察上图,Word2Vec的整个建模过程实际上与自编码器(auto-encoder)的思想很相似,即先基于训练数据构建一个神经网络,当这个模型训练好以后,我们并不会用这个训练好的模型处理新的任务,我们真正需要的是这个模型通过训练数据所学得的参数,例如隐层的权重矩阵——后面我们将会看到这些权重在Word2Vec中实际上就是我们试图去学习的“word vectors”。

句子中每个单词以Onehot形式作为输入,然后乘以学好的Word Embedding矩阵Q,就直接取出单词对应的Word Embedding了

同一个单词占的是同一行的参数空间,这导致两种不同的上下文信息都会编码到相同的word embedding空间里去。所以word embedding无法区分多义词的不同语义,这就是它的一个比较严重的问题。

CBOW 周围预测中心

2013年,Tomas Mikolov连放几篇划时代的论文,其中最为重要的是两篇,《Efficient estimation of word representations in vector space》首次提出了CBOW和Skip-gram模型,进一步的在《Distributed Representations of Words and Phrases and their Compositionality》中,又介绍了几种优化训练的方法,包括Hierarchical Softmax(当然,这个方法早在2003年,Bengio就在他提出NNLM论文中的Future Work部分提到了这种方法,并于2005年把它系统化发表了一篇论文), Negative Sampling和Subsampling技术。放出两篇论文后,

当时仍在谷歌工作的Mikolov又马不停蹄的放出了大杀器——word2vec工具,并在其中开源了他的方法。

顺便提一下,很多人以为word2vec是一种模型和方法,其实word2vec只是一个工具,背后的模型是CBOW或者Skip-gram,并且使用了Hierarchical Softmax或者Negative Sampling这些训练的优化方法。所以准确说来,word2vec并不是一个模型或算法,只不过Mikolov恰好在当时把他开源的工具包起名叫做word2vec而已。不过为了叙述简单,在下文我将用word2vec来指代上面提到Mikolov两篇论文中的一整个相关的优化思想。

word2vec对于前人的优化,主要是两方面的工作:模型的简化和训练技巧的优化。我们先来看看模型的简化方面,也就是耳熟能详的CBOW和Skip-gram。

对于CBOW而言,我们可以从它的名字上一窥究竟,它的全称是Continuous Bag-of-Words,也就是连续的词袋模型,为什么取这个名字,先来看看它的目标函数:

首先,CBOW没有隐藏层,本质上只有两层结构,输入层将目标词语境c中的每一个词向量简单求和(当然,也可以求平均)后得到语境向量,然后直接与目标词的输出向量求点积,目标函数也就是要让这个与目标词向量的点积取得最大值,对应的与非目标词的点积尽量取得最小值。

从这可以看出,CBOW的三个特点

  • 取消了NNLM中的隐藏层,直接将输入层和输出层相连
  • 在求语境context向量时候,语境内的词序已经丢弃(这个是名字中Continuous的来源)
  • 因为最终的目标函数仍然是语言模型的目标函数,所以需要顺序遍历语料中的每一个词(这个是名字中Bag-of-Words的来源)

因此有了这些特点(尤其是第二点和第三点),Mikolov才把这个简单的模型取名叫做CBOW,简单却有效的典范。

需要注意的是这里每个词对应到两个词向量,在上面的公式中都有体现,其中 e(wt) 是词的输入向量,而 e’(wt) 则是词的输出向量,或者更准确的来讲,前者是CBOW输入层中跟词 wt 所在位置相连的所有边的权值(其实这就是词向量)组合成的向量,而是输出层中与词 wt 所在位置相连的所有边的权值组合成的向量,所以把这一向量叫做输出向量。

Skip-gram 中心预测周围

同样地,和CBOW对应,Skip-gram的模型基本思想和CBOW非常类似,只是换了一个方向:CBOW是让目标词的输出向量 e’(wt) 拟合语境的向量 x ;而Skip-gram则是让语境中每个词的输出向量尽量拟合当前输入词的向量 e(wt) ,和CBOW的方向相反,因此它的目标函数如下:

可以看出目标函数中有两个求和符号,最里面的求和符号的意义便是让当前的输入词分别和该词对应语境中的每一个词都尽量接近,从而便可以表现为该词与其上下文尽量接近。

和CBOW类似,Skip-gram本质上也只有两层:输入层和输出层,输入层负责将输入词映射为一个词向量,输出层负责将其经过线性映射计算得到每个词的概率大小。

再细心一点的话,其实无论CBOW还是Skip-gram,本质上都是两个全连接层的相连,中间没有 任何其他的层。

因此,这两个模型的参数个数都是 2 * |e| * |V| ,其中|e|和|V|分别是词向量的维度和词典的大小,相比上文中我们计算得到NNLM的参数个数|V|(1+|H|+|e|n) + |H|(1+|e|n-|e|)已经大大减小,且与上下文所取词的个数无关了,也就是终于避免了N-gram中随着阶数N增大而使得计算复杂度急剧上升的问题。

然而,Mikolov大神说了,这些公式是“impractical”的,他的言下之意是计算复杂度依然和词典大小有关,而这通常都意味着非常非常大,以下是他的原话:

…, and W is the number of words in the vocabulary. This formulation is impractical because the cost of computing ∇ log p(wO|wI ) is proportional to W, which is often large (10^5–10^7 terms).

不得不说,大神就是大神,将模型已经简化到了只剩两个全连接层(再脱就没了),依然不满足,还“得寸进尺”地打起了词典的“小算盘”,那么Mikolov的“小算盘”是什么呢?

他在论文中首先提到了Hierachical Softmax,认为这是对full softmax的一种优化手段,而Hierachical Softmax的基本思想:

  • 首先将词典中的每个词按照词频大小构建出一棵Huffman树,保证词频较大的词处于相对比较浅的层,词频较低的词相应的处于Huffman树较深层的叶子节点,每一个词都处于这棵Huffman树上的某个叶子节点;
  • 第二,将原本的一个|V|分类问题变成了 log |V| 次的二分类问题,做法简单说来就是,原先要计算 P(wt|ct) 的时候,因为使用的是普通的softmax,势必要求词典中的每一个词的概率大小,为了减少这一步的计算量,在Hierachical Softmax中,同样是计算当前词 wt 在其上下文中的概率大小,只需要把它变成在Huffman树中的路径预测问题就可以了,因为当前词wt在Huffman树中对应到一条路径
    • 这条路径由这棵二叉树中从根节点开始,经过一系列中间的父节点,最终到达当前这个词的叶子节点而组成,那么在每一个父节点上,都对应的是一个二分类问题(本质上就是一个LR分类器),而Huffman树的构造过程保证了树的深度为 log |V|,所以也就只需要做 log |V|次二分类便可以求得P(wt|ct)的大小,这相比原来|V|次的计算量,已经大大减小了。

接着,Mikolov又提出了负采样的思想,而这一思想也是受了C&W模型中构造负样本方法启发,同时参考了Noise Contrastive Estimation (NCE)的思想,用CBOW的框架简单来讲就是:

  • 负采样每遍历到一个目标词,为了使得目标词的概率P(wt|ct)最大,根据softmax函数的概率公式,也就是让分子中的 e’(wt)Tx最大,而分母中其他非目标词的 e’(wi)Tx 最小,普通softmax的计算量太大就是因为它把词典中所有其他非目标词都当做负例了
  • 而负采样的思想特别简单,就是每次按照一定概率随机采样一些词当做负例,从而就只需要计算这些负采样出来的负例了,那么概率公式便相应变为:

仔细和普通softmax进行比较便会发现,将原来的|V|分类问题变成了K分类问题,这便把词典大小对时间复杂度的影响变成了一个常数项,而改动又非常的微小,不可谓不巧妙。

除此之外,Mikolov还提到了一些其他技巧,比如对于那些超高频率的词,尤其是停用词,可以使用Subsampling的方法进行处理,不过这已经不是word2vec最主要的内容了。

自此,经过模型和训练技巧的双重优化,终于使得大规模语料上的训练成为了现实,更重要的是,得到的这些词向量能够在语义上有非常好的表现,能将语义关系通过向量空间关系表征出来。

word2vec的出现,极大的促进了NLP的发展,尤其是促进了深度学习在NLP中的应用(不过有意思的是,word2vec算法本身其实并不是一个深度模型,它只有两层全连接),利用预训练好的词向量来初始化网络结构的第一层几乎已经成了标配,尤其是在只有少量监督数据的情况下,如果不拿预训练的embedding初始化第一层,几乎可以被认为是在蛮干。

在此之后,一大批word embedding方法大量涌现,比较知名的有GloVe和fastText等等,它们各自侧重不同的角度,并且从不同的方向都得到了还不错的embedding表征。

GloVe 加入共现频率

先来看看GloVe的损失函数:

其中 Xij是两个词i和j在某个窗口大小中的共现频率(不过GloVe对其做了一些改进,共现频率相应有一个衰减系数,使得距离越远的词对共现频率越小一些), f(Xij) 是一个权重系数,主要目的是共现越多的pair对于目标函数贡献应该越大,但是又不能无限制增大,所以对共现频率过于大的pair限定最大值,以防训练的时候被这些频率过大的pair主导了整个目标函数。

剩下的就是算法的核心部分了,两个b值是两个偏置项,撇去不谈,那么剩下的 ( wiT wj - log Xij)2 其实就是一个普通的均方误差函数, wi 是当前词的向量, wj 对应的是与其在同一个窗口中出现的共现词的词向量,两者的向量点乘要去尽量拟合它们共现频率的对数值。

从直观上理解,如果两个词共现频率越高,那么其对数值当然也越高,因而算法要求二者词向量的点乘也越大,而二个词向量的点乘越大,其实包含了两层含义:

  • 第一,要求各自词向量的模越大,通常来说,除去频率非常高的词(比如停用词),对于有明确语义的词来说,它们的词向量模长会随着词频增大而增大,因此两个词共现频率越大,要求各自词向量模长越大是有直觉意义的,比如“魑魅魍魉”假如能被拆分成两个词,那么“魑魅”和“魍魉”这两个词的共现频率相比““魑魅”和其他词的共现频率要大得多,对应到“魑魅”的词向量,便会倾向于在某个词向量维度上持续更新,进而使得它的模长也会比较偏大
  • 第二,要求这两个词向量的夹角越小,这也是符合直觉的,因为出现在同一个语境下频率越大,说明这两个词的语义越接近,因而词向量的夹角也偏向于越小

此外,可以从GloVe使用的损失函数中发现,它的训练主要是两个步骤:统计共现矩阵训练获取词向量

  • 这个过程其实是没有我们通常理解当中的模型的,更遑论神经网络,它整个的算法框架都是基于矩阵分解的做法来获取词向量的
  • 本质上和诸如LSA这种基于SVD的矩阵分解方法没有什么不同,只不过SVD分解太过于耗时,运算量巨大,相同点是LSA也是输入共现矩阵,不过一般主要以词-文档共现矩阵为主
  • 另外,LSA中的共现矩阵没有做特殊处理,而GloVe考虑到了对距离较远的词对做相应的惩罚等等
  • 然而,相比word2vec,GloVe却更加充分的利用了词的共现信息,word2vec中则是直接粗暴的让两个向量的点乘相比其他词的点乘最大,至少在表面上看来似乎是没有用到词的共现信息,不像GloVe这里明确的就是拟合词对的共现频率

不过更有意思的还是,GloVe和word2vec似乎有种更为内在的联系,再来看看他们的目标函数有什么不一样,这是Skip-gram的目标函数(这里在原来的基础上加上了对于语料的遍历N):

而这个目标函数是按照语料的顺序去遍历,如果先把语料当中的相关词进行合并,然后按照词典序进行遍历,便可以证明Skip-gram实际上和GloVe的优化目标一致,有兴趣的可以参考引用11中的证明细节,这里不再赘述。

fastText 加入语序信息

word2vec和GloVe都不需要人工标记的监督数据,只需要语言内部存在的监督信号即可以完成训练。

而与此相对应的,fastText则是利用带有监督标记的文本分类数据完成训练,本质上没有什么特殊的,模型框架就是CBOW,只不过与普通的CBOW有两点不一样:

  • 输入数据和预测目标的不同:在输入数据上,CBOW输入的是一段区间中除去目标词之外的所有其他词的向量加和或平均,而fastText为了利用更多的语序信息,将bag-of-words变成了bag-of-features,也就是下图中的输入x不再仅仅是一个词,还可以加上bigram或者是trigram的信息等等
  • 第二个不同在于,CBOW预测目标是语境中的一个词,而fastText预测目标是当前这段输入文本的类别,正因为需要这个文本类别,因此才说fastText是一个监督模型
  • 而相同点在于,fastText的网络结构和CBOW基本一致,同时在输出层的分类上也使用了Hierachical Softmax技巧来加速训练

这里的 xn,i 便是语料当中第n篇文档的第i个词以及加上N-gram的特征信息。从这个损失函数便可以知道fastText同样只有两个全连接层,分别是A和B,其中A便是最终可以获取的词向量信息。

fastText最大的特点在于快,论文中对这一点也做了详细的实验验证,在一些分类数据集上,fastText通常都可以把CNN结构的模型要耗时几小时甚至几天的时间,急剧减少到只需要消耗几秒钟,不可谓不“fast”.

不过说个八卦,为什么fastText结构和CBOW如此相似(感兴趣的读者想要继续深挖的话,还可以看看2015年ACL上的一篇论文《Deep Unordered Composition Rivals Syntactic Methods for Text Classification》,结构又是何其相似,并且比fastText的论文探讨的更为深入一些,但是fastText是2016年的文章,剩下的大家自己去想好了),这里面大概一个特别重要的原因就是fastText的作者之一便是3年前CBOW的提出者Mikolov本人,只不过昔日的Mikolov还在谷歌,如今3年时间一晃而过,早已是Facebook的人了。

后续模型的发展

除了在word级别的embedding方法上有大量模型和算法的涌现,同样地,在char级别、句子级别和段落级别同样有大量模型提出。

word2vec开源随后的第一年,也就是在2014年,还是Mikolov,在他和另一位作者合作的一篇论文《Distributed Representations of Sentences and Documents》中,提出了可以借鉴word2vec思想的两种结构:PV-DM和PV-DBOW,分别对应word2vec中的CBOW和Skip-gram.

本篇仅到此为止,有兴趣继续了解的同学可以阅读原文

展望

两个方向:

1、如何更充分挖掘数据的价值,无论是有标注数据、弱标注数据还是无标注数据,都是数据驱动模型的重要命题,仍然有很多开放问题等待解决。即使 2018 年有了 BERT,未来还会有更多的学习机制等待探索。

BERT模型:一种预训练语言模型,利用大规模无标注文本数据学习一般的语言知识,充分利用更多数据自动学习预训练模型,改进相关任务的性能。具有里程碑意义。

2、如何更好地将结构化知识融入相关自然语言处理模型中,相当于把基于符号表示的各种先验知识和规则,引入到自然语言计算模型中。Google、DeepMind 等研究机构很重视这个方向,现在比较流行的图神经网络可以看做这方面的重要尝试。我们有望通过图神经网络将结构化知识融入深度学习,实现各种计算与推理任务。

对话清华NLP实验室刘知远:NLP搞事情少不了知识库与图神经网络