AI教程

CS224N中文笔记(2)词向量的计算与评价

本文为2019版CS224N中文笔记系列第二篇文章。本节,我们将进一步研究如何向计算机表示单词:整理各类词向量的表示方法,并尝试评估词向量的好坏。

往期文章:【第一篇-计算机如何理解单词】

笔者最近有点懒……原本是想一周双更的,结果却成了两周一更。反正慢工出细活。等读者看到本系列文章的时候,CS224N二十讲中文笔记应该已经更新完了。

1. 多种多样的词向量表示方法

上一讲中,我们知道,计算机是以编码(如“UTF-8”编码)的形式存储语言文字的。比如汉字“你”在UTF-8编码下是“&#x4F60”,“我”在Unicode编码下是“\u6211”。

然而,这样的表示只有助于信息的存储,不利于信息的计算。我们知道“快乐”和“悲伤”是反义词,而计算机无法知道“\u5feb\u4e50”(快乐)和“\u60b2\u4f24”(悲伤)的关系。

为此,人们提出来很多解决方法(复习上一讲内容):

1.1 WordNet(词网):

人为定义词与词之间的关系。定义词之间的同义关系、反义关系、父子关系等等。

WordNet上 “good”与其近义词

而然这种定义过于主观、需要大量人力,且定义过于死板。

1.2 Bag of words(词袋模型):

给定一个长度为N的字典:{‘啊’,‘我’,‘你’,……,‘白’}。这样,任何一个词、一句话或者一段文字都可以用一个长度为N的向量表示。该向量上第i个位置的取值为字典上第i个词在文本中出现的次数。

如“技术杂学铺的文章写得好好看啊” 在长度为8的字典[“今”, “我”, “好”, “看”, “文”, “章”, “天”, “你”]中可以表示为[0, 0, 2, 1, 1, 1, 0, 0]

词袋模型

虽然词袋模型抹去了语句的先后顺序——“我吃饭”和“饭吃我”在词袋模型中表达是一样的。但该方法在传统机器学习中很常用。尤其是在用朴素贝叶斯算法对文本分类上,效果很可观。

另外,在深度学习中,我们常会用到one-hot编码,其与词袋模型很相近。One-Hot编码是指,给定一个长度为N的字典,每个词可用一个长度为N的向量来表示。若该词在字典中第i个位置出现,则向量的第i个位置为1,其他地方都为0。

one-hot编码

1.3 Word vector(词向量):

词向量(word vector)也叫词嵌入(word embedding),指每一个词用一串等长浮点数来表示(向量)。NLP任务中,常见的词向量的长度在100-300之间。

我们希望词义相近的两个词,其词向量欧式距离很近或两向量夹角很小;词义相反的两个词,其词向量欧式距离很大,或两个向量的夹角很大。

另外,我们还希望词向量可以捕获一些语法和语义信息。比如“国王”的词向量减去 “女王”的词向量约等于“男孩”词向量减“女孩”词向量;“中国” – “中国人” 约等于“德国” – “德国人”……

然而怎么实现上述的两个希望呢?人为定义每一个词的词向量吗?不,我们用算法来实现

假设我们有大量文本,我们应该设计怎样的算法才能利用这些文本,算出每个词的词向量?

我们最常用的办法是:一个词的词向量由其周围的词算出

1.3.1 HAL算法(Hyperspace Analogue to Language method):

“一个词的词向量由其周围的词算出”,那么我们可以用词X周围其他词出现的频率来代表词X

如有三句话:

  • I like deep learning.
  • I like NLP.
  • I enjoy flying.

我们计算每个词前后各N个词的出现次数,于是就构成了一个共现矩阵(co-occurrence matrix)。下图中的共现矩阵记录了每个词前后各1个词出现的次数。很明显,共现矩阵是对称的。

于是单词“I”可以用向量[0, 2, 1, 0, 0, 0, 0, 0]来表示。单词“flying”可以用[0, 0, 1, 0, 0, 0, 0, 1]来表示。

在文本足够多,N取值适当的情况下,我们可以想到“苹果”,“香蕉”周围“水果”,“吃”,“甜”这些词出现的次数很频繁。“苹果”,“香蕉”的词向量在“水果”,“吃”这些维度上应该很相近。于是乎,我们就捕获了词之间的相似性。

然而,这个算法最大的问题就是,当文本很大、不同的单词很多时(如有5000个不同的词),共现矩阵会非常巨大(5000*5000),也会非常稀疏,每个单词的维度也非常的大(5000)。

1.3.2 LSA算法(Latent Semantic Analysis):

LSA算法在上述算法上应用了SVD降维,将词向量维度大大减少,同时又保留了极为有用的信息。

首先,LSA与HAL算法在开始步骤上一样,同样是统计词出现的次数,计算共现矩阵(co-occurrence matrix)。

