基本知识
BOW,word2vector,glovehtml
cbow,skip-grampython
Hierarchical Softmax,Negative Samplinggit
https://www.cnblogs.com/wkang/p/9611257.htmlgithub
https://www.cnblogs.com/wkang/p/9978364.html算法
word2vec原理(一) CBOW与Skip-Gram模型基础网络
word2vec原理(二) 基于Hierarchical Softmax的模型数据结构
word2vec原理(三) 基于Negative Sampling的模型dom
0001,
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维,有一个有趣的研究代表,用下图的词向量表示咱们的词时,咱们能够发现:
King→−Man→+Woman→=Queen→

可见咱们只要获得了词汇表里全部词对应的词向量,那么咱们就能够作不少有趣的事情了。不过,怎么训练获得合适的词向量呢?一个很常见的方法是使用神经网络语言模型。
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原理(一) 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函数,即:
P(+)=σ(xTwθ)=11+e−xTwθ
其中xw
是当前内部节点的词向量,而θ
则是咱们须要从训练样本求出的逻辑回归的模型参数。
使用霍夫曼树有什么好处呢?首先,因为是二叉树,以前计算量为V
,如今变成了log2V
。第二,因为使用霍夫曼树是高频的词靠近树根,这样高频词须要更少的时间会被找到,这符合咱们的贪心优化思想。
容易理解,被划分为左子树而成为负类的几率为P(−)=1−P(+)
。在某一个内部节点,要判断是沿左子树仍是右子树走的标准就是看P(−),P(+)谁的几率值大。而控制P(−),P(+)谁的几率值大的因素一个是当前节点的词向量,另外一个是当前节点的模型参数θ
。
对于上图中的w2
,若是它是一个训练样本的输出,那么咱们指望对于里面的隐藏节点n(w2,1)的P(−)几率大,n(w2,2)的P(−)几率大,n(w2,3)的P(+)
几率大。
回到基于Hierarchical Softmax的word2vec自己,咱们的目标就是找到合适的全部节点的词向量和全部内部节点θ
, 使训练样本达到最大似然。那么如何达到最大似然呢?
2. 基于Hierarchical Softmax的模型梯度计算
咱们使用最大似然法来寻找全部节点的词向量和全部内部节点θ
。先拿上面的w2例子来看,咱们指望最大化下面的似然函数:
∏i=13P(n(wi),i)=(1−11+e−xTwθ1)(1−11+e−xTwθ2)11+e−xTwθ3
对于全部的训练样本,咱们指望最大化全部样本的似然函数乘积。
为了便于咱们后面通常化的描述,咱们定义输入的词为w
,其从输入层词向量求和平均后的霍夫曼树根节点词向量为xw, 从根节点到w所在的叶子节点,包含的节点总数为lw, w在霍夫曼树中从根节点开始,通过的第i个节点表示为pwi,对应的霍夫曼编码为dwi∈{0,1},其中i=2,3,...lw。而该节点对应的模型参数表示为θwi, 其中i=1,2,...lw−1,没有i=lw
是由于模型参数仅仅针对于霍夫曼树的内部节点。
定义w
通过的霍夫曼树某一个节点j的逻辑回归几率为P(dwj|xw,θwj−1)
,其表达式为:
P(dwj|xw,θwj−1)={σ(xTwθwj−1)1−σ(xTwθwj−1)dwj=0dwj=1
那么对于某一个目标输出词w
,其最大似然为:
∏j=2lwP(dwj|xw,θwj−1)=∏j=2lw[σ(xTwθwj−1)]1−dwj[1−σ(xTwθwj−1)]dwj
在word2vec中,因为使用的是随机梯度上升法,因此并无把全部样本的似然乘起来获得真正的训练集最大似然,仅仅每次只用一个样本更新梯度,这样作的目的是减小梯度计算量。这样咱们能够获得w
的对数似然函数L
以下:
L=log∏j=2lwP(dwj|xw,θwj−1)=∑j=2lw((1−dwj)log[σ(xTwθwj−1)]+dwjlog[1−σ(xTwθwj−1)])
要获得模型中w
词向量和内部节点的模型参数θ, 咱们使用梯度上升法便可。首先咱们求模型参数θwj−1
的梯度:
∂L∂θwj−1=(1−dwj)(σ(xTwθwj−1)(1−σ(xTwθwj−1)σ(xTwθwj−1)xw−dwj(σ(xTwθwj−1)(1−σ(xTwθwj−1)1−σ(xTwθwj−1)xw=(1−dwj)(1−σ(xTwθwj−1))xw−dwjσ(xTwθwj−1)xw=(1−dwj−σ(xTwθwj−1))xw(1)(2)(3)
若是你们看过以前写的逻辑回归原理小结,会发现这里的梯度推导过程基本相似。
一样的方法,能够求出xw
的梯度表达式以下:
∂L∂xw=∑j=2lw(1−dwj−σ(xTwθwj−1))θwj−1
有了梯度表达式,咱们就能够用梯度上升法进行迭代来一步步的求解咱们须要的全部的θwj−1
和xw
。
3. 基于Hierarchical Softmax的CBOW模型
因为word2vec有两种模型:CBOW和Skip-Gram,咱们先看看基于CBOW模型时, Hierarchical Softmax如何使用。
首先咱们要定义词向量的维度大小M
,以及CBOW的上下文大小2c,这样咱们对于训练样本中的每个词,其前面的c个词和后面的c
个词做为了CBOW模型的输入,该词自己做为样本的输出,指望softmax几率最大。
在作CBOW模型前,咱们须要先将词汇表创建成一颗霍夫曼树。
对于从输入层到隐藏层(投影层),这一步比较简单,就是对w
周围的2c个词向量求和取平均便可,即:
xw=12c∑i=12cxi
第二步,经过梯度上升法来更新咱们的θwj−1
和xw,注意这里的xw是由2c个词向量相加而成,咱们作梯度更新完毕后会用梯度项直接更新原始的各个xi(i=1,2,,,,2c)
,即:
θwj−1=θwj−1+η(1−dwj−σ(xTwθwj−1))xw
xw=xw+η∑j=2lw(1−dwj−σ(xTwθwj−1))θwj−1(i=1,2..,2c)
其中η
为梯度上升法的步长。
这里总结下基于Hierarchical Softmax的CBOW模型算法流程,梯度迭代使用了随机梯度上升法:
输入:基于CBOW的语料训练样本,词向量的维度大小M
,CBOW的上下文大小2c,步长η
输出:霍夫曼树的内部节点模型参数θ
,全部的词向量w
1. 基于语料训练样本创建霍夫曼树。
2. 随机初始化全部的模型参数θ
,全部的词向量w
3. 进行梯度上升迭代过程,对于训练集中的每个样本(context(w),w)
作以下处理:
a) e=0, 计算xw=12c∑i=12cxi
b) for j = 2 to lw
, 计算:
f=σ(xTwθwj−1)
g=(1−dwj−f)η
e=e+gθwj−1
θwj−1=θwj−1+gxw
c) 对于context(w)
中的每个词向量xi(共2c个)进行更新:
xi=xi+e
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(xi|xw),i=1,2...2c最大。此时咱们注意到因为上下文是相互的,在指望P(xi|xw),i=1,2...2c最大化的同时,反过来咱们也指望P(xw|xi),i=1,2...2c最大。那么是使用P(xi|xw)好仍是P(xw|xi)好呢,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
, 计算:
f=σ(xTiθwj−1)
g=(1−dwj−f)η
e=e+gθwj−1
θwj−1=θwj−1+gxi
iii)
xi=xi+e
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原理(一) 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
。
在逻辑回归中,咱们的正例应该指望知足:
P(context(w0),wi)=σ(xTw0θwi),yi=1,i=0
咱们的负例指望知足:
P(context(w0),wi)=1−σ(xTw0θwi),yi=0,i=1,2,..neg
咱们指望能够最大化下式:
∏i=0negP(context(w0),wi)=σ(xTw0θw0)∏i=1neg(1−σ(xTw0θwi))
利用逻辑回归和上一节的知识,咱们容易写出此时模型的似然函数为:
∏i=0negσ(xTw0θwi)yi(1−σ(xTw0θwi))1−yi
此时对应的对数似然函数为:
L=∑i=0negyilog(σ(xTw0θwi))+(1−yi)log(1−σ(xTw0θwi))
和Hierarchical Softmax相似,咱们采用随机梯度上升法,仅仅每次只用一个样本更新梯度,来进行迭代更新获得咱们须要的xwi,θwi,i=0,1,..neg
, 这里咱们须要求出xw0,θwi,i=0,1,..neg
的梯度。
首先咱们计算θwi
的梯度:
∂L∂θwi=yi(1−σ(xTw0θwi))xw0−(1−yi)σ(xTw0θwi)xw0=(yi−σ(xTw0θwi))xw0(1)(2)
一样的方法,咱们能够求出xw0
的梯度以下:
∂L∂xw0=∑i=0neg(yi−σ(xTw0θwi))θwi
有了梯度表达式,咱们就能够用梯度上升法进行迭代来一步步的求解咱们须要的xw0,θwi,i=0,1,..neg
。
4. Negative Sampling负采样方法
如今咱们来看看如何进行负采样,获得neg个负例。word2vec采样的方法并不复杂,若是词汇表的大小为V
,那么咱们就将一段长度为1的线段分红V份,每份对应词汇表中的一个词。固然每一个词对应的线段长度是不同的,高频词对应的线段长,低频词对应的线段短。每一个词w的线段长度由下式决定:
len(w)=count(w)∑u∈vocabcount(u)
在word2vec中,分子和分母都取了3/4次幂以下:
len(w)=count(w)3/4∑u∈vocabcount(u)3/4
在采样前,咱们将这段长度为1的线段划分红M
等份,这里M>>V,这样能够保证每一个词对应的线段都会划分红对应的小块。而M份中的每一份都会落在某一个词对应的线段上。在采样的时候,咱们只须要从M个位置中采样出neg
个位置就行,此时采样到的每个位置对应到的线段所属的词就是咱们的负例词。

在word2vec中,M
取值默认为108
。
5. 基于Negative Sampling的CBOW模型
有了上面Negative Sampling负采样的方法和逻辑回归求解模型参数的方法,咱们就能够总结出基于Negative Sampling的CBOW模型算法流程了。梯度迭代过程使用了随机梯度上升法:
输入:基于CBOW的语料训练样本,词向量的维度大小Mcount
,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∑i=12cxi
b) for i= 0 to neg, 计算:
f=σ(xTw0θwi)
g=(yi−f)η
e=e+gθwi
θwi=θwi+gxw0
c) 对于context(w)
中的每个词向量xk(共2c个)进行更新:
xk=xk+e
d) 若是梯度收敛,则结束梯度迭代,不然回到步骤3继续迭代。
6. 基于Negative Sampling的Skip-Gram模型
有了上一节CBOW的基础和上一篇基于Hierarchical Softmax的Skip-Gram模型基础,咱们也能够总结出基于Negative Sampling的Skip-Gram模型算法流程了。梯度迭代过程使用了随机梯度上升法:
输入:基于Skip-Gram的语料训练样本,词向量的维度大小Mcount
,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, 计算:
f=σ(xTw0iθwj)
g=(yj−f)η
e=e+gθwj
θwj=θwj+gxw0i
iii) 词向量更新:
xw0i=xw0i+e
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以及中心词、输出词对的构造。
按 Ctrl+C 复制代码
按 Ctrl+C 复制代码
其次,是Tensorflow计算图的构造,包括输入输出的定义、输入层-隐藏层,隐藏层-输出层的构造以及损失函数、优化器的构造。最后输出每一个词的word embedding。具体代码以下所示:
按 Ctrl+C 复制代码
按 Ctrl+C 复制代码
上述代码的计算图能够简单表示为如下形式:

最后,打印出每一个单词的词嵌入向量以下所示:

当词嵌入向量训练完成后,咱们能够进行一个简单的测试,这里经过计算词嵌入向量间的欧氏距离寻找相近的词:
# 测试 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)模型深度解析