语言模型与词向量

词向量

Posted by Wwt on June 9, 2017

​ Deeping Learning算法已经在图像和音频领域取得了惊人的成果,但是在NLP领域中尚未见到如此激动人心的结果。关于这个原因,引一条我比较赞同的微博。

@王威廉:Steve Renals算了一下icassp录取文章题目中包含deep learning的数量,发现有44篇,而naacl则有0篇。有一种说法是,语言(词、句子、篇章等)属于人类认知过程中产生的高层认知抽象实体,而语音和图像属于较为底层的原始输入信号,所以后两者更适合做deep learning来学习特征。

2013年3月4日 14:46

​ 第一句就先不用管了,毕竟今年的ACL已经被灌了好多Deep Learning的论文了。第二句我很认同,不过我也有信心以后一定有人能挖掘出语言这种高层次抽象中的本质。不论最后这种方法是不是Deep Learning,就目前而言,Deep Learning 在NLP领域中的研究已经将高深莫测的人类语言撕开了一层神秘的面纱。

​ 我觉得其中最有趣也是最基本的,就是“词向量”了。 ​ 将词用“词向量”的方式表示可谓是将Deep Learning算法引入NLP领域的一个核心技术。大多数宣称用了Deep Learning的论文,其中往往也用了词向量。

词向量是什么

​ 自然语言理解的问题要转化为机器学习的问题,第一步肯定要找一种方法把这些符号数学化。

​ NLP中最直观,也是到目前为止最常用的词表示方法是 One-hot Representation,这种方法把每个词表示为一个很长的向量,这个向量的维度是词的大小,其中绝大多数元素为0,只有一个维度的值为1,这个维度就代表了当前的词。

​ 举个例子,

​ “话筒”表示为[0 0 0 1 0 0 0 0 0 0···]

​ “麦克风”表示为[0 0 0 0 0 0 0 0 1 0···]

每个词都是茫茫0海中的一个1.

​ 这种One-hot Representation如果采用稀疏方式存储,会是非常简洁:也就是给每个词分配一个数字ID。比如刚才的例子中话筒记为3,麦克记为8(假设从0开始记)。如果要编程实现的话,用hash表给每个词分配一个编号就可以了。这么简洁的表示方法配合上最大熵、SVM、CRF等等算法已经很好的完成了NLP领域的各种主流任务。

​ 当然这种表示方法也存在一个重要问题就是“词汇鸿沟”现象:任意两个词之间都是孤立的。光从这两个向量中看不出两个词是否有关系,哪怕是话筒和麦克风这样的同义词也不能幸免于难。此外,这种表示方法还容易发生维数灾难,尤其在Deep Learning相关的一些应用中。

Distributed representation 词向量表示

​ 既然上述这种易于理解的One-hot Representation 词向量表示方法具有这样的重要缺陷,那么就需要一种既能表示词本身又可以考虑语义距离的词向量方法,这就是我们接下来介绍的Distrbuted Representation词向量表示方法.

​ Deep Learning中一般用到的词向量并不是刚才提到的One-hot Representation表示的那种很长很长的词向量,而是用Distrbuted Representation(不知道这个应该怎么翻译,因为还存在一种叫“Distributional Representation”的表示方法,又是另一个不同的概念)表示的一种低维实数向量。这种向量一般长成这个样子:

​ [0.792,-0.177,-0.107,0.109,-0.542,···]

维度以50维和100维比较常见。这种向量的表示不是唯一的,后文会提到目前计算这种向量的主流方法。

​ Distrbuted Representation最大的贡献就是让相关或者相似的词,在距离上更接近了。向量的距离可以用最传统的欧式距离来衡量,也可以用cos夹角来衡量。用这种方式表示的向量,“麦克风”和“话筒”的距离会远远小于“麦克风”和“天气”。可能理想情况下“麦克风”和“话筒”的表示应该是完全一样的,但是由于有些人会把英文名“迈克”也写成“麦克”,导致“麦克”一词带上了一些人名的语义,因此不会和“话筒”完全一致。

词向量的来历

​ Distributed representation最早是Hinton在1986年的论文《Learning distributed representations of concepts》中提出的。虽然这篇文章没说要将词做Distributed representation,但至少这种先进的思想在那个时候就在人们的心中埋下了火种,到2000年之后开始逐渐被人重视。

​ Distributed representation用来表示词,通常被称为“Word Representation”或“Word Embedding”,中文俗称“词向量”。真的只能叫“俗称”,算不上翻译。@南大周志华这篇微博中给了一个合适的翻译:词嵌入。Embedding一词的意义可以参考维基百科的相应页面(链接)。后文提到的所有“词向量”都是指用Distributed Representation表示的词向量。