接着,LSA通过某个算法,将共现矩阵上所有的数值缩放到0-1之间。(算法略微复杂,可见下图)

w为原始词向量,w’为标准化后的词向量

再之后,我们对标准化的矩阵进行SVD分解。

SVD属于线性代数上的知识,SVD可将一个矩阵X分解为U、∑、V三个矩阵,U、V表示空间映射,∑中斜对角线的取值表示对应空间映射的重要程度。

SVD具体算法这里不作过多说明。有兴趣的读者可见《机器学习实战》,该书电子版关注“技术杂学铺”公众号,回复“机器学习”即可下载。

SVD算法在降维、数据压缩、产品智能推荐等领域有很大的作用。

SVD算法

在上图中,在将X分解为三个矩阵后,我们舍弃U、∑、V中被框住的部分(这些部分存储的信息不是很重要)。之后我们将剩余的U、∑、V矩阵重新合并为X矩阵。

新的X矩阵会比原来的矩阵小很多(根据舍弃U、∑、V矩阵的比例而定),且保留的内容都是十分重要的信息。

对于共现矩阵X的计算,除了传统的计算次数,我们可以使用一些小技巧:

  • the/he/has这里词出现的次数太多了。可以限制共现矩阵X中每个值最大取100,或者在矩阵X中删除所有the/he/has这样常见的词。
  • 计算次数时可以加权计算。离中心词之后越近的词权重越高,越远的词权重越低。
  • ……

另外,除了用SVD降维,我们还可以使用PCA来实现降维。

1.3.3 神经网络自学成才法:

我们将词向量当做神经网络中的一层(词嵌入层)。

假设我们的词典有N个词,词向量长度为D。那么我们输入一个词的one-hot编码(X = [0, 0, 0, …, 1, … 0, 0]^T,大小为N*1),经过词嵌入层W(大小为N*D)之后,我们会得到该词的词向量(如X’ = [0.23, -0.49, 0.32, …, 0.48],长度为D)。

即一个词的词向量为

此处词嵌入W的取值,我们可以采用之前HAL、LSA、word2vec等算法得到的词向量,也可以完全随机初始化

只要我们的训练样本足够大,我们完全可以让执行某一NLP任务(如机器翻译、阅读理解、文本分类等等)的神经网络的词嵌入层随机初始,词嵌入层会随着神经网络的训练而不断更新,直到收敛。

在有海量训练数据的情况下,随着神经网络一起训练的词向量往往学习到了词与词之间的相似性,以及捕获了语法和语义的信息。

关于如何用神经网络执行NLP任务,我们将会在下一篇文章中讲述。

1.3.4 迭代法:

我们的任务是针对某一个目标函数,通过不断迭代(如梯度下降法、牛顿法)的方法使目标函数不断变小。当目标函数收敛至某一极小值时,我们选择该过程的中间产物作为我们的词向量。

常见的算法有word2vec、Glove。我们将会在下面的内容中依次讲述。

2. word2vec算法

word2vec算法我们在上一讲中已经深入讨论过了。这里做一个简单的复习以及进一步研究。

2.1 基础知识复习

word2vec算法是建立于“一个词的意思由经常出现在其附近的单词给出”的假设之上。

word2vec算法可以细分成CBOW和skip-grams两种模型。我们如果通过上下文来预测中心的词是什么,这样的方法叫做Continuous Bag of Words(CBOW)。反之,我们若是想要通过中心词来预测上下文的词,这样的方法叫Skip-grams。

skip-grams算法示意图

以skip-grams为例。我们知道中心词c,则上下文词是o的概率为下图,是一个softmax函数:

上述概率中的u和v是两个长度为N的向量,是需要我们使用某种方法进行学习的。

那如何学习到u和v的取值呢?我们定义了一个目标函数,通过梯度下降法不断调整v和u的取值,直到这个目标函数收敛到极小值。此时u和v即为我们想要的内容。

目标函数

每个词的词向量为该词的u向量和v向量之和。(如果你愿意,也可以将u向量和v向量拼接起来,或者不分u和v向量,令二者都是u向量。不过实验证明分两个向量并求和的词向量在NLP任务中表现更好)

上述skip-grams内容为一个简单的复习。读者若是希望深入研究其背后的数学原理,可见第一讲讲义。读者若是想快速入门NLP,简单了解此类算法即可,具体原理可以不了解。

2.2 负采样

我们上讲中讲过,skip-grams/CBOW算法,本质上是一个两层的神经网络。u,v向量是这个网络中的参数。

word2vec本质是一个神经网络

然而,这个算法有一个很大的问题,那就是出在最后的softmax上。

