word2vec原理解析及简单实践

前些日子,google提出的BERT可以说是nlp届新的里程碑,而Tomas Mikolov等人2013年提出的word2vec[1][2]可以说是当年的里程碑。

word2vec在nlp领域有非常广泛的应用,要想用好word2vec,最好要弄明白其中的原理,经过我这几天的研究,对word2vec有一点粗浅的理解,试图通过本篇对word2vec原理进行一个较为清楚的解析,同时有使用gensim的简单实践。

本文内容是基于网络上的资源,加上自己的理解,有错误在所难免,希望大家不吝赐教。

一、 词的表示简述

在现实应用中,文本或词的表示是非常重要的。最常用的文本表示技术有:

  • Local representations

    • N-grams
    • Bag-of-words
    • 1-of-N coding
  • Continuous representions

    • Latent Semantic Analysis
    • Latent Dirichlet Allocation
    • Distributed Representations

第一类是Local representations(局部表示)。其中,N-grams就是将文章序列通过大小为N的窗口,切割成一个个的group,然后依据这些group去做统计,得到一些文本的统计特征来作为文本的表示。 Bag-of-words,词袋模型,就是词的统计来得到词的特征,最简单的,统计个数。 1-of-N coding,就是独热(one hot)编码,来表示一个词。第二类是Continuous representations(连续表示)。方法有三种,前两种笔者也不熟悉,第三种叫做Distributed Representations(分布式表示)。word2vec就属于这一类里。

第一类的确是不错的方法,但是缺点也很明显,主要表现在几点。

  1. 词的意义问题。 通过词的表示,很难得知同义词之间的关系,也很难计算词之间的相似度。
  2. 对新词不友好。 如果出现新词,几乎不可能更新到最新的词表。
  3. 主观,需要人力去创造和适应。 这些特征基本上是依据人类对语言的理解去做的,从这个件角度而言,相当于加入了很多的先验知识,这就会使得表征的信息有局限性。
  4. 空间稀疏性。 one hot编码中,一个词的表示,除了一个1,其他都是0。人类的词汇量很大,在说话的常用词有20k个,大词汇量表有500k个,google的词汇表有13m个,过于稀疏,对于后面的存储和计算都不太友好。

简单的想法就是Continuous representions中的Distributed Representations,多位研究人员为此做出过努力和贡献,值得关注的是Bengio的2003年的paper——A neural probabilistic language model。 Distributed Representations可以基于语言模型从不同的神经网络模型中获得,有Feedforward neural net language model(前馈神经网络语言模型)和Recurrent neural net language model(循环神经网络语言模型),他们二者都是神经网络语言模型,可以简写为NNLM。

上图就是Bengio的Four-gram neural net language model架构。该模型通过随机梯度下降(SGD)和误差反向传播算法(BP)进行训练。其中,词的向量存在矩阵U中。从理解上来讲,该网络是一个神经网络,有输入,有输出。但是这是训练另一个虚假任务,因为该神经网络最终要的结果并不是去预测,而是训练后的中间产物,U矩阵。该网络的问题在于,训练的时候太消耗资源,而且,V和W等并不是想要的,却浪费了很多资源,使得数据集太大的时候,训练时长特别长,同时,获得词向量并不是非常有效。到2013年的时候,word2vec出世,word2vec的主要工作在于简化了此模型,使得大量的数据(b级别)可以在上面运行,并做了一些创新。下面将介绍word2vec。

二、 word2vec原理解析

2.1 word2vec总览

上文提到,NNLM的问题在于,训练并不有效和高效。Mikolov等人提出了以下解决方法:

  • 输出层的softmax函数可以由以下方法替代:
    • Hierarchical softmax (Morin and Bengio)
    • Hinge loss (Collobert and Weston)
    • Noise contrastive estimation (Mnih et al.)
    • Negative sampling (Mikolov et al.)
  • 移除了隐藏层,对于较大的模型,可以加速训练1000倍以上,同时提出了以下两种训练方法
    • Continuous bag-of-words model(简称CBOW模型)
    • Continuous skip-gram model(简称SG模型)

