我想去面试系列——Word2vec

2020-10-19

搜罗万象,找寻word2vecv背后的奥妙…

基本原理

N-gram

在NLP领域,如何计算一段文本序列在某种语言下出现的概率?对于一段文本序列$S=w_1, w_2, … , w_T$,统计语言模型将序列的联合概率转化为一系列条件概率的乘积:

由于其巨大的参数空间,这样一个原始的模型在实际中并没有什么用。我们更多的是采用其简化版本 —— N-gram模型,常见的如bi-gram模型(N=2)和tri-gram模型(N=3)。事实上,由于模型复杂度和预测精度的限制,我们很少会考虑N>3的模型。我们可以用最大似然法去求解N-gram模型的参数——等价于去统计每个N-gram的条件词频:

N-gram模型的缺点是,①无法处理更长的context(N>3)②没有考虑词与词之间内在的联系性。Ngram本质上是将词当做一个个孤立的原子单元,然后形式化表达为一个个one-hot向量。显然,one-hot向量的维度等于词典的大小。这在动辄上万甚至百万词典的实际应用中,面临着巨大的维度灾难问题(The Curse of Dimensionality)。于是连续的分布式向量表示(Distributed representation)就产生了。Distributed representation可以解决One-Hot编码存在的问题,它的思路是通过训练,将原来One-Hot编码的每个词都映射到一个较短的词向量上来,而这个较短的词向量的维度可以由我们自己在训练时根据任务需要来自己指定。

Word2vec

首先要说明的一点是,word2vec并不是一个深度模型,其背后只是一个浅层神经网络,并且是一个无监督学习模型。另外需要强调的一点是,word2vec是一个计算word vector的开源工具。当我们在说word2vec算法或模型的时候,其实指的是其背后用于计算word vector的CBoW模型和Skip-gram模型。很多人以为word2vec指的是一个算法或模型,这也是一种谬误。

Word2Vec 的训练模型本质上是只具有一个隐含层的神经元网络。

它的输入是采用One-Hot编码的词汇表向量,它的输出也是One-Hot编码的词汇表向量。使用所有的样本,训练这个神经元网络,等到收敛之后,从输入层到隐含层的那些权重,便是每一个词的采用Distributed Representation的词向量。这样我们就把原本维数为V的词向量变成了维数为N的词向量(N远小于V),并且词向量间保留了一定的相关关系。Word2Vec其实就是指的是网络中的权重矩阵W,因为输入的每个onehot乘以W只会有一列起作用。

Word2Vec的论文中提出了CBOW和Skip-gram两种模型,CBOW适合于数据集较小的情况,而Skip-Gram在大型语料中表现更好。下面分别进行介绍:

CBOW

从数学上看,CBoW(Continuous Bag-of-Words)模型等价于一个词袋模型的向量乘以一个Embedding矩阵,从而得到一个连续的embedding向量。这也是CBoW模型名称的由来。

  • 输入层:c个上下文单词向量$c_i \in R^V$,每个向量都是长度为V(词表大小)的one-hot向量,这里的cskip_window参数决定,c=2*skip_window,即在左右两侧分别选skip_window个上下文单词;边界条件:当中心词位于边缘时,窗口大小减小(word2vec没有做padding处理)
  • 隐藏层:所有onehot分别乘以共享的输入权重矩阵 $W_{V \times N}$,得到维数为N的向量$w_1,w_2,…,w_c$,这里的$N$自行定义为多少。将这些向量$w_1,w_2,…,w_c$加权求平均作为隐藏层向量$h$;
  • 输出层:$h$再乘一个权重矩阵$W’_{N \times V}$,再过一个激活函数(直接对词典里的V个词计算相似度并归一化,显然是一件极其耗时的impossible mission。为此,Mikolov引入了两种优化算法:层次Softmax和负采样),得到$y \in R^V$,该向量的每一维代表了相对应的单词的概率分布。
  • $y$中概率最大的元素所指示的单词为预测出的中间词(target word),与true label的onehot词向量做比较,误差越小越好。损失函数一般为交叉熵代价函数。训练完成后矩阵$W_{V \times N}$就是所需要的word embedding。