softmax的分母是所有单词的u向量与中心词的v向量相乘,之后再进行指数操作。这个过程计算量过于庞大,以至于进行skip-grams/CBOW时,九成以上的计算量都花在了计算softmax上。

在word2vec算法原论文中,作者提出了一个解决方法——负采样算法。

我们以前是研究一个多分类问题:给定一个单词,预测其周围的单词可能是N个中的哪一个(N为字典的长度)。

而负采样算法将问题变为了二分类:给定两个词,预测这两个词是否应该挨在一起。

我们令x为第一个词o的U词向量和第二个词c的V词向量的乘积,即x = Uo*Vc

如果这两个词应该挨在一起,比如“我”,“是”这两个词挨在一起的概率很大,组合可以是“我是个好人”。那么x = Uo*Vc后,我们希望x是一个很大的整数。

若两个词毫无关联,如“蚂蚁”,“木星”,我们希望x是一个绝对值很大的负数。

我们使用σ函数(σ读作sigma)可以将x的取值缩放到0-1之间

如果x的取值为正无穷,则σ(x)的取值为1;如果x的取值为负无穷,则σ(x)的取值为0;如果x的取值为0,则σ(x)的取值为0.5。我们用σ(x)来表示两个词是否应该挨在一起的概率。

σ函数

关于目标函数:

词o与词c相邻,我们将[o,c]作为正样本,同时,我们再选K个与词c不相邻的词(K取值一般不超过15),组成K个负样本。

此时,我们的目标函数如下:

若词o与词c相邻,则Uo*Vc应该是一个很大的整数,σ(Uo*Vc)接近1,-log (σ(Uo*Vc))将会是一个大于零但趋近于零的数。同理,设词k与词c不相邻,则Uk*Vc应该是一个很小的数,-Uk*Vc就是一个很大的整数,-log(σ(Uk*Vc)) 也会是一个大于零但趋近于零的数。

与之前一样,我们使用梯度下降法,不断更新u和v的值,直到目标函数收敛到极小值。

关于样本选取:

那如何选取K个与词c不相邻的词呢?

设一个词w的词频为U(w),那么我们选择词w作为与c不相邻的概率为:

其中,分母Z是用于归一化,以使所有的词被选取的概率和等于一,即∑p(w)=1。

而对词频求四分之三的幂是因为0.002的四分之三次幂为0.0094,数值提升了四倍多。0.9的四分之三次幂为0.92,数值几乎没有太大变化。取幂后可以让低频词被选中的概率更大一些

选四分之三作为幂仅仅是一个人为经验,是一个超参数。读者要是愿意的话,取二分之一、三分之一次幂均可。

这样的选词方法是可能会选到中心词本身或者本应在中心词周围的词。如我们选择k个与词“我”不相邻的词,我们可能会选到“我”,“是”这样的词作为负样本。不过这个问题造成的影响可以忽略不计。

另外,word2vec论文中还提出使用Hierarchical Softmax来减少计算量。不过算法略微复杂,读者有兴趣可以见第一讲讲义,或word2vec论文

3. Glove算法

基于计数的算法(LSA,HAL)和直接预测的算法(skip-gram,CBOW)各有各的优点和缺点。

基于计数的算法训练速度相对较快,且很好地利用了统计学的知识。但是这种算法只是捕捉到了词与词之间的相似度。

而使用直接预测的算法,虽然能捕获到词语相似度以外更复杂的语义信息,使用此类算法得到的词向量用于NLP任务时可以获得更好的效果,但此类算法没有很好地利用统计学知识。

Glove算法是计数算法和直接预测算法两种的综合,Glove很好地继承了上述两种算法的优点。

比例可以告诉我们很多有意义的信息

比如某一个词x,它在ice(冰)周围出现的概率很大,在steam(蒸汽)这个词周围出现的概率很小。那么 P(x|ice)/P(x|steam)将会是一个很大的值,那就意味着x和ice关系极大,和steam关系极小。

在word2vec负采样中,我们希望两个词的词向量Uo*Vc很大代表词o和词c相邻,Uo*Vc很小代表词o和词c不相邻。类比这个思想,在Glove中,每个词只有一个词向量,我们希望词i和词j的词向量乘积可以代表词i在词j周围出现的概率。

这样的话,我们可以通过如下公式来推断x是和a有关还是和b有关。

Wx ( Wa – Wb )越大,x与a有关;越小,x与b有关;取值适中,x可能与a和b都有关或都无关。

这里的P(x|a)是词a周围x出现的频率,是通过计数的方法来统计的,类似LSA算法,需要计算共现矩阵(co-occurrence matrix)

我们前面说到,Glove算法是基于计数的算法和预测算法的综合。计算共现矩阵是基于计数算法最明显的特点,那预测算法的特点在哪里体现呢?

我们会人为指定一个目标函数,在最小化目标函数的过程中学习词向量。