​ 如果用传统的稀疏表示法表示词,在解决某些任务的时候(比如构建语言模型)会造成维数灾难 [Bengio 2003]。使用低维的词向量就没这样的问题。同时从实践上看,高维的特征如果套用Deep Learning,其复杂程度几乎是难以接受的,因此低维的向量在这里也饱受追捧。

词向量的训练

​ 要介绍词向量是怎么训练得到的。就不得不提到语言模型。到目前为止我了解到的所有训练方法都是在训练语言模型的同时,顺便得到词向量

​ 这也比较容易理解,要从一段无标注的自然文本中学习出一些东西,无非就是统计出词频、词的出现、词的搭配之类的信息。而要从自然文本中统计出并建立一个语言模型,无疑是要求最为精确的一个任务(也不排除以后有人创造出更好更有用的方法)。既然构建语言模型这一任务要求这么高,其中必然也需要对语言进行更精细的统计和分析,同时也会需要更好的模型,更大的数据来支撑。目前最好的词向量都来自于此,也就不难理解了。

​ 这里介绍的工作均为从大量未标注的普通文本数据中无监督地学习出词向量(语言模型本来就是基于这个想法而来的),可以猜测,如果用上了有了标注的语料,训练词向量的方法肯定会更多。不过视目前的语料规模,还是使用未标注语料的方法靠谱一些。

​ 词向量的训练最经典的有3个工作,C&W 2008、M&H 2008、Mikolov 2010。当然在说这些工作之前,不得不介绍一下这一系列中的Bengio的经典之作。

语言模型简介

​ 语言模型其实就是看一句话是不是正常人说出来的。这玩意很有用,比如机器翻译、语音识别得到若干候选之后,可以利用语言模型挑一个尽量靠谱的结果。在NLP的其他任务也都能用到。

​ 语言模型形式化的描述就是给定一个字符串,看它是自然语言的概率$P(ω_1,ω_2,···,ω_t)$。$ω_1$到$ω_t$依次表示这句话中的各个词。有个很简单的推论是:

​ 常用的语言模型都是在近似地求$P(ω_t \mid(ω_1,ω_2,···,ω_{t-1}))$.比如n-gram模型就是用$P(ω_t \mid(ω_{t-n+1},··,ω_{t-1}))$近似表示前者。N-gram 是一种基于统计语言模型的算法。它的基本思想是将文本里面的内容按照字节进行大小为N的窗口滑动操作,形成了长度为N字节片段序列。

​ 每一个字节片段称为gram,对所有gram的出现频度进行统计,并且按照事先设定好的阈值进行过滤,形成gram列表,也就是这个文本的向量特征空间,列表中的每一个gram就是一个特征向量维度。

  1. LSA矩阵分解模型

    采用线性代数中的奇异值分解方法,选取前几个比较大的奇异值所对应的特征向量将原矩阵映射到低维空间中,从而达到词矢量的目的。

  2. PLSA潜在语义分析概率模型

    从概率学的角度重新审视了矩阵分解模型,并得到一个从统计概率角度上推导出来的和LSA相当的词矢量模型。

  3. LDA文档生成模型

    按照文档生成的过程,使用贝叶斯估计统计学方法,将文档用多个主题来表示。LDA不只解决了同义词的问题,还解决了一次多义的问题。目前训练LDA模型的方法有原始论文中的基于EM和 差分贝叶斯方法以及后来出现的Gibbs Samplings 采样算法。

  4. word2Vector模型

    最近几年刚刚火起来的算法,通过神经网络机器学习算法来训练N-gram 语言模型,并在训练过程中求出word所对应的vector的方法。本文将详细阐述此方法的原理。

Bengio 的经典之作

​ 用神经网络训练语言模型的思想最早由百度IDL的徐伟提出。其论文《Can Artificial Neural Networks Learn Language Models》提出一种用神经网络构建二元语言模型(即$P(ω_t\mid ω_{t-1})$)的方法。文中的基本思路与后续的语言模型的差别已经不大了。

​ Bengio用了一个三层的神经网络来构建语言模型,同样也是n-gram模型。如下图

1