简单而言,word2vec的核心思想就是预测每个单词与其上下文单词之间的关系。

共两种算法:

  1. Skip-grams(SG):给定单词,去预测此词的上下文单词(位置独立)。
  2. Continuous Bag of Words(CBOW):从给定的上下文单词,去预测该单词。

两种(适度有效的)训练方法:

  1. Hierarchical softmax
  2. Negative sampling

下文以skip-grams算法,连接原始的softmax为例子介绍word2vec。

2.2 Skip-grams(SG)算法解析

正如前文所说,通过神经网络的方法获得词向量,其实是建立一个(虚假)任务,最终通过训练模型,我们只需要保留模型中的一部分权重参数,来获得词向量。在Skip-grams这里,要训练的任务就是给定单词,去预测该词前面m个窗口和后面m个窗口的词。如图2所示,就是说,该网络的输入是单词,目标输出是2*m个单词。 图2中,输入就是banking,这里称之为center word(中心词),turning,into,crises,as等单词叫做output context words(输出上下文单词)。

针对每个单词t=1,2,…,T,预测该单词周围“半径”为m的窗口里的单词,也就是针对每个单词,预测的目标是该单词前面m个单词及后面m个单词。所以,我们得到该训练任务的目标函数:给定当前中心词,最大化上下文单词的概率。目标函数如图3,其中,此处$w_t$是表示t时刻的中心词, $\theta$代表我们将会优化的变量,对应于平常我们所说的权重。 我们的目标$J’(\theta)$就是要在最大化每一个中心词周围的词的概率。下面的$J(\theta)$是对$J’(\theta)$取了负号和对数。之所以这样,是因为在机器学习里,我们通常习惯于去优化最小值。$J(\theta)$被称之为目标函数。所以,此处我们目标就是去最小化目标函数$J(\theta)$。(注:在机器学习领域,目标函数等价于损失函数,也等价于代价函数)。对于目标函数的理解,也可以理解为交叉熵损失。写出交叉熵的公式,再对比,就可以明白了。

从上图3,我们可以得知,目标函数中最核心的还是概率$p(w_{t+j} | w_t)$,既然如此重要,怎么计算呢?最简单的公式就是如下图4。

上图中,o表示输出(outside or output)单词对应的单词索引,c表示中心(center)单词索引。$v_c$和$u_o$分别是索引为c和o的“中心”(center)和“外面”(outside)的向量。此处可能并不是太好理解,可以等看了后面,再思考这个,就很容易理解了。在此处可以做一些简单的理解,$u_o$和$v_c$都是列向量,这没问题。然后o和c都是索引,也没问题。分母中累加的个数V是单词总个数,也就是数据集中所有不同单词的个数。其实,$v_c$是索引为c的单词对应的词向量。如果为$w_t$,$v_c$也就变成了$v_t$了。剩余的可以等后面再去理解。

softmax函数,就是使用单词c(单词索引为c的意思)去获得输出单词o的概率分布。 怎么获取概率,就是下图5和图6要讲的问题。图5就是讲点乘的计算方法,这个大家都熟悉。前文也提到u和v都是列向量,两者点乘的结果是一个实数,二者越相似,该实数越大。 于是,当w分别为1,2,…V时,$u_w^Tv$就可以得出每个单词与v的相似程度了。(注:图中是w=1…W,笔者认为W应该改为V,属于图中错误)

笔者之前一直不太理解,为何sotfmax为何要这么写,看了图6就完全明白了。这么理解,给定一个数字分布,要将其中的每个数字都映射为概率,要求是每个数字越大,你要映射的概率越大。概率是什么,0到1之间的数字。所以这个数字必须先转换为正数,怎么转换呢?图中u是一个列向量,$u_i$是表示列向量的第i个数字,是个实数,$u_j$同理,也是实数。图6就是很简单将该数字做了映射,也就是$u_i$映射为$e^{u_i}$,选择指数函数,因为首先它是递增的,同时指数函数都是大于等于0的。同时除以分母,就完成了归一化。这也是softmax之所以长这个样子的原因。

