论文:1. Distributed Representations of Words and Phrases and their Compositionality
2. Efficient Estimation of Word Representations in Vector Space
源码:gensim

背景

用低维实值向量表示word,而不使用one-hot来表示?

  1. One-hot编码稀疏,高维数据中不适用。
  2. One-hot编码不能捕捉到上下文(context)信息。

Word2vec是从大量文本语料中以无监督的方法学习语义知识的一种模型,广泛地用于自然语言处理当中。实际上Word2vec是通过学习文本来用词向量的方式表征词的语义信息,即通过一个嵌入空间使得语义上相似的单词在该空间内距离很近。Embedding是一个映射,将单词从原先所属的空间映射到新的多维空间中,也就是把原先词所在空间嵌入到一个新的空间中去。

模型

Word2vec主要包含Skip-Gram和CBOW两种模型。从直观上理解,Skip-Gram是给定input word来预测上下文。而CBOW是给定上下文,来预测input word。本文仅讲解常用的Skip-Gram模型。

Word2Vec模型实际上分为了两个部分:第一部分为建立模型,第二部分是通过模型获取嵌入词向量。Word2Vec的整个建模过程实际上与自编码器(auto-encoder)的思想很相似,即先基于训练数据构建一个神经网络,当这个模型训练好以后,我们并不会用这个训练好的模型处理新的任务,我们真正需要的是这个模型通过训练数据所学得的参数,例如隐层的权重矩阵——后面我们将会看到这些权重在Word2Vec中实际上就是我们试图去学习的“word vectors”。

训练样本

