Vector Representations of Words(字词的向量表示)
词的向量表示
在本教程中,我们看一下Mikolov等人的word2vec模型。该模型用于学习单词的矢量表示,称为“单词嵌入”。
重点
本教程旨在强调在TensorFlow中构建word2vec模型的有趣实质部分。
- 我们首先给出了为什么我们想要将单词表示为向量的动机。
- 我们来看看模型背后的直觉以及它是如何操练的(用数学的方法来衡量)。
- 我们还在TensorFlow中展示了一个简单的模型实现。
- 最后,我们研究如何让纯粹的版本更好地扩展。
在本教程中,我们会稍后介绍代码,但如果您希望直接进入,请随时查看tensorflow / examples / tutorials / word2vec / word2vec_basic.py中的简约实现。此基本示例包含下载所需的代码一些数据,对它进行一些操练并将结果可视化。一旦你对阅读和运行基本版本感到满意,你可以毕业到模型/ tutorials / embedding / word2vec.py这是一个更严格的实现,它展示了一些更高级的TensorFlow原则,关于如何有效地使用线程将数据移动到文本中模型,操练期间如何检查点等。
但首先,让我们看看为什么我们想要首先学习单词嵌入。如果您是嵌入式专业版,请随意跳过本节,并且您只是想弄清楚您的手上的细节。
动机:为什么学习单词嵌入?
图像和音频处理系统与丰富的高维数据集一起工作,该数据集被编码为用于图像数据的各个原始像素强度的矢量,或者例如用于音频数据的功率谱密度系数。对于像对象或语音识别这样的任务,我们知道成功执行任务所需的所有信息都编码在数据中(因为人类可以从原始数据执行这些任务)。然而,自然语言处理系统传统上把单词当作离散的原子符号来处理,因此'cat'可能被表示为Id537
而‘dog’Id143
。这些编码是任意的,并且不会向系统提供有关各个符号之间可能存在的关系的有用信息。这意味着该模型在处理关于‘dog’的数据(例如它们都是动物,四足动物,宠物等)时可以充分利用‘cat’的知识。将单词表示为独特的离散ID还会导致数据稀疏,并且通常意味着我们可能需要更多数据才能成功训向练统计模型。使用向量表示可以克服这些障碍中的一些。
向量空间模型(VSM)表示(嵌入)连续向量空间中的单词,其中语义上相似的单词被映射到附近的点('彼此嵌入在一起')。VSM在NLP中有着悠久丰富的历史,但是所有方法都以某种方式依赖于分布假说,该假设指出出现在相同语境中的词语具有语义意义。利用这一原理的不同方法可以分为两类:基于计数的方法
(如潜在语义分析)和预测方法
(如神经概率语言模型)。
Baroni等人详细阐述了这种区别,但简而言之:基于计数的方法计算某个词在大型文本语料库中与其相邻词汇共同出现的频率的统计数据,然后将这些统计数据映射到每个词的小型密集向量。预测模型直接尝试根据学习的小密集嵌入向量
(考虑模型的参数)来预测来自其邻居的单词。
Word2vec是一种特别有效的计算预测模型,用于从原始文本中学习单词嵌入。它有两种类型,即连续词袋模型(CBOW)和Skip-Gram模型(Mikolov等人的 3.1节和3.2节)。)。从算法上讲,这些模型是相似的,除了CBOW从源上下文单词('猫坐在'上)预测目标单词(例如'mat'),而跳跃单词则执行反转并预测来自目标的源上下文单词话。这种倒置可能看起来像是一种任意的选择,但从统计上看,它具有CBOW平滑许多分布信息的效果(通过将整个上下文视为一个观察)。对于大多数情况来说,这对于较小的数据集来说是有用的。然而,skip-gram将每个上下文对象作为一个新的观察对象,而当我们拥有更大的数据集时,这往往会做得更好。我们将在本教程的其余部分重点介绍skip-gram模型。
加强噪声对比训练
神经概率语言模型使用的是传统培训的最大似然(ML)的原则,以最大限度地考虑到以前的话(H)的下一个单词(W_T)(对于“目标”)的概率(对于“历史”)中的条款SOFTMAX
功能,
$$ \begin{align} P(w_t | h) &= \text{softmax} (\text{score} (w_t, h)) \ &= \frac{\exp { \text{score} (w_t, h) } } {\sum_\text{Word w' in Vocab} \exp { \text{score} (w', h) } } \end{align} $$
其中(\ text {score}(w_t,h))计算词(w_t)与上下文(h)的兼容性(常用的点积)。我们通过最大化操练集上的对数似然来操练这个模型,即通过最大化
$$ \begin{align} J_\text{ML} &= \log P(w_t | h) \ &= \text{score} (w_t, h) - \log \left( \sum_\text{Word w' in Vocab} \exp { \text{score} (w', h) } \right). \end{align} $$
这为语言建模提供了一个适当的标准化概率模型。然而,这是非常昂贵的,因为我们需要在每个操练步骤
中使用当前上下文(h)中的所有其他(V)词(w')的分数来计算和归一化每个概率。
另一方面,对于word2vec中的特征学习,我们不需要完整的概率模型。CBOW和skip-gram模型使用二元分类目标(逻辑回归)来训练,以在同一上下文中区分来自(k)虚数(噪音)单词(\ tilde w)的真实目标词(w_t)。我们在下面对CBOW模型进行说明。对于skip-gram,方向是简单的倒置。
在数学上,目标(对于每个示例)是最大化的
$$J_\text{NEG} = \log Q_\theta(D=1 |w_t, h) + k \mathop{\mathbb{E}}_{\tilde w \sim P_\text{noise}} \left \log Q_\theta(D = 0 |\tilde w, h) \right$$
其中(Q_ \ theta(D = 1 | w,h))是在数据集(D)的上下文(h)中查看单词(w)的模型下的二元逻辑回归概率,嵌入向量(\ theta)。实际上,我们通过从噪声分布中绘制(k)对比词来逼近期望值(即,我们计算蒙特卡罗平均值)。
当模型将高概率分配给真实词时,这个目标被最大化,并且低概率分配给噪音词
。从技术上讲,这被称为负采样,并且有使用这种损失函数的良好数学动机:它提出的更新近似于极限中softmax函数的更新。但从计算角度来看,它特别吸引人,因为计算损失函数现在只与我们选择的噪音词
((k))的数量成比例,而不是词汇表((V))中的所有词
。这使得训练速度更快。实际上,我们将使用非常相似的噪声对比估计(NCE)损失,对此TensorFlow具有方便的帮助功能tf.nn.nce_loss()
。
让我们直观地感受这将如何在实践中发挥作用!
跳跃模型
作为一个例子,我们来考虑一下数据集
the quick brown fox jumped over the lazy dog
我们首先形成一个单词数据集和它们出现的上下文。我们可以用任何有意义的方式来定义“语境”,事实上人们已经看过语法上下文(即当前目标词的语法依赖,例如参见Levy等人),词语的左边目标,目标词语等。现在让我们坚持香草定义,并将“上下文”定义为目标单词左侧和右侧的单词窗口。使用1的窗口大小,然后我们有数据集
([the, brown], quick), ([quick, fox], brown), ([brown, jumped], fox), ...
的(context, target)
对。回想一下,skip-gram会颠倒上下文和目标,并试图从目标词中预测每个上下文单词,因此任务将从'quick','quick'和'fox'预测'brown'和'brown' '等,因此我们的数据集成为
(quick, the), (quick, brown), (brown, quick), (brown, fox), ...
在(input, output)对中。目标函数是在整个数据集上定义的,但我们通常使用一个示例(或通常在示例的'minibatch'中)通过随机梯度下降(SGD)对其进行优化。所以我们来看看这个过程的一个步骤。batch_size16 <= batch_size <= 512
让我们想象一下,在操练步骤(T)我们观察上面的第一次训练的情况下,其目的是预测the
的quick
。我们num_noise
通过从一些噪声分布(通常是单字分布)(P(w))中选取一些噪声(对比)例子来选择。为了简单,让我们说num_noise=1
,我们选择sheep
一个嘈杂的例子。接下来,我们计算这对观察到的和有噪声的例子的损失,即时间步长(t)的目标变为
$$J^{(t)}_\text{NEG} = \log Q_\theta(D=1 | \text{the, quick}) + \log(Q_\theta(D=0 | \text{sheep, quick}))$$
目标是对嵌入参数(\ theta)进行更新以改进(在这种情况下,最大化)该目标函数。我们通过推导关于嵌入参数(\ theta)的损失梯度来实现这一点,即(\ frac {\ partial} {\ partial \ theta} J_ \ text {NEG})(幸运的是TensorFlow提供了简单的辅助函数这样做!)。然后,我们通过向渐变方向迈出一小步来更新嵌入。当这个过程在整个训练集上重复时,这会对每个单词产生“移动”嵌入向量的效果,直到模型成功地区分真实单词和噪音单词为止。
我们可以通过使用例如类似t-SNE降维技术的方式将它们投影到2维来可视化所学习的矢量。当我们检查这些可视化时,很明显这些向量捕获了一些关于单词的语义信息以及它们之间的关系,并且实际上非常有用。当我们首次发现诱导向量空间中的某些方向专注于某些语义关系时,例如男性 - 女性
,动词时态
甚至是单词之间的国家 - 资本
关系,这一点非常有趣,如下图所示(另请参见Mikolov等人,2013)。
这解释了为什么这些向量作为许多典型的NLP预测任务的特征也是有用的,例如词性标注或命名实体识别(参见例如Collobert等,2011(pdf)的最初的工作,Turian等人2010年的后续工作)。
但现在,让我们用它们画出漂亮的图画!
建立图表
这是关于嵌入的,所以我们来定义我们的嵌入矩阵。这只是一个很大的随机矩阵开始。我们将初始化单位立方体中的值。
embeddings = tf.Variable(
tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
噪声对比估计损失是用逻辑回归模型来定义的。为此,我们需要为词汇表中的每个单词定义权重和偏差(也称为output weights
相反input embeddings
)。所以我们来定义一下。
nce_weights = tf.Variable(
tf.truncated_normal([vocabulary_size, embedding_size],
stddev=1.0 / math.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
现在我们已经有了这些参数,我们可以定义我们的skip-gram模型图。为了简单起见,我们假设我们已经将文本语料库与词汇表进行了整合,以便将每个词表示为一个整数(有关详细信息,请参阅tensorflow / examples / tutorials / word2vec / word2vec_basic.py)。跳跃模型需要两个输入。一个是批量充满代表源语境词语的整数,另一个是针对目标词语的整数。让我们为这些输入创建占位符节点,以便稍后可以输入数据。
# Placeholders for inputs
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
现在我们需要做的是查找批处理中每个源词的向量。在TensorFlow中的这些操作很便捷。
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
好吧,现在我们已经为每个单词嵌入了,我们希望使用噪声对比操练目标来预测目标单词。
# Compute the NCE loss, using a sample of the negative labels each time.
loss = tf.reduce_mean(
tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
labels=train_labels,
inputs=embed,
num_sampled=num_sampled,
num_classes=vocabulary_size))
现在我们有一个损失节点,我们需要添加计算梯度所需的节点并更新参数等。为此,我们将使用随机梯度下降,并且TensorFlow也有简单便捷的操作。
# We use the SGD optimizer.
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0).minimize(loss)
操训模型
训练模型就像使用a feed_dict
将数据推入占位符并tf.Session.run
在循环中调用这些新数据一样简单。
for inputs, labels in generate_batch(...):
feed_dict = {train_inputs: inputs, train_labels: labels}
_, cur_loss = session.run([optimizer, loss], feed_dict=feed_dict)
参见tensorflow / examples / tutorials / word2vec / word2vec_basic.py中的完整示例代码。
可视化嵌入
操练结束后,我们可以使用t-SNE可视化学习的嵌入。
看,正如预期的那样,相似的单词最终彼此聚集在一起。对于更重量级的word2vec实现来说明更多TensorFlow的高级功能,请参阅models / tutorials / embedding / word2vec.py中的实现。
评估嵌入:类比推理
嵌入对NLP中的各种预测任务很有用。缺乏对完整的词类模型或命名实体模型的训练,评估嵌入的一种简单方法是直接使用它们来预测类似的句法和语义关系king is to queen as father is to ?
。这被称为类比推理
,Mikolov及其同事介绍了这一任务。从download.tensorflow.org下载此任务的数据集。
要了解我们如何执行此评估,请查看models / tutorials / embedding / word2vec.py中的build_eval_graph()
和eval()
函数。
超参数的选择会严重影响此任务的准确性。要在这个任务上实现最先进的性能,需要对一个非常大的数据集进行训练,仔细调整超参数并利用诸如子采样数据的技巧,这超出了本教程的范围。
优化实施
我们的实施展示了TensorFlow的灵活性。例如,改变操练目标就像把呼叫换成tf.nn.nce_loss()
现成的替代品一样简单,例如tf.nn.sampled_softmax_loss()
。如果您对损失函数有新的想法,您可以在TensorFlow中为新目标手动编写一个表达式,并让优化器计算它的派生值。这种灵活性在机器学习模型开发的探索阶段是非常宝贵的,我们正在尝试几个不同的想法并快速迭代。
一旦你有了一个你满意的模型结构,可能值得优化你的实现来更有效地运行(并在更短的时间内覆盖更多的数据)。例如,我们在本教程中使用的朴素代码会受到损害,因为我们使用Python来读取和提供数据项 - 每个代码在TensorFlow后端上只需很少的工作。如果您发现您的模型严重阻碍了输入数据,那么您可能需要针对您的问题实施自定义数据读取器,如新数据格式中所述。对于Skip-Gram建模的情况,我们实际上已经在models / tutorials / embedding / word2vec.py中为您做了这个例子。
如果您的模型不再受I / O限制,但您希望获得更高的性能,您可以通过编写自己的TensorFlow Ops来进一步做些事情,如添加新操作。我们再次为Skip-Gram case models / tutorials / embedding / word2vec_optimized.py提供了一个例子。随意将这些标准相互对比来衡量每个阶段的性能改进。
结论
在本教程中,我们介绍了word2vec模型,这是一个用于学习单词嵌入的高效计算模型。我们激发了为什么嵌入是有用的,讨论了高效的训练技术,并展示了如何在TensorFlow中实现所有这些。总的来说,我们希望这已经展示了TensorFlow如何为您提供早期实验所需的灵活性,以及您后来需要为定制优化实施所做的控制。