目录

1 单词表达 

1.1 Word embedding

1.2 独热(One hot representation)

1.2.1 独热编码介绍

1.2.2 优缺点分析

1.3 Dristributed representation

1.4 共现矩阵 (Cocurrence matrix)

3 word2vec

3.1 word2vec介绍

3.2 CBOW模型

3.2.1 Simple CBOW Model

 3.2.2 CBOW Multi-Word Context Model

 3.2.3 训练连续词袋模型

3.3 Skip-gram 模型

3.3.1 Skip-gram 模型介绍

3.3.2 训练跳字模型

3.4 使用gensim进行训练

3.5 tricks1:hierarchical softmax (CBOW)

3.5.1 Huffman Tree(哈夫曼树)

3.5.2 预备知识

3.5.3 word2vec的hierarchical softmax结构

3.5.4 目标函数

3.5.5 参数更新

3.5.5 伪代码

3.6 tricks1:hierarchical softmax (Skip-gram)

3.6.1 Skip-gram模型网络结构

3.6.2 Skip-gram的目标函数

3.6.3 参数更新

3.6.4 伪代码

3.7 tricks2:negative sampling (CBOW)

3.8 tricks2:negative sampling (Skip-gram)

3.9 关于Word2Vec若干问题的思考

3.10 python实现(gensim)


1 单词表达 

1.1 Word embedding

        Embedding是数学领域的有名词,是指某个对象 X 被嵌入到另外一个对象 Y 中,映射 f : X → Y ,例如有理数嵌入实数。

        Word embedding 是NLP中一组语言模型(language modeling)和特征学习技术(feature learning techniques)的总称,这些技术会把词汇表中的单词或者短语(words or phrases)映射成由实数构成的向量上。 

        Word embedding就是要从数据中自动学习 输入空间到Distributed representation空间的映射f 。

        最简单的一种Word Embedding方法,就是基于词袋(BOW)One-Hot表示,还有另外一个方法:共现矩阵 (Cocurrence matrix)。

1.2 独热(One hot representation)

1.2.1 独热编码介绍

        独热编码即 One-Hot 编码,又称一位有效编码,其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。举个例子,假设我们有四个样本(行),每个样本有三个特征(列),如图:

        我们的feature_1有两种可能的取值,比如是男/女,这里男用1表示,女用2表示。feature_2 和feature_3各有4种取值(状态)。one-hot编码就是保证每个样本中的单个特征只有1位处于状态1,其他的都是0。上述状态用one-hot编码如下图所示:

考虑一下三个特征:

  • ["male", "female"]
  • ["from Europe", "from US", "from Asia"]
  • ["uses Firefox", "uses Chrome", "uses Safari", "uses Internet Explorer"]

将它换成独热编码后,应该是:

  • feature1=[01,10]
  • feature2=[001,010,100]
  • feature3=[0001,0010,0100,1000]

1.2.2 优缺点分析

  • 优点:一是解决了分类器不好处理离散数据的问题,二是在一定程度上也起到了扩充特征的作用。
  • 缺点:在文本特征表示上有些缺点就非常突出了。首先,它是一个词袋模型,不考虑词与词之间的顺序(文本中词的顺序信息也是很重要的);其次,它假设词与词相互独立(在大多数情况下,词与词是相互影响的);最后,它得到的特征是离散稀疏的。

        为什么得到的特征是离散稀疏的?

       上面举例比较简单,但现实情况可能不太一样。比如如果将世界所有城市名称作为语料库的话,那这个向量会过于稀疏,并且会造成维度灾难。

  • 杭州 [0,0,0,0,0,0,0,1,0,……,0,0,0,0,0,0,0]
  • 上海 [0,0,0,0,1,0,0,0,0,……,0,0,0,0,0,0,0]
  • 宁波 [0,0,0,1,0,0,0,0,0,……,0,0,0,0,0,0,0]
  • 北京 [0,0,0,0,0,0,0,0,0,……,1,0,0,0,0,0,0]

        在语料库中,杭州、上海、宁波、北京各对应一个向量,向量中只有一个值为1,其余都为0。

        能不能把词向量的维度变小呢?

        Dristributed representation(词向量)可以解决One hot representation的问题。

1.3 Dristributed representation(词向量)

        然而每个单词彼此无关这个特点明显不符合我们的现实情况。我们知道大量的单词都是有关。

  • 语义:girl和woman虽然用在不同年龄上,但指的都是女性。
  • 复数:word和words仅仅是复数和单数的差别。
  • 时态:buy和bought表达的都是“买”,但发生的时间不同。

        所以用one hot representation的编码方式,上面的特性都没有被考虑到。

        我们更希望用诸如“语义”,“复数”,“时态”等维度去描述一个单词。每一个维度不再是0或1,而是连续的实数,表示不同的程度。

        Dristributed representation可以解决One hot representation的问题,它的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,进而可以用普通的统计学的方法来研究词与词之间的关系。这个较短的词向量维度是多大呢?这个一般需要我们在训练时自己来指定。

        比如下图我们将词汇表里的词用"Royalty","Masculinity", "Femininity"和"Age"4个维度来表示,King这个词对应的词向量可能是(0.99,0.99,0.05,0.7)。当然在实际情况中,我们并不能对词向量的每个维度做一个很好的解释。

        我们将king这个词从一个可能非常稀疏的向量所在的空间,映射到现在这个四维向量所在的空间,必须满足以下性质:

(1)这个映射是单射(单射:任给x1和x2属于X,若x1≠x2,则f(x1)≠f(x2),称f为单射);
(2)映射之后的向量不会丢失之前的那种向量所含的信息。

        这个过程称为word embedding(词嵌入),即将高维词向量嵌入到一个低维空间。如图

        经过我们一系列的降维神操作,有了用Dristributed representation表示的较短的词向量,我们就可以较容易的分析词之间的关系了,比如我们将词的维度降维到2维,有一个有趣的研究表明,用下图的词向量表示我们的词时,我们可以发现:

        出现这种现象的原因是,我们得到最后的词向量的训练过程中引入了词的上下文。

        You shall know a word by the company it keeps.

        举个例子:

        你想到得到"learning"的词向量,但训练过程中,你同时考虑了它左右的上下文,那么就可以使"learning"带有语义信息了。通过这种操作,我们可以得到近义词,甚至cat和它的复数cats的向量极其相近。

1.4 共现矩阵 (Cocurrence matrix)

         一个非常重要的思想是,我们认为某个词的意思跟它临近的单词是紧密相关的。这时我们可以设定一个窗口(大小一般是5~10),如下窗口大小是2,那么在这个窗口内,与rests 共同出现的单词就有life、he、in、peace。然后我们就利用这种共现关系来生成词向量。

        

        例如,现在我们的语料库包括下面三份文档资料:

        I like deep learning. 

        I like NLP. 

        I enjoy flying.

         作为示例,我们设定的窗口大小为1,也就是只看某个单词周围紧邻着的那个单词。此时,将得到一个对称矩阵——共现矩阵。因为在我们的语料库中,I 和 like做为邻居同时出现在窗口中的次数是2,所以下表中I 和like相交的位置其值就是2。这样我们也实现了将word变成向量的设想,在共现矩阵每一行(或每一列)都是对应单词的一个向量表示。

        虽然Cocurrence matrix一定程度上解决了单词间相对位置也应予以重视这个问题。但是它仍然面对维度灾难。也即是说一个word的向量表示长度太长了。这时,很自然地会想到SVD或者PCA等一些常用的降维方法。当然,这也会带来其他的一些问题,例如,我们的词汇表中有新词加入,那么就很难为他分配一个新的向量。

3 word2vec

3.1 word2vec介绍

        word2vec模型其实就是简单化的神经网络。word2vec是用一个一层的神经网络(即CBOW)把one-hot形式的稀疏词向量映射称为一个n维(n一般为几百)的稠密向量的过程。为了加快模型训练速度,其中的tricks包括Hierarchical softmaxnegative samplingHuffman Tree等。

        在NLP中,最细粒度的对象是词语。如果我们要进行词性标注,用一般的思路,我们可以有一系列的样本数据(x,y)。其中x表示词语,y表示词性。而我们要做的,就是找到一个x -> y的映射关系,传统的方法包括BayesSVM等算法。但是我们的数学模型,一般都是数值型的输入。但是NLP中的词语,是人类的抽象总结,是符号形式的(比如中文、英文、拉丁文等等),所以需要把他们转换成数值形式,或者说——嵌入到一个数学空间里,这种嵌入方式,就叫词嵌入(word embedding),而 Word2vec,就是词嵌入( word embedding) 的一种。

        输入是One-Hot VectorHidden Layer没有激活函数,也就是线性的单元。Output Layer维度跟Input Layer的维度一样,用的是Softmax回归。当这个模型训练好以后,我们并不会用这个训练好的模型处理新的任务,我们真正需要的是这个模型通过训练数据所学得的参数,例如隐层的权重矩阵

        这个模型是如何定义数据的输入和输出呢?一般分为CBOW(Continuous Bag-of-Words)Skip-Gram两种模型。

  • CBOW模型训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。 
  • Skip-Gram模型CBOW的思路是反着来的,即输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。

        CBOW小型数据库比较合适,而Skip-Gram大型语料中表现更好。

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

上面提到的这种方法实际上会在无监督特征学习(unsupervised feature learning)中见到,最常见的就是自编码器(auto-encoder):通过在隐层将输入进行编码压缩,继而在输出层将数据解码恢复初始状态,训练完成后,我们会将输出层“砍掉”,仅保留隐层。

3.2 CBOW模型

        连续词袋模型与跳字模型类似。与跳字模型最大的不同在于,连续词袋模型假设基于某中心词在文本序列前后的背景词来生成该中心词。在同样的文本序列“the”“man”“loves”“his”“son”里,以“loves”作为中心词,且背景窗口大小为2时,连续词袋模型关心的是,给定背景词“the”“man”“his”“son”生成中心词“loves”的条件概率,也就是

3.2.1 Simple CBOW Model

        先从Simple CBOW model(仅输入一个词,输出一个词)框架说起。