Glove目标函数

上图中w是词向量,X是共现矩阵,b是偏置(神经网络中很常见)。f(x)是一个人为规定的函数,该函数如下图。可近似为min(t,100),意义是降低常见的 “the”,”a” 这类词的重要性。

我们最小化上述目标函数J,其意义是我们希望两个词向量的乘积可以代表着两个词共同出现的频率。

Glove算法训练快。即便是针对小型语料库和小型词向量,Glove的效果依旧很好。

读者若对Glove算法感兴趣,其详细细节可见Glove论文

4. 词向量评价指标

在前面的内容中,我们讲述了各种各样的词向量,这些词向量在我们之后进行NLP任务时会起到很大的作用。

但是如何评价一个词向量的好坏呢?

我们有两种方法,一种是Intrinsic(内部评价),另一种是extrinsic(外部评价)

Intrinsic Evaluation(内部评价)是在一个中间任务上评价词向量的好坏。该方法计算速度快,能帮助我们更好地理解系统。但是在Intrinsic上表现好并不意味着词向量在真实任务中会有好的效果。

Extrinsic Evaluation(外部评价)是在一个真正的NLP任务(如文本分类、机器翻译)中使用词向量,以此来评判词向量的好坏。但是计算Extrinsic任务会消耗很长的时间。即使Extrinsic任务出现了问题,我们也不清楚是词向量的问题还是其他子系统的问题。

关于Intrinsic:

我们希望词向量能捕获语义和语法信息。(语义如 男孩:女孩 = 男人:女人,语法则如 small:smaller = tall:taller)

比如词向量可以知道“man”类比于“woman”就相当于“king”类比于“queen”;“eat”类比于“ate”就相当于“drink”类比于“drunk”;“small”类比于“smaller”就相当于“tall”类比于“taller”等等。

这样的类比在词向量中其实很简单,“woman”词向量减去“man”词向量近似于“king”词向量减去“queen”词向量即可。就如下图:

我们先人为规定几百条、上千条这样的类比规则。然后采用man:woman = king:?的方式,已知三个词,求另一个词。算法如下:

在词向量应用上图算法得到另一个词之后,我们对比词向量得到的词是否与我们人为规定的词相符。如man:woman = king:? 中,问号处应该填queen。

我们统计其预测的正确率,以此来代表词向量的好坏。

让他们有人比较了不同算法(CBOW,Glove等)在不同词向量维度上(100维、300维、1000维)以及不同训练文本大小上训练的词向量在某一个Intrinsic评价数据集上的结果。(Sem.代表语义得分,Syn.代表句法得分,Tot.代表总分)

上图说明:

  • 不同的词向量训练算法的效果截然不同。
  • 词向量的效果随着训练文本量的增加而增加。
  • 词向量维度过低或过高时,训练效果不好。(过低模型偏差大,过高则模型方差大)

下图则为Glove的词向量效果和其超参数的关系。

备注:上述两张图都很科研。我们写论文的时候,很重要的一点就是要作比较

Intrinsic评判标准除了上述的a:b=c:? 查看语义语法以外,还有查看词语之间的相似度。

比如找10个人,问他们认为“老虎”和“猫”之间的相似度是多少,让他们从0-10中打分。之后我们平均这10个人的打分,得到“老虎”这个单词和“猫”这个单词的相似度。以这样的方法,我们人为标记单词之间的相似度。计算词向量中两个单词的相似度(欧氏距离、乘积、向量夹角等方法),然后对比其与我们人为定义的标准的差距。

其他:

一词多义的现象很普遍。像“好”这样的词,可以是“质量好”的意思,也可以是“非常”的意思,如“好奇怪”。此时,如果能将一个词的多个意思用不同的词向量来表示就更好的。

读者若是有兴趣可以参考Linear Algebraic Structure of Word Senses, with Applications to Polysemy。该文章讲述了如何将多义词用多个词向量来表达。

有趣的是,一个多义词的词向量等于其各个意思的词向量的加权和

总结

  • 计算词向量的方法多种多样。
  • 定义并最小化一个目标函数,从而学习到某些参数。这是深度学习中最常见的方法。
  • 负采样方法将多分类问题变为二分类问题,避免了计算softmax函数,大大降低了计算量。
  • Glove算法结合了共现矩阵与优化目标函数。由Glove算法的到的词向量表现效果很好。
  • 评价词向量的好坏有Intrinsic Evaluation(内部评价)和Extrinsic Evaluation(外部评价)两种。内部评价可快速得知词向量的对于语法、语义等信息的捕获效果,但是不知道词向量在真实任务中的效果如何。外部评价可以知道词向量的应用效果如何,但计算太慢。

额外内容

One thought on “CS224N中文笔记(2)词向量的计算与评价
发表回复