前文讲了那么多,一直没有讲到skip-grams的框架,下图7就是框架,该图非常简单明了。该图采用的是skip-gram框架,后面结了softmax层,并没有使用Hierarchical softmax或Negative sampling主要是为了方便理解。值得注意的是在word2vec的框架里,没有激活函数,也就是没有sigmoid,tanh等。输入为$w_t$,输出是窗口半径为m的单词,也就是输出为$w_{t-m}, w_{t-m+1}…,w_{t-1},w_{t+1},…,w_{t+m-1},w_{t+m}$,图中只画了$w_{t-1}, w_{t-2},w_{t-3}$这三个输出为例。

最左侧为输入,输入$w_t$的表示是用的one hot编码。最右侧是期望的输出$w_{t-3}, w_{t-2}, w_{t-1}…$,期望的表示也用one hot编码表示。所谓one hot编码,前文也有说明,很简单,就是一个列向量,该向量长度等于(数据集中)所有词汇的个数。对于某个单词,除了该词汇对应的索引位置对应的值为1,其他的都为0。在该图中,词汇个数是V,所以$w_t$的维数是v*1。图中可以看到$w_t$的表示就是$(0, 0, 0, 0, 1, 0, 0)^T$。输入层的下一层是映射层(projection),映射层的下一层就是输出层,到达该层后,需要再通过一个softmax函数,转成概率输出。前文有说明,中间没有激活函数。也就是说参数只有图中的两个矩阵,一个是输入层与映射层之间的权重矩阵$W$,维数为d*V,一个是映射层与输出层之间的权重矩阵$W’$。所谓神经网络的训练,就是获得权重的合适值,训练完成后,权重矩阵$W$就是我们所需要的词向量矩阵。为何这么讲,因为我们可以看到映射层$v_c$是通过矩阵运算$Ww_t$得到的,而$w_t$只有一个1,也就是最终向量是W矩阵的某一列,该列就是表征了$w_t$的向量。所以说$W_{w_t}$等于$v_c$。于是$v_c$就是W矩阵的一列。同样的$W’$为V*d的矩阵,$W’v_c$得到输出的(未经过softmax前的)向量。将$W’$的每一行取出来,并转换为列向量,命名为$u_i$,可以看到$u_i$和$v_c$一样都是d*1的列向量。此处的u和v和前文提到的u和v是完全一样的意义,所以可以回头理解那里的u和v了。可以看到,未经过softmax前的第i个结果等价于$u_iv_c$。然后再经过softmax层,大家也都熟悉。然后与真实的、期望的目标对比,得到损失,就可以根据sgd进行推导和更新权重了。

通常,我们会将模型中的所有参数都放在一起,做一个长的参数,统一叫做$\theta$。在这个模型中,就两个权重矩阵,也可以用v和u表示。如下图图8,$v_{aardvark}…v_{zebra}$表示从第一个单词对应的v到最后一个单词对应的v,u同理。v和u都是维数为d*1,都是有V个,所以最终$\theta$的含有2dV*1维的参数列向量。

至此,Skip-grams算法框架已经讲完了。在机器学习里,训练一个网络就是更新权重,更新权重就要求导。类似于bp算法,但是因为此网络特别简单,所以推导倒数简单多了。下一节2.3节会计算梯度的导,基于此导数就可以更新权重了。如果不想费脑细胞,可以跳过2.3,直接看2.4节。

2.3 Skip-grams(SG)算法求梯度的导

所谓bp算法,不过是链式法则在神经网络计算中的应用而单独起的名字。如果叫做链式法则,大家应该就会觉得easy很多。看下图图9和图10,主要是向量求导的基本公式,其中$x, a$二者都是列向量。该公式的推导,可以将公式展开就可以明白。至于链式法则,这个是基本内容,无需多言。

