word2vec模型之Skip-Gram Model
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了word2vec模型之Skip-Gram Model相关的知识,希望对你有一定的参考价值。
参考技术A 本文介绍一种基于神经网络结构的Word2Vec模型,Word2Vec是目前NLP领域的基础知识,这里仅对Word2Vec模型中的Skip-Gram模型进行详细介绍。Skip-Gram神经网络模型是一种非常简单的神经网络结构,是一个仅有一个Hidden Layer的神经网络结构。Skip-Gram模型的训练过程可以视作一个“Fake Task(伪任务)”,为何称之为“Fake Task”?是因为训练该模型的目的并不是将训练好的模型用于任何的分类任务,而是为了学习得到隐层的权重矩阵,而通过这些矩阵我们会得到想要的单词的特征向量,总体框架入下图所示。下面详细介绍这个Skip-Gram模型的训练过程。
给定一个特定的word作为输入,我们从该word的附近随机挑选一个word,该网络模型会告诉我们词汇表中的每个单词出现在“附近”的概率。这里的“附近”指的是在特定window size范围内。输出概率与在输入词附近找到每个单词的可能性有关。这里,我们使用文本中指定window size内的word pair(inputword,outputword)来训练神经网络模型。word pairs的获取方式如下图所示。
这里详细介绍一下Skip-Gram模型的训练过程。首先,神经网络模型只接受数值型的输入,故不能直接将每个单词直接输入到一个神经网络中,故而需要一种针对神经网络模型的单词表示方式,为此需要针对训练集中的所有不同的单词构建一个词汇表(vocabulary),然后将词汇表中的每个单词以 One-Hot编码 的方式进行表示。比如,现在有一个大小为10000的词汇表,我们需要为每个单词构建一个One-Hot向量,要求每个单词对应的当前单词的位置为1,其他所有位置为0,因此我们会得到10000个长度为10000的向量,其中每个向量都只有一个位置为1。神经网络的输出是一个10000维的向量,表示针对输入单词,词汇表中所有的单词出现在输入单词附近的预测概率。如下图所示:
上述的神经网络结构隐层中的神经元没有激活函数,但输出层的每个神经元使用了softmax函数。训练的过程使用word pair(inputword,outputword),输入是一个One-Hot的向量,输出的也是一个表示输出单词的One-Hot的向量。但是当在一个输入词在训练好的网络上计算时,输出的向量实际上是一个概率分布,并不是一个One-Hot向量。因为每个输出的单元使用了 Softmax ,且没有激活函数。
同样的针对上述问题,有10000个单词,假设需要为每个单词学习一个300维的向量,那么隐层可以由一个10000*300的矩阵来表示(300个神经元,每个神经元都有一个10000维的权重向量),如下图所示。
竖着看这个隐层的权重矩阵,每一列对应一个神经元中的参数向量,而如果横着看这个权重矩阵,每一行就是一个300维的向量,而这这就是我们需要通过学习得来的词向量!也就是说,10000个单词的向量表示就是这个10000*300的矩阵,每行对应一个单词的向量表示。那么Skip-Gram最终的目的就是学习这个隐层的权重矩阵。而为什么针对词汇表里的单词要进行One-Hot编码,这里解释一下。如下图所示,如果我们用一个One-Hot向量乘以这个权重矩阵,那么得出的向量结果就是对应单词的特征表示。这意味着这个模型的隐层实际上只是作为一个查找表,而隐层的输出则是输入的单词的“词向量(word vector)”。
输出层为softmax回归分类器,每个输出神经元(词汇表中的每个单词都有一个对应的输出神经元)将产生0到1之间的输出,所有这些输出值的总和将等于1。具体来说,每个输出神经元都有一个权重向量,它将权重向量与隐层中的向量相乘,然后将指数函数应用于结果。最后,为了使输出之和达到1,我们将这个结果除以来自所有10,000个输出节点的结果之和。如下图所示:
如果两个不同的单词有非常相似的“上下文”(也就是说,它们周围可能出现什么单词),那么该模型应当为这两个单词输出非常相似的结果。网络输出这两个单词相似上下文预测的一种表达形式就是这两个单词的单词向量相似。换言之,如果两个单词有相似的上下文,那么该网络就有能力为这两个单词出学习相似的单词向量!
以上部分介绍了Skip-Gram模型的具体实现思路,接下来会针对Skip-Gram在实际训练中的一些问题进行优化。通过分析上述的Skip-Gram神经网络模型,可以发现一个问题,由于需要为每个单词学习一个固定长度的向量表示,因此以上面的例子为例,当需要训练10000个单词的300维的向量表示时,我们需要计算出300万个权重值。而在更大的数据集上,这样的训练过程是十分缓慢的,基本上不可行,因此Skip-Gram的作者针对这个问题提出了几种解决方案。常用的方案有Subsampling frequent words和Negative Sampling,接下来会详细介绍这两种解决方案。
Subsampling主要目的是通过削减训练集的训练样本数来降低训练代价。由于在文本中,许多单词出现的频率很高,这就导致了这个单词对应的word pair (inputword,outputword)在训练集中的数量会非常多,因此需要针对这些高频词进行二次采样以降低这些高频词在训练集中的规模。具体采样策略如下:
假设 w i 表示词汇表中的第 i 个单词, z(w i ) 表示该词在语料库中出现的频率。例如一个单词 w i 在大小为10000的语料库中出现的次数为100,那么 z(w i ) =0.01。知道了每个单词在语料库中的出现频率之后,那么对于每个单词 w i 的subsampling采样率如下:
该函数有一些有趣的点:
Subsampling虽然能明显地缩小训练神经网络模型时的训练集大小,但是并不能从根本上解决隐层矩阵规模大而带来的计算问题。也就是说,对于训练集中的每个样本,每次训练都需要更新隐层中的所有参数,因此Skip-Gram模型的作者又提出了另外一种方式来优化计算问题。
由于训练神经网络模型为了达到更高的精度,需要通过训练样本中每次细微地调整每个神经元的权重参数,因此每个训练每个训练样本都会微调神经网络中的所有参数。由于SubSampling在极限情况下,对训练集的削减程度不会低于原规模的3.3%,然而 ,这种程度的削减对于一个字典特别大的训练场景的影响是微弱的。为此作者又提出了一种Negative Sampling的方式。
Negative Sampling通过让每个训练样本只修改一小部分权重(而不是网络中的全部权重)来解决计算量特别大的问题。接下来可以看一下Negative Sampling的工作原理。
正常情况下,我们对每个单词语料训练神经网络模型,模型的输出是一个one-hot的向量。在Negative Sampling时,我们随机选择若干个(假设5个)negative word去更新对应的权重,(这里Negative word 对应的时One-Hot向量中值为0的单词,而我们的目标单词可以理解为Positive word,即对应One-Hot向量中值为1的单词)。
回想一下,我们的模型输出层有个300×10000的权重矩阵,如果每个训练样本只更新5个negative word和当前的positive word对应的权重,那么每次训练对应输出层只需要更新6*300个权重,此时更新比例只有6/10000=0.06%。
上面提到了,针对不同的数据集,Negative Sampling会选择2-20个negative word,下面介绍一下如何挑选这个Negative word。首先针对一个语料库,每次Negative Sampling挑选出的样本的可能性取决于该样本在语料库中出现的频数。
其中 f ( w i )表示单词 w i 在语料库中出现的频数。作者在他们的论文中指出,他们尝试了这个公式的一些变体,其中表现最好的是将每个单词出现的频数提高到3/4次方。如下所示:
处理一些样本之后会发现,与简单的公式相比,这个公式有增加不太频繁单词的概率和减少更频繁单词的概率的趋势。以上就是对Negative Sampling的一些简单描述。
Word Pairs and “Phrases”的主要思想是将经常成对出现或者某个短语当成一个Word,以此来降低整个训练过程中的计算复杂度。该方法在自然语言处理中有很大的应用场景。
参考:
1. http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
2. http://mccormickml.com/2017/01/11/word2vec-tutorial-part-2-negative-sampling/
word2vec 之 skip-gram
参考技术AWord2vec 主要有两种形式,CBOW 和Skip-gram,其中CBOW是通过上下文context来预测当前位置词,SKip-gram则是通过当前词来预测上下文
Fake Task
word2vec 实际上分为两部分,1,建立模型,2,通过模型获取词的嵌入向量(隐层参数)。整个过程与自编码器的思想类似,即基于训练数据训练一个神经网络,模型训练好后,并不会用这个模型处理后续的任务,真正需要的是这个模型学到的参数,如隐层的权重矩阵,基于训练数据建模的过程叫“Fake Task”,意味着建模并不是我们最终的目的。
Train
如何训练我们的神经网络模型?假如我们有一个句子“ The dog barked at the mailman”。
首先,我们选择句子中一个词作为我们的input word, 如 dog
然后,我们需要定义一个skip_window参数,来指定上下文的大小,即input word 一侧选取词的数量,假如skip_window=2,那将从dog出发向左右两个方向取最近的两个word,即(the, dog,barked,at),此时的span = skip_window * 2 + 1 = 5
另一个需要定义的参数是num_skips,即从上下文中选取多少个word来作为output word,这个参数应该小于等于2 * skip_window,即最多将所有上下文都作为output,但是不能重复。如设置num_skips = 2,此时从上下文选取2个词作为output,如(the, barked),最终我们将得到两组训练数据(dog, the) (dog, barked)
神经网络将基于这些训练数据输出一个概率分布,这个概率分布代表着在输入数据下,词典中每个词是output的概率。如拿数据(dog, barked)来训练,则模型将会告诉我们每个单词是’barked’的概率大小。
模型的输出概率代表着词典中每个单词有多大可能性跟input word同时出现。举个栗子,如果我们向神经网络模型中输入一个单词“Soviet“,那么最终模型的输出概率中,像“Union”, ”Russia“这种相关词的概率将远高于像”watermelon“,”kangaroo“非相关词的概率。因为”Union“,”Russia“在文本中更大可能在”Soviet“的窗口中出现。
我们将通过给神经网络输入文本中成对的单词来训练它完成上面所说的概率计算。下面的图中给出了一些我们的训练样本的例子。我们选定句子“The quick brown fox jumps over lazy dog”,设定我们的窗口大小为2(window_size = 2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。
模型将会从每队单词出现的次数中习得统计结果,模型可能会得到更多的(’soviet’, ‘union’)样本对,而(soviet, dog)这样的组合看到的很少。因此,当模型训练完成后,给定一个单词 soviet,输出结果中union 或者russia会比dog有更高的概率。
输入
常用做法是用训练文档构建词汇表,然后再对单词进行0ne-hot编码。
编码后的向量,形如dog = [0, 0, 1, 0, …0], 如果词汇表大小为10000, 那这个向量包含了10000的概率,即为当前词为输入的概率
下图是神经网络结构:
我们基于成对的单词来对神经网络进行训练, 训练样本是(input word, output word)这样的单词对,input word 和 output word都是one-hot编码的向量,最终的模型输出是一个概率分布。
隐层
如果我们想要用300个特征来表示一个词(即每个词是300维的向量),即隐层有300个神经元,隐层的权重为10000 * 300的矩阵,下图中的左右两个图代表了不同角度看隐层权重,左图中每列代表一个10000维的词向量与隐层单个神经元的连接权重,右图每行代表了一个单词的词向量。
我们最终的目标就是学习这个隐层权重矩阵。
输入被one-hot编码后,实际上只有一个位置不为0,所以这个向量相当稀疏,那如果我们将1 10000的向量与10000 300的矩阵相乘,相当消耗计算资源,为了高效计算,仅仅会选择矩阵中对应的向量中纬度为1的索引行
即实际不会进行矩阵乘法计算,而是根据输入向量中不为0 的维度去索引。这样模型中的隐层权重矩阵便成了一个查找表(lookup table),输出就是输入单词的嵌入词向量
输出层
隐层的输出是一个1*300的向量,而输出层是一个softmax回归分类器,他的每个结点将会输出一个0-1之间的值(概率),而结点的概率之和为1.
我们会发现Word2Vec模型是一个超级大的神经网络(权重矩阵规模非常大)。
举个栗子,我们拥有10000个单词的词汇表,我们如果想嵌入300维的词向量,那么我们的输入-隐层权重矩阵和隐层-输出层的权重矩阵都会有 10000 x 300 = 300万个权重,在如此庞大的神经网络中进行梯度下降是相当慢的。更糟糕的是,你需要大量的训练数据来调整这些权重并且避免过拟合。百万数量级的权重矩阵和亿万数量级的训练样本意味着训练这个模型将会是个灾难(太凶残了)。
Word2Vec的作者在它的第二篇论文中强调了这些问题,下面是作者在第二篇论文中的三个创新:
事实证明,对常用词抽样并且对优化目标采用“negative sampling”不仅降低了训练过程中的计算负担,还提高了训练的词向量的质量。
word pairs and phases
一些单词组合的含义和拆开以后具有完全不同的意义,比如 New York,单独的New 和York无法表达这个词组的含义。因此,应该把New York作为一个单独的词组来生成其词向量。
对高频词抽样
对于高频词,如 the ,按上面的处理方式会有两个问题:
如果直接删除掉这些高频词,会有两个问题
1.删除后,the这个单词永远也不会出现在我们的上下文窗口
2.训练样本会减少
所以word2vec 采用抽样的方式来解决这种高频词问题。他的基本思想是:对于我们在训练原始文本中遇到的每一个单词,他们都有一定概率被我们从文本中删除掉,而这个被删除的概率与单词的频率有关。
wi 是一个单词,Z(wi)是这个单词在所有预料中出现的频次。P(wi)是被保留的概率。
负采样
训练一个神经网络意味着要输入训练样本并且不断的调整神经元的权重,不断提高对目标的准确预测。而vocabulary的大小决定了skip-gram神经网络将拥有大规模的权重矩阵,所有的这些权重需要通过我们数以亿计的样本来训练调整,非常消耗计算资源,并且实际中会非常慢。
负采样解决了这个问题,不同于原本每个训练样本更新所有权重,负采样每次让一个训练样本仅仅更新一部分权重,减小计算量。
对于训练样本(fox,quick),都是经过one-hot编码的,当vocabulary的大小为10000时,我们期望输出对应的quick单词的那个神经元的输出是1,其余9999个都是0,这9999个输出为0的神经元所对应的单词称为negative word
隐层-输出层拥有300 10000的权重,而负采样时,我们仅仅更新quick 和我们选择的其他5个negative word的结点对应的权重,共6个神经元,300 6 = 1800 个权重,相当于只计算了0.06%的权重,计算效率大大提高。
其中f(wi)代表每个单词出现的频次,p(wi)代表被选中的概率。
负采样的C语言实现非常的有趣。unigram table有一个包含了一亿个元素的数组,这个数组是由词汇表中每个单词的索引号填充的,并且这个数组中有重复,也就是说有些单词会出现多次。那么每个单词的索引在这个数组中出现的次数该如何决定呢,由公式P(wi) * table_size,也就是说计算出的负采样概率*1亿=单词在表中出现的次数。
有了这张表以后,每次去我们进行负采样时,只需要在0-1亿范围内生成一个随机数,然后选择表中索引号为这个随机数的那个单词作为我们的negative word即可。一个单词的负采样概率越大,那么它在这个表中出现的次数就越多,它被选中的概率就越大。
以上是关于word2vec模型之Skip-Gram Model的主要内容,如果未能解决你的问题,请参考以下文章