如上图所示:

  • input layer输入的X是单词的one-hot representation(考虑一个词表V,里面的每一个词  都有一个编号i∈{1,...,|V|},那么词  的one-hot表示就是一个维度为|V|的向量,其中第i个元素值非零,其余元素全为0,例如:);
  • 输入层到隐藏层之间有一个权重矩阵W,隐藏层得到的值是由输入X乘上权重矩阵得到的(细心的人会发现,0-1向量乘上一个矩阵,就相当于选择了权重矩阵的某一行,如图:输入的向量X是[0,0,1,0,0,0],W的转置乘上X就相当于从矩阵中选择第3行[2,1,3]作为隐藏层的值);
  • 隐藏层到输出层也有一个权重矩阵W',因此,输出层向量y的每一个值,其实就是隐藏层的向量点乘权重向量W'的每一列,比如输出层的第一个数7,就是向量[2,1,3]和列向量[1,2,1]点乘之后的结果;
  • 最终的输出需要经过softmax函数,将输出向量中的每一个元素归一化到0-1之间的概率,概率最大的,就是预测的词。

 3.2.2 CBOW Multi-Word Context Model

         把单个输入换成多个输入(划红线部分)。

  1. 输入层(Input Layer):上下文单词的onehot,假设单词向量空间dim为V,上下文单词个数为C。
  2. 所有onehot分别乘以共享的输入权重矩阵W,(V,N)矩阵,N为自己设定的数,初始化权重矩阵W。
  3. 隐藏层(Hidden Layer):所得的向量 (因为是onehot所以为向量) 相加求平均作为隐层向量, size为1N.
  4. 乘以输出权重矩阵W' (NV
  5. 输出层(Output Layer):得到向量 (1V) 激活函数处理得到V-dim概率分布(因为是onehot,其中的每一维代表着一个单词)
  6. 概率最大的index所指示的单词为预测出的中间词(target word)与true label的onehot做比较,误差越小越好(根据误差更新权重矩阵)

          因为连续词袋模型的背景词有多个,我们将这些背景词向量取平均,然后使用和跳字模型一样的方法来计算条件概率。设分别表示词典中索引为 i 的词作为背景词中心词的向量(注意符号的含义与跳字模型中的相反)。设中心词在词典中索引为 c,背景词,在词典中索引为,那么给定背景词生成中心词的条件概率

                    

        为了让符号更加简单,我们记,且,那么上式可以简写成:

                            

        给定一个长度为 T 的文本序列,设时间步 t 的词为,背景窗口大小为m。连续词袋模型的似然函数是由背景词生成任一中心词的概率:

               

        此处使用点积来表示相似性,两个向量的相似性越大,他们的点积就越大。

 3.2.3 训练连续词袋模型

        训练连续词袋模型同训练跳字模型基本一致。连续词袋模型的最大似然估计:

         

        等价于最小化损失函数

             

          =   

         注意到:

                  因为:

                   所以:

        通过微分,我们可以计算出上式中条件概率的对数有关任一背景词向量的梯度:

        

        有关其他词向量的梯度同理可得。同跳字模型不一样的一点在于,我们一般使用连续词袋模型的背景词向量作为词的表征向量。

3.3 Skip-gram 模型