如前文所述,$\theta$表示模型中的所有参数,$J(\theta)$表示我们的目标函数。如图11, 众所周知,梯度下降的参数更下公式如下图,针对每一个$\theta_j$,都是旧的$\theta_j$减去步长$\alpha$乘以$J(\theta)$对$\theta_j$的导数。

梯度下降的效果展示如下图12。蓝色的箭头之间是一步,红色的圈是等值线,越里面表示值越小,也就是目标函数越小。从图上来看,逐渐目标函数减至最小。

上面就是对链式法则和梯度下降的基本认识。基于此,我们需要求对参数的导数,去更新迭代参数。 通过图13,再来回顾下我们的目标函数及概率公式。目标函数是一个累加和,那么只需要关注$logp(w_{t+j}|w_t)$的求导就可以了。依据前文$p(o | c)$的公式,得到图13中的$logp(o | c)$的公式。(注:图13中目标函数前面少一个负号,不过不影响计算过程,读者知道就好。)

前文提到,$\theta$参数包含两个矩阵$W$和$W’$,而$W$是由$v_1,v_2,…,v_V$等V个列向量组成,$W’$由$u_i,u_2,…,u_V$组成。 所谓求梯度,就是针对v和u进行求梯度。针对每一个输入$w_t$,假定对应的索引是c,可以从图7知道,到了映射层就只剩下$v_c$,也就是跟$v_i, i=1,…,V, i \neq c$无关。所以每一次迭代的求导,只需要求$\frac {dJ(\theta)}{v_c}$及$
\frac {dJ(\theta)}{u_i}, i=1,2,\ldots,V$。我们先推导$\frac {dJ(\theta)}{v_c}$,然后再去推导$
\frac {dJ(\theta)}{u_i}, i=1,2,\ldots,V$。如上一段所言,求$\frac {dJ(\theta)}{v_c}$,最核心的就是求$\frac {dlogp(o|c)}{dv_c}$。

到此处,值得注意的一个问题是,这里的求梯度,更像传统的机器学习的方法,设定一个目标函数,然后求梯度,然后去优化参数。在深度学习里,通常是计算误差,然后bp算法,就是误差反向传播。所以此处,就是要去优化这个目标函数$J(\theta)$。好, 我们继续推导。

下图14,其实同图13是一样的。m是窗口长度,T是文本长度,就是一共有多少个词(可重复)。u和v前文已经提到, o和c是分别是输入词和输出词的索引。

$\frac {dp(o|c)}{v_c}$可以分为下图15中的式子1的求导和式子2的求导。其中$v_c$和$u_o$都是列向量,${u_o^T}{v_c}$是实数。公式1的求导就非常简单,基本公式就可以得知。

下图16,就是式子2的推导。第一步,依据链式法则,可以从第一行推导到第二行。第二行中,变量名称w变为x,这是因为不要跟前面的w混淆,前面的w是累加和,后面的x,因为后面要用到。对累加和求导等于求导后的累加和,求导可以进到里面。然后再次利用链式法则,就可以得到倒数第二行。倒数第二行利用基本公式就可以得到最后一行。

通过前面的推导,将式子1和式子2进行整理,就得到了图17。至于后面的整理,读者自己看一看就能明白。值得说明和再思考的是结果。前面时$u_o$,这个是列向量,是$W’$矩阵的第o行转置后的列向量。 $p(x|c)$是模型(经过softmax)的输出,是个实数,也就是输入$w_t$,模型的输出,可以参看图7中的倒数第二列。所以,最后求导的结果是观察到的值$u_o$与给定输入输出的平均期望的差值。这一点,不理解也没关系。总之,至此,$\frac {J(\theta)}{dv_c}$已经求解了,然后就可以愉快的更新权重矩阵$W$,也就是$W$中的$v_c$列向量了。

