本文目录一览:
Word2Vec教程-Skip-Gram模型
原文: Word2Vec Tutorial - The Skip-Gram Model
skip-gram实际上是非常简单的神经网络模型形式;我认为任何所有微小的调整和变化都会使解释困扰。
我们进一步看,你可能在机器学习使用Word2Vec时用到下面一个技巧:使用一个隐藏层的简单神经网络来执行某个任务,但是接下来我们将不会在训练模型任务时使用那样的神经网络,而是仅仅是使用它来学习隐层神经网络的权重,在Word2Vec中指的是“词向量”。
现在我们需要讨论一下这种“伪”任务,我们要构建并运行神经网络,然后我们间接地获得到的后面所需的词向量。
我们要训练skip-gram神经网络做以下任务:给出一个句子中间的某个单词(输入词),观察输入单词旁边的单词并随机选择一个。而我们训练的神经网络将告诉我们词汇表中每个单词被选作为“邻近单词”(nearby word)的概率。
输出概率与输入单词与每个词汇表单词邻近程度相关。举例来说,训练的神经网络的输入单词为“苏联”,那么像“联盟”和“俄罗斯”的输出概率将会远大于像“西瓜”和“袋鼠”不相关单词的概率。
我们将通过“喂养”在训练文档中找到的“单词对”(word pair)来训练神经网络。下面的例子显示了一些训练样本(单词对),句子为“The quick brown fox jumps over the lazy dog.”,窗口大小为2,蓝色突出显示的是输入单词。
思考下,这些单词应该怎么被表示哪?
首先,我们不能够将单词作为一个字符串输入到神经网络,所以我们需要一种方式去表示单词。为了达到目的,我们从训练文档中创建一个单词词汇表,假如我们现在有一个具有10000个不同单词的词汇表。
我们将输入单词比如“蚂蚁”(ants)表示为一个one-hot向量,这种向量有10000个元素(词汇表中的每个单词都被表示为这种形式)。1 的位置对应该词在词典中的位置,其他全为0。
下面是我们神经网络的结构:
在隐藏层中没有使用激活函数,而在输出层使用了softmax,我们稍后在讨论这个原因。
假如,我们要学习有关词向量的300个特征(比如词性,语义等等),那么隐藏层结构将会表示为一个权重矩阵:10000行(代表着词汇表中的每个单词)和300列(代表每一个隐层的神经单元)。
现在你可能反问自己,-“one hot向量几乎全部是0,那么它的作用是什么呢?”如果你将一个1×10000 one hot向量乘以10000×300的矩阵,那么就会有效地选中矩阵中与1对应的行。下面是一个例子:
这就意味着模型中的隐藏层其实运作为一个单词查找表,隐藏层的输出为输入单词的“词向量”。
隐藏层产生的 1×300 的词向量将会传送到输出层,这个输出层是一个softmax regressio分类器,其要领就是每一个输出神经单元将会产生一个介于0到1的输出,并且所有输出值的和为1。
每个输出单元有一个权重矩阵,然后与来自隐藏层的词向量相乘,然后对结果运用 exp(x) 函数。最后,为了将输入结果加起来为1,我们将结果除以10000个输出节点的所有之和。
下面是单词“car”输出单元的计算。
到这里,我们来进一步了解下skip gram model。如果有两个不同的单词非常相似的“上下文”(就是,出现在这个两个单词周围的词语非常相似),对于这两个单词模型会输出非常相似的结果。如果两个单词的词向量非常相似,那么模型预测输出来的上下文也将是非常相似。
什么是所谓的两个单词有非常相似的上下文?我猜你想到近义词,比如“intelligent”和“smart”。或者是单词是非常相关的,比如“transmission”和“engine”
你可能注意到 skip-gram 神经网络将包含一个非常大的weights向量。例如,一个带有300个特征,含有10000词的词汇表,那么在隐藏层和输出增将会产生3百万维的weights向量。在如此巨大的数据集训练代价将会是非常高的,接下来我们将会讲下word2vec作者优化的措施
【Word2Vec Resources】
Word2Vec教程-Negative Sampling 负采样
word2vec 之 skip-gram
Word2vec 主要有两种形式,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即可。一个单词的负采样概率越大,那么它在这个表中出现的次数就越多,它被选中的概率就越大。
常用NLP模型的简介
符号统一:X即样本矩阵
LSI:即SVD(奇异值)分解,X≈UΣVT(VT代表V的转置),设X为n×m矩阵,共m个文档,每个文档由n个词构成,而Σ为s×s矩阵,代表s个主题,V为m×s,每行代表每个文档分别和每个主题的相似度。通常文本分类用到V矩阵。U矩阵即n×s,每行代表每个词和每个主题的相似度。U和V分别成为左、右奇异矩阵。PS:我们知道PCA是对XTX求特征值和特征向量,设X=UΣVT,由于U和V都是正交阵,则XTX=VΣ²VT,即右奇异矩阵就是PCA分解后的特征矩阵,XXT→左奇异矩阵也是同理。
Word2Vec: 最常用的自然语言的数字化表达,简而言之就是若共100篇文章,vocabulary_size=10000,那么每个词都可以用10000维的one-hot向量表达,而经过word2vec的embedding后,每个词都可以用例如300维向量表示,不仅降了维度,而且相似词义的词将拥有更接近的向量表示。word2vec分为两种模型,CBOW - 根据上下文预测词语,SkipGram - 根据词语预测上下文。
CBOW:对C个上下文的词语每个词用C组不同的权重W全连接映射到一个N维的隐层上,得到C个N维隐层值,取平均,再对这个平均的N维隐层用权重W'的全连接+softmax对V个词语赋予概率值,V即vocabulary_sizee。W和W'分别是V×N和N×V的矩阵(全连接↔左乘权重矩阵的转置),假设某词word是Vocabulary的第j个词,那么W的第j行称为word的输入词向量,W'的第j列向量为word的输出词向量。 点此 查看原论文,不过我没看懂所谓的word embedding最终输出的到底是哪个向量,输入、输出、还是隐层。
SkipGram: CBOW的反向操作。
两者都是用BP算法更新。
LSTM:假设输入一句话长度10,每个词用32维向量展示,则LSTM将这个[10, 32]的向量转化为[k]的输出(hidden state),k可以是小于32的任意整数。也可输出每个time_step的输出([10,32]),和最后的cell state。并可对输出的k维向量加一个全连接降维或再加一个softmax实现分类。LSTM和RNN的区别在于有门控制,主要是通过忘记门来保留长期记忆,避免梯度消失。
Seq2Seq:Seq2Seq本来有着非常广阔的定义,但通常是指一个基于神经网络与 LSTM 的 经典seq2seq模型 ,后续流行的Transformer、BERT、GPT3等都是基于此。大致功能是实现机器翻译,通过encoder将原文转换为一个理解的矩阵后,用decoder一词一词地根据这个理解输出目标语言对应的句子。结构大致是先将原文每个词embedding后输入一个名为encoder的LSTM,并将其输出(即LSTM的最后一个cell的h和c)作为对原文的理解。然后一个名为decoder的LSTM会将这个理解转化为答案,decoder用BOS代表句子开始,EOS代表结束。训练时,例如将“我爱你”翻译为“I love you”,第一个输入是BOS,embedding后经过cell(用之前得到的h和c初始化)后用softmax激活得到第一个输出“I”,“I”作为第二个输入再次embedding经过cell后softmax激活得到第二个输出“love”,同理得到第三个输出“you”,最后得到EOS。通过BP梯度下降训练完成后,以同样的方式完成机器翻译的预测任务。
Attention:最早用于Seq2Seq模型。例如要将“我爱你”翻译为“I love you”时,首先根据encoder获得对原文每个词的理解,即encoder_output。decoder每次输出时,对当前输入找一个合适的attention分布,即权重(所谓更注意的地方,即权重更高的地方),该权重点乘encoder_output后求和即经过注意力机制后当前的输入。简而言之,在decoder每次输出时,会根据输入对encoder_output给予一个不同的attention分布。( 此段话尚未验证,需读过论文后修正 )
Self-Attention: 第一次出现在Transformer,由于Transformer没有使用传统s2s所用的RNN或LSTM,所以自称Attention is all you need (You don't need RNN or LSTM)。Self-Attention和Attention的区别有很多,首先对它而言可以不需要decoder直接在encoder内完成自注意力,其次它没有关注序列信息,因此需要额外先进行Positional Encoding,添加序列信息。它的重点在于在关注一个词时,可以将它和上下文的其他词联系到一起计算,对关联较大的词赋予更多的关注(权重)。计算方法是通过三个不同的权重矩阵来乘输入X,获得Q K V三个矩阵,即将原文X拆分为了3部分:问题、目录、内容,通过QKT除以一个常数(原论文是根号dk,这个数越大则概率分布越平均,越小则越极端,也会导致梯度消失)后softmax,来找到和Q对应的K,再通过这个K找到相应的V,即代表此时的关注的内容。
Transformer( 论文 及 较好的一篇介绍 ): 虽然也有encoder和decoder,但后续的发展改进通常只用encoder,而encoder的主要部分是self-attention。整体结构是6个encoder层接6个decoder层。每个encoder层大致结构是输入X经Positional Encoding后,经self-attention加工和自身残差连接后相加并norm(所谓残差连接相当于给了X两个通道,一个是直接输入,一个是经过attention输入),随后再通过前向网络分别对每个输出全连接处理后(一方面避免直接concat维度太大,一方面可以并行计算)与自身残差相加并norm。decoder大致相同,就在中间多了一个Mask的Self Attention来接受encoder的输入,mask是因为decoder按顺序输出,需要把后面的mask掉。
word2vec概述
文本表示是自然语言处理中的基础工作,文本表示的好坏直接影响到整个自然语言处理系统的性能。文本向量化是文本表示的一种重要方式。文本向量化就是讲文本表示成一系列能够表达文本语义的向量。
当前阶段,对文本向量化的大部分研究都是通过词向量化来实现的。与此同时,也有相当一部分研究者将文章或者句子作为文本基本处理单元,提出了doc2vec和ste2vec技术。
基于embedding的词表示,其核心思想是: 上下文相似的词,其语义也相似。 这就是著名的 词空间模型(word space model) ,词向量通常使用神经网络模型训练得到,神经网络模型就是根据上下文与目标词之间的关系进行建模。
Word2vec主要有CBOW和Skip-gram两种模式,其中CBOW是从原始语句推测目标字词,而Skip-gram是从目标字词推测出原始语句(滑动窗口范围内),其中CBOW对小型数据比较合适,Skip-fram在大型语料中表现得更好。
接下来介绍两种优化方法
在木有进行优化前,word2vec的隐藏层有 V*N 个参数,其中V是全局词的数量,比如10W个,N是预设的词嵌入向量维度,如300,那么这个计算量太大了,因此要进行优化。
先复习一下 霍夫曼树 ,这里我图省事,直接把 刘建平 老师的博客贴上来。
回顾下传统的神经网络词向量语言模型,里面一般有三层,输入层(词向量),隐藏层和输出层(softmax层)。里面最大的问题在于从隐藏层到输出的softmax层的计算量很大,因为要计算所有词的softmax概率,再去找概率最大的值。这个模型如下图所示。其中V是词汇表的大小。
由于我们把之前所有都要计算的从输出softmax层的概率计算变成了一颗二叉霍夫曼树,那么我们的softmax概率计算只需要沿着树形结构进行就可以了。如下图所示,我们可以沿着霍夫曼树从根节点一直走到我们的叶子节点的词 w2 。
如何“沿着霍夫曼树一步步完成”呢?在word2vec中,我们采用了二元逻辑回归的方法,即规定沿着左子树走,那么就是负类(霍夫曼树编码1),沿着右子树走,那么就是正类(霍夫曼树编码0)。判别正类和负类的方法是使用sigmoid函数,即:
其中 xw 是当前内部节点的词向量,而θ则是我们需要从训练样本求出的逻辑回归的模型参数。
使用霍夫曼树有什么好处呢?首先,由于是二叉树,之前计算量为V,现在变成了log2V。第二,由于使用霍夫曼树是高频的词靠近树根,这样高频词需要更少的时间会被找到,这符合我们的贪心优化思想。
容易理解,被划分为左子树而成为负类的概率为 P(−)=1−P(+) 。在某一个内部节点,要判断是沿左子树还是右子树走的标准就是看 P(−),P(+) 谁的概率值大。而控制 P(−),P(+) 谁的概率值大的因素一个是当前节点的词向量,另一个是当前节点的模型参数 θ 。
对于上图中的 w2 ,如果它是一个训练样本的输出,那么我们期望对于里面的隐藏节点 n(w2,1) 的 P(−) 概率大, n(w2,2) 的 P(−) 概率大, n(w2,3) 的 P(+) 概率大。
回到基于Hierarchical Softmax的word2vec本身,我们的目标就是找到合适的所有节点的词向量和所有内部节点 θ , 使训练样本达到最大似然。那么如何达到最大似然呢?
在讲基于Negative Sampling的word2vec模型前,我们先看看Hierarchical Softmax的的缺点。的确,使用霍夫曼树来代替传统的神经网络,可以提高模型训练的效率。但是如果我们的训练样本里的中心词w是一个很生僻的词,那么就得在霍夫曼树中辛苦的向下走很久了。能不能不用搞这么复杂的一颗霍夫曼树,将模型变的更加简单呢?
Negative Sampling就是这么一种求解word2vec模型的方法,它摒弃了霍夫曼树,采用了Negative Sampling(负采样)的方法来求解,下面我们就来看看Negative Sampling的求解思路。
既然名字叫Negative Sampling(负采样),那么肯定使用了采样的方法。采样的方法有很多种,比如之前讲到的大名鼎鼎的MCMC。我们这里的Negative Sampling采样方法并没有MCMC那么复杂。
比如我们有一个训练样本,中心词是 w ,它周围上下文共有 2c 个词,记为 context(w) 。由于这个中心词 w ,的确和 context(w) 相关存在,因此它是一个真实的正例。通过 Negative Sampling 采样,我们得到 neg 个和 w 不同的中心词 wi,i=1,2,..neg ,这样 context(w) 和 wi 就组成了 neg 个并不真实存在的负例。利用这一个正例和 neg 个负例,我们进行二元逻辑回归,得到负采样对应每个词 wi 对应的模型参数 θi ,和每个词的词向量。
从上面的描述可以看出, Negative Sampling 由于没有采用霍夫曼树,每次只是通过采样 neg 个不同的中心词做负例,就可以训练模型,因此整个过程要比 Hierarchical Softmax 简单。
不过有两个问题还需要弄明白:1)如何通过一个正例和neg个负例进行二元逻辑回归呢? 2) 如何进行负采样呢?
一些细节:
在skip gram和CBOW中,中心词词向量在迭代过程中是不会更新的,只更新窗口词向量,这个中心词对应的词向量需要下一次在作为非中心词的时候才能进行迭代更新。
参考文章
1.刘建平老师的博客园, 地址
2. bitcarmanlee的CSDN博客