词向量模型skip-gram以及负采样技术图解

一、前言

请勿全文复制转载!尊重劳动成果!

        在使用词向量之前,我们往往用one-hot向量来表示一个字词,这样有两个缺点:

             ① 维度过大。使用one-hot向量来表示字词,那么一个字词就需要一个词表大小的向量,而NLP任务中的词表往往是上万甚至上百万的,这会极大地增加神经网络中的参数数量。

             ② 距离一致。使用one-hot表示字词,会使得所有字词之间的相似性是一致的,而在我们的认知中,“苹果”和“梨子”要比“苹果”和“英语”更接近。

         而词向量能将原本用one-hot表示的字词压缩到更小的维度,而且相似字词之间的距离更小。那词向量是如何表示字词的这种相似性的呢。很简单,词向量通过字词的上下文语境来表示这个字词,而相似的字词往往拥有相似的上下文语境。比如“苹果”和“梨子”可能都经常和“吃”、“水果”等词一起出现,那么“苹果”和“梨子”的词向量就会比较接近,而“英语”的上下文语境往往是“说”、“学习”等,那么“苹果”、“梨子”与“英语”之间就会差异较大。

        要表达这种相似性,只需要建立这样一个模型:当输入“吃”的时候,模型输出“苹果”的概率较大,输出“英语”的概率较小。即输入一个字词,模型输出与这个字词共同出现的其他字词的概率。模型会学习到这种相似性,并将其体现到隐层权重之上。

       显然,上面的模型与语言模型极其相似,语言模型的任务是根据上下文预测下一个字词。因此我们可以借用语言模型来训练词向量,词向量从一开始就和语言模型是密不可分的。

二、词向量

        在开始讲解词向量之前,请你先思考这样一个问题:给你一个字词,需要你构建一个最简单的神经网络模型来预测下一个字词是什么。

        对于这个问题,不使用词向量,不使用RNN,我想你最先能想出来的就是下面这个两层的全连接模型了。

词向量模型skip-gram以及负采样技术图解_第1张图片

模型使用one-hot向量来表示一个字词,所以input层表示一个one-hot向量的输入,经过一个全连接层后得到一个隐藏层向量,在经过softmax层得到一个概率输出,概率最大对应的字词就是我们预测出的字词。

       实际上,这已经是一个最简单的语言模型了,只不过最早的NNLM的input层是连续的多个字词,而且隐藏层要更多。

以 “小明不但喜欢吃梨子,还喜欢吃苹果” 为例,观察上述模型。对于这句话,我们可以获得数据集如下:

(小明,不但)
(不但,喜欢)
(喜欢,吃)
(吃,梨子)
(还,喜欢)
(喜欢,吃)
(吃,苹果)

将上述数据放入模型训练,毫无疑问当出现“喜欢”时,下一个词大概率会是“吃”,而出现“吃”时,很有可能会是“梨子”或者“苹果”。

       其实,上述模型就是我们用来训练词向量的模型,与语言模型不同的是,语言模型通过前n个词预测下一个词,而词向量通过上下文来预测当前词,或者通过当前词来预测词上下文,而 "前n个词" 不也是上下文的一种吗。

三、skip-gram

        skip-gram是一种通过当前词预测上下文的词向量训练方法,为了更好地体现不同词之间的依赖情况,显然不能只选取当前词的前一个词作为上下文。为了选定当前词的上下文,skip-gram采取这样一种策略:记当前词为w_i,从和中随机选取n个词作为w_i的上下文,即:从当前词的前k个词和后k个词中选择n个。k和n均为超参数,n<=2k。

注:当语料库较小时,建议采用5-20等较大的k值,当语料库较大时,建议采用1-4等较小的k值。

以下面这句话为例,假设k为2,n为2,当前词为 “喜欢” 时,从前2个词和后2个词中假设随机选取了“小明”和“梨子”作为上下文,于是得到两组训练数据:(喜欢,小明)、(喜欢,梨子)

而对大量语句按如上方法提取出训练数据,输入模型训练完毕后,模型就能够学习到具有相似上下文语境的字词表示,训练完成后的W1矩阵就是我们需要的词向量矩阵。为了进一步理解这个概念,我们分析模型的前向传播过程,为了方便理解,假设词表大小为5,模型隐藏层大小为4,该模型描述如下:

词向量模型skip-gram以及负采样技术图解_第2张图片

为了简单起见,我使用w1表示权重矩阵W1中的所有元素,使用w2表示权重矩阵W2中的所有元素,使用x1表示隐藏层中间向量中X1的所有元素,使用x2表示softmax输出的X2中的所有元素(大写表示矩阵或向量,小写表示元素)。

由于许多词的上下文语境可能会很相似,如对于“苹果”和“梨子”,出现下面的(input,label)对的情况可能非常多:

(梨子,吃)         (苹果,吃)
(梨子,水果)       (苹果,水果)

两个词的上下文语境相似,体现在训练数据中就是他们对应的label会有许多重合。观察模型的后半部分,当模型训练完成,W2固定,在softmax output层要输出相似的label,那么softmax output层的输入也要相似,即“苹果”和“梨子”的hidden layer层的输出(即X1)会比较相似。实际上,X1就是我们要求的词向量。