前面时$J(\theta)$对$v_c$的求导,此处来求$J(\theta)$对$u_i$的导。这个求导应该分两种情况进行讨论求导,一个是当$i=o$时,也就是$J(\theta)$对$u_o$的求导,另一个是当$i=1,2,\ldots,V 且 i \neq o$时,$J(\theta)$对$u_i$的导。 这两种情况的求导也是比较容易的。可以看图18和图19。图18对应第一种情况$i=o$时的求导,图19对应第二种情况的求导。

至此,目标函数对于参数的求导已经完成,可以用梯度下降法进行迭代参数了。也是至此,skip-grams算法算是解析完了。

2.4 Continuous Bag of Words(CBOW)算法

前面2.1节提到,word2vec有两种算法,一个是skip-grams算法,一个是Continuous Bag of Words(CBOW)算法。2.2节和2.3节是skip-grams的精讲。有节讲解CBOW算法,由于CBOW算法与skip-grams算法比较类似,本节只说明原理及区别。

图20是skip-grams算法的框架,图21是CBOW算法的框架。我们已经很熟悉skip-grams算法了。输入层,然后是映射层,再然后是输出层。skip-grams的逻辑是,给定输入的单词$w_t$,去预测该词的上下文。而CBOW则是给定某个词的上下文,去预测该词。同样的,两个模型的最终优化的都是两个权重参数矩阵$W$和$W’$。CBOW多个词到映射层后,进行累加,得到映射层的向量,然后再乘以$W’$矩阵,得到输出,就这么多区别。图21中,$w_t$画的长度较长,实际上,它的长度是与$w_{t-2}$等是一致的,都是one hot编码后的向量。

我们再来看看,skip-grams 与 CBOW各自的优缺点,本文列举网页[11]的观点。 对于CBOW,优点是 1. 预测单词的概率是很自然的事情 2. 需要的内存少。缺点是:1. CBOW取的是上下文的平均值。比如,apple既可以是水果,也可以是公司,通过CBOW的方法会使apple的向量位于水果和公司之间。2.如果没有一个合适的优化,可能要训练很久。对于skip-grams的算法,优点是:1. skip-grams可以捕获一个单词的两种语义,比如apple,它将有两个向量,一个表示水果,一个表示公司。2. 具有negative sub-sampling的skip-grams通常泛化性能优于其他方法。

笔者认为,关于skip-grams能区分一个单词的两种语义,从模型上来看,似乎有那么些道理,但是两个向量不知道在哪里。同时关于CBOW的缺点2,我不太认可,对于神经网络而言,一般来说,一般赋予较小的随机初始值,训练都不会有太大问题。至于skip-grams效果好,这个倒是对,从Mikolov的paper的结果来看,skip-grams总体的确更好一些。

2.5 Hierarchical softmax

前文的2.2,2.3,2.4主要是讲skip-grams和CBOW两种算法。接下来的2.5,2.6,2.7节三节主要讲算法的优化方法。因为数据量的增加,对于大规模的数据集,需要提供更有效的训练方法。

我们可以注意到,在2.2节的两个参数矩阵中,我们要的是$W$矩阵,而$W’$和sotfmax层都不是我们要的,它们仅在训练的时候存在。但是从2.3节的求导,发现,每一个输入$w_t$,都要计算一遍$W’w_t$,同时,也要计算一遍每个词的softmax以得到概率。这个效率是非常低的,所以这里就引入了第一个方法,用Hierarchical softmax来替代映射层之后的$W’$矩阵及sotfmax层。这里等价于说,在新的框架里是,输入层到映射层之间不变,原先映射层之后的全部替换了,映射层到输出层是一个Hierarchical softmax。映射层经过Hierarchical softmax之后就能得到前文提到的概率p(o|c)。至于怎么得到的,就是下文要讲的。

Hierarchical中文翻译叫做分级的意思,Hierarchical softmax又简称H-softmax,该方法是由Morin and Bengio在2005年的paper[13]提出的,在word2vec里,作者利用了这种方法去提高训练速度。通过使用Hierarchical softmax替换传统的softmax,可以将复杂度由之前的O(V)降低至至O(logV)。

