译:https://adventuresinmachinelearning.com/word2vec-keras-tutorial/
很多博客都阐述了word2vec这个模型的原理,例如空间映射、稠密向量表示,cbow/skip-gram,softmax求解,霍夫曼树,blabla...
但数学公式理解起来比较抽象,结合工程实现可以更好的帮助理解。
理解Word2Vec单词嵌入是机器学习历程中的关键组成部分。词嵌入是在机器学习模型中执行高效自然语言处理的必要步骤。本文将展示如何在Keras深度学习框架中执行Word2Vec单词嵌入。同时,本文还将展示当我们的词汇表非常大时,使用基于softmax的词嵌入训练方案会导致训练过程极其缓慢。一种称为“负采样”的技术已经解决这个问题,并且已经在TensorFlow中实现了(nce_loss)。
不幸的是,这个损失函数在Keras中不存在,因此在本文中,我们将自己实现它。这也是因祸得福了,因为自己实现它将有助于我们理解负采样如何工作,从而更好地理解Word2Vec Keras过程。
词嵌入
如果我们用一批文档去训练某种自然语言机器学习系统(比如聊天机器人),我们需要创建所有文档中最常用单词的词汇表。在某些情况下,该词汇表的长度可能大于10,000个单词。为了向我们的机器学习模型表示一个单词,一种很淳朴的方式是使用one-hot表示,即一个全0的矢量,除了表示我们那个单词的位置设置为1。但是这是一种低效的方式 ,一个10,000字的向量是很难训练的。另一个问题是,这些one-hot向量不包含单词语义信息,如何让它在文档的上下文中连贯使用?(比如一词多义?)
词嵌入的目的是将大的one-hot向量“压缩”成更小的向量(几百个元素),这些向量保留了单词的一些含义和上下文,而Word2Vec是最常用的词嵌入方法。
上下文,word2Vec以及skip-gram模型
表达词的上下文是word2Vec最关键的作用。“the cat sat on the mat”这句话中“sat”一词的上下文是(“the”,“cat”,“on”,“the”,“mat”)。换句话说,这些词是通常出现在目标词“sat”周围的词。具有相似上下文的单词在Word2Vec下具有相似的词义,并且压缩它们的向量维度后,所表示的意义也是相似的。在word2Vec的skip-gram模型版本中(稍后将详细介绍),目标是获取目标词,即“sat”,并预测其周围的上下文词。
这种学习的最终产物是网络中的嵌入层,这个嵌入层其实是一种查找表:每行表示我们词汇表中相应单词的向量。举个栗子:我们设词汇表有6个词,one-hot表示就是一个6维的向量,通过词嵌入将维度压缩到3(假设数据哦):
原词汇表 | one-hot | 压缩后的向量 |
---|---|---|
我 | [1,0,0,0,0,0] | [0.2,-0.5,-0.7] |
爱 | [0,1,0,0,0,0] | [0.8,0.5,0.7] |
机 | [0,0,1,0,0,0] | [0.4,-0.3,0.6] |
器 | [0,0,0,1,0,0] | [0.1,-0.1,-0.9] |
学 | [0,0,0,0,1,0] | [0.2,-0.3,0.6] |
习 | [0,0,0,0,0,1] | [0.9,-0.4,-0.8] |
可以看到,每个单词(行)由大小为3的向量表示。这种嵌入层/查找表可以使用简单的神经网络和softmax输出训练得出,如下图所示:
上面的神经网络的输入是以one-hot表示的目标词,接着通过隐藏层的训练,让有效上下文单词的概率增加,同时让无效上下文单词的概率降低(也就是在目标单词的周围上下文中从不出现的单词)。softmax函数负责输出最后的概率。训练完成后,输出层将被丢弃,我们的嵌入向量就是隐藏层的权重。
Word2Vec有两种形式:skip-gram和CBOW。skip-gram输入目标词预测周围的上下文词,而CBOW正好相反,输入一组上下文词预测其中的目标词。本文我们只考虑skip-gram(效果好一些)。
softmax VS negative sampling
使用softmax输出的问题在于它的计算量非常大。考虑softmax函数的定义:
这里的P为输出为类的概率。分子的算法:将隐藏层的输出与连接到类输出层的权重相乘;分母的算法:对所有输出类采用与分子相同的算法,并求和。如果输出采用一个10000维的one-hot向量表示,那采用梯度下降算法求解模型时,需要计算数以百万计的权重,这会非常耗时且效率低下。
还有一种称为负抽样的解决方案。它在Mikolov等人的原始Word2Vec论文中有所描述。简单理解为:所有将目标词与目标词上下文连在一起的网络,则增强它们的权重;而对于非目标词上下文的网络,不是去降低它们的权重,而是简单的对它们进行下采样。这就是negative sampling。
为了在Keras中使用负样本训练嵌入层,我们可以重新设想我们训练网络的方式。我们可以将原来的softmax层替换为简单的二分类器。对于在目标词的上下文中的单词,我们希望我们的网络输出1,对于我们的负样本,我们希望我们的网络输出0。因此,我们基于keras实现的Word2Vec网络的输出层只是一个sigmoid。
我们还需要一种方法来确保进过网络训练后,相似的单词最终具有类似的嵌入向量。因此,我们要确保在相同上下文中出现的词,网络总是输出1,而从未在相同上下文中出现的词,网络总是输出0。因此,我们需要提供sigmoid输出层一个矢量相似性得分:相似矢量输出高分和不相似矢量输出低分。两个向量之间使用的最典型的相似性度量是余弦相似度得分:
这种度量方法的分母用于归一化结果,真正的相似性操作在分子上:向量和之间的点积。
综上所述,我们基于Keras的Word2Vec实现新的负抽样网络具有以下特点:
- 目标词的(整数)输入,上下文词的正负例
- 嵌入层查找(即在嵌入矩阵中查找单词的整数索引以获取单词向量)
- 点积运算
- 输出sigmoid层
实现的体系结构如下所示:
让我们更仔细地考虑这个架构。首先,我们词汇表中的每个单词都被赋予一个介于0和我们词汇表大小之间的整数索引(在本例中为10,000)。我们将两个单词传递到网络中,一个是目标词,另一个是来自周围上下文的单词或负样本。我们利用输入词的索引来“查找”嵌入层(10,000 x 300权重张量)的行,以检索这个词对应的维度为300单词向量。然后,我们在这些向量之间执行点积运算以获得相似性。最后,我们将相似性输出到sigmoid层,匹配上了上下文的词输出1,未匹配的输出0。
实现
本节将展示如何创建自己的基于Keras的Word2Vec实现 - 代码在此。
数据提取
我们首先需要一些数据。在Word2Vec TensorFlow教程中,我们将使用此处的文档数据集。为了提取信息,我将使用上述Word2Vec教程中的一些相同的文本提取函数,特别是collect_data函数。这个函数实现了下载数据,将文本数据转成了由字符串表示的整数(词汇表中的每个单词由唯一的整数表示)。要调用此函数,我们运行:
vocab_size = 10000
data, count, dictionary, reverse_dictionary = collect_data(vocabulary_size=vocab_size)
数据集前7个词是:
[‘anarchism’, ‘originated’, ‘as’, ‘a’, ‘term’, ‘of’, ‘abuse’]
运行collect_data函数后,这7个词词表示为:
[5239, 3082, 12, 6, 195, 2, 3134]
从collect_data返回还有两个词典:第一个可以查找单词并获得其整数表示,第二个正好相反。
接下来,我们需要为训练定义一些常量,并创建一组验证集以便我们检查词向量的学习情况。
常量和验证集
window_size = 3
vector_dim = 300
epochs = 1000000
valid_size = 16 # Random set of words to evaluate similarity on.
valid_window = 100 # Only pick dev samples in the head of the distribution.
valid_examples = np.random.choice(valid_window, valid_size, replace=False)
- 第一个常量 window_size是目标单词周围的单词窗口,用于从中绘制上下文单词。
- 第二个常量vector_dim是每个字嵌入向量的大小:我们的嵌入层的大小为10,000 x 300。
- 最后,我们有一个大的epochs变量:指定了的训练迭代次数。即使采用负抽样,词嵌入也可能是一个耗时的过程。
下一组参数与我们要检查的单词相关,以查看其他单词与此验证集的相似之处。在训练过程中,我们将通过词嵌入向量来检查哪些单词开始就被认为是相似的,并确保这些单词与我们对这些单词的含义的理解相符。我们将从数据集前100个最常见的单词中随机选择16个单词进行检查(collect_data 按升序分配数据集整数中最常用的单词,即最常用的单词分配1,下一个最常见的2)。
接下来,我们来看看keras为我们准备的skip-gram函数。
未完待续。。。