word2vec是google在2013年推出的一个NLP工具,它的特点是将所有的词向量化,这样词与词之间就可以定量的去度量他们之间的关系,挖掘词之间的联系。Word2vector可以说是nlp的基石,但是其思想不仅仅局限在nlp,比如广告推荐中id的embedding也可以应用,本篇就来梳理一下word2vector,主要总结如何用恰当的词向量表示词,以及如何建模和训练,其中主要内容包括word2vector的基本思想、CBOW与skip-gram模型、霍夫曼树与Hierarchical Softmax、负采样(negative sampling)等
目录
1,Word2vector基础
词向量表示词,有很多种方法。早期的词向量是比较冗余的,比如one-hot编码:一个只有一个是1,其他全为0的向量,向量长度是词汇表大小。这种方法用来表示词向量很简单,但是也有很多问题,如果词汇表特别大,内存消耗特别严重,而且其表达效率不高,因为除了个别位置是1,其他位置均为0。
Distributed representation可以解决One hot representation的问题。它的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,进而可以用普通的统计学的方法来研究词与词之间的关系。这个较短的词向量维度是多大呢?这个一般需要我们在训练时自己来指定。
可以理解为通过训练的方式,降维压缩成一个低维的稠密矩阵,类似自编码思想,所以,很容易我们就联想到了神经网络的思想。神经网络是可以进行词向量的训练的,比如一个三层的神经网络,定义好输入输出数据,取隐藏层作为我们的词向量。
那么输入输出如何定义?一般有CBOW(Continuous Bag-of-Words )与Skip-Gram两种模型
2,CBOW与skip-Gram方法
CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。
Skip-Gram模型和CBOW的思路是反着来的,即输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。
这就是神经网络语言模型中CBOW与Skip-Gram来训练模型的基本过程。但是这和word2vec中用CBOW与Skip-Gram来训练模型与得到词向量的过程有很多的不同。word2vec并没有用线程的DNN模型,因为DNN模型参数多,计算量大,训练非常耗时,其中主要消耗计算的地方在softmax输出层。
那么,有什么方法可以简化计算过程吗?——霍夫曼树
3,霍夫曼树原理
softmax计算量巨大,霍夫曼树可以简化计算,用霍夫曼树来代替隐藏层和输出层的神经元,霍夫曼树的叶子节点起到输出层神经元的作用,叶子节点的个数即为词汇表的小大。而内部节点则起到隐藏层神经元的作用。我们首先看下霍夫曼树的基本原理。
霍夫曼树的建立过程如下:
输入:权值为(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,同时约定左子树的权重不小于右子树的权重。
以上就是霍夫曼树,他是如何应用在word2vector中的?它的参数是什么?loss是什么?又是如何进行计算和优化的?下面来分析
4. 基于Hierarchical Softmax的模型
我们说,word2vector没有直接用神经网络模型,而是对其做了优化,那具体都有哪些呢?
1)首先,对于从输入层到隐藏层的映射,没有采取神经网络的线性变换加激活函数的方法,而是采用简单的对所有输入词向量求和并取平均的方法。比如输入的是三个4维词向量:(1,2,3,4),(9,6,11,8),(5,10,7,12)那么我们word2vec映射后的词向量就是(5,6,7,8),这里是从多个词向量变成了一个词向量。
2)其次,从隐藏层到输出的softmax层这里的计算量个改进。为了避免要计算所有词的softmax概率,word2vec采样了霍夫曼树来代替从隐藏层到输出softmax层的映射。我们在上一节已经介绍了霍夫曼树的原理。如何映射呢?这里就是理解word2vec的关键所在了。
由于我们把之前所有都要计算的从输出softmax层的概率计算变成了一颗二叉霍夫曼树,那么我们的softmax概率计算只需要沿着树形结构进行就可以了。如下图所示,我们可以沿着霍夫曼树从根节点一直走到我们的叶子节点的词w2。
与之前的神经网络相比,霍夫曼树的内部节点类似于隐藏层的神经元,根节点的词向量对应我们的投影后的词向量,而所有叶子节点就类似于之前神经网络softmax输出层的神经元,叶子节点的个数就是词汇表的大小,在霍夫曼树中,softmax这个映射不是一下就完成的,而是沿着霍夫曼树一步步完成的,因此这种softmax取名为"Hierarchical Softmax"。我们把每一步看做是一个二元逻辑回归的方法,即规定沿着左子树走,那么就是负类(霍夫曼树编码1),沿着右子树走,那么就是正类(霍夫曼树编码0)。判别正类和负类的方法是使用sigmoid函数:
P
(
+
)
=
σ
(
x
w
T
θ
)
=
1
1
+
e
−
x
w
T
θ
P(+)=\sigma(x_w^T\theta)=\frac{1}{1+e^{-x_w^T\theta}}
P(+)=σ(xwTθ)=1+e−xwTθ1
其中
x
w
x_w
xw是当前内部节点的词向量,而θ则是我们需要从训练样本求出的逻辑回归的模型参数。
特点:
1)由于是二叉树,之前计算量为V,现在变成了log2V。
2)由于使用霍夫曼树是高频的词靠近树根,这样高频词需要更少的时间会被找到,这符合我们的贪心优化思想。
对于上图中的 w 2 w_2 w2,如果它是一个训练样本的输出,那么我们期望对于里面的隐藏节点 n ( w 2 , 1 ) n(w_2,1) n(w2,1)的P(−)概率大, n ( w 2 , 2 ) n(w_2,2) n(w2,2)的P(−)概率大, n ( w 2 , 3 ) n(w_2,3) n(w2,3)的P(+)概率大,这样沿着节点一路走过来,才能得到我们想要的结果。我们的目标就是找到合适的所有节点的词向量和所有内部节点θ, 使训练样本达到最大似然。
5. 基于Hierarchical Softmax的模型优化
先回忆下前面的例子,我们用公式表达出 w 2 w_2 w2的最大似然函数:
∏ i = 1 3 p ( n ( w i ) , i ) = ( 1 − 1 1 + e − x w T θ 1 ) ( 1 − 1 1 + e − x w T θ 2 ) 1 1 + e − x w T θ 3 \prod_{i=1}^{3}p(n(w_i),i)=(1-\frac{1}{1+e^{-x_w^T\theta_1}})(1-\frac{1}{1+e^{-x_w^T\theta_2}})\frac{1}{1+e^{-x_w^T\theta_3}} i=1∏3p(n(wi),i)=(1−1+e−xwTθ11)(1−1+e−xwTθ21)1+e−xwTθ31
如果我们能得到所有样本的最大似然函数,对其求最大似然估计即可。首先我们得定义一些参数:我们定义输入的词为w,其从输入层词向量求和平均后的霍夫曼树根节点词向量为 x w x_w xw, 从根节点到w所在的叶子节点,包含的节点总数为 l w l_w lw, w在霍夫曼树中从根节点开始,经过的第i个节点表示为 p w i p_{w_i} pwi,对应的霍夫曼编码为 d w i ∈ 0 , 1 d_{w_i}∈{0,1} dwi∈0,1,其中i=2,3,…lw。而该节点对应的模型参数表示为 θ w i θ_{w_i} θwi, 其中i=1,2,…lw−1,没有i= l w l_w lw是因为模型参数仅仅针对于霍夫曼树的内部节点。
每个霍夫曼树节点j的逻辑回归概率表达为:
P ( d j w ∣ x w , θ j − 1 w ) = { σ ( x w T θ j − 1 w ) d j w = 0 1 − σ ( x w T θ j − 1 w ) d j w = 1 P(d_j^w|x_w,\theta_{j-1}^w)=\left\{ \begin{aligned} & \sigma(x_w^T\theta_{j-1}^w) & d_j^w=0\\ & 1- \sigma(x_w^T\theta_{j-1}^w) & d_j^w=1\\ \end{aligned}\right. P(djw∣xw,θj−1w)={σ(xwTθj−1w)1−σ(xwTθj−1w)djw=0djw=1
那么对于某一个目标输出词w,其最大似然为:
∏ j = 2 l w P ( d j w ∣ ( x w , θ j − 1 w ) = ∏ j = 2 l w [ σ ( x w T θ j − 1 w ) ] 1 − d j w [ 1 − σ ( x w T θ j − 1 w ) ] d j w \prod_{j=2}^{l_w}P(d_j^w|(x_w,\theta_{j-1}^{w})=\prod_{j=2}^{l_w}[\sigma(x_w^T\theta_{j-1}^w) ]^{1-d_j^{w}}[1-\sigma(x_w^T\theta_{j-1}^w) ]^{d_j^w} j=2∏lwP(djw∣(xw,θj−1w)=j=2∏lw[σ(xwTθj−1w)]1−djw[1−σ(xwTθj−1w)]djw
解释两点:
1)为什么j从2开始。从根节点到子节点看做1到lw,第一层节点作为输出,没有输入。
2)为什么公式是针对某一个目标输出词w,而不是全部词。每个词的路径不同,能优化的只是这条路径上的参数,这样可以减少梯度计算量。
下面就是梯度求解的常规做法,取log,求导数:
L = l o g ∏ j = 2 l w P ( d j w ∣ ( x w , θ j − 1 w ) = ∑ j = 2 l w ( ( 1 − d j w ) l o g [ σ ( x w T θ j − 1 w ) ] ( d j w ) l o g [ 1 − σ ( x w T θ j − 1 w ) ] ) L = log\prod_{j=2}^{l_w}P(d_j^w|(x_w,\theta_{j-1}^{w}) =\sum_{j=2}^{l_w}((1-d_j^{w})log[\sigma(x_w^T\theta_{j-1}^w) ] ({d_j^w})log[1-\sigma(x_w^T\theta_{j-1}^w) ]) L=logj=2∏lwP(djw∣(xw,θj−1w)=j=2∑lw((1−djw)log[σ(xwTθj−1w)](djw)log[1−σ(xwTθj−1w)])
∂ L ∂ θ j − 1 w = ( 1 − d j w − σ ( x w T θ j − 1 w ) ) x w \frac{\partial L}{\partial{\theta_{j-1}^{w}}}=(1-d_j^w-\sigma(x_w^T\theta_{j-1}^w))x_w ∂θj−1w∂L=(1−djw−σ(xwTθj−1w))xw
∂ L ∂ x w = ∑ j = 2 l w ( 1 − d j w − σ ( x w T θ j − 1 w ) ) θ j − 1 w \frac{\partial L}{\partial x_w}=\sum_{j=2}^{l_w}(1-d_j^w-\sigma(x_w^T\theta_{j-1}^w))\theta_{j-1}^w ∂xw∂L=j=2∑lw(1−djw−σ(xwTθj−1w))θj−1w
我们说word2vector有两种模型CBOW和skip-gram这两种模型。他们分别是怎么实现优化的呢?
5.1 基于Hierarchical Softmax的CBOW模型优化
首先我们要定义词向量的维度大小M,以及CBOW的上下文大小2c,这样我们对于训练样本中的每一个词,其前面的c个词和后面的c个词作为了CBOW模型的输入,该词本身作为样本的输出,期望softmax概率最大。
在做CBOW模型前,我们需要先将词汇表建立成一颗霍夫曼树(可以将频率作为权重)。对于从输入层到隐藏层(投影层),这一步比较简单,就是对w周围的2c个词向量求和取平均即可,即
x w = 1 2 c ∑ i = 1 2 c x i x_w=\frac{1}{2c}\sum_{i=1}^{2c}x_i xw=2c1i=1∑2cxi
第二步,通过梯度上升法来更新我们的θwj−1和xw,注意这里的xw是由2c个词向量相加而成,我们做梯度更新完毕后会用梯度项直接更新原始的各个xi(i=1,2…2c),即:
θ j − 1 w = θ j − 1 w + η ( 1 − d j w − σ ( x w T θ j − 1 w ) ) x w \theta_{j-1}^w =\theta_{j-1}^w + \eta (1-d_j^w-\sigma(x_w^T\theta_{j-1}^w))x_w θj−1w=θj−1w+η(1−djw−σ(xwTθj−1w))xw
x i = x i + η ∑ j = 2 l w ( 1 − d j w − σ ( x w T θ j − 1 w ) ) θ j − 1 w x_i = x_i+\eta\sum_{j=2}^{l_w}(1-d_j^w-\sigma(x_w^T\theta_{j-1}^w))\theta_{j-1}^w xi=xi+ηj=2∑lw(1−djw−σ(xwTθj−1w))θj−1w
5.2,基于Hierarchical Softmax的skip-gram模型优化
在做CBOW模型前,我们需要先将词汇表建立成一颗霍夫曼树。skip-gram模型从输入层到隐藏层(投影层),这一步比CBOW简单,由于只有一个词,所以,即xw就是词w对应的词向量。
第二步,通过梯度上升法来更新我们的 θ j − 1 θ_j−1 θj−1和 x w x_w xw,注意这里的 x w x_w xw周围有2c个词向量,此时如果我们期望 P ( x i ∣ x w ) , i = 1 , 2...2 c P(x_i|x_w),i=1,2...2c P(xi∣xw),i=1,2...2c最大。此时我们注意到由于上下文是相互的,在期望 P ( x i ∣ x w ) , i = 1 , 2...2 c P(x_i|x_w),i=1,2...2c P(xi∣xw),i=1,2...2c最大化的同时,反过来我们也期望 P ( x w ∣ x i ) , i = 1 , 2...2 c P(x_w|x_i),i=1,2...2c P(xw∣xi),i=1,2...2c最大。那么是使用 P ( x i ∣ x w ) P(x_i|x_w) P(xi∣xw)好还是 P ( x w ∣ x i ) P(x_w|x_i) P(xw∣xi)好呢,word2vec使用了后者,这样做的好处就是在一个迭代窗口内,我们不是只更新 x w x_w xw一个词,而是 x i , i = 1 , 2...2 c x_i,i=1,2...2c xi,i=1,2...2c共2c个词。这样整体的迭代会更加的均衡。因为这个原因,Skip-Gram模型并没有和CBOW模型一样对输入进行迭代更新,而是对2c个输出进行迭代更新。
6,基于负采样(negative sampling)的模型
使用霍夫曼树来代替传统的神经网络,可以提高模型训练的效率,但是如果我们的训练样本里的中心词w是一个很生僻的词,那么就得在霍夫曼树中辛苦的向下走很久了。能不能不用搞这么复杂的一颗霍夫曼树,将模型变的更加简单呢?
Negative Sampling就是这么一种求解word2vec模型的方法,它摒弃了霍夫曼树,采用了Negative Sampling(负采样)的方法来求解,下面我们就来看看Negative Sampling的求解思路。
比如我们有一个训练样本,中心词是w,它周围上下文共有2c个词,记为context(w)。由于这个中心词w,的确和context(w)相关存在,因此它是一个真实的正例。通过Negative Sampling采样,我们得到neg个和w不同的中心词 w i , i = 1 , 2 , . . n e g w_i,i=1,2,..neg wi,i=1,2,..neg,这样context(w)和 w i w_i wi就组成了neg个并不真实存在的负例。利用这一个正例和neg个负例,我们进行二元逻辑回归,得到负采样对应每个词 w i w_i wi对应的模型参数 θ i θ_i θi,和每个词的词向量。
是不是简单多了,没有霍夫曼树,每次得到数据以后,采样neg个不同的中心词作为负样本就可以进行二元逻辑回归训练。摆在我们面前的有两个问题:1)如何负采样?2)如何进行一个正样本与neg个负样本的二元逻辑回归?
6.1 负采样
每个词都是有权重的,采样也肯定跟权重相关,单词的权重用一下公式表示:
l
e
n
(
w
)
=
c
o
u
n
t
(
w
)
3
/
4
∑
c
o
u
n
t
(
u
)
3
/
4
len(w)=\frac{count(w)^{3/4}}{\sum count(u)^{3/4}}
len(w)=∑count(u)3/4count(w)3/4
很明显这跟词频相关,为什么取3/4?很多时候这种参数都是经验值
word2vec采样的方法并不复杂,如果词汇表的大小为V,那么我们就将一段长度为1的线段分成V份,每份对应词汇表中的一个词。当然每个词对应的线段长度是不一样的,高频词对应的线段长,低频词对应的线段短。每个词w的线段长度按照权重分配。
在采样前,我们将这段长度为1的线段划分成M(默认10^8)等份,这里M>>V,这样可以保证每个词对应的线段都会划分成对应的小块。而M份中的每一份都会落在某一个词对应的线段上。在采样的时候,我们只需要从M个位置中采样出neg个位置就行,此时采样到的每一个位置对应到的线段所属的词就是我们的负例词。
6.2 优化
通过负采样,我们得到了neg个负例 ( c o n t e x t ( w ) , w i ) i = 1 , 2 , . . n e g (context(w),w_i)i=1,2,..neg (context(w),wi)i=1,2,..neg。为了统一描述,我们将正例定义为w_0。
在逻辑回归中,正样本期望满足:
p
(
c
o
n
t
e
x
t
(
w
0
)
,
w
i
)
=
σ
(
x
w
0
T
θ
w
i
)
,
y
i
=
1
,
i
=
0
p(context(w_0),w_i)=\sigma(x_{w_0}^T\theta^{w_i}),y_i=1,i=0
p(context(w0),wi)=σ(xw0Tθwi),yi=1,i=0
对于负样本期望满足:
p
(
c
o
n
t
e
x
t
(
w
0
)
,
w
i
)
=
1
−
σ
(
x
w
0
T
θ
w
i
)
,
y
i
=
0
,
i
=
1
,
2....
n
e
g
p(context(w_0),w_i)=1-\sigma(x_{w_0}^T\theta^{w_i}),y_i=0,i=1,2....neg
p(context(w0),wi)=1−σ(xw0Tθwi),yi=0,i=1,2....neg
那么我们期望可以最大化一下公式:
∏
i
=
0
n
e
g
P
(
c
o
n
t
e
x
t
(
w
0
)
,
w
i
)
=
σ
(
x
w
0
T
θ
w
0
)
∏
i
=
0
n
e
g
(
1
−
σ
(
x
w
0
T
θ
w
i
)
)
\prod_{i=0}^{neg}P(context(w_0),w_i)=\sigma(x_{w_0}^T\theta^{w_0})\prod_{i=0}^{neg}(1-\sigma(x_{w_0}^T\theta^{w_i}))
i=0∏negP(context(w0),wi)=σ(xw0Tθw0)i=0∏neg(1−σ(xw0Tθwi))
继续表达成似然函数,以及最大似然函数:
∏
i
=
0
n
e
g
σ
(
x
w
0
T
θ
w
i
)
y
i
(
1
−
σ
(
x
w
0
T
θ
w
i
)
)
1
−
y
i
\prod_{i=0}^{neg}\sigma(x_{w_0}^T\theta^{w_i})^{y_i}(1-\sigma(x_{w_0}^T\theta^{w_i}))^{1-y_i}
i=0∏negσ(xw0Tθwi)yi(1−σ(xw0Tθwi))1−yi
取log然后求偏导
∂
L
∂
θ
w
i
=
(
y
i
−
σ
(
x
w
0
T
θ
w
i
)
)
x
w
0
\frac{\partial L}{\partial \theta^{w_i}}=(y_i-\sigma(x_{w_0}^T \theta ^{w_i}))x_{w_0}
∂θwi∂L=(yi−σ(xw0Tθwi))xw0
∂
L
∂
x
w
0
=
∑
i
=
0
n
e
g
(
y
i
−
σ
(
x
w
0
T
θ
w
i
)
)
θ
w
i
\frac{\partial L}{\partial x^{w_0}}=\sum_{i=0}^{neg}(y_i-\sigma(x_{w_0}^T \theta ^{w_i}))\theta^{w_i}
∂xw0∂L=i=0∑neg(yi−σ(xw0Tθwi))θwi
通过梯度下降算法就可以迭代求解了。
7,总结
1)w2v就是单词的向量表示
2)CBOW与skip-gram是两种w2v思路,主要区别在于上下文与单词本身输入输出关系。
3)w2v处理的语料库比较大,参数众多,一般通过霍夫曼树或者负采样简化计算,这两种方法,都是在输出端进行的。
4)Python可以用gensim库实现w2v
参考文献:
http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
https://www.jianshu.com/p/1405932293ea
https://www.jianshu.com/p/d8bfaae28fa9
https://blog.csdn.net/github_36235341/article/details/78607323
https://www.cnblogs.com/pinard/p/7243513.html