下面是细讲。

首先,我们需要有预备知识——哈夫曼树(Huffman Tree)。我们来看一下建立哈夫曼树的过程。

输入:权重为$(w_1,w_2,\ldots,w_n)$的n个节点。

输出:对应哈夫曼树。

  1. 将$(w_1,w_2,\ldots,w_n)$看做n颗树的森林,每个树仅有一个节点。
  2. 将森林里权重最小的两棵树进行合并,得到一棵树,将这两棵树分别做为新树的左右节点,且左节点权重小于右节点,树的根节点为左右两颗树的权重之和。
  3. 将之前权重最小的两棵树从森林里删除,并把新树加入森林。
  4. 重复2-3的步骤,直至森林里只剩一棵树为止。

下图22,是我从网上找的一个简单的哈夫曼树例子。建立哈夫曼树的好处是什么呢?就是对其进行哈弗曼编码(Huffman encoding),左侧为0,右边为1。可以看到这种长短不一的编码,权重越高,越接近根节点,编码长度越小。这种编码的期望编码长度是最小的。

在word2vec这里怎么用的呢?首先利用统计得到的单词的频率(或叫权重)按照上面的方法建立一个哈夫曼树,如下图图23所示。可以看到这颗哈夫曼树的叶子节点就是V个单词。不同的是这棵树的每个非叶子节点都有名称$n(w, j)$,且每个非叶子节点都有一个列向量$v’$(可以类比前文的$u$),维数也是d*1。同前文类似,这里的模型的参数就是$v$及$v’$,刚开始一样是随机初始化,然后进行梯度下降法。

那么如何怎么计算某个叶子节点的概率呢?可以参考下图24,值得关注的是,此处的部分符号与前文有些出入。我们希望的计算$p(o|c)$,$\sigma$表示sigmoid函数,$\sigma(x) = \frac {1}{1 + e^{-x}}$,h此处是$w_t$到映射层的列向量,等价于前文的$v_c$。$\sigma(-u) = 1 - \sigma(u)$这个是sigmoid函数的性质,通过展开公式就可以得到。

下图中,计算的是$p(o|c)$,计算的时候就是第一个分叉为左的概率*第二个分叉为左的概率*第三个分叉为右的概率。第一个分叉的概率为左侧的概率就是$\sigma(v_{w_2}’ * v_c)$,其中$v_c$等于图中$h$,是列向量, $v’$是模型中的参数。类似于前文的softmax是天生的将实数转化为概率的方法,sigmoid函数正式二分类的实数转成概率的方法。下面的概率计算类似,最终三个概率相乘,就得到了,我们想要的$p(o|c)$。

对比,2.2和2.3节,此处的概率计算只有很少词(图24中的只有3次),而之前是要计算V次。所以,复杂度从之前的O(V)降低为O(logV)。

2.6 Negative sampling

这里是Tomas Mikolov等人提出的方法。前文提到,Hierarchical softmax和Negative sampling两种都是优化方法,这两种优化方法是二选一的,类似于skip-grams和CBOW的二选一。

以skip-grams为例,原来的sotfmax的逻辑及目标函数的逻辑,就是我们预测的$w_{t-m},\ldots,w_{t-1},w_{t+1},\ldots,w_{t+m}$的概率最大化,非目标词概率最小化。每一个输入$w_t$,都要去改变目标词输出的权重$u_o$,同时也会更改非目标词的权重$u_i,i=1,\ldots,V, i \neq o$。每一次都是考虑了输出词的概率,也考察了所有非输出词。我们知道,所有非输出词才是众多的(V-2m-1个)。但是,我们需要思考一个问题,所有的非输出词有必要每次都考察吗?答案,没有那个必要。如果从二分类的视角去考察,$w_{t-m},\ldots,w_{t-1},w_{t+1},\ldots,w_{t+m}$这些输出是正,而其他所有词都是负。可以得知,负样本是非常多,基于此,Negative sampling的核心思想,就是对负样本进行抽样,从所有负样本中只抽取几个去更新权重。