假设选定句子“The quick brown fox jumps over lazy dog”,设定我们的窗口大小为2(window_size=2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。

模型将会从每对单词出现的次数中习得统计结果。例如,我们的神经网络可能会得到更多类似(‘Soviet’,‘Union’)这样的训练样本对,而对于(‘Soviet’,‘Sasquatch’)这样的组合却看到的很少。因此,当我们的模型完成训练后,给定一个单词’Soviet’作为输入,输出的结果中’Union’或者’Russia’要比’Sasquatch被赋予更高的概率。

模型输入

如何来表示这些单词?

首先,我们都知道神经网络只能接受数值输入,我们不可能把一个单词字符串作为输入,因此我们得想个办法来表示这些单词。最常用的办法就是基于训练文档来构建我们自己的词汇表(vocabulary)再对单词进行one-hot编码。

假设从我们的训练文档中抽取出10000个唯一不重复的单词组成词汇表。我们对这10000个单词进行one-hot编码,得到的每个单词都是一个10000维的向量,向量每个维度的值只有0或者1,假如单词ants在词汇表中的出现位置为第3个,那么ants的向量就是一个第三维度取值为1,其他维都为0的10000维的向量。

还是上面的例子,“The dog barked at the mailman”,那么我们基于这个句子,可以构建一个大小为5的词汇表(忽略大小写和标点符号):(“the”, “dog”, “barked”, “at”, “mailman”),我们对这个词汇表的单词进行编号0-4。那么”dog“就可以被表示为一个5维向量[0, 1, 0, 0, 0]。

模型的输入如果为一个10000维的向量,那么输出也是一个10000维度(词汇表的大小)的向量,它包含了10000个概率,每一个概率代表着当前词是输入样本中output word的概率大小。

神经网络结构如下所示:

隐层没有使用任何激活函数,但是输出层使用了sotfmax。

我们基于成对的单词来对神经网络进行训练,训练样本是 (input word, output word) 这样的单词对,input word和output word都是one-hot编码的向量。最终模型的输出是一个概率分布。

隐层

说完单词的编码和训练样本的选取,我们来看下我们的隐层。如果我们现在想用300个特征来表示一个单词(即每个词可以被表示为300维的向量)。那么隐层的权重矩阵应该为10000行,300列(隐层有300个结点)。
Google在最新发布的基于Google news数据集训练的模型中使用的就是300个特征的词向量。词向量的维度是一个可以调节的超参数(在Python的gensim包中封装的Word2Vec接口默认的词向量大小为100,window_size为5)。

看下面的图片,左右两张图分别从不同角度代表了输入层-隐层的权重矩阵。左图中每一列代表一个10000维的词向量和隐层单个神经元连接的权重向量。从右边的图来看,每一行实际上代表了每个单词的词向量。

所以我们最终的目标就是学习这个隐层的权重矩阵。

上面我们提到,input word和output word都会被我们进行one-hot编码。仔细想一下,我们的输入被one-hot编码以后大多数维度上都是0(实际上仅有一个位置为1),所以这个向量相当稀疏,那么会造成什么结果呢。如果我们将一个1 x 10000的向量和10000 x 300的矩阵相乘,它会消耗相当大的计算资源,为了高效计算,它仅仅会选择矩阵中对应的向量中维度值为1的索引行,如下图所示。

我们来看一下上图中的矩阵运算,左边分别是1 x 5和5 x 3的矩阵,结果应该是1 x 3的矩阵,按照矩阵乘法的规则,结果的第一行第一列元素为0×17+0×23+0×4+1×10+0×11=100 \times 17+0 \times 23+0 \times 4+1 \times 10+0 \times 11=10,同理可得其余两个元素为12,19。如果10000个维度的矩阵采用这样的计算方式是十分低效的。

为了有效地进行计算,这种稀疏状态下不会进行矩阵乘法计算,可以看到矩阵的计算的结果实际上是矩阵对应的向量中值为1的索引,上面的例子中,左边向量中取值为1的对应维度为3(下标从0开始),那么计算结果就是矩阵的第3行(下标从0开始)—— [10,12,19],这样模型中的隐层权重矩阵便成了一个“查找表”(lookup table),进行矩阵计算时,直接去查输入向量中取值为1的维度下对应的那些权重值。隐层的输出就是每个输入单词的“嵌入词向量”。

输出层

经过神经网络隐层的计算,ants这个词会从一个1x10000的向量变成1x300的向量,再被输入到输出层。输出层是一个softmax回归分类器,它的每个结点将会输出一个0-1之间的值(概率),这些所有输出层神经元结点的概率之和为1。

下面是一个例子,训练样本为 (input word: “ants”, output word: “car”) 的计算示意图。

高效训练

根据上述模型,我们会发现Word2Vec模型是一个超级大的神经网络(权重矩阵规模非常大)。

举个栗子,我们拥有10000个单词的词汇表,我们如果想嵌入300维的词向量,那么我们的输入-隐层权重矩阵和隐层-输出层的权重矩阵都会有 10000 x 300 = 300万个权重,在如此庞大的神经网络中进行梯度下降是相当慢的。更糟糕的是,你需要大量的训练数据来调整这些权重并且避免过拟合。百万数量级的权重矩阵和亿万数量级的训练样本意味着训练这个模型将会是个灾难。

Word2Vec的作者在它的第二篇论文中强调了这些问题,下面是作者在第二篇论文中的三个创新:

  1. 将常见的单词组合(word pairs)或者词组作为单个“words”来处理。
  2. 对高频次单词进行抽样来减少训练样本的个数。
  3. 对优化目标采用“negative sampling”方法,这样每个训练样本的训练只会更新一小部分的模型权重,从而降低计算负担。

事实证明,对常用词抽样并且对优化目标采用“negative sampling”不仅降低了训练过程中的计算负担,还提高了训练的词向量的质量。

Word pairs and “phases”

论文的作者指出,一些单词组合(或者词组)的含义和拆开以后具有完全不同的意义。比如“Boston Globe”是一种报刊的名字,而单独的“Boston”和“Globe”这样单个的单词却表达不出这样的含义。因此,在文章中只要出现“Boston Globe”,我们就应该把它作为一个单独的词来生成其词向量,而不是将其拆开。同样的例子还有“New York”,“United Stated”等。

在Google发布的模型中,它本身的训练样本中有来自Google News数据集中的1000亿的单词,但是除了单个单词以外,单词组合(或词组)又有3百万之多。

论文中“learning phrase”这一章中介绍模型如何进行文档中的词组抽取。

高频词抽样

以原始文本“The quick brown fox jumps over the laze dog”为例,如果我使用大小为2的窗口,那么我们可以得到上图中展示的那些训练样本。

但是对于“the”这种常用高频单词,这样的处理方式会存在下面两个问题:

  1. 当我们得到成对的单词训练样本时,(“fox”, “the”) 这样的训练样本并不会给我们提供关于“fox”更多的语义信息,因为“the”在每个单词的上下文中几乎都会出现。
  2. 由于在文本中“the”这样的常用词出现概率很大,因此我们将会有大量的(“the”,…)这样的训练样本,而这些样本数量远远超过了我们学习“the”这个词向量所需的训练样本数。

Word2Vec通过“抽样”模式来解决这种高频词问题。它的基本思想如下:对于我们在训练原始文本中遇到的每一个单词,它们都有一定概率被我们从文本中删掉,而这个被删除的概率与单词的频率有关。

如果我们设置窗口大小span=10(即skip_window=5),并且从我们的文本中删除所有的“the”,那么会有下面的结果:

  1. 由于我们删除了文本中所有的“the”,那么在我们的训练样本中,“the”这个词永远也不会出现在我们的上下文窗口中。
  2. 当“the”作为input word时,我们的训练样本数至少会减少10个。

抽样率根据以下公式计算:

P(w_i)=(Z(w_i)0.001+1)×0.001Z(w_i)P\left(w\_{i}\right)=(\sqrt{\frac{Z\left(w\_{i}\right)}{0.001}}+1) \times \frac{0.001}{Z\left(w\_{i}\right)}

w_iw\_i是一个单词,Z(w_i)Z(w\_i)w_iw\_i这个单词在所有语料中出现的频次。参数sample=0.001,值越小意味着这个单词被保留下来的概率越小(即有越大的概率被我们删除)。

图中x轴代表着Z(w_i)Z(w\_i),即单词w_iw\_i在语料中出现频率,y轴代表某个单词被保留的概率。

负采样(negative sampling)

训练一个神经网络意味着要输入训练样本并且不断调整神经元的权重,从而不断提高对目标的准确预测。每当神经网络经过一个训练样本的训练,它的权重就会进行一次调整。

正如我们上面所讨论的,vocabulary的大小决定了我们的Skip-Gram神经网络将会拥有大规模的权重矩阵,所有的这些权重需要通过我们数以亿计的训练样本来进行调整,这是非常消耗计算资源的,并且实际中训练起来会非常慢。

负采样(negative sampling)解决了这个问题,它是用来提高训练速度并且改善所得到词向量的质量的一种方法。不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯度下降过程中的计算量。

当我们用训练样本 ( input word: “fox”,output word: “quick”) 来训练我们的神经网络时,“ fox”和“quick”都是经过one-hot编码的。如果我们的vocabulary大小为10000时,在输出层,我们期望对应“quick”单词的那个神经元结点输出1,其余9999个都应该输出0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称为“negative” word。

当使用负采样时,我们将随机选择一小部分的negative words(比如选5个negative words)来更新对应的权重。我们也会对我们的“positive” word进行权重更新(在我们上面的例子中,这个单词指的是”quick“)。

在论文中,作者指出指出对于小规模数据集,选择5-20个negative words会比较好,对于大规模数据集可以仅选择2-5个negative words。

回忆一下我们的隐层-输出层拥有300 x 10000的权重矩阵。如果使用了负采样的方法我们仅仅去更新我们的positive word-“quick”的和我们选择的其他5个negative words的结点对应的权重,共计6个输出神经元,相当于每次只更新1800个权重。对于3百万的权重来说,相当于只计算了0.06%的权重,这样计算效率就大幅度提高。

如何选择negative words?
我们使用“一元模型分布(unigram distribution)”来选择“negative words”。

要注意的一点是,一个单词被选作negative sample的概率跟它出现的频次有关,出现频次越高的单词越容易被选作negative words。

在word2vec的C语言实现中,你可以看到对于这个概率的实现公式。每个单词被选为“negative words”的概率计算公式与其出现的频次有关。计算公式如下:

P(w_i)=f(w_i)3/4_j=0n(f(w_j)3/4)P\left(w\_{i}\right)=\frac{f\left(w\_{i}\right)^{3 / 4}}{\sum\_{j=0}^{n}\left(f\left(w\_{j}\right)^{3 / 4}\right)}

每个单词被赋予一个权重,即f(w_i)f(w\_i), 它代表着单词出现的频次。公式中开3/4的根号完全是基于经验的,论文中提到这个公式的效果要比其它公式更加出色。

参考:

  1. http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
  2. https://zhuanlan.zhihu.com/p/27234078

联系作者