​ 图中最下方的$ω_{t-n+1},···,ω_{t-2},ω_{t-1}$就是前$n-1$个词。现在需要根据这已知的$n-1$个词预测一下个词$ω_t$。$C_(ω)$表示词ω所对应的词向量,整个模型中使用的是一套唯一的词向量,存在矩阵$C$(一个 $\mid V \mid ×m)$的矩阵中。其中$\mid V \mid $表示词表大小(语料中的总词数),m表示词向量的维度。ω到$C(ω)$的转化就是从矩阵中取出一行。

​ 网络的第一层(输出层)是将$C(w_{t-n+1}), …, C(w_{t-2}), C(w_{t-1})$这n-1个向量首尾相接拼起来,形成一个$m(n-1)$维的向量,下面记为$x$.

网络的第二层(隐藏层)就如同普通的神经网络,隐藏层的节点个数为h,为了将输入层输出的$m(n-1)​$维向量$x​$转换为隐藏层(维度为h)的输入,在输入层和隐藏层之间需要一个参数矩阵$H​$($H​$的规模为$h*m(n-1)​$),直接使用$d+Hx​$计算得到。d是一个偏置项。在此之后,使用$\tanh​$作为激活函数。隐藏层的输出就是$\tanh (Hx+d)​$。

​ 网络的第三层(输出层)一共有$\mid V \mid$个节点,每个节点$y_i$表示下一个词为$i$的未归一化$log$概率。最后使用softmax激活函数将输出值$y$归一化成概率。我们希望网络的输出可以对第n个单词的分布作出概率预测,所以输出的维度$\mid V \mid$,即整个词表的大小,最终,$y$的计算公式为:

​ 式子中的$U$(一个$\mid V \mid×h$的矩阵)是隐藏层到输出层的参数,整个模型的多数计算集中在$U$和隐藏层的矩阵乘法中。

​ 式子中还有一个矩阵$W$($\mid V \mid ×(n-1)m$),这个矩阵包含了从输入层到输出层的直连边。直连边就是从输入层直接到输出层的一个线性变换,好像也是神经网络中的一种常用技巧。如果不需要直连边的话,将$W$置为0就可以了。

Word2vec算法思想

​ 什么是word2vec?你可以理解为word2vec就是将词表征为实数值向量的一种高效的算法模型,其利用深度学习的思想,可以通过训练,把对文本内容的处理简化为K维向量空间中的向量运算,而向量空间上的相似度可以用来表示文本语义上的相似。

​ word2vec的输出向量可以被用来做很多NLP相关的工作,比如聚类、找同义词、词性分析等等。如果换个思路。把词当做特征,那么word2vec就可以把特征映射到k维向量空间,可以为文本数据寻求更加深层次的特征表示。

​ word2vec使用的词向量不是我们上述提到的one-hot Representation那种向量,而是Distributed representation 的词向量表示方式。其基本思想是通过训练将每个词映射成K实数向量(k一般为模型中的超参数),通过词之间的距离(比如cosine相似度、欧式距离等)来判断它们之间的语义相似度,其采用一个三层的神经网络,输出层-隐层-输出层。有个核心的技术是 根据词频用Huffman编码,使得所有词频相似的词隐藏层激活的内容基本一致,出现频率越高的词语,它们激活的隐藏层数目越少,这样有效的降低了计算的复杂度。而word2vec大受欢迎的一个原因是其高效性。Mikolov 在论文中指出,一个优化的单机版本一天可训练上千亿词。

​ 这个三层神经网络本身是 对语言模型进行建模,但也同时 获得一种单词在向量空间上的表示,而这个副作用才是Word2vec的真正目标。

​ 与潜在语义分析(Latent Semantic Index, LSI)、潜在狄立克雷分配(Latent Dirichlet Allocation,LDA)的经典过程相比,Word2vec利用了词的上下文,语义信息更加地丰富。

2

​ 取一个适当大小的窗口当做语境,输入层读入窗口内的词,将它们的向量(k维,初始随机)加和在一起,形成隐藏层K个节点。输出层是一个巨大的二叉树,叶节点代表语料里所有的词(语料含有V个独立的词,则二叉树有$\mid V \mid $个叶节点)。而这整颗二叉树的算法就是Huffman树。这样,对于叶节点的每一个词,就会有一个全局唯一的编码,形如“010011”,不妨记左子树为1,右子树为0.接下来,隐层的每一个节点都会跟二叉树的内节点有连边,于是,对于二叉树的每一个内节点都会有K条连边,每条边上都会有权值。

3