Skip-gram

Skip-gram是和CBOW反过来,从直观上理解,Skip-Gram是给定input word来预测上下文。它的做法是,将一个词所在的上下文中的词作为输出,而那个词本身作为输入,也就是说,给出一个词,希望预测可能出现的上下文的词。假如我们有一个句子“The dog barked at the mailman”。首先我们选句子中间的一个词作为我们的输入词,例如我们选取“dog”作为input word;有了input word以后,我们再定义一个叫做skip_window的参数,它代表着我们从当前input word的一侧(左边或右边)选取词的数量。

如果我们设置skip_window=2,那么我们最终获得窗口中的词(包括input word在内)就是[‘The’, ‘dog’,’barked’, ‘at’]。skip_window=2代表着选取左input word左侧2个词和右侧2个词进入我们的窗口,所以整个窗口大小span=2x2=4。另一个参数叫num_skips,它代表着我们从整个窗口中选取多少个不同的词作为我们的output word,当skip_window=2,num_skips=2时,我们将会得到两组 (input word, output word) 形式的训练数据,即 (‘dog’, ‘barked’),(‘dog’, ‘the’)。模型训练的时候就相当于有两个样本数据,每次前向传播softmax求的是当前窗口内的这个词和当前输入词在同一窗口的概率。模型的输出概率代表着到我们词典中每个词有多大可能性跟input word同时出现。对于上边的样例,“The dog barked at the mailman”,假设输入词是”barked”,上下文词有[the, dog, at, the],那么barked这一个batch中就有四个训练样例,[the,barked], [dog,barked], [barked,at], [barked,the],然后相当于一个barked要训练四次。但是在预测的时候,只会输入一个barked,然后最后对单词表softmax,得到可能作为barked上下文出现的词的概率分布,最终的topK个单词即为最后的上下文中窗口中预测出现的词。(这就出现了一个问题,比如上下文中有两个the,但是只能预测出来一个,所以这就凸显出来了词袋模型的弊端,忽略次数)

需要注意一点的是,实际上skip-gram的预测部分是没人用的,也就是说word2vec它这个模型的初衷就是为了训练而生的,而不是为了下游任务而生的。所以这也验证了之前经常看过的一句话“word2vec词向量其实是模型的一个副产品而已”,所以根本就不用纠结怎么拿word2vec进行预测,只需要掌握它训练的精髓就好了。

Hierarchical Softmax

Word2vec 本质上是一个语言模型,它的输出节点数是 V 个,对应了 V 个词语,本质上是一个多分类问题,但实际当中,词语的个数非常非常多,会给计算造成很大困难,所以需要用技巧来加速训练。下边两个技巧其实并不是word2vec的精髓,只需要简单了解下即可。

Hierarchical Softmax是为了降低原始模型中softmax对于大量词表计算时的计算复杂度,本质是把 N 分类问题变成 log(N)次二分类。它对原模型的改进主要有两点,第一点是从输入层到隐藏层的映射,没有采用原先的与矩阵W相乘然后相加求平均的方法,而是直接对所有输入的词向量求和。假设输入的词向量为(0,1,0,0)(0,0,0,1),那么隐藏层的向量为(0,1,0,1)。第二点改进是采用哈夫曼树(根据频率建模,出现频率高的越靠近根节点)来替换了原先的从隐藏层到输出层的矩阵W'。哈夫曼树的叶节点个数为词汇表的单词个数V,一个叶节点代表一个单词,而从根节点到该叶节点的路径确定了这个单词最终输出的词向量。这棵哈夫曼树除了根结点以外的所有非叶节点中都含有一个由参数θ确定的sigmoid函数,不同节点中的θ不一样。训练时隐藏层的向量与这个sigmoid函数进行运算,根据结果进行分类,若分类为负类则沿左子树向下传递,编码为0;若分类为正类则沿右子树向下传递,编码为1。

Negative Sampling

