转:词向量word2vector那些事儿
基本知识
BOW,word2vector,glove
cbow,skip-gram
Hierarchical Softmax,Negative Sampling
https://www.cnblogs.com/wkang/p/9611257.html
https://www.cnblogs.com/wkang/p/9978364.html
word2vec原理(一) CBOW与Skip-Gram模型基础
word2vec原理(二) 基于Hierarchical Softmax的模型
word2vec原理(三) 基于Negative Sampling的模型
0001,
word2vec原理(一) CBOW与Skip-Gram模型基础
word2vec原理(一) CBOW与Skip-Gram模型基础
word2vec原理(二) 基于Hierarchical Softmax的模型
word2vec原理(三) 基于Negative Sampling的模型
word2vec是google在2013年推出的一个NLP工具,它的特点是将所有的词向量化,这样词与词之间就可以定量的去度量他们之间的关系,挖掘词之间的联系。虽然源码是开源的,但是谷歌的代码库国内无法访问,因此本文的讲解word2vec原理以Github上的word2vec代码为准。本文关注于word2vec的基础知识。
1. 词向量基础
用词向量来表示词并不是word2vec的首创,在很久之前就出现了。最早的词向量是很冗长的,它使用是词向量维度大小为整个词汇表的大小,对于每个具体的词汇表中的词,将对应的位置置为1。比如我们有下面的5个词组成的词汇表,词"Queen"的序号为2, 那么它的词向量就是(0,1,0,0,0)
。同样的道理,词"Woman"的词向量就是(0,0,0,1,0)
。这种词向量的编码方式我们一般叫做1-of-N representation或者one hot representation.
One hot representation用来表示词向量非常简单,但是却有很多问题。最大的问题是我们的词汇表一般都非常大,比如达到百万级别,这样每个词都用百万维的向量来表示简直是内存的灾难。这样的向量其实除了一个位置是1,其余的位置全部都是0,表达的效率不高,能不能把词向量的维度变小呢?
Distributed representation可以解决One hot representation的问题,它的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,进而可以用普通的统计学的方法来研究词与词之间的关系。这个较短的词向量维度是多大呢?这个一般需要我们在训练时自己来指定。
比如下图我们将词汇表里的词用"Royalty","Masculinity", "Femininity"和"Age"4个维度来表示,King这个词对应的词向量可能是(0.99,0.99,0.05,0.7)
。当然在实际情况中,我们并不能对词向量的每个维度做一个很好的解释。
有了用Distributed Representation表示的较短的词向量,我们就可以较容易的分析词之间的关系了,比如我们将词的维度降维到2维,有一个有趣的研究表明,用下图的词向量表示我们的词时,我们可以发现:
可见我们只要得到了词汇表里所有词对应的词向量,那么我们就可以做很多有趣的事情了。不过,怎么训练得到合适的词向量呢?一个很常见的方法是使用神经网络语言模型。
2. CBOW与Skip-Gram用于神经网络语言模型
在word2vec出现之前,已经有用神经网络DNN来用训练词向量进而处理词与词之间的关系了。采用的方法一般是一个三层的神经网络结构(当然也可以多层),分为输入层,隐藏层和输出层(softmax层)。
这个模型是如何定义数据的输入和输出呢?一般分为CBOW(Continuous Bag-of-Words 与Skip-Gram两种模型。
CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。比如下面这段话,我们的上下文大小取值为4,特定的这个词是"Learning",也就是我们需要的输出词向量,上下文对应的词有8个,前后各4个,这8个词是我们模型的输入。由于CBOW使用的是词袋模型,因此这8个词都是平等的,也就是不考虑他们和我们关注的词之间的距离大小,只要在我们上下文之内即可。
这样我们这个CBOW的例子里,我们的输入是8个词向量,输出是所有词的softmax概率(训练的目标是期望训练样本特定词对应的softmax概率最大),对应的CBOW神经网络模型输入层有8个神经元,输出层有词汇表大小个神经元。隐藏层的神经元个数我们可以自己指定。通过DNN的反向传播算法,我们可以求出DNN模型的参数,同时得到所有的词对应的词向量。这样当我们有新的需求,要求出某8个词对应的最可能的输出中心词时,我们可以通过一次DNN前向传播算法并通过softmax激活函数找到概率最大的词对应的神经元即可。
Skip-Gram模型和CBOW的思路是反着来的,即输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。还是上面的例子,我们的上下文大小取值为4, 特定的这个词"Learning"是我们的输入,而这8个上下文词是我们的输出。
这样我们这个Skip-Gram的例子里,我们的输入是特定词, 输出是softmax概率排前8的8个词,对应的Skip-Gram神经网络模型输入层有1个神经元,输出层有词汇表大小个神经元。隐藏层的神经元个数我们可以自己指定。通过DNN的反向传播算法,我们可以求出DNN模型的参数,同时得到所有的词对应的词向量。这样当我们有新的需求,要求出某1个词对应的最可能的8个上下文词时,我们可以通过一次DNN前向传播算法得到概率大小排前8的softmax概率对应的神经元所对应的词即可。
以上就是神经网络语言模型中如何用CBOW与Skip-Gram来训练模型与得到词向量的大概过程。但是这和word2vec中用CBOW与Skip-Gram来训练模型与得到词向量的过程有很多的不同。
word2vec为什么 不用现成的DNN模型,要继续优化出新方法呢?最主要的问题是DNN模型的这个处理过程非常耗时。我们的词汇表一般在百万级别以上,这意味着我们DNN的输出层需要进行softmax计算各个词的输出概率的的计算量很大。有没有简化一点点的方法呢?
3. word2vec基础之霍夫曼树
word2vec也使用了CBOW与Skip-Gram来训练模型与得到词向量,但是并没有使用传统的DNN模型。最先优化使用的数据结构是用霍夫曼树来代替隐藏层和输出层的神经元,霍夫曼树的叶子节点起到输出层神经元的作用,叶子节点的个数即为词汇表的小大。 而内部节点则起到隐藏层神经元的作用。
具体如何用霍夫曼树来进行CBOW和Skip-Gram的训练我们在下一节讲,这里我们先复习下霍夫曼树。
霍夫曼树的建立其实并不难,过程如下:
输入:权值为(w1,w2,...wn)
的n
个节点
输出:对应的霍夫曼树
1)将(w1,w2,...wn)
看做是有n
棵树的森林,每个树仅有一个节点。
2)在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树分布作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和。
3) 将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林。
4)重复步骤2)和3)直到森林里只有一棵树为止。
下面我们用一个具体的例子来说明霍夫曼树建立的过程,我们有(a,b,c,d,e,f)共6个节点,节点的权值分布是(20,4,8,6,16,3)。
首先是最小的b和f合并,得到的新树根节点权重是7.此时森林里5棵树,根节点权重分别是20,8,6,16,7。此时根节点权重最小的6,7合并,得到新子树,依次类推,最终得到下面的霍夫曼树。
那么霍夫曼树有什么好处呢?一般得到霍夫曼树后我们会对叶子节点进行霍夫曼编码,由于权重高的叶子节点越靠近根节点,而权重低的叶子节点会远离根节点,这样我们的高权重节点编码值较短,而低权重值编码值较长。这保证的树的带权路径最短,也符合我们的信息论,即我们希望越常用的词拥有更短的编码。如何编码呢?一般对于一个霍夫曼树的节点(根节点除外),可以约定左子树编码为0,右子树编码为1.如上图,则可以得到c的编码是00。
在word2vec中,约定编码方式和上面的例子相反,即约定左子树编码为1,右子树编码为0,同时约定左子树的权重不小于右子树的权重。
我们在下一节的Hierarchical Softmax中再继续讲使用霍夫曼树和DNN语言模型相比的好处以及如何训练CBOW&Skip-Gram模型。
0002,
word2vec原理(二) 基于Hierarchical Softmax的模型
word2vec原理(一) CBOW与Skip-Gram模型基础
word2vec原理(二) 基于Hierarchical Softmax的模型
word2vec原理(三) 基于Negative Sampling的模型
在word2vec原理(一) CBOW与Skip-Gram模型基础中,我们讲到了使用神经网络的方法来得到词向量语言模型的原理和一些问题,现在我们开始关注word2vec的语言模型如何改进传统的神经网络的方法。由于word2vec有两种改进方法,一种是基于Hierarchical Softmax的,另一种是基于Negative Sampling的。本文关注于基于Hierarchical Softmax的改进方法,在下一篇讨论基于Negative Sampling的改进方法。
1. 基于Hierarchical Softmax的模型概述
我们先回顾下传统的神经网络词向量语言模型,里面一般有三层,输入层(词向量),隐藏层和输出层(softmax层)。里面最大的问题在于从隐藏层到输出的softmax层的计算量很大,因为要计算所有词的softmax概率,再去找概率最大的值。这个模型如下图所示。其中V
是词汇表的大小,
word2vec对这个模型做了改进,首先,对于从输入层到隐藏层的映射,没有采取神经网络的线性变换加激活函数的方法,而是采用简单的对所有输入词向量求和并取平均的方法。比如输入的是三个4维词向量:(1,2,3,4),(9,6,11,8),(5,10,7,12)
,那么我们word2vec映射后的词向量就是(5,6,7,8)
。由于这里是从多个词向量变成了一个词向量。
第二个改进就是从隐藏层到输出的softmax层这里的计算量个改进。为了避免要计算所有词的softmax概率,word2vec采样了霍夫曼树来代替从隐藏层到输出softmax层的映射。我们在上一节已经介绍了霍夫曼树的原理。如何映射呢?这里就是理解word2vec的关键所在了。
由于我们把之前所有都要计算的从输出softmax层的概率计算变成了一颗二叉霍夫曼树,那么我们的softmax概率计算只需要沿着树形结构进行就可以了。如下图所示,我们可以沿着霍夫曼树从根节点一直走到我们的叶子节点的词w2
。
和之前的神经网络语言模型相比,我们的霍夫曼树的所有内部节点就类似之前神经网络隐藏层的神经元,其中,根节点的词向量对应我们的投影后的词向量,而所有叶子节点就类似于之前神经网络softmax输出层的神经元,叶子节点的个数就是词汇表的大小。在霍夫曼树中,隐藏层到输出层的softmax映射不是一下子完成的,而是沿着霍夫曼树一步步完成的,因此这种softmax取名为"Hierarchical Softmax"。
如何“沿着霍夫曼树一步步完成”呢?在word2vec中,我们采用了二元逻辑回归的方法,即规定沿着左子树走,那么就是负类(霍夫曼树编码1),沿着右子树走,那么就是正类(霍夫曼树编码0)。判别正类和负类的方法是使用sigmoid函数,即:
其中xw
是当前内部节点的词向量,而θ
则是我们需要从训练样本求出的逻辑回归的模型参数。
使用霍夫曼树有什么好处呢?首先,由于是二叉树,之前计算量为V
,现在变成了log
。第二,由于使用霍夫曼树是高频的词靠近树根,这样高频词需要更少的时间会被找到,这符合我们的贪心优化思想。
容易理解,被划分为左子树而成为负类的概率为P
。在某一个内部节点,要判断是沿左子树还是右子树走的标准就是看P谁的概率值大。而控制P谁的概率值大的因素一个是当前节点的词向量,另一个是当前节点的模型参数θ
。
对于上图中的w2
,如果它是一个训练样本的输出,那么我们期望对于里面的隐藏节点n(w2,1)的P概率大,n(w2,2)的P概率大,n(w2,3)的P
概率大。
回到基于Hierarchical Softmax的word2vec本身,我们的目标就是找到合适的所有节点的词向量和所有内部节点θ
, 使训练样本达到最大似然。那么如何达到最大似然呢?
2. 基于Hierarchical Softmax的模型梯度计算
我们使用最大似然法来寻找所有节点的词向量和所有内部节点θ
。先拿上面的w2例子来看,我们期望最大化下面的似然函数:
对于所有的训练样本,我们期望最大化所有样本的似然函数乘积。
为了便于我们后面一般化的描述,我们定义输入的词为w
,其从输入层词向量求和平均后的霍夫曼树根节点词向量为xw, 从根节点到w所在的叶子节点,包含的节点总数为lw, w在霍夫曼树中从根节点开始,经过的第i个节点表示为pwi,对应的霍夫曼编码为d,其中i=2,3,...lw。而该节点对应的模型参数表示为θwi, 其中i=1,2,...lw−1,没有i=lw
是因为模型参数仅仅针对于霍夫曼树的内部节点。
定义w
经过的霍夫曼树某一个节点j的逻辑回归概率为P
,其表达式为:
那么对于某一个目标输出词w
,其最大似然为:
在word2vec中,由于使用的是随机梯度上升法,所以并没有把所有样本的似然乘起来得到真正的训练集最大似然,仅仅每次只用一个样本更新梯度,这样做的目的是减少梯度计算量。这样我们可以得到w
的对数似然函数L
如下:
要得到模型中w
词向量和内部节点的模型参数θ, 我们使用梯度上升法即可。首先我们求模型参数θwj−1
的梯度:
如果大家看过之前写的逻辑回归原理小结,会发现这里的梯度推导过程基本类似。
同样的方法,可以求出xw
的梯度表达式如下:
有了梯度表达式,我们就可以用梯度上升法进行迭代来一步步的求解我们需要的所有的θwj−1
和xw
。
3. 基于Hierarchical Softmax的CBOW模型
由于word2vec有两种模型:CBOW和Skip-Gram,我们先看看基于CBOW模型时, Hierarchical Softmax如何使用。
首先我们要定义词向量的维度大小M
,以及CBOW的上下文大小2c,这样我们对于训练样本中的每一个词,其前面的c个词和后面的c
个词作为了CBOW模型的输入,该词本身作为样本的输出,期望softmax概率最大。
在做CBOW模型前,我们需要先将词汇表建立成一颗霍夫曼树。
对于从输入层到隐藏层(投影层),这一步比较简单,就是对w
周围的2c个词向量求和取平均即可,即:
第二步,通过梯度上升法来更新我们的θwj−1
和xw,注意这里的xw是由2c个词向量相加而成,我们做梯度更新完毕后会用梯度项直接更新原始的各个xi(i=1,2,,,,2c)
,即:
其中η
为梯度上升法的步长。
这里总结下基于Hierarchical Softmax的CBOW模型算法流程,梯度迭代使用了随机梯度上升法:
输入:基于CBOW的语料训练样本,词向量的维度大小M
,CBOW的上下文大小2c,步长η
输出:霍夫曼树的内部节点模型参数θ
,所有的词向量w
1. 基于语料训练样本建立霍夫曼树。
2. 随机初始化所有的模型参数θ
,所有的词向量w
3. 进行梯度上升迭代过程,对于训练集中的每一个样本(context(w),w)
做如下处理:
a) e=0, 计算xw=12c
b) for j = 2 to lw
, 计算:
c) 对于context(w)
中的每一个词向量xi(共2c个)进行更新:
d) 如果梯度收敛,则结束梯度迭代,否则回到步骤3继续迭代。
4. 基于Hierarchical Softmax的Skip-Gram模型
现在我们先看看基于Skip-Gram模型时, Hierarchical Softmax如何使用。此时输入的只有一个词w
,输出的为2c个词向量context(w)
。
我们对于训练样本中的每一个词,该词本身作为样本的输入, 其前面的c
个词和后面的c
个词作为了Skip-Gram模型的输出,,期望这些词的softmax概率比其他的词大。
Skip-Gram模型和CBOW模型其实是反过来的,在上一篇已经讲过。
在做CBOW模型前,我们需要先将词汇表建立成一颗霍夫曼树。
对于从输入层到隐藏层(投影层),这一步比CBOW简单,由于只有一个词,所以,即xw
就是词w
对应的词向量。
第二步,通过梯度上升法来更新我们的θwj−1
和xw,注意这里的xw周围有2c个词向量,此时如果我们期望P最大。此时我们注意到由于上下文是相互的,在期望P最大化的同时,反过来我们也期望P最大。那么是使用P好还是P好呢,word2vec使用了后者,这样做的好处就是在一个迭代窗口内,我们不是只更新xw一个词,而是xi,i=1,2...2c共2c个词。这样整体的迭代会更加的均衡。因为这个原因,Skip-Gram模型并没有和CBOW模型一样对输入进行迭代更新,而是对2c
个输出进行迭代更新。
这里总结下基于Hierarchical Softmax的Skip-Gram模型算法流程,梯度迭代使用了随机梯度上升法:
输入:基于Skip-Gram的语料训练样本,词向量的维度大小M
,Skip-Gram的上下文大小2c,步长η
输出:霍夫曼树的内部节点模型参数θ
,所有的词向量w
1. 基于语料训练样本建立霍夫曼树。
2. 随机初始化所有的模型参数θ
,所有的词向量w
,
3. 进行梯度上升迭代过程,对于训练集中的每一个样本(w,context(w))
做如下处理:
a) for i =1 to 2c:
i) e=0
ii)for j = 2 to lw
, 计算:
iii)
b)如果梯度收敛,则结束梯度迭代,算法结束,否则回到步骤a继续迭代。
5. Hierarchical Softmax的模型源码和算法的对应
这里给出上面算法和word2vec源码中的变量对应关系。
在源代码中,基于Hierarchical Softmax的CBOW模型算法在435-463行,基于Hierarchical Softmax的Skip-Gram的模型算法在495-519行。大家可以对着源代码再深入研究下算法。
在源代码中,neule对应我们上面的e
, syn0对应我们的xw, syn1对应我们的θij−1, layer1_size对应词向量的维度,window对应我们的c
。
另外,vocab[word].code[d]指的是,当前单词word的,第d个编码,编码不含Root结点。vocab[word].point[d]指的是,当前单词word,第d个编码下,前置的结点。
以上就是基于Hierarchical Softmax的word2vec模型,下一篇我们讨论基于Negative Sampling的word2vec模型。
0003,
word2vec原理(三) 基于Negative Sampling的模型
word2vec原理(一) CBOW与Skip-Gram模型基础
word2vec原理(二) 基于Hierarchical Softmax的模型
word2vec原理(三) 基于Negative Sampling的模型
在上一篇中我们讲到了基于Hierarchical Softmax的word2vec模型,本文我们我们再来看看另一种求解word2vec模型的方法:Negative Sampling。
1. Hierarchical Softmax的缺点与改进
在讲基于Negative Sampling的word2vec模型前,我们先看看Hierarchical Softmax的的缺点。的确,使用霍夫曼树来代替传统的神经网络,可以提高模型训练的效率。但是如果我们的训练样本里的中心词w
是一个很生僻的词,那么就得在霍夫曼树中辛苦的向下走很久了。能不能不用搞这么复杂的一颗霍夫曼树,将模型变的更加简单呢?
Negative Sampling就是这么一种求解word2vec模型的方法,它摒弃了霍夫曼树,采用了Negative Sampling(负采样)的方法来求解,下面我们就来看看Negative Sampling的求解思路。
2. 基于Negative Sampling的模型概述
既然名字叫Negative Sampling(负采样),那么肯定使用了采样的方法。采样的方法有很多种,比如之前讲到的大名鼎鼎的MCMC。我们这里的Negative Sampling采样方法并没有MCMC那么复杂。
比如我们有一个训练样本,中心词是w
,它周围上下文共有2c个词,记为context(w)。由于这个中心词w,的确和context(w)相关存在,因此它是一个真实的正例。通过Negative Sampling采样,我们得到neg个和w不同的中心词wi,i=1,2,..neg,这样context(w)和wi就组成了neg个并不真实存在的负例。利用这一个正例和neg个负例,我们进行二元逻辑回归,得到负采样对应每个词wi对应的模型参数θi
,和每个词的词向量。
从上面的描述可以看出,Negative Sampling由于没有采用霍夫曼树,每次只是通过采样neg个不同的中心词做负例,就可以训练模型,因此整个过程要比Hierarchical Softmax简单。
不过有两个问题还需要弄明白:1)如果通过一个正例和neg个负例进行二元逻辑回归呢? 2) 如何进行负采样呢?
我们在第三节讨论问题1,在第四节讨论问题2.
3. 基于Negative Sampling的模型梯度计算
Negative Sampling也是采用了二元逻辑回归来求解模型参数,通过负采样,我们得到了neg个负例(context(w),wi)i=1,2,..neg
。为了统一描述,我们将正例定义为w0
。
在逻辑回归中,我们的正例应该期望满足:
我们的负例期望满足:
我们期望可以最大化下式:
利用逻辑回归和上一节的知识,我们容易写出此时模型的似然函数为:
此时对应的对数似然函数为:
和Hierarchical Softmax类似,我们采用随机梯度上升法,仅仅每次只用一个样本更新梯度,来进行迭代更新得到我们需要的xwi,θwi,i=0,1,..neg
, 这里我们需要求出xw0,θwi,i=0,1,..neg
的梯度。
首先我们计算θwi
的梯度:
同样的方法,我们可以求出xw0
的梯度如下:
有了梯度表达式,我们就可以用梯度上升法进行迭代来一步步的求解我们需要的xw0,θwi,i=0,1,..neg
。
4. Negative Sampling负采样方法
现在我们来看看如何进行负采样,得到neg个负例。word2vec采样的方法并不复杂,如果词汇表的大小为V
,那么我们就将一段长度为1的线段分成V份,每份对应词汇表中的一个词。当然每个词对应的线段长度是不一样的,高频词对应的线段长,低频词对应的线段短。每个词w的线段长度由下式决定:
在word2vec中,分子和分母都取了3/4次幂如下:
在采样前,我们将这段长度为1的线段划分成M
等份,这里M,这样可以保证每个词对应的线段都会划分成对应的小块。而M份中的每一份都会落在某一个词对应的线段上。在采样的时候,我们只需要从M个位置中采样出neg
个位置就行,此时采样到的每一个位置对应到的线段所属的词就是我们的负例词。
在word2vec中,M
取值默认为108
。
5. 基于Negative Sampling的CBOW模型
有了上面Negative Sampling负采样的方法和逻辑回归求解模型参数的方法,我们就可以总结出基于Negative Sampling的CBOW模型算法流程了。梯度迭代过程使用了随机梯度上升法:
输入:基于CBOW的语料训练样本,词向量的维度大小M
,CBOW的上下文大小2c,步长η
, 负采样的个数neg
输出:词汇表每个词对应的模型参数θ
,所有的词向量xw
1. 随机初始化所有的模型参数θ
,所有的词向量w
2. 对于每个训练样本(context(w0),w0)
,负采样出neg个负例中心词wi,i=1,2,...neg
3. 进行梯度上升迭代过程,对于训练集中的每一个样本(context(w0),w0,w1,...wneg
做如下处理:
a) e=0, 计算xw0=12c
b) for i= 0 to neg, 计算:
c) 对于context(w)
中的每一个词向量xk(共2c个)进行更新:
d) 如果梯度收敛,则结束梯度迭代,否则回到步骤3继续迭代。
6. 基于Negative Sampling的Skip-Gram模型
有了上一节CBOW的基础和上一篇基于Hierarchical Softmax的Skip-Gram模型基础,我们也可以总结出基于Negative Sampling的Skip-Gram模型算法流程了。梯度迭代过程使用了随机梯度上升法:
输入:基于Skip-Gram的语料训练样本,词向量的维度大小M
,Skip-Gram的上下文大小2c,步长η
, , 负采样的个数neg。
输出:词汇表每个词对应的模型参数θ
,所有的词向量xw
1. 随机初始化所有的模型参数θ
,所有的词向量w
2. 对于每个训练样本(context(w0),w0)
,负采样出neg个负例中心词wi,i=1,2,...neg
3. 进行梯度上升迭代过程,对于训练集中的每一个样本(context(w0),w0,w1,...wneg
做如下处理:
a) for i =1 to 2c:
i) e=0
ii) for j= 0 to neg, 计算:
iii) 词向量更新:
b)如果梯度收敛,则结束梯度迭代,算法结束,否则回到步骤a继续迭代。
7. Negative Sampling的模型源码和算法的对应
这里给出上面算法和word2vec源码中的变量对应关系。
在源代码中,基于Negative Sampling的CBOW模型算法在464-494行,基于Hierarchical Softmax的Skip-Gram的模型算法在520-542行。大家可以对着源代码再深入研究下算法。
在源代码中,neule对应我们上面的e
, syn0对应我们的xw, syn1neg对应我们的θwi, layer1_size对应词向量的维度,window对应我们的c。negative对应我们的neg, table_size对应我们负采样中的划分数M
。
另外,vocab[word].code[d]指的是,当前单词word的,第d个编码,编码不含Root结点。vocab[word].point[d]指的是,当前单词word,第d个编码下,前置的结点。这些和基于Hierarchical Softmax的是一样的。
以上就是基于Negative Sampling的word2vec模型,希望可以帮到大家,后面会讲解用gensim的python版word2vec来使用word2vec解决实际问题。
(欢迎转载,转载请注明出处。欢迎沟通交流: liujianping-ok@163.com)
0004,
词嵌入的那些事儿(一)
1. 词向量介绍
在讨论词嵌入之前,先要理解词向量的表达形式,注意,这里的词向量不是指Word2Vec。关于词向量的表达,现阶段采用的主要有One hot representation和Distributed representation两种表现形式。
1.1 One hot representation
顾名思义,采用独热编码的方式对每个词进行表示。
例如,一段描述“杭州和上海今天有雨”,通过分词工具可以把这段描述分为[‘杭州’,‘和’,‘上海’,今天’,‘有’,‘雨’],因此词表的长度为6,那么‘杭州’、‘上海’、'今天'的One hot representation分别为[1 0 0 0 0 0],[0 0 1 0 0 0],[0 0 0 1 0 0]。
可以看到,One hot representation编码的每个词都是一个维度,元素非0即1,且词与词之间彼此相互独立。
1.2 Distributed representation
Distributed representation在One hot representation的基础上考虑到词与词之间的联系,例如词义、词性等信息。每一个维度元素不再是0或1,而是连续的实数,表示不同的程度。Distributed representation 又包含了以下三种处理方式:
- 基于矩阵的分布表示。,矩阵中的一行,就成为了对应词的表示,这种表示描述了该词的上下文的分布。由于分布假说认为上下文相似的词,其语义也相似,因此在这种表示下,两个词的语义相似度可以直接转化为两个向量的空间距离。
- 基于聚类的分布表示。
- 基于神经网络的分布表示。
而我们现在常说的Distributed representation主要是基于神经网络的分布式表示的。例如‘杭州’、‘上海’的Distributed representation分别为[0.3 1.2 0.8 0.7] 和 [0.5 1.2 0.6 0.8 ] 。
所以对于词嵌入,我们可以理解为是对词的一种分布式表达方式,并且是从高维稀疏向量映射到了相对低维的实数向量上。
2. 为什么使用词嵌入
词嵌入,往往和Distributed representation联系在一起。这里主要从计算效率、词关系和数量这三点说明。
- 计算效率。采用One hot representation的每个词的向量长度是由词汇表的数量决定,如果词汇表数量很大,那么每个词的长度会很长,同时,由于向量元素只有一个元素为1,其余元素为0,所以,每个词的向量表达也会非常稀疏。而对于海量的词语来讲,计算效率是需要考虑的。
- 词关系。和One hot representation相比,Distributed representation能够表达词与词之间的关系。
- 数量。对于把词语作为模型输入的任务,对于相似的词语,可以通过较少样本完成目标任务的训练,而这是One hot representation所无法企及的优势。
3. Language Models
由于词嵌入目的是为了能更好地对NLP的输入做预处理。所以在对词嵌入技术作进一步讨论之前,有必要对语言模型的发展做一些介绍。
3.1 Bag of words model
Bag of words model又称为词袋模型,顾名思义,一段文本可以用一个装着这些词的袋子来表示。词袋模型通常将单词和句子表示为数字向量的形式,其中向量元素为句子中此单词在词袋表出现的次数。然后将数字向量输入分类器(例如Naive Bayes),进而对输出进行预测。这种表示方式不考虑文法以及词的顺序。
例如以下两个句子:
- John likes to watch movies. Mary likes movies too.
- John also likes to watch football games.
基于以上两个句子,可以建构词袋表:[ "John", "likes", "to", "watch", "movies", "also", "football", "games", "Mary", "too" ]
由于词袋表的长度为10,所以每个句子的数字向量表示长度也为10。下面是每个句子的向量表示形式:
- [1, 2, 1, 1, 2, 0, 0, 0, 1, 1]
- [1, 1, 1, 1, 0, 1, 1, 1, 0, 0]
Bag of words model的优缺点很明显:优点是基于频率统计方法,易于理解。缺点是它的假设(单词之间完全独立)过于强大,无法建立准确的模型。
3.2 N-Gram model
N-gram model的提出旨在减少传统Bag of words model的一些强假设。
语言模型试图预测在给定前t个单词的前提下观察t第 + 1个单词w t + 1的概率:
利用概率的链式法则,我们可以计算出观察整个句子的概率:
可以发现,估计这些概率可能是困难的。因此可以用最大似然估计对每个概率进行计算:
然而,即使使用最大似然估计方法进行计算,仍然非常困难:我们通常无法从语料库中观察到足够多的数据,并且计算长度仍然很长。因此采用了马尔可夫链的思想。
马尔可夫链规定:系统下一时刻的状态仅由当前状态决定,不依赖于以往的任何状态。即第t + 1个单词的发生概率表示为:
因此,一个句子的概率可以表示为:
同样地,马尔可夫假设可以推广到:系统下一时刻的状态仅由当前0个、1个、2个...n个状态决定。这就是N-gram model的N的意思:对下一时刻的状态设置当前状态的个数。下面分别给出了unigram(一元模型)和bigram(二元模型)的第t + 1个单词的发生概率:
可以发现,N-Gram model 在Bag of words model的基础上,通过采用马尔科夫链的思想,减少了概率计算的复杂度,同时考虑了单词间的相关性。
3.3 Word2Vec Model
Word2Vec模型实际上分为了两个部分,第一部分为训练数据集的构造,第二部分是通过模型获取词嵌入向量,即word embedding。
Word2Vec的整个建模过程实际上与自编码器(auto-encoder)的思想很相似,即先基于训练数据构建一个神经网络,当这个模型训练好以后,并不会用这个训练好的模型处理新任务,而真正需要的是这个模型通过训练数据所更新到的参数。
关于word embedding的发展,由于考虑上下文关系,所以模型的输入和输出分别是词汇表中的词组成,进而产生出了两种词模型方法:Skip-Gram和CBOW。同时,在隐藏层-输出层,也从softmax()方法演化到了分层softmax和negative sample方法。
所以,要拿到每个词的词嵌入向量,首先需要理解Skip-Gram和CBOW。下图展示了CBOW和Skip-Gram的网络结构:
本文以Skip-Gram为例,来理解词嵌入的相关知识。Skip-Gram是给定input word来预测上下文。我们可以用小学英语课上的造句来帮助理解,例如:“The __________”。
关于Skip-Gram的模型结构,主要分为几下几步:
- 从句子中定义一个中心词,即Skip-Gram的模型input word
- 定义skip_window参数,用于表示从当前input word的一侧(左边及右边)选取词的数量。
- 根据中心词和skip_window,构建窗口列表。
- 定义num_skips参数,用于表示从当前窗口列表中选择多少个不同的词作为output word。
假设有一句子"The quick brown fox jumps over the lazy dog" ,设定的窗口大小为2(),也就是说仅选中心词(input word)前后各两个词和中心词(input word)进行组合。如下图所示,以步长为1对中心词进行滑动,其中蓝色代表input word,方框代表位于窗口列表的词。
所以,我们可以使用Skip-Gram构建出神经网络的训练数据。
我们需要明白,不能把一个词作为文本字符串输入到神经网络中,所以我们需要一种方法把词进行编码进而输入到网络。为了做到这一点,首先从需要训练的文档中构建出一个词汇表,假设有10,000个各不相同的词组成的词汇表。那么需要做的就是把每一个词做One hot representation。此外神经网络的输出是一个单一的向量(也有10000个分量),它包含了词汇表中每一个词随机选择附近的一个词的概率。
3.4 Skip-Gram网络结构
下图是需要训练的神经网络结构。左侧的神经元Input Vector是词汇表中进行One hot representation后的一个词,右侧的每一个神经元则代表着词汇表的每一个词。实际上,在对该神经网络feed训练数据进行训练时,不仅输入词input word(中心词)是用One hot representation表示,输出词output word也是用One hot representation进行表示。但当对此网络进行评估预测时,输出向量实际上是通过softmax()函数计算得到的一个词汇表所有词的概率分布(即一堆浮点值,而不是一个One hot representation)。
3.5 Word2Vec Model隐藏层
假设我们正在学习具有300个特征的词向量。因此,隐藏层将由一个包含10,000行(每个单词对应一行)和300列(每个隐藏神经元对应一列)的权重矩阵来表示。(注:谷歌在其发布的模型中的隐藏层使用了300个输出(特征),这些特征是在谷歌新闻数据集中训练出来的(您可以从这里下载)。特征的数量300则是模型进行调优选择后的“超参数”)。
下面左右两张图分别从不同角度代表了输入层-隐层的权重矩阵。
从左图看,每一列代表一个One hot representation的词和隐层单个神经元连接的权重向量。从右图看,每一行实际上代表了每个词的词向量,或者词嵌入。
所以我们的目标就是学习输入层-隐藏层的权矩阵,而隐藏层-输出层的部分,则是在模型训练完毕后不需要保存的参数。这一点,与自编码器的设计思想是类似的。
你可能会问自己,难道真的分别要把每一个One hot representation的词(1 x 10000)与一个10000 x 300的权矩阵相乘吗?实际上,并不是这样。由于One hot representation的词具有只有一个元素这为1,其余元素值为0的特性,所以可以通过查找One hot representation中元素为1的位置索引,进而获得对应要乘以的10000 x 300的权矩阵的向量值,从而解决计算速度缓慢的问题。下图的例子,可帮助我们进一步理解。
可以看到,One hot representation中元素为1的位置索引为3,所以只需要乘以10000 x 300的权矩阵中位置索引同样为3的向量值即可得到相应的输出。
3.6 Word2Vec Model输出层
下面是计算“car”这个单词的输出神经元的输出的例子:
4. 基于Tensorflow的Skip-Gram极简实现
网上找了一些Tensorflow版本的skip-gram实现,但都有一个问题,输入单词并没有按照论文的要求做One hot representation,不知道是不是出于计算速度方面的考虑。因此,本小节的代码还是遵循原论文的描述,对输入单词及输出单词首先做了One hot representation。
首先,是训练数据的构造,包括skip_window上下文参数、词的One hot representation以及中心词、输出词对的构造。
其次,是Tensorflow计算图的构造,包括输入输出的定义、输入层-隐藏层,隐藏层-输出层的构造以及损失函数、优化器的构造。最后输出每个词的word embedding。具体代码如下所示:
上述代码的计算图可以简单表示为以下形式:
最后,打印出每个单词的词嵌入向量如下所示:
当词嵌入向量训练完成后,我们可以进行一个简单的测试,这里通过计算词嵌入向量间的欧氏距离寻找相近的词:
# 测试
def euclidean_dist(vec1, vec2):
"""欧氏距离"""
return np.sqrt(np.sum((vec1 - vec2) ** 2))
def find_closest(word_index, vectors):
min_dist = 10000 # to act like positive infinity
min_index = -1
query_vector = vectors[word_index]
for index, vector in enumerate(vectors):
if euclidean_dist(vector, query_vector) < min_dist and not np.array_equal(vector, query_vector):
min_dist = euclidean_dist(vector, query_vector)
min_index = index
return min_index
print('与 king 最接近的词是:', int2word[find_closest(word2int['king'], vectors)])
print('与 queen 最接近的词是:', int2word[find_closest(word2int['queen'], vectors)])
print('与 royal 最接近的词是:', int2word[find_closest(word2int['royal'], vectors)])
下面是输出的测试结果:
5. 总结
- 词嵌入是一种把词从高维稀疏向量映射到了相对低维的实数向量上的表达方式。
- Skip-Gram和CBOW的作用是构造神经网络的训练数据。
- 目前设计的网络结构实际上是由DNN+softmax()组成。
- 由于每个输入向量有且仅有一个元素为1,其余元素为0,所以计算词嵌入向量实际上就是在计算隐藏层的权矩阵。
- 对于单位矩阵的每一维(行)与实矩阵相乘,可以简化为查找元素1的位置索引从而快速完成计算。
6. 结束了吗?
仔细阅读代码,我们发现prediction时,使用的是softmax()。即输入词在输出层分别对词汇表的每一个词进行概率计算,如果在海量词汇表的前提下,计算效率是否需要考虑在内?有没有更快的计算方式呢?
此外,本文第3节提到的分层softmax是什么?negative samples又是什么?Huffman code又是怎样使用的?关于这些问题的思考,请关注:词嵌入的那些事儿(二)
7. 参考资料
[1] Word2Vec Tutorial - The Skip-Gram Model
0005,
词嵌入的那些事儿(二)
在文章词嵌入的那些事儿(一)中,我们得到了以下结论:
- 词嵌入是一种把词从高维稀疏向量映射到了相对低维的实数向量上的表达方式。
- Skip-Gram和CBOW的作用是构造神经网络的训练数据。
- 目前设计的网络结构实际上是由DNN+softmax()组成。
- 计算词嵌入向量实际上就是在计算隐藏层的权矩阵。
- 对于单位矩阵的每一维(行)与实矩阵相乘,可以简化为查找元素1的位置索引从而快速完成计算。
本文主要是在上文的基础上,对模型的隐藏层-输出层的设计做进一步探索。
1. 霍夫曼编码
霍夫曼编码(Huffman Coding),又译为哈夫曼编码、赫夫曼编码,是一种用于无损数据压缩的熵编码(权编码)算法。
霍夫曼树常处理符号编写工作。根据整组数据中符号出现的频率高低,决定如何给符号编码。如果符号出现的频率越高,则给符号的码越短,相反符号的号码越长。假设我们要给一个英文单字"F O R G E T"进行霍夫曼编码,而每个英文字母出现的频率分别如下图所示。
1.1 创建霍夫曼树
进行霍夫曼编码前,我们先创建一个霍夫曼树,具体步骤如下:
- 将每个英文字母依照出现频率由小排到大,最小在左,如上图所示。
- 每个字母都代表一个终端节点(叶节点),比较F.O.R.G.E.T六个字母中每个字母的出现频率,将最小的两个字母频率相加合成一个新的节点。如Fig.2所示,发现F与O的频率最小,故相加2+3=5。
- 比较5.R.G.E.T,发现R与G的频率最小,故相加4+4=8。
- 比较5.8.E.T,发现5与E的频率最小,故相加5+5=10。
- 比较8.10.T,发现8与T的频率最小,故相加8+7=15。
- 最后剩10.15,没有可以比较的对象,相加10+15=25。
- 最后产生的树状图就是霍夫曼树,参考下图。
1.2 进行编码
给霍夫曼树的所有左节点设置为'0',所有右节点设置为'1'。
从根节点到叶子节点依序记录所有字母的编码,如下图所示:
以上步骤就是对词进行霍夫曼编码的操作步骤。可以看到,词的出现频率越高,越靠近根节点,且编码长度越短。
2. Hierarchical Softmax的理解
首先回顾一下softmax函数。softmax(规范化指数函数)是网络输出层的函数,用于计算包含至少两种不同类型的词嵌入向量。此外,它也经常被用作为神经网络的激活函数,类似的还包括sigmoid和tanh等函数。softmax的公式如下:
其中,激活输出向量的每个元素都是在给定输入单词I的情况下,等于词汇表中第j个单词时的概率。同时,激活输出向量的所有元素之和等于1且每个元素映射到区间[0,1]。这个算法的计算复杂度即是词汇表的大小O(V)。实践表明,我们可以通过使用二叉树结构来有效地地减少此计算复杂度。下面,将介绍Hierarchical Softmax。
使用Hierarchical Softmax的主要原因是其计算复杂度是以2为底V的对数。
每个单词都可以通过从根节点-内部节点的路径到达,此外,对这个路径的度量可以由沿着这条路径的各概率乘积表示。各个概率值由sigmoid函数产生:
其中x由输入和输出向量的点积求出,n(w,j)表示为从根节点到叶子结点w(即上下文单词)的路径上的第j个节点。
实际上,我们可以用概率p来代替sigmoid函数。对于每个内部节点,我们都选择了一个任意子节点(左或右),并将正的sigmoid函数值赋给其中的一个(通常是左子节点)。
通过保留这些约束,节点n的左子节点的sigmoid函数可以描述为:
同理,节点n的右子节点的sigmoid函数可以描述为:
所以,输出词的计算概率为:
其中,L(w)表示霍夫曼树的深度,ch(n)表示节点n的子节点;角大括号表示布尔检验是否为真或假:如果布尔检验为True,说明节点n与其子节点ch(n)都在树的左边,即其子节点为左子节点。反之,如果布尔值为False,即其子节点ch(n)为右子节点。
回顾词嵌入的那些事儿(一)基于Tensorfow的Skip-Gram极简实现的内容,模型输出的其实是预测目标词的概率,也就是说每一次预测都要基于全部的数据集进行softmax()概率计算。神经网络结构如下图所示:
而采用Hierarchical Softmax后,由于替换了之前的softmax()函数,所以,隐藏层的词嵌入向量不需要对词汇表每个单词计算其为输出词的概率。
例如假设输出词是w2,因此可以沿着霍夫曼树从根节点(即词嵌入向量)一直走到我们的叶子节点w2(输出词)。由下图可以观察到,仅需执行3步的sigmoid函数计算,就可以确定叶子节点w2的位置。这无疑大大减少了操作数。
实际上,我们在计算词嵌入向量所采用的霍夫曼编码与第一节的介绍基本一致,区别只是对左右节点的0 1计数有所不同,比如:
3. Negative Sampling的理解
那么,霍夫曼树是不是计算词嵌入向量的最优解?假设我们的训练样本里的中心词w是一个很生僻的词,那么就得在霍夫曼树中一直往下寻找路径。能不能不用搞这么复杂的一颗霍夫曼树,将模型变的更加简单呢?Negative Sampling就是这么一种求解word2vec模型的方法,它摒弃了霍夫曼树,采用了Negative Sampling(负采样)的方法来求解,下面我们就来看看Negative Sampling的求解思路。
首先,需要了解噪声对比估计(NCE)。
3.1 噪声对比估计(NCE)
噪声对比估计(NCE)的核心思想是通过logistic回归将一个多分类问题转化为一个二分类问题,同时保留学习到的词向量的质量。在NCE中,词向量不再是通过从中心词中预测上下文单词来学习,相反通过学习如何从(target, random word from vocabulary)对中区分出真实的(target, context)对从而完成词向量的计算。换句话说,如果一个模型能够从随机噪声中分辨出实际的目标词对和上下文词对,那么好的词向量就会被学习。
3.2 Negative Sampling
而Negative Sampling是基于噪声对比估计(类似于生成对抗性网络)的一种方法。
即一个好的模型应该通过逻辑回归来区分假信号和真实信号。同时Negative Sampling背后的思想类似于随机梯度下降:不是每次都改变所有的权重,考虑到我们所拥有的成千上万的观测数据,我们只使用了其中的K个,并且显著地提高了计算效率:
正如上图公式,与随机梯度下降法的区别在于,我们不仅考虑了一个观测结果还考虑了其中的K个。
对于训练数据集,我们使用的是具有噪声分布的数据集。之所以使用这种噪声分布数据集,是为了区分真实数据和我们试图解决的假数据。具体来说,对于每个正样本(即 true target/context pair),我们从噪声分布中随机抽取k个负样本,并feed进模型。对于小的训练数据集,建议k值在5到20之间,而对于非常大的数据集,k值在2到5之间就足够了。我们的模型只有一个输出节点,它可以预测这对数据是随机噪声数据还是真实有效的target/context对。
由于采用了随机采样,所以需要假定一个概率分布。在词汇表中每个单词wi被采样到的概率由下式决定,其中幂为3/4。之所取3/4是因为可以减弱由于不同频次差异过大造成的单词采样差异的影响,使得小频次的单词也有一定被采样的概率。f(w)是词汇表中单词w出现的频率:
举例说明:
在采样前,我们将长度为1的线段划分成M等份,这里M>>V,这样可以保证每个词对应的线段都会划分成对应的区间块。在采样时,我们只需要从M个区间中采样出neg个区间,此时采样到的每一个区间块对应到的线段所属的词就是我们的负例词。
可能会有疑问:使用Negative Sampling后,负样本数量较多,正样本只有一个,会不会出现样本不均衡的现象从而导致逻辑回归模型分错左右子节点?实际上,样本不均衡这种问题主要出现在分类算法中。而我们这里词向量的训练本质不是一个分类问题,所以问题不大。
最后,一般来讲,NCE是一种渐近无偏的一般参数估计技术,而Negative Sampling更经常被用在二分类模型(例如逻辑回归)中,它们对词向量学习有用,但不是作为通用估计器去执行其他机器学习任务。具体可以参考这篇论文:Notes on Noise Contrastive Estimation and Negative Sampling 。
4. 总结
- 霍夫曼编码会使得出现频率最高的词编码长度最短,且路径最短。
- Negative Sampling的核心思想是每次训练只随机取一小部分的负例使他们的概率最小,以及对应的正例概率最大。
- 相比于Hierarchical Softmax,Negative Sampling不再采用霍夫曼树,而是采用随机负采样。
- 从计算效率上讲,Negative Sampling优于Hierarchical Softmax优于Softmax。
5. 参考资料
[1] 维基百科:霍夫曼编码
[2] Language Models, Word2Vec, and Efficient Softmax Approximations
[3] word2vec(cbow skip-gram hierarchical softmax Negative sampling)模型深度解析