​ 对于语料中的某个词$w_t$,对应着二叉树的某个叶子节点,因此它必然有一个二进制编码,如“010011”。在训练阶段,当给定上下文,要预测后面的$w_t$的时候,我们就从二叉树的根节点开始遍历,这里的目标就是预测这个词的二进制编号的每一位。即对于给定的上下文,我们的目标是使得预测词的二进制编码概率最大。形象地说,我们希望在根节点,词向量和根节点相连经过logistic计算得到bit=1的概率尽量接近0,在第二层,希望其bit=1的概率尽量接近1,这么一直下去,我们把一路上计算得到的概率相乘,即得到目标词$w_t$在当前网络下的概率$P(w_t)$,那么对于当前这个sample的误差就是$1-P(w_t)$,于是就可以使用梯度下降训练法则这个网络得到所有的参数值了。显而易见,按照目标词的二进制编码计算得到的最后概率值就是归一化的。

Hierarchical SoftMax

​ Hierarchical Softmax用Huffman编码构造二叉树,其实借助了分类问题中,使用一连串二分类近似多分类的思想。例如我们是把所有的词都作为输出,那么“桔子”、“汽车”都是混在一起。给定$w_t$的上下文,先让模型判断$w_t$是不是名词,再判断是不是食物名,再判断是不是水果,再判断是不是“桔子”。

​ 但是在训练过程中,模型会赋予这些抽象的中间节点一个合适的向量, 这个向量代表了它对应的所有子节点。因为真正的单词公用了这些抽象节点的向量,所以Hierarchical Softmax方法和原始问题并不是等价的,但是这种近似并不会显著带来性能上的损失同时又使得模型的求解规模显著上升。

​ 没有使用这种二叉树,而是直接从隐层直接计算每一个输出的概率–即传统的softmax,就需要对$\mid V \mid$中的每一个词都算一遍,这个过程时间复杂度是$O(\mid V \mid)$的。而使用了二叉树(如Word2vec中的Huffman树),其时间复杂度就降到了$O(log2(\mid V \mid))$,速度大大地加快了。

  现在这些词向量已经捕捉到上下文的信息。我们可以利用基本代数公式来发现单词之间的关系(比如,“国王”-“男人”+“女人”=“王后”)。这些词向量可以代替词袋用来预测未知数据的情感状况。该模型的优点在于不仅考虑了语境信息还压缩了数据规模(通常情况下,词汇量规模大约在300个单词左右而不是之前 模型的100000个单词)。因为神经网络可以替我们提取出这些特征的信息,所以我们仅需要做很少的手动工作。但是由于文本的长度各异,我们可能需要利用所有词向量的平均值作为分类算法的输入值,从而对整个文本文档进行分类处理。

Negative Sampling

NS仅仅选择一小部分列向量进行更新,和HS相比,显得相对简单一点。

对于每条数据,首先我们将原始的$\mid V\mid$ 个词划分成正样本$w_o$和负样本$w_neg$,正样本也就是要预测的单词,剩下的就是负样本。负样本非常多,我们需要采样$K$个负样本,与正样本一起训练。从前我们需要对所有$\mid V\mid$个词进行softmax计算,现在对于我们只使用到了正样本和负样本,只针对这几个词进行计算,计算量可以大大减小。

负样本选取方式:

NS是一种概率采样的方式,可以根据词频进行随机抽样,我们倾向于选择词频较大的负样本,比如“的”,这种词语其实是对我们的目标单词没有很大的贡献的。

Word2vec则在词频基础上去了0.75次幂,减小词频之间差异过大所带来的的影响,使得词频较小的负样本也有机会被采到。

$weight(w)=\frac{count (w)^{0.75}}{\sum_ucount(w)^{0.75}}$

Loss函数定义为: $E==log\sigma(u^{‘}{wo}h)-\sum{w_j\in W_{neg}log\sigma(-u^{‘}_{wj}h)}$

极大化正样本出现的概率,同时极小化负样本出现的概率,以sigmoid来代替softmax,相当于进行二分类,判断这个样本到底是不是正样本。

那么对于$W^{‘}$,经过求导,$v^{‘}$更新公式为:

$\nu^{‘(new)}{w_j}=\nu^{‘(old)}{w_j}-\eta\big(\sigma(\nu^{‘T}_{w_j}h)-t_j\big)h$

也就是说,这里不需要更新所有$\nu^{‘}$向量,只需要更新部分$\nu’$向量,这里的$w_j$是正样本$w_o$和负样本$w_neg$的集合,只更新这些样本所对应的$\nu^{‘}$向量。

参考

Deep Learning in NLP (一)词向量和语言模型

Algorithm & NLP] 文本深度表示模型——word2vec&doc2vec词向量模型