再观察模型的前半部分,hidden layer层是输入层的one-hot向量与权重矩阵W1做矩阵运算的结果,记输入的one-hot向量为1的元素下标为i,与W1做矩阵运算后的结果实际就是W1的第i行向量,因此,W1就是我们要求的词向量矩阵,它是整个词汇表中的字词的词向量在0轴上的堆叠

四、负采样

        Softmax的计算公式为:

                                                                              p(x_i)=\frac{e^{x_i}}{\sum_{j=1}^{voc}e^{x_j}}

其中,voc是词汇表的长度,而在我们的语料库中,词汇表的的长度往往高达数十万,使用softmax做一个数十万类的分类任务,这个计算量显然太大了,因此,我们需要优化模型中最后的softmax层,而对于一个类别过大的分类任务,我们可以使用负采样技术。

记词汇表大小为vocab_size,softmax层每次输出一个vocab_size的的向量,向量中的每个元素代表预测是该词的概率,然后我们选取概率最大的作为预测标签,标记为1,其余vocab_size-1个元素全部标记为0,这实际上可以看做是vocab_size个二元分类器的集合,训练的过程就是使得正确标签对应的二元分类器能够输出1,而其他二元分类器输出0。

而我们的模型要做的,就是对于某个输入,将正确的类别标签与其他标签分离开来。softmax所做的是计算所有标签的可能性,然后增大正确标签所对应的概率、减小其他标签的概率来将其与其他所有标签分离。这是一个非常庞大而且严格的分离过程,为什么说它庞大严格?对于一个词汇表大小为100,000的词向量训练模型来说,每次需要将1个标签与其他99,999个标签分离,这显然很庞大,其次,每次训练都增大其中一个标签的预测概率,而其他99,999个标签的概率全部都减小,这显然是严格的,因为在词向量模型中,同样的输入可能会对应不同的正确输出,如(梨子,吃)和(梨子,水果),当输入梨子时,输出可以是吃,也可以是水果。

那么,为了将正确标签与其他标签分离开来,我们无需每次都计算所有标签,而是只计算正确标签和一部分其他标签,即从错误标签中随机选取部分,与正确标签作为对照,以将正确标签从其他标签中分离,这就是负采样技术。

回到词向量中来,仍旧以“小明不但喜欢吃梨子” 为例,当中心词为“喜欢”时,我们得到了(喜欢,小明)和(喜欢,梨子)两组数据。

我们只看(喜欢,小明)这组数据,对于“喜欢” 来说,“小明”就是他的正标签,而对应的,我们从词汇表中选取num_sample个词作为负标签,以形成对照,于是得到如下训练数据:

词向量模型skip-gram以及负采样技术图解_第3张图片

图中示例num_sample=2。记中心词为wi,正标签词为C,负标签词集合为C_{nce},则我们需要最大化以下函数:

                                  J=logQ(label=1|w_i,C)+\sum_{c_i\in C_{nce}}^{ }logQ(label=0|w_i,c_i)

其中logQ(label=1|w_i,C)是当wi出现时看到C同时出现的概率。

        在tensorflow中提供了tf.nn.nce_loss()函数能够很方便地实现以上功能,在tensorflow中称之为噪声对比估算 (NCE) 损失,它的具体实现原理如下:

词向量模型skip-gram以及负采样技术图解_第4张图片

        同样的使用的是之前的词向量训练模型,我们只关注后半部分,tf.nn.nce_loss()不会在整个词汇表范围上做计算,而是首先拿出真实标签的下标,并从其他标签中随机选取num_sample个,如图中红色部分标注的真实标签(index=3)和两个随机噪声标签(index=0,index=2),根据选取出来的下标从权重矩阵W2中分理出第0,2,3列,与hidden layer中的X1做矩阵运算,得到x_{2,0},x_{2,2},x_{2,3},之后对x_{2,0},x_{2,2},x_{2,3}使用sigmoid函数激活得到X3,与从true label中分离出来的label计算sigmoid cross entropy,输出loss。

        这也就是为什么有人说使用负采样技术实际上是只更新部分权重,效率比完整的softmax快很多的原因,使用负采样技术后,每次训练迭代更新的都只是真实标签和num_sample个噪声标签所对应的权重。

然后介绍一下tf.nn.nce_loss()

def nce_loss(weights,
             biases,
             labels,
             inputs,
             num_sampled,
             num_classes,
             num_true=1,
             sampled_values=None,
             remove_accidental_hits=False,
             partition_strategy="mod",
             name="nce_loss"):

参数解释:

weights:上述W2权重矩阵
biases:与W2对应的偏移矩阵
labels:真实标签的对应下标,shape=[batch_size,1]
inputs:上述hidden layer的输出,其实就是词向量集合,shape=[batch_size, embed_size]
num_sampled:负采样个数
num_classes:完整分类数,其实就是词表大小

因此,在使用tf.nn.nce_loss()函数时,我们的输入数据仍旧是下面这种形式:

(喜欢,小明)
(喜欢,梨子)

函数会自动帮我们针对每一组输入进行负采样,以输出噪声对比估算损失。

你可能感兴趣的:(机器学习,负采样,词向量,skip-gram,自然语言处理,语言模型)