在Negative sampling的思想下,就可以替换softmax层了。目标函数如下图图25。前文提到$\sigma$是用来转换成概率的,k是我们要用到的负样本的个数。左侧的$log\sigma(u_o^Tv_c)$是正样本的输出,我们希望此概率越大越好。而右侧是表示要抽出k个负样本,每个负样本j都要概率符合分布$P(w)$,然后他们的概率累加和是越小越好。sigmoid函数是单调递增函数,且恒大于零。

下图26中的目标函数公式更加清晰,值得关注的是,这里的目标函数是希望越大越好。如果想像以前一样,概率越小越好,前面加负号就可以了。所谓$P(w)$分布,就是原始单词的概率分布的概率开3/4次方,并归一化。也就是整体而言,原始样本中单词出现次数越高,越容易作为负样本进行训练。至于为何是3/4,这个是实验测的,换成其他的也没太多问题。

2.7 Subsampling of Frequent Words(高频词汇的重采样)

这一段是常用词降采样。这个更没什么好说的了。简单而言,就是不用所有的词训练。因为有次常用词,比如a、an、the等出现的次数很高,如果全部样本用来训练,可能也没太多必要。

最终,就是抽样,一个词出现频率越高,那它抽取的比例越低。这个比例怎么算呢?是采用一个公式。假定单词$w_t$在训练集中出现的概率(或叫比例)是$z(w_t)$,则我们会随机保留$P(w_t)$比例的$w_t$样本进行训练,其他不会训练。比例的计算公式如图27。

我们可以将$w(w_t)$与$P(w_t)$转换函数画出来,如图28。可以看到,此函数是单调递减的,也就是说,单词$w_t$原始比例越多,保留的比例越小。有几个重要节点,可以关注。

  1. $z(w_t) \leq 0.0026$时,$P(w_t) = 1$。
  2. $z(w_t) = 0.00746$时,$P(w_t) = 0.5$。
  3. $z(w_t) = 1$时,$P(w_t) = 0.033$。

以上就是word2vec的所有核心解析了。

2.8 word2vec效果展示

word2vec效果,大家通常喜欢讲这个公式 $king - man + woman = queen$。下面图29,30,31就是一些词汇在空间中的映射降维后的展示,可以明显看到,通过word2vec可以学到词与词之间的关系。

图29可以看到,word2vec学到了词语之间的矢量加法。图30表示,word2vec学会了词语之间的矢量关系,也就是内在关系。图31表示word2vec学会了词之间的过去时、过去完成时之间的转换关系。图32表示word2vec学会了国家与首都之间的矢量对应关系。

word2vec在翻译中的应用。当有了相匹配的语料进行训练时,词向量之间应该有相似的架构。在不同的语言之间,也应该保留这种相似性。下图33左侧是英文,右侧是西班牙语,经过旋转和缩放后,可以看到,相似的动物形成的词向量是很相近的。

三、 word2vec代码实践

明白了原理之后,我们就要去实践中用这些词向量了,这里是词向量的简单使用。一个是可以使用google基于google news训练好的词向量模型,一个是自己去训练词向量。推荐一个工具包gensim,使用的是python语言。下面我们会使用这个工具包进行加载google的word2vec模型,及自己训练模型。

3.1 使用google预训练好的模型

google的预训练好的模型,可以从此处[10]下载。大约1.5g,解压后3.4g。安装好gensim库后,调用就非常简单了。代码示例如下:

1
2
3
4
5
6
7
8
9
from gensim.models import KeyedVectors


g_w2v_model_file = 'GoogleNews-vectors-negative300.bin'
w2v_model = KeyedVectors.load_word2vec_format(get_abspath(g_w2v_model_file),
binary=True)
computer_vector = w2v_model['computer']
print(computer_vector)
print(computer_vector.shape)

