TF入门04-TF实现Word2Vec

2020-07-23 17:51:16 浏览数 (2)

Word2Vec是一组用来产生词嵌入的模型,包括两种主要的模型:skip-gram和CBOW。

Skip-gram vs CBOW 算法层面上, 两种模型很相似,CBOW模型是从两边预测中心词,skip-gram模型是中心词预测两边。比如,对于“The quick brown fox jumps”,CBOW模型从上下文"the, “quick”, “fox”, 和"jumps"预测中心词“brown”,skip-gram模型从“brown”预测上下文"the, “quick”, “fox”, 和"jumps"。 统计层面上,CBOW模型通过将上下文看做一个样本组成,将分布信息做了平滑处理。大多数情况下,cbow模型适用于小型数据集;skip-gram模型将每个上下文-中心词对看做一个样本,用在大型数据集上表现更好。

在本文中,我们使用skip-gram模型来构建word2vec。为了得到词嵌入向量,我们需要构建一个单隐藏层的神经网络,然后用来执行特定任务来完成训练;但是训练得到的模型并不是我们需要的。我们只关注隐藏层的权重,这些权重就是词嵌入向量。

上面的特定任务是指给定中心词预测上下文。对于句子中的某个词,在词的上下文中随机选择一个词;网络模型可以输出整个词典中每个词是中心词上下文的概率。

Softmax、Negative Sampling & Noise Contrastive Estimation(NCE)

为了得到某个词的所有可能上下文词的概率分布,我们使用softmax来实现。softmax函数根据输入xix_ixi​输出一个概率值pip_ipi​。在这里xix_ixi​表示中心词的一个可能的上下文: softmax(xi)=exp(xi)∑jexp(xj) softmax(x_i) = frac{exp(x_i)}{sum_j exp(x_j)} softmax(xi​)=∑j​exp(xj​)exp(xi​)​ 但是,softmax用于归一化的分母的计算需要遍历整个词典,通常情况下词典长度在百万级别,而且指数的计算也比较耗时,这就导致了softmax的计算量进一步加大

为了规避这个计算瓶颈,我们可以使用分层softmax(hierarchical softmax)和基于采样的softmax。论文Distributed Representations of Words and Phrases and their Compositionality 指出训练skip-gram模型,和分层softmax方法相比,使用negative sampling的方法训练速度更快,得到的词向量更好。

Negative Sampling(负采样)是基于采样方法的一种。基于采样的方法也包括重要性采样(importance sampling)和目标采样(target sampling)。负采样方法是NCE的简化版:负采样对噪声样本(负样本)的采样数量k以及噪声数据服从的分布Q做了假定,kQ(w)=1

负采样方法用于学习词嵌入表示,并不能保证其梯度值和softmax函数梯度值相近;而NCE方法随着负样本采样数的增加其提取值也愈来愈逼近于softmax的梯度值。Mnih and Teh(2012)表明使用25个噪声样本的计算结果与softmax的计算值差距不大,而且运算速度能加快45倍。因此,我们使用NCE来实现word2vec。

基于采样的方法,无论是负采样还是NCE方法,只适用于训练阶段;在应用阶段还需要执行softmax来得到正则化的概率结果。

数据介绍

2006年3月3日的维基百科文本的100MB数据text8。

100MB数据训练得到的词嵌入虽然表现不太好,但是从中也能看到一些有趣的现象。使用空格切分数据后,文本包括17005207个词。为了得到更好的词嵌入,需要使用更大的数据集。

Overview

使用TensorFlow实现模型,需要景观两个阶段:定义计算图以及图的运行。

阶段一:图定义

  1. 导入数据(tf.data 、placeholders)
  2. 定义权重
  3. 定义模型
  4. 定义损失函数loss
  5. 定义优化器

阶段二:执行运算图

  1. 变量初始化
  2. 初始化迭代器/向模型传送数据
  3. 执行前向计算
  4. 计算cost
  5. 梯度计算来调整模型参数

阶段一:图定义

1. 创建dataset,生成样本

skip-gram模型的输入为(中心词,上下文词)pair对。数据传送到模型之前,需要将字符串类型转换成indices表示,如果“computer”是词典中第100个单词,那么对象下标为99。

每一个样本数据是一个标量,BATCH_SIZE个输入样本的构成tensor的shape 为[BATCH_SIZE],输出样本的shape为[BATCH_sIZE, 1].

2.定义权重

在embedding矩阵中每一行表示一个词的向量表示。如果词向量长度为EMBED_SIZE,embedding矩阵的shape为[VOCAB_SIZE, EMBED_SIZE]

3. Inference

为了从embed_matrix中得到对应输入的词向量表示,我们可以使用tf.nn.embedding_lookup来实现:

这个函数相当于一个查表操作,根据输入ids在params找到对应的向量。

如果输入是one_hot表示,向量乘以矩阵可以很快地找到one_hot非零值对应的向量(one_hot中非零值为第4个,相乘后结果就是矩阵的第4行);使用相乘方法,由于one_hot表示有很多0值进而会产生许多不必要的计算;使用tf.nn.lookup就可以节省这些不必要的计算。

为了得到中心词的embedding表示,

代码语言:javascript复制
embed = tf.nn.embedding_lookup(embed_matrix, center_words, name='embed')
4. 定义损失函数

​ TensorFlow已经为我们实现了NCE损失函数:

代码语言:javascript复制
tf.nn.nce_loss(
    weights,
    biases,
    labels,
    inputs,
    num_sampled,
    num_classes,
    num_true=1,
    sampled_values=None,
    remove_accidental_hits=False,
    partition_strategy='mod',
    name='nce_loss'
)

为了计算NCE loss,需要定义计算loss的隐藏层权重weights和偏置biases。在训练过程中这些参数会自动更新、优化。在采样之后,最终结果的计算过程如下:

代码语言:javascript复制
tf.matmul(embed, tf.transpose(nce_weight))   nce_bias

这项计算包含在tf.nn_nce_loss的计算过程中。

代码语言:javascript复制
nce_weight = tf.get_variable('nce_weight', shape=[VOCAB_SIZE, EMBED_SIZE], initializer=tf.truncated_normal_initializer(stddev=1.0 / (EMBED_SIZE ** 0.5)))
nce_bias = tf.get_variable('nce_bias', initializer=tf.zeros([VOCAB_SIZE]))

损失函数定义如下:

代码语言:javascript复制
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weight,
                              biases=nce_bias,
                              labels=target_words,
                              inputs=embed,
                              num_sampled=NUM_SAMPLED,
                              num_classes=VOCAB_SIZE))
5. 定义优化器
代码语言:javascript复制
optimizer = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)

阶段二:图的执行

创建一个会话来执行优化op。

代码语言:javascript复制
with tf.Session() as sess:
    # 迭代器初始化
    sess.run(iterator.initializer)
    # 变量初始化
    sess.run(tf.global_variables_initializer())

    writer = tf.summary.FileWriter('graphs/word2vec_simple', sess.graph)

    for index in range(NUM_TRAIN_STEPS):
        try:
            # 执行优化,计算loss
            loss_batch, _ = sess.run([loss, optimizer])
        except tf.errors.OutOfRangeError:
            sess.run(iterator.initializer)
    writer.close()

完整代码地址:ClickMe

Reference

Stanford CS 20: Tensorflow for Deep Learning Research

0 人点赞