对于一些不常见、较生僻的词汇,哈夫曼树在计算它们的词向量时仍然需要做大量的运算。负采样是另一种用来提高Word2Vec效率的方法,它是基于这样的观察:训练一个神经网络意味着使用一个训练样本就要稍微调整一下神经网络中所有的权重,这样才能够确保预测训练样本更加精确,如果能设计一种方法每次只更新一部分权重,那么计算复杂度将大大降低。具体方法就是选取一些期望神经网络输出为0的神经元对应的单词进行训练,这样的话就可以通过较少数目的负样本来更新大部分0对应的权重。本质是预测总体类别的一个子集

Glove

Glove全称Global Vectors for Word Representation,是基于全局词频统计的词表征工具。他结合了LSA算法可以有效收集语料库全局统计信息的优点(但是无法捕捉上下文信息),还结合了word2vec这种滑动窗口机制的可以通过局部上下文特征表达更丰富语义的特点。它的核心思想是,对于任意的词$i$和$j$,假如有第三个词$k$,如果词$k$与$i$相比于词$k$与$j$有更深的关联,那么我们可以得到$k$在$i$的窗口词中出现的概率要大于$k$在$j$的窗口词中出现的概率,且数值较大。如果$k$和$i$与$j$的关系都不大,那么前述的概率应该是约等于的。Glove模型表示的语义词向量相似度尽可能接近在统计共现矩阵中统计相似度,并且不同共现的词有不同权值。可形式化为$F(w_i,w_j,\tilde{w}_k) = \frac{P_{ik}}{P_{jk}}$,式子左边是向量相似度函数,右边是全局的共现统计值。具体实现分为三步,可参考这篇文章

  • 根据语料库(corpus)构建一个共现矩阵$X$,矩阵中的每一个元素$X_{ij}$代表单词$i$和上下文单词$j$在特定大小的上下文窗口内共同出现的次数。一般而言,这个次数的最小单位是1,但是GloVe不这么认为:它根据两个单词在上下文窗口的距离$d$,提出了一个衰减函数:$decay=1/d$用于计算权重,也就是说距离越远的两个单词所占总计数(total count)的权重越小

  • 构建词向量(Word Vector)和共现矩阵$X$之间的近似关系,损失函数描述如下:

    其中,$w_{i}^{T}$和$\tilde{w_{j}}$是我们最终要求解的词向量,$b_i$和$\tilde{b_j}$分别是两个词向量的bias term,$f(X_{ij})$的作用主要是描述相似度的程度,比如共现次数多的单词权重要大于那些共现次数少的(非递减),权重不能太过大到了一定程度就应该停止增加,没有共现过的词应该$f(0)=0$,所以采用了一个分段函数

  • Glove其实是有监督的,这个label就是$\log(X_{ij})$,$\log(X_{ij})$是通过共现矩阵的统计数据可以直接计算出来的。

细节 & 面试题搜集

后续更新…

参考文献

  1. Word2Vec:https://zhuanlan.zhihu.com/p/140705629
  2. Word2Vec详解:https://zhuanlan.zhihu.com/p/61635013
  3. Glove详解:http://www.fanyeong.com/2018/02/19/glove-in-detail/
  4. Glove模型的理解:https://zhuanlan.zhihu.com/p/80335195
  5. 【深度学习】Word2Vec:https://www.hrwhisper.me/deep-learning-word2vec/
  6. Embedding之word2vec:https://zhuanlan.zhihu.com/p/59396559
  7. 关于word2vec的一些相关问题整理 & 思考:https://blog.csdn.net/liujian20150808/article/details/105215414
  8. 搞懂NLP中的词向量,看这一篇就足够:https://www.infoq.cn/article/PFvZxgGDm27453BbS24W
  9. GloVe与word2vec的区别:https://zhuanlan.zhihu.com/p/31023929
  10. 基于TensorFlow实现Skip-Gram模型:https://www.leiphone.com/news/201706/QprrvzsrZCl4S2lw.html

本文来源:「想飞的小菜鸡」的个人网站 vodkazy.cn

版权声明:本文为「想飞的小菜鸡」的原创文章,采用 BY-NC-SA 许可协议,转载请附上原文出处链接及本声明。

原文链接:https://vodkazy.cn/2020/10/19/我想去面试系列——Word2vec

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者,更多文章请访问想飞的小菜鸡