上面的这段代码,完成了对预训练好的模型的加载,输出了computer的词向量及维数。

值得关注的是,该模型是区分大小写的,比如computer和Computer是不同的,但是总体还是很相似的。

一些关于google预训练的模型的疑问及解答[10]。

  1. 它包含停用词吗?

一些停用词a、an、and、of被此模型排除在外,但是the、also、should等此被保留了下来。

  1. 是否包含词汇的错拼/错写?

是的。比如mispelled和misspelled两次都在里面,其中,misspelled这个词是正确拼法/写法。

  1. 是否有常用的成对出现的词?

是。比如包含Soviet_Union(苏联)、New_York(纽约)。

  1. 是否包含数字?

并不直接是。 比如,你不能找到数字 100, 但是它的确包含类似于 ###MHz_DDR2_SDRAM ,这里假设 # 可以匹配任何数字。

3.2 自己语料训练模型

使用gensim也可以根据自己的数据集训练模型,用法也非常简单。

下面是训练的方法:

1
2
3
4
5
6
7
8
9
10
from gensim.models.word2vec import Word2Vec


texts = [["hello", "world"], ["we", "are", "right"]]
w2v_size = 300 # 词向量长度
w2v_min_count = 1 # 最低计入训练的词频
w2v_model_file = 'w2v.model' # word2vec模型保存文件名称
w2v_model = Word2Vec(sentences=texts, size=w2v_size,
min_count=w2v_min_count)
w2v_model.save(w2v_model_file)

下面是自己训练的模型的加载及使用方法

1
2
3
4
5
6
7
8
from gensim.models.word2vec import Word2Vec


w2v_model_file = 'w2v.model' # word2vec模型保存文件名称
w2v_model = Word2Vec.load(w2v_model_file)
we_vector = w2v_model['we']
print(we_vector)
print(we_vector.shape)

通过此部分,相信你已经可以进行一些实战了。关于word2vec的更多用法,建议可以去看gensim的官方文档。关于google预训练的模型的更多问题,可以查看博客[15]的分享及代码。

后续更新

如果有后续更新,将会在本网址更新: http://blog.yucc.me/p/a634168a/

参考资料

  1. Tomas Mikolov, Kai Chen, Greg Corrado, and Jeffrey Dean. Efficient Estimation of Word Representations in Vector Space. In Proceedings of Workshop at ICLR, 2013.
  2. Tomas Mikolov, Ilya Sutskever, Kai Chen, Greg Corrado, and Jeffrey Dean. Distributed Representations of Words and Phrases and their Compositionality. In Proceedings of NIPS, 2013.
  3. https://docs.google.com/file/d/0B7XkCwpI5KDYRWRnd1RzWXQ2TWc/edit
  4. https://code.google.com/archive/p/word2vec/
  5. http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
  6. http://mccormickml.com/2017/01/11/word2vec-tutorial-part-2-negative-sampling/
  7. http://mccormickml.com/2016/04/27/word2vec-resources/
  8. https://www.youtube.com/watch?v=ERibwqs9p38&list=PL3FW7Lu3i5Jsnh1rnUwq_TcylNr7EkRe6&index=3&t=0s
  9. https://www.youtube.com/watch?v=ASn7ExxLZws&list=PL3FW7Lu3i5Jsnh1rnUwq_TcylNr7EkRe6&index=4&t=0s
  10. https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit?usp=sharing
  11. https://www.analyticsvidhya.com/blog/2017/06/word-embeddings-count-word2veec/
  12. https://ronxin.github.io/wevi/
  13. Morin, Frederic, and Yoshua Bengio. “Hierarchical probabilistic neural network language model.” Aistats. Vol. 5. 2005.
  14. https://www.cnblogs.com/pinard/p/7160330.html
  15. http://mccormickml.com/2016/04/12/googles-pretrained-word2vec-model-in-python/
谢谢你,可爱的朋友。