3.3.1 Skip-gram 模型介绍

        从直观上理解,Skip-Gram是通过给定输入,即input word来预测上下文。

      

  • 输入层(Input Layer):输入层的输入是数据的数字表示,那么该如何用数字表示文本数据呢?其实,它的输入使用的是one-hot编码。假设有n个词,则每一个词可以用一个n维的向量来表示,这个n维向量只有一个位置是1,其余位置都是0。
  • 隐藏层(Hidden Layer):隐藏层的神经单元数量,代表着每一个词用向量表示的维度大小。假设我们的hidden_size取300,也就是我们的隐藏层有300个神经元,那么对于每一个词,我们的向量表示就是一个1*N的向量。有多少个词,就有多少个这样的向量。所以对于输入层隐藏层之间的权值矩阵W,它的形状应该是[vocab_size, hidden_size]的矩阵。
  • 输出层(Output Layer):从上面的图上可以看出来,输出层是一个[vocab_size]大小的向量,每一个值代表着输出一个词的概率。为什么要这样输出?因为我们想要知道,对于一个输入词,它接下来的词最有可能的若干个词是哪些,换句话说,我们需要知道它接下来的词的概率分布。Output Layer维度跟Input Layer的维度一样,用的是Softmax回归。

        跳字模型假设基于某个词来生成它在文本序列周围的词。举个例子,假设文本序列是“the”“man”“loves”“his”“son”。以“loves”作为中心词,设背景窗口大小为2。如图10.1所示,跳字模型所关心的是,给定中心词“loves”,生成与它距离不超过2个词的背景词“the”“man”“his”“son”的条件概率,即 

          

        假设给定中心词的情况下,背景词的生成是相互独立的,那么上式可以改写成

        在word2vec的两个模型中,有一个叫做skip_window,即C的参数,它代表着我们从当前输入数据的数字表示,即input word的一侧(左边或右边)选取词的数量。如果我们选定句子“The quick brown fox jumps over lazy dog”,设定窗口大小为2(window_size=2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。

        

       在跳字模型中,每个词被表示成两个 d 维向量,用来计算条件概率。假设这个词在词典中索引为 i,当它为中心词时向量表示为 ,而为背景词时向量表示为。设中心词在词典中索引为 c,背景词在词典中索引为 o,给定中心词生成背景词的条件概率可以通过对向量内积做softmax运算而得到:

                    

        其中词典索引集。假设给定一个长度为 T 的文本序列,设时间步 t的词为。假设给定中心词的情况下背景词的生成相互独立,当背景窗口大小为 m 时,跳字模型的似然函数即给定任一中心词生成所有背景词的概率:

                      

         这里小于1或大于T的时间步可以被忽略。

同款疑问:word2vec的skip-gram输出层时是怎么回事? - 知乎

        假设Vocabulary大小为C,embedding以后的向量大小为v。

        则Word2Vec中的权重个数为2C*v个,其中输入层到隐层的W1为[C, v]的一个矩阵,隐层到输出层W2 同样也是[C, v]的矩阵。W1代表center word的向量,W2代表context words的向量。

        隐层出来的center word的向量去乘W2,此时实际是在计算centor word和context words中每个词向量的similarity(logits),计算出的logits用softmax归一化以后,再用label优化就好了,label部分只有对应的那个词的位置是1,其余都是0。

3.3.2 训练跳字模型

         假设现在有一个文本序列:I, want, to, learn, NLP

         首先遍历这句话,选取中心词,假设现在遍历到了“to”这个单词,然后这里有一个超参数叫做window size。假设window_size=2,则这个window中包括中心词前后各两个单词。根据skip-gram,给定中心词“to”,生成窗口内单词的概率(注意这里暂时不考虑停用词):

          

       假设在给定中心词的条件下每个单词出现的概率是独立的,这里很类似朴素贝叶斯的条件独立性假设,可以大大的简化运算。

         

        上面模型介绍中也提到,输出层通过一个softmax将结果转换成了概率分布,因此计算的概率公式如下:

           

           其中分别代表上下文词和中心词,代表上下文词词向量和中心向量,是全体词库。这个概率就是给定中心词的条件下,某个词是上下文词的概率。根据上面的简化:

           每个词的条件概率相互独立,所以可以写成连乘的形式:

       

       其中,t表示中心词的位置,m是窗口大小这样就得到了每个中心词的计算上下文词的概率,在这个式子中变量是上下文向量和中心词向量,于 是只要改变参数使概率最大化就可以了。在这里使用极大似然估计,首先对上式进行化简,对上式取负对数: 

           

        跳字模型的参数是每个词所对应的中心词向量和背景词向量。训练中我们通过最大化似然函数来学习模型参数,即最大似然估计。这等价于最小化以下损失函数

              

        如果使用随机梯度下降,那么在每一次迭代里我们随机采样一个较短的子序列来计算有关该子序列的损失,然后计算梯度来更新模型参数。梯度计算的关键是条件概率的对数有关中心词向量和背景词向量的梯度。根据定义,首先看到

               

        通过微分,我们可以得到上式中的梯度

               

        它的计算需要词典中所有词以为中心词的条件概率。有关其他词向量的梯度同理可得。

        训练结束后,对于词典中的任一索引为 i 的词,我们均得到该词作为中心词和背景词的两组词向量。在自然语言处理应用中,一般使用跳字模型的中心词向量作为词的表征向量。

       训练完毕后,输入层的每个单词与矩阵W相乘得到的向量的就是我们想要的词向量(word embedding),这个矩阵(所有单词的word embedding)也叫做 look up table,其实这个look up table就是矩阵W自身,也就是说,任何一个单词的onehot乘以这个矩阵都将得到自己的词向量。有了look up table就可以免去训练过程直接查表得到单词的词向量了。

注意:

  • skip-gram并没有考虑语序问题。
  • 建模是fake task,真正目的是获得权重矩阵,权重矩阵的每行分别是每个单词的词向量。
  • 在隐层只是用了矩阵乘法将高维onehot向量映射为一个低维稠密向量,这个向量就是中心词向量,并没有使用激活函数。
  • 在输出层是将隐层输出的中心词向量和上下文词向量相乘后输入softmax分类器,最终输出的是概率分布。
  • 在训练时每个词都会交替变成中心词向量和上下文词向量

3.4 使用gensim进行训练

用gensim学习word2vec

示例:

>>> from gensim.test.utils import common_texts, get_tmpfile
>>> from gensim.models import Word2Vec
>>> path = get_tmpfile("word2vec.model")
>>> model = Word2Vec(common_texts, sg=1, size=100, window=5, min_count=1, workers=4)
>>> model.save("word2vec.model")
>>> print(model.wv.similarity('沙瑞金'.decode('utf-8'), '高育良'.decode('utf-8')))
>>> print(model.wv["沙瑞金"]

        其中,sg=1是skip-gram算法,对低频词敏感;默认sg=0为CBOW算法。

        其余参数说明参见models.word2vec – Word2vec embeddings — gensim

    "model = Word2Vec(sentences=None,size=100,alpha=0.025,window=5, min_count=5, max_vocab_size=None, sample=0.001,seed=1, workers=3, min_alpha=0.0001, sg=0, hs=0, negative=5, cbow_mean=1, hashfxn=<built-in function hash>,iter=5,null_word=0, trim_rule=None, sorted_vocab=1, batch_words=10000)\n",
    "# 参数解释:\n",
    "# 1. sentences  第一个参数是预处理后的训练语料库。是可迭代列表,但是对于较大的语料库,可以考虑直接从磁盘/网络传输句子的迭代。对于大语料集,建议使用BrownCorpus,Text8Corpus或lineSentence构建。\n",
    "# 2. sg=1是skip-gram算法,对低频词敏感;默认sg=0为CBOW算法。\n",
    "# 3. size(int) 是输出词向量的维数,默认值是100。这个维度的取值与我们的语料的大小相关,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。值太小会导致词映射因为冲突而影响结果,值太大则会耗内存并使算法计算变慢,一般值取为100到200之间,不过见的比较多的也有300维的。\n",
    "# 4. alpha: 是初始的学习速率,在训练过程中会线性地递减到min_alpha。\n",
    "# 5. window(int)即词向量上下文最大距离,是一个句子中当前单词和预测单词之间的最大距离,skip-gram和cbow算法是基于滑动窗口来做预测,window越大,则和某一词较远的词也会产生上下文关系。默认值为5。windows越大所需要枚举的预测此越多,计算的时间越长。对于一般的语料这个值推荐在[5,10]之间。\n",
    "# 6. min_count 忽略所有频率低于此值的单词。默认值为5。\n",
    "# 7. max_vocab_size: 设置词向量构建期间的RAM限制,设置成None则没有限制。\n",
    "# 8. seed: 用于随机数发生器。与初始化词向量有关。\n",
    "# 9. workers表示训练词向量时使用的线程数,默认是当前运行机器的处理器核数。workers控制训练的并行,此参数只有在安装了Cpython后才有效,否则只能使用单核。\n",
    "# 10. min_alpha: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha给出了最小的迭代步长值。随机梯度下降中每轮的迭代步长可以由iter,alpha, min_alpha一起得出。对于大语料,需要对alpha, min_alpha,iter一起调参,来选择合适的三个值。\n",
    "# 11. hs: 即我们的word2vec两个解法的选择了,如果是0, 则是Negative Sampling,是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。\n",
    "# 12. negative:如果大于零,则会采用negative sampling,用于设置多少个noise words(一般是5-20)。\n",
    "# 13. cbow_mean: 仅用于CBOW在做投影的时候,为0,则采用上下文的词向量之和,为1则为上下文的词向量的平均值。默认值也是1,不推荐修改默认值。\n",
    "# 14. hashfxn: hash函数来初始化权重,默认使用python的hash函数。\n",
    "# 15. iter: 随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。\n",
    "# 16. trim_rule: 用于设置词汇表的整理规则,指定那些单词要留下,哪些要被删除。可以设置为None(min_count会被使用)。\n",
    "# 17. sorted_vocab: 如果为1(默认),则在分配word index 的时候会先对单词基于频率降序排序。\n",
    "# 18. batch_words:每一批的传递给线程的单词的数量,默认为10000。\n",
    "# 19. negative和sample可根据训练结果进行微调,sample表示更高频率的词被随机下采样到所设置的阈值,默认值为1e-3。"

Word2Vec中的数学:​​​​​​​有道云笔记

3.5 tricks1:hierarchical softmax (CBOW)

69-负采样和Hierarchical Softmax-词嵌入模型-自然语言处理-深度学习-pytorch_哔哩哔哩_bilibili

具体内容见 Word2Vec中的数学:​​​​​​​有道云笔记4.1 节

     基于hierarchical Softmax 的 word2vec 与传统的神经网络词向量语言模型相比有两个改进:

  • 首先,对于从输入层到隐藏层的映射,没有采取神经网络的线性变换加激活函数的方法,而是采用简单的对所有输入词向量求和并取平均的方法。比如输入的是三个4维词向量:(1,2,3,4),(9,6,11,8),(5,10,7,12),那么我们word2vec映射后的词向量就是(5,6,7,8)。这里是从多个词向量变成了一个词向量。
  • 第二个改进就是从隐藏层到输出的softmax层这里的计算量个改进。为了避免要计算所有词的softmax概率,基于hierarchical Softmax 的采用了霍夫曼树来代替从隐藏层到输出softmax层的映射。

        word2vec在最后预测输出向量时候,大小是1*V的向量,本质上是个多分类的问题。通过hierarchical softmax的技巧,把V分类的问题变成了log(V)次二分类。

3.5.1 Huffman Tree(哈夫曼树)

        哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树

哈夫曼树的构造:

 例子:有A B C D 四个词,数字表示词频,构造过程如下:

 哈夫曼树编码:

左子树为0,右子树为1:

         那么D编码为0,B编码为10,C编码为110,A编码为111。

3.5.2 预备知识

3.5.3 word2vec的hierarchical softmax结构

 

  • 输入层:是指  中所包含的  个词的词向量 
  • 投影层:指的是直接对  个词向量进行累加,累加之后得到下式:
  • 输出层:是一个Huffman树,其中叶子节点共N个,对应于N个单词,非叶子节点N-1个(对应上图中标成黄色的结点)。Word2Vec基于层次Softmax的方式主要的精华部分都集中在了哈夫曼树这部分

         

        和传统的神经网络输出不同的是,word2vec的hierarchical softmax结构是把输出层改成了一颗哈夫曼树,其中图中

  • 白色的叶子节点表示词汇表中所有的|V|个词,
  • 黑色节点表示非叶子节点,

        每一个叶子节点也就是每一个单词,都对应唯一的一条从root节点出发的路径。我们的目的是使的w=wO这条路径的概率最大,即: P(w=wO|wI)最大,假设最后输出的条件概率是W2最大,那么我只需要去更新从根结点到w2这一个叶子结点的路径上面节点的向量即可,而不需要更新所有的词的出现概率,这样大大的缩小了模型训练更新的时间。

        我们应该如何得到某个叶子结点的概率呢?

         假设我们要计算W2叶子节点的概率,我们需要从根节点到叶子结点计算概率的乘积。我们知道,本模型替代的只是原始模型的softmax层,因此,某个非叶子节点的值即隐藏层到输出层的结果仍然是uj,我们对这个结果进行sigmoid之后,得到节点往左子树走的概率p1-p则为往右子树走的概率。  

3.5.4 目标函数

3.5.5 参数更新

3.5.5 伪代码

        CBOW模型中采用随机梯度上升更新各参数的伪代码:

Hierarchical Softmax(层次Softmax):Hierarchical Softmax(层次Softmax) - 知乎

3.6 tricks1:hierarchical softmax (Skip-gram)

3.6.1 Skip-gram模型网络结构

        下图给出了Skip-gram模型的网络结构,同CBOW模型的网络结构一样,它也包括三层:输入层、投影层和输出层。下面以样本  为例,对这三层做简要说明。

  • 输入层:只含当前样本的中心词  的词向量  。
  • 投影层:这是个恒等投影,把  投影到  。因此,这个投影层其实是多余的,这里之所以保留投影层主要是方便和CBOW模型的网络结构做对比。
  • 输出层:和CBOW模型一样,输出层也是一颗Huffman树。

3.6.2 Skip-gram的目标函数

 至此,已经推导出了Skip-gram模型的目标函数(公式11),接下来同样利用随机梯度上升法对其进行优化。而梯度类算法的关键是给出相应的梯度计算公式,进行反向传播。

3.6.3 参数更新

3.6.4 伪代码

 

         e:可以看作梯度的累加。

3.7 tricks2:negative sampling (CBOW)

69-负采样和Hierarchical Softmax-词嵌入模型-自然语言处理-深度学习-pytorch_哔哩哔哩_bilibili

negative sampling解决了之前说的两个问题:

  • 仅对K个参数进行采样
  • 放弃softmax函数,采用sigmoid函数,这样就不存在先求一遍窗口中所有单词的‘“得分”的情况了。

        本质上是对训练集进行了采样,从而减小了训练集的大小。

        本节将介绍基于Negative Sampling的CBOW和Skip-gram模型。Negative Sampling(简称为NEG)是Tomas Mikolov等人在论文《Distributed Representations of Words and Phrases and their Compositionality》中提出的,它是NCE(Noise Contrastive Estimation)的一个简化版,目的是用来提高训练速度并改善所得词向量的质量。与Hierarchical Softmax相比,NEG不再使用复杂的Huffman树,而是利用相对简单的随机负采样,能大幅度提高性能,因而可作为Hierarchical Softmax的一种替代。

        NCE 的细节有点复杂,其本质是利用已知的概率密度函数来估计未知的概率密度函数。简单来说,假设未知的概率密度函数为X,已知的概率密度为Y,如果得到了X和Y的关系,那么X也就可以求出来了。具体可以参考论文《 Noise-contrastive estimation of unnormalized statistical models, with applications to natural image statistics》。

 

3.8 tricks2:negative sampling (Skip-gram)

 

 

3.9 关于Word2Vec若干问题的思考

​​​​​​​【Embedding】Word2Vec:词嵌入的一枚银弹 - 云+社区 - 腾讯云

(1)Word2Vec两个算法模型的原理是什么,网络结构怎么画?

(2)网络输入输出是什么?隐藏层的激活函数是什么?输出层的激活函数是什么?

(3)目标函数/损失函数是什么?

(4)Word2Vec如何获取词向量?

(5)推导一下Word2Vec参数如何更新?

(6)Word2Vec的两个模型哪个效果好哪个速度快?为什么?

         效果:CBOW 像是小学时做的填空题:I come ___ China,而 Skip-Gram 像是给你一个 from 让你预测上下文,理论上来说应该是 CBOW 的效果更好,但实际情况却恰恰相反。我觉得可能是因为 CBOW 是取上下文的输入向量的质心从而导致一部分有效信息损失,而 Skip-Gram 虽然看起来荒唐,但每个单词都会得到单独的训练不会损失有效信息,其实 Skip-Gram 比 CBOW 的效果好,主要是针对低频词而言,举个例子,让你补全 It is a ___ day,是不是突然有很多可能的答案,你大概率会填写一个高频词汇,如:nice、sun 等,而不会想到要填写 gorgeous,而给你 gorgeous 单词,其特征很明显会想到这可以用来形容 day、moon、girl 等等。其次 gorgeous 本身用量就没有 nice 那么多,如果再和其他上下文放在一起取质心,其很容易被忽略,从而没法充分训练。
          速度:我觉得 Skip-Gram 的速度慢可能是因为其预测值比较多,需要分别计算多个 Softmax,时间复杂度为 O(kn),而 CBOW 虽然也有多个输入,但我们求其质心简化了操作,时间复杂度为 O(n)。

        需要说明的是,当语料较少时使用CBOW方法比较好,当语料较多时采用skip-gram表示比较好。

(7)Word2Vec加速训练的方法有哪些?

        当语料比较大时,词典规模会比较大,求softmax速度会变得很慢,严重影响了训练速度。此时有两种方法进行改进:(1)分层softmax; (2)负采样。分层softmax的原理很简单,就是构建Huffman树(使得计算概率的次数最小),正例词都在叶子结点,其他词为中间节点,分层进行softmax。负采样的思想也很简单,就是不计算所有词的概率算softmax,而是采样一些负样本,算对数sigmoid函数,近似softmax。具体原理就是最大化正例概率,最小化负例出现的概率。

(8)介绍下Negative Sampling,对词频低的和词频高的单词有什么影响?为什么?

        通过负采样避免更新全部参数,对词频高的友好;

(9)Word2Vec和隐狄利克雷模型(LDA)有什么区别与联系?

        谈到Word2Vec与LDA的区别和联系,首先,LDA是利用文档中单词的共现关系来对单词按主题聚类,也可以理解为对“文档-单词”矩阵进行分解,得到“文档-主题”和“主题-单词”两个概率分布。而Word2Vec其实是对“上下文-单词”矩阵进行学习,其中上下文由周围的几个单词组成,由此得到的词向量表示更多地融入上下文共现的特征。也就是说,如果两个单词所对应的Word2Vec向量相似度较高,那么它们很可能经常在同样的上下文中出现。需要说明的是,上述分析的是 

        LDA与Word2Vec的不同,不应该作为主题模型和词嵌入两类方法的主要差异。主题模型通过一定的结构调整可以基于“上下文-单词”矩阵进行主题推理。同样地,词嵌入方法也可以根据“文档-单词”矩阵学习出词的隐含向量表示。主题模型和词嵌入两类方法最大的不同其实在于模型本身,主题模型是一种基于概率图模型的生成式模型,其似然函数可以写成若干条件概率连乘的形式,其中包括需要推测的隐含变量(即主题);而词嵌入模型一般表达为神经网络的形式,似然函数定义在网络的输出之上,需要通过学习网络的权重以得到单词的稠密向量表示。

(10)介绍下Hierarchical Softmax的计算过程,怎么把 Huffman 放到网络中的?参数是如何更新的?对词频低的和词频高的单词有什么影响?为什么?

        Hierarchical Softmax利用了Huffman树依据词频建树,词频大的节点离根节点较近,词频低的节点离根节点较远,距离远参数数量就多,在训练的过程中,低频词的路径上的参数能够得到更多的训练,所以效果会更好。所以 Hierarchical Softmax 对词频低的单词效果会更好。

(11)Word2Vec有哪些参数,有没有什么调参的建议?

  • Skip-Gram 的速度比CBOW慢一点,小数据集中对低频次的效果更好;
  • Sub-Sampling Frequent Words可以同时提高算法的速度和精度,Sample 建议取值为  ;
  • Hierarchical Softmax对低词频的更友好;
  • Negative Sampling对高词频更友好;
  • 向量维度一般越高越好,但也不绝对;
  • Window Size,Skip-Gram一般10左右,CBOW一般为5左右。

(12)Word2Vec有哪些局限性?

Word2Vec作为一个简单易用的算法,其也包含了很多局限性:

  • Word2Vec只考虑到上下文信息,而忽略的全局信息;
  • Word2Vec只考虑了上下文的共现性,而忽略的了彼此之间的顺序性;

 (13)Hierarchical Softmax 方法中哈夫曼树是如何初始化生成的?也就是哈夫曼树是如何构建的呢?

答:Hierarchical Softmax 依据词频构建 Huffman 树,词频大的节点离根节点较近,词频低的节点离根节点较远,距离远参数数量就多。

(14)Negative Sampling 是一种什么采样方式?是均匀采样还是其它采样方法?

答:词典 D 中的词在语料 C 中出现的次数有高有低,对于那些高频词,被选为负样本的概率就应该比较大,反之,对于那些低频词,其被选中的概率就应该比较小。这就是我们对采样过程的一个大致要求,本质上就是一个带权采样问题。

(15)详细介绍一下 Word2Vec 中负采样方法?

答:先将概率以累积概率分布的形式分布到一条线段上,以 a=0.2,b=0.3,c=0.5 为例, a所处线段为[0,0.2] ,b所处线段为 [0.2,0.5] , c 所处线段为 [0.5,1] ,然后定义一个大小为M 的数组,并把数组等距离划分为 m个单元,然后与上面的线段做一次映射,这样我们便知道了数组内的每个单元所对应的字符了,这种情况下算法的时间复杂度为O(1) ,空间复杂度为O(M) , m越小精度越大。

(16)gensim中word2vec 结果解释

syn0数组实际上保存了原始的单词向量。从用于训练单词向量的神经网络的角度来看,这些向量是一个“投影层”,可以将单词的一次性编码转换为正确维度的密集嵌入向量。

在gensim4.0.0之后,想要获得与model.syn0一样的输出需要使用:

model.wv.vectors

syn0 :就是词向量的大矩阵,第i行表示vocab中下标为i的词
syn1:用hs算法时用到的辅助矩阵,即文章中的Wx
syn1neg:negative sampling算法时用到的辅助矩阵,组成的矩阵。
Next_random:作者自己生成的随机数,线程里面初始化就是:

 (17)word2vec增量训练

【技术分享】修改word2vec源码实现词向量增量更新 - 云+社区 - 腾讯云

基于Gensim实现word2vec词向量增量训练_Steven灬的博客-CSDN博客

(18)word2vec没有对词向量进行正则化?

word2vec没有对词向量进行正则化? - 知乎

word2vec在尝试用哈夫曼和EM的思路为自然语言提供了新的正则方案。

word2vec的前身是同样基于CBOW和Skip-gram的概率多层神经网络,其本质类似于一个Encoder-Decoder模型,前端将文字映射到一定维度模拟适用于自然语言的降维到词向量的过程,达到去噪的效果,而Decoder则起到还原词向量到文字的作用。而既然是神经网络,当然可以采用传统正则,不管是regularize还是dropout。

而这个方案在自然语言并不是个很优的解,因为不管是CBOW还是Skip-gram都要求用滑窗对文本遍历,也就意味着样本远远多于正常的模型,这时候regularize或是dropout显得过慢。word2vec选择将输出换为哈夫曼树,这样在训练过程中,每当前端神经网络产生出Encoder过后的词向量的预测,哈夫曼树会对其产生一个最小熵的编码,也就类似于求当前词向量方式的Expectation,而后下次再做Encoder时,就会用这个局部最优的Expectation去生成预测,也就是M的过程。而哈弗曼树编码最小对应着一个足够短的词向量,这样才能尽可能少的作树的分叉。因此在word2vec通过E过程和M过程的相互影响,用哈弗曼树对词向量进行一定程度的正则。而这种正则是自适应的,比传统人为设置超参数的正则要更快更好。

word2vec中目标函数为什么不用加正则化项?_技术交流_牛客网

加正则的本质是减少数据中的误差对模型的影响。word2vec中输入数据是one hot encoding没有误差所以不用加。

灵魂拷问之word2vec - 知乎

(19)cbow 与 skip-gram的比较?

  • CBOW是利用上下文预测中心词,Skip-gram是利用中心词预测上下文
  • Skip-gram效果比CBOW好。 
  • Skip-gram训练时间长,但是对低频词(生僻词)效果好;CBOW训练时间短,对低频词效果比较差。
  1. 为什么?

        cbow和skip-gram都是在word2vec中用于将文本进行向量表示的实现方法,具体的算法实现细节可以去看word2vec的原理介绍文章。我们这里大体讲下两者的区别,尤其注意在使用当中的不同特点。

       在cbow方法中,是用周围词预测中心词,从而利用中心词的预测结果情况,使用GradientDesent方法,不断的去调整周围词的向量。当训练完成之后,每个词都会作为中心词,把周围词的词向量进行了调整,这样也就获得了整个文本里面所有词的词向量。

        要注意的是, cbow的对周围词的调整是统一的:求出的gradient的值会同样的作用到每个周围词的词向量当中去。

        可以看到,cbow预测行为的次数跟整个文本的词数几乎是相等的(每次预测行为才会进行一次backpropgation, 而往往这也是最耗时的部分),复杂度大概是O(V);

        而skip-gram是用中心词来预测周围的词。在skip-gram中,会利用周围的词的预测结果情况,使用GradientDecent来不断的调整中心词的词向量,最终所有的文本遍历完毕之后,也就得到了文本所有词的词向量。

        可以看出,skip-gram进行预测的次数是要多于cbow的:因为每个词在作为中心词时,都要使用周围词进行预测一次​​​​​​​。这样相当于比cbow的方法多进行了K次(假设K为窗口大小),因此时间的复杂度为O(KV),训练时间要比cbow要长。

        但是在skip-gram当中,每个词都要受到周围的词的影响,每个词在作为中心词的时候,都要进行K次的预测、调整。因此, 当数据量较少,或者词为生僻词出现次数较少时, 这种多次的调整会使得词向量相对的更加准确。因为尽管cbow从另外一个角度来说,某个词也是会受到多次周围词的影响(多次将其包含在内的窗口移动),进行词向量的跳帧,但是他的调整是跟周围的词一起调整的,grad的值会平均分到该词上, 相当于该生僻词没有收到专门的训练,它只是沾了周围词的光而已。 

       因此,从更通俗的角度来说:

       在skip-gram里面,每个词在作为中心词的时候,实际上是 1个学生 VS K个老师,K个老师(周围词)都会对学生(中心词)进行“专业”的训练,这样学生(中心词)的“能力”(向量结果)相对就会扎实(准确)一些,但是这样肯定会使用更长的时间;

        cbow是 1个老师 VS K个学生,K个学生(周围词)都会从老师(中心词)那里学习知识,但是老师(中心词)是一视同仁的,教给大家的一样的知识。至于你学到了多少,还要看下一轮(假如还在窗口内),或者以后的某一轮,你还有机会加入老师的课堂当中(再次出现作为周围词),跟着大家一起学习,然后进步一点。因此相对skip-gram,你的业务能力肯定没有人家强,但是对于整个训练营(训练过程)来说,这样肯定效率高,速度更快。

3.10 python实现(gensim)

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# @Time : 2021/10/25 17:02 
# @Author : song.xiangyu

import gensim
import jieba
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence


def word_seg(source_file, target_file):
    with open(source_file) as f:
        with open(target_file, "w") as f1:
            line_num = 1
            line = f.readline()
            while line:
                print("---- processing ", line_num, "article----------------")
                a = jieba.cut(line)
                line_seg = " ".join(a)
                f1.writelines(line_seg)
                line_num = line_num+1
                line = f.readline()


def w2v(target_file):
    with open(target_file) as f:
        sentences = LineSentence(f)
        model = Word2Vec(LineSentence(f), sg=1, vector_size=123, window=10, min_count=0, workers=15, sample=1e-3, hs=1, negative=0)
        # model = gensim.models.Word2Vec()  # 建立一个空的模型对象
        # model.build_vocab(sentences)  # 遍历一次语料库建立词典
        # model.train(sentences) # 第二次遍历语料库建立神经网络模型

        # 输出内容
        print(model.wv["女儿"])
        print(model.wv.similarity('女儿', '钱钟书'))
        print(model.wv.index_to_key)
        print(1)


if __name__ =="__main__":
    source_file = "/home/pythonCode/practice_new/embedding_practice/we.txt"
    target_file = "/home/pythonCode/practice_new/embedding_practice/target.txt"
    # word_seg(source_file, target_file)
    w2v(target_file)

详见word2vec 中的数学原理详解word2vec 中的数学原理详解 - peghoty - 博客园

文章正在审核中... - 简书

 Word2Vec中的数学:有道云笔记

深入浅出Word2Vec原理解析:深入浅出Word2Vec原理解析 - 知乎

10.1. 词嵌入(word2vec) — 《动手学深度学习》 文档

word2vec(cbow+skip-gram+hierarchical softmax+Negative sampling)模型深度解析 - 知乎

深度学习推荐系统中各类流行的 Embedding 方法(上) - AIQ

更多推荐