1. CBOW与Skip-Gram基础模型
前面我们讲到one-hot编码有2个大的缺点:
(1)维度灾难:词汇表一般都非常大,假如词汇表有 10k 个词,那么一个词向量的长度就需要达到 10k,而其中却仅有一个位置是1,其余全是0,内存占用过高且表达效率很低。
(2)无法体现出词与词之间的关系:比如 “爱” 和 “喜欢” 这两个词,它们的意思是相近的,但基于 one-hot 编码后的结果取决于它们在词汇表中的位置,不能很好地刻画词与词之间的相似性。因为任何两个one-hot向量之间的内积都是0,很难区分他们之间的差别。
也讲了词向量的形式和优势,为了生成这种词向量,研究者们提出了很多方法,这节我们讲解一下目前通用的生成词向量的模型——Google2013年开源的一款用于词向量计算的工具——Word2Vec。
首先,Word2Vec利用浅层神经网络可以在百万数量级的词典和上亿的数据集上进行高效地训练,不仅提高了表示的质量,还显著提升了训练速度和效率;其次,该工具训练过程中得到的词向量(word embedding),可以很好地度量词与词之间的相似性。
Word2Vec只是一种工具的名称,其本身不生成词向量,依靠的是其背后用于计算word vector(Continuous Bag-of-Words )的 CBOW 模型和 Skip-gram 模型。接下来让我们分别介绍这两个模型以及它们的训练方法。
1.1 CBOW模型
CBOW模型,即Continuous Bag-of-Words ,顾名思义就是一袋子词语,那么如何选择这些词语呢?
如上,answer是想要学习的中心词,假设窗口大小为3,前面的“is the best”和后面的“to study natural”就是该中心词对应的上下文,就是模型的输入。由于CBOW使用的是词袋模型,因此这8个词都是平等的,也就是不考虑和关注的词之间的距离大小,只要在上下文之内即可。
如图所示是CBOW模型的结构,
(1)首先关注一下输入输出,
输入:某一个中心词的上下文相关对应的单词(根据设置的窗口大小决定单词数C=2c)的one-hot编码(C*V,其中C是两边的上下文的词语总数,V是词汇量的长度);
输出:中心词的one-hot编码(1*V)。
(2)学习一下模型的数据流的处理过程,
第一步是将上下文独热编码X=C*V输入模型,然后与隐藏层的参数矩阵W=V*D相乘得到隐藏层X’=XW=(C*V)(V*D)=C*D;
第二步是将隐藏层的矩阵X’在行维度上求平均数得到向量h=1*D;
第三步是将中间向量h=1*D再乘上参数矩阵W’=D*V得到一个中间结果向量P=hW’=(1*D)(D*V)=1*V,然后将其softmax归一化,这样得到词汇表中(长度为V)所有单词的概率,值最大的就是预测得到的中心词。
第四步就是将标签值和预测值代入损失函数并利用优化算法优化中间的2个参数矩阵,直到满足训练要求。
(3)词向量到底在哪?了解了整个训练过程,似乎没看到词向量在哪里。事实上,词向量就是训练时第一个参数矩阵W=V*D,而且是经过训练将词汇表中的所有V个词的词向量一起训练得到的。
1.2. Skip-gram模型
Skip-Gram模型和CBOW的思路是反着来的(互为镜像),即使用中心词来预测上下文词。还是上面的例子,上下文大小取值为3, 特定的中心词"answer"是输入,而窗口内的6个上下文词是输出。
(1)首先关注一下输入输出,
输入是特定中心词的one-hot编码(1*V),
输出是中心词对应的所有上下文单词的one-hot编码。
(2)学习一下模型的数据流的处理过程,
第一步是将中心词独热编码X=1*V输入模型,然后与隐藏层的参数矩阵W=V*D相乘得到隐藏向量h=XW=(1*V)(V*D)=1*D;
第二步是将隐藏向量h分别乘上C个参数矩阵W’=V*D得到C个结果向量P=1*V,然后分别将这C个结果向量归一化softmax得到词汇表(长度为V)中每个单词的概率;
第三步是将标签值和预测值代入损失函数并利用优化算法优化中间的2个参数矩阵,直到满足训练要求。
(3)词向量到底在哪?词向量同样是训练时第一个参数矩阵W=V*D,而且是经过训练将词汇表中的所有V个词的词向量一起训练得到的。
2 分层Softmax
上面我们介绍了基础的CBOW与Skip-Gram模型,但在实际训练中一般不会直接使用基础模型。为什么呢?这是因为基础模型的训练过程非常耗时。词汇表一般在百万级别以上,而基础模型的输出层需要进行softmax计算各个词的输出概率,对于每一次预测,我们都要计算所有V个单词出现的概率,这意味着很大的计算量。Skip-Gram对于一个训练样本就需要做多次多分类任务,因此Skip-Gram相对于CBOW的计算量更大。有没有简化一点点的方法呢?
有的,研究者们提出了两种高效训练的方法:层序softmax (hierarchical softmax)和负采样(negative sampling)。二者都是在计算输出概率的时候对计算量进行了优化,分层Softmax和负采样是可以相互替代的作为Word2Vec的一种加速计算的方式。接下来将详细介绍一下。
2.1哈夫曼树
层序softmax的核心是哈夫曼树,我们先来看一看什么是哈夫曼树,它又是怎么应用的。
哈夫曼树是特殊的二叉树结构,划分为多个节点。现在的关键就是明确每个节点代表什么,每个节点是如何创建的?
哈夫曼树的节点
哈夫曼树有2大类节点叶子节点和内节点,
叶子节点:一类是下面无子节点的叶子节点起到输出层神经元的作用,叶子节点的个数即为词汇表的大小。
内部节点:有子节点的内部节点,起到隐藏层神经元的作用。根节点是特殊的内部节点,是整个哈夫曼树的起点。
哈夫曼树编码方式:一般对于一个哈夫曼树的节点(根节点除外),可以约定左子树编码为0,右子树编码为1。
哈夫曼树的创建
哈夫曼树是根据词汇表的词频来创建的,所谓词频,就是一个单词在语料库中出现的次数,词频越大的单词在哈夫曼树的层级越浅(越靠近根节点)。
举个简单的例子来看哈夫曼树具体创建的过程,下面是一个语料库中出现的词语的次数和索引,
(1)将单词和词频作为一个节点的属性,创建所有词语的节点。
(2)将所有节点放入到优先队列中(词频越小越靠前),每次取出两个最小频次的节点索引,例如2、6,组成新的节点node1,该节点的频次(权重)为二者之和(权重2)。同理我们用7、8和5、9组合成node2(权重2)和node3(权重2)节点;
(3)此时队列中最小的频次(权重)为2,继续取出节点进行合并,过程依次为将1、3合并为node4(权重4),node2、4合并为node5(权重4),node1、node3合并为node6(权重4),node4、node5合并成node7(权重8),然后将node6和1合并成node8(权重9),最后将node7和node8合并为node9(权重17)得到Huffman树,如下图所示。
对应于每一个非叶子节点,向左子树方向的编码为 0 ,向右子树方向的编码为 1 。根据路径我们可以得到从根节点到叶子节点(也就是每个单词)的路径,如下表所示。
2.2. 基于分层softmax的CBOW模型
创建完成哈夫曼树后,介绍一下基于哈夫曼树的CBOW模型是如何工作的。
如上图,模型的前半部分是没有变化的,变化的是后面加了哈夫曼树的部分,我们逐步看一下:
(1)首先关注一下输入输出,
输入:某一个中心词的上下文相关对应的单词(根据设置的窗口大小决定单词数C=2c)的one-hot编码(C*V,其中C是两边的上下文的词语总数,V是词汇量的长度);
输出:中心词的哈夫曼树编码路径(1*k,k为路径长度,不确定值)。
(2)模型的数据流和基础模型相同的处理过程,
第一步是将上下文独热编码X=C*V输入模型,然后与隐藏层的参数矩阵W=V*D相乘得到隐藏层X’=XW=(C*V)(V*D)=C*D;
第二步是将隐藏层的矩阵X’在行维度上求平均数得到向量h=1*D;
前两步和基础模型一致,关键看一下后面的步骤。
(3)基于哈夫曼树的数据流处理过程。
前面2步获得了中间向量h=1*D,也建立了基于词频的哈夫曼树,那么怎么输出最终的预测结果,也就是词语的哈夫曼树路径呢?
我们需要先给予先前创建的哈夫曼树的每个非叶子节点一个参数向量θ=D*1,第三步就是利用第二步获得的中间向量h=1*D与参数θ相乘,再加上偏移量b’,然后取 Sigmoid 得到正向的概率:
相应的,反向的概率。这样遍历每个非叶子节点,得到了每个非叶子节点往左还是往右的概率值;
第4步就是,根据从根节点到叶子节点(词语)的路径,通过每个非叶子节点往左还是往右的概率值求得根节点到每个叶子节点的概率:
其中,d取0或1,往左还是往右走,l是词语从根节点到叶子节点(词语)的路径。
第5步就是根据根节点到每个叶子节点的概率值,最大的为网络训练预测的词语。然后根据实际标签路径,代入损失函数进行网络训练优化,使得预测路径向正确路径靠拢。
(3)分层softmax如何优化计算量的呢?在原本的Word2Vec模型的 Softmax 层中,对于每一次预测,我们都要计算所有 V个单词出现的概率,在分层 Softmax中,由于使用了 Huffman 树,我们最多计算V个单词的概率,平均计算为logV次,在数量级巨大时,优化计算十分明显。例如当V=1000000 时,在 Softmax 层中,我们将计算 1000000 次e^x运算,而log(1000000)≈14 次(Sigmoid运算), 这个优化十分巨大
(4)词向量到底在哪?了解了整个训练过程,似乎没看到词向量在哪里。事实上,词向量就是训练时第一个参数矩阵W=V*D,而且是经过训练将词汇表中的所有V个词的词向量一起训练得到的。
2. 3 Skip-gram
skip-gram模型的结构与CBOW模型正好相反,skip-gram模型输入某个单词,输出对它上下文词向量的预测。
Skip-gram的核心同样是一个哈夫曼树, 每一个单词从树根开始到达叶节点,可以预测出它上下文中的一个单词。对每个单词进行N-1次迭代,得到对它上下文中所有单词的预测, 根据训练数据调整词向量得到足够精确的结果。
3负采样
在原本的Word2Vec模型中,在Softmax层中,我们会进行 V 次 e^x运算,这个计算量在 V较大时,计算的时间复杂度特别高。问题的关键就在于我们需要将词汇表所有词语的概率都计算出来,而负采样的基本思想就是不再单独计算词汇表里每个词语的概率,而是从词汇表中选择少数几个负样本来参与每次的训练。
那么什么是负样本呢?在CBOW中,根据上下文预测的中心词为正样本,非中心词则都为负样本;在Skip-gram中,根据中心词预测的上下文为正样本,非上下文则都为负样本;使用少数几个样本作为负样本,例如我们令负样本数 k = 5 (通常 k为 5 ∼ 20 ),这将把计算时间复杂度将为常数级。
在负采样中,通常不使用Softmax多分类,而是使用Sigmoid函数进行二分类。我们通常将这 k个负例分别与正例使用 Sigmoid函数做二分类计算获得每个样例的得分并组合成得分向量,将多分类转换成k个二分类,最后使用Softmax归一化得分得到样例的概率值。
比如我们有一个训练样本,中心词是w,它周围上下文共有2c个词,记为context(w)。由于这个中心词w的确和context(w)相关存在,因此它是一个真实的正例。通过Negative Sampling采样,我们得到neg个和w不同的中心词wi,i=1,2,..neg,这样context(w)和wi就组成了neg个并不真实存在的负例。利用这一个正例和neg个负例,进行二元逻辑回归,得到负采样对应每个词wi的模型参数\theta _{i},和每个词的词向量。(w和wi都是中心词)
如上为负采样的网络架构,可以看出,前面仍和基础模型一致,只是后面采用了负采样的方法。
(1)首先关注一下输入输出,
输入:某一个中心词的上下文相关对应的单词(根据设置的窗口大小决定单词数C=2c)的one-hot编码(C*V,其中C是两边的上下文的词语总数,V是词汇量的长度);
输出:k组二分类结果。
(2)学习一下模型的数据流的处理过程,
第一步是将上下文独热编码X=C*V输入模型,然后与隐藏层的参数矩阵W=V*D相乘得到隐藏层X’=XW=(C*V)(V*D)=C*D;
第二步是将隐藏层的矩阵X’在行维度上求平均数得到向量h=1*D;
(3)负采样层,第三步是将中间向量h=1*D再乘上参数矩阵θ’=D*1然后对其sigmoid求是正还是负的概率值,转变为二分类问题。
第四步就是将标签值和预测值代入损失函数并利用优化算法优化中间的2个参数矩阵,直到满足训练要求。
(3)词向量到底在哪?了解了整个训练过程,似乎没看到词向量在哪里。事实上,词向量就是训练时第一个参数矩阵W=V*D,而且是经过训练将词汇表中的所有V个词的词向量一起训练得到的。
参考资料
- 《动手学深度学习》 — 动手学深度学习 2.0.0 documentation
【万字长文】Word2Vec计算详解(一)CBOW模型_word2vec实例详解-CSDN博客