【问题标题】:Understanding `tf.nn.nce_loss()` in tensorflow理解 tensorflow 中的 `tf.nn.nce_loss()`
【发布时间】:2017-01-04 23:54:19
【问题描述】:

我正在尝试了解 Tensorflow 中的 NCE 损失函数。 NCE 损失用于 word2vec 任务,例如:

# Look up embeddings for inputs.
embeddings = tf.Variable(
    tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
embed = tf.nn.embedding_lookup(embeddings, train_inputs)

# Construct the variables for the NCE loss
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]))

# Compute the average NCE loss for the batch.
# tf.nce_loss automatically draws a new sample of the negative labels each
# time we evaluate the loss.
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))

更多详情请参考Tensorflowword2vec_basic.py

  1. NCE 函数中的输入和输出矩阵是什么?

在 word2vec 模型中,我们对构建单词表示感兴趣。在训练过程中,给定一个滑动窗口,每个词都会有两个嵌入:1)当词是中心词时; 2)当词是上下文词时。这两个嵌入分别称为输入和输出向量。 (more explanations of input and output matrices)

在我看来,输入矩阵是embeddings,输出矩阵是nce_weights。对吗?

  1. 什么是最终嵌入?

根据与nce 相关的 s0urcer 的post,它说最终的嵌入矩阵只是输入矩阵。而some others sayingfinal_embedding=input_matrix+output_matrix。哪个是正确的/更常见的?

【问题讨论】:

  • 回答您的第二个问题:您使用哪种策略可能并不重要,但我建议您按照 s0urcer 所说的进行
  • 我试图在这篇文章中解释 NCE 损失。请看一下。 linkedin.com/pulse/…
  • @GabrielChu 您发现每个词都有两个嵌入(中心词和上下文词)。您介意确定这些在任何答案中的位置吗?我的印象是隐藏层权重矩阵就是其中之一。但是哪一个?另一个在哪里?
  • 好吧,经过一番思考 - 直观上看起来 nce_weights 是“其他”嵌入(毕竟,您正在执行一行与另一列的点积) - 最终它是刚刚被丢弃?

标签: python tensorflow


【解决方案1】:

让我们看一下word2vec示例(examples/tutorials/word2vec)中的相关代码。

embeddings = tf.Variable(
    tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
embed = tf.nn.embedding_lookup(embeddings, train_inputs)

这两行代码创建了嵌入表示。 embeddings 是一个矩阵,其中每一行代表一个词向量。 embedding_lookup 是一种快速获取与train_inputs 对应的向量的方法。在 word2vec 示例中,train_inputs 由一些 int32 数字组成,代表目标词的 id。基本上,可以通过隐藏层特征来放置

# Construct the variables for the NCE loss
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]))

这两行创建参数。它们将在训练期间由优化器更新。我们可以使用tf.matmul(embed, tf.transpose(nce_weights)) + nce_biases 来获得最终的输出分数。换句话说,分类中的最后一个内积层可以用它代替

loss = tf.reduce_mean(
      tf.nn.nce_loss(weights=nce_weights,     # [vocab_size, embed_size]
                   biases=nce_biases,         # [vocab_size]
                   labels=train_labels,       # [bs, 1]
                   inputs=embed,              # [bs, embed_size]
                   num_sampled=num_sampled, 
                   num_classes=vocabulary_size))

这些行创建nce loss@garej给出了很好的解释。 num_sampled指的是nce算法中的负采样数。


为了说明nce的用法,我们可以将它应用到mnist示例(examples/tutorials/mnist/mnist_deep.py)中,分两步:

1.用隐藏层输出替换嵌入。隐藏层的维度为1024,num_output 为10num_sampled 的最小值为 1。记得删除 deepnn() 中的最后一个内积层。

y_conv, keep_prob = deepnn(x)                                            
                                                                           
num_sampled = 1                                                          
vocabulary_size = 10                                                     
embedding_size = 1024                                                    
with tf.device('/cpu:0'):                                                
  embed = y_conv                                                         
  # Construct the variables for the NCE loss                             
  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])) 

2。创建损失并计算输出。计算输出后,我们可以用它来计算精度。请注意,这里的标签不是 softmax 中使用的 one-hot 向量。标签是训练样本的原始标签。

loss = tf.reduce_mean(                                   
    tf.nn.nce_loss(weights=nce_weights,                           
                   biases=nce_biases,                             
                   labels=y_idx,                                  
                   inputs=embed,                                  
                   num_sampled=num_sampled,                       
                   num_classes=vocabulary_size))                  
                                                                    
output = tf.matmul(y_conv, tf.transpose(nce_weights)) + nce_biases
correct_prediction = tf.equal(tf.argmax(output, 1), tf.argmax(y_, 1))

当我们设置num_sampled=1 时,val 准确度将在98.8% 左右结束。如果我们设置num_sampled=9,我们可以获得与softmax训练的几乎相同的val准确度。但请注意ncesoftmax 不同。

nce 的完整培训代码mnist 可以在here 找到。希望对您有所帮助。

【讨论】:

  • 我认为上述代码中的y_y_idx,对吧?而且,在答案的开头是train_labels
  • nce_biases 维度应该是vocabulary_size 而不是embed_size,对吧?可能是您的评论中的错字。
  • @JenkinsY 谢谢。 nce_biases的维度已经修改。
【解决方案2】:

embeddings 张量是您的最终输出矩阵。它将单词映射到向量。在您的单词预测图中使用它。

输入矩阵是从训练文本生成的一批centre-word : context-word 对(分别为train_inputtrain_label)。

虽然我还不知道nce_loss 操作的确切工作原理,但基本思想是它使用单层网络(参数nce_weightsnce_biases)来映射输入向量(选自@ 987654328@ 使用embed 操作)与输出单词进行比较,然后将输出与训练标签(训练文本中的相邻单词)以及所有其他单词的随机子样本(num_sampled)进行比较词汇,然后修改输入向量(存储在embeddings中)和网络参数以最小化错误。

【讨论】:

    【解决方案3】:

    NCE函数中的输入输出矩阵是什么?

    以skip gram模型为例,对于这句话:

    the quick brown fox jumped over the lazy dog

    输入和输出对是:

    (quick, the), (quick, brown), (brown, quick), (brown, fox), ...

    更多信息请参考the tutorial

    最后的嵌入是什么?

    您应该提取的最终嵌入通常是输入和隐藏层之间的 {w}

    为了更直观地说明,请看下图:

    上图中的一个热向量[0, 0, 0, 1, 0]为输入层,输出为词嵌入[10,12,19],W(上图中)为之间的矩阵。

    详细解释请阅读this tutorial

    【讨论】:

    • 词嵌入 [10, 12, 19] 不是输出层。相反,它是隐藏层。
    • 是的。 那个隐藏层的输出。
    【解决方案4】:

    1) 简而言之,它在总体上是正确的,但对于所讨论的功能来说只是部分正确。见tutorial

    噪声对比估计损失定义为 逻辑回归模型。为此,我们需要定义权重和 词汇表中每个单词的偏差(也称为output weights 相对于input embeddings)。

    所以函数nce_loss 的输入是output weightsinput embeddings 的一小部分,等等。

    2) “最终”嵌入(又名词向量,又名词的向量表示)就是您所说的 input matrix。嵌入是该矩阵的字符串(向量),对应于每个单词。

    警告 事实上,由于在 NN 环境中使用输入和输出概念,这个术语令人困惑。嵌入矩阵不是 NN 的输入,因为 NN 的输入在技术上是一个输入层。您在训练过程中获得此矩阵的最终状态。尽管如此,这个矩阵应该在编程中初始化,因为算法必须从这个矩阵的某个随机状态开始,在训练过程中逐渐更新它。

    权重也是如此——它们也将被初始化。它发生在以下行:

    nce_weights = tf.Variable(
            tf.truncated_normal([50000, 128], stddev=1.0 / math.sqrt(128)))
    

    每个嵌入向量都可以乘以权重矩阵中的向量(以字符串到列的方式)。所以我们将得到 NN output layer 中的标量。这个标量的范数被解释为目标词(来自输入层)将伴随有标签[或上下文]词对应于output layer中标量位置的概率。


    所以,如果我们说的是函数的输入(参数),那么两个矩阵都是这样的:权重和从嵌入中提取的批量大小:

    tf.nn.nce_loss(weights=nce_weights,            # Tensor of shape(50000, 128)
                   biases=nce_biases,              # vector of zeros; len(128)
                   labels=train_labels,            # labels == context words enums
                   inputs=embed,                   # Tensor of shape(128, 128)
                   num_sampled=num_sampled,        # 64: randomly chosen negative (rare) words
                   num_classes=vocabulary_size))   # 50000: by construction
    

    这个nce_loss 函数输出一个batch_size 的向量——在TensorFlow 示例中是一个shape(128,) 张量。 然后reduce_mean() 将此结果简化为取这 128 个值的平均值的标量,这实际上是进一步最小化的目标。

    希望这会有所帮助。

    【讨论】:

    • 在警告部分,目标和上下文只是您在声明模型时调用参数所做的选择。您打算使用的模型是 skipgrams 模型。
    • 我并不是说你的答案是错误的。我的评论是想说术语“上下文”和“目标”取决于您想要做什么(并且您想要做的是 skipgram 模型),而 nce 损失本身并没有区别。
    【解决方案5】:

    来自论文通过噪声对比估计有效地学习词嵌入

    NCE 基于将密度估计简化为概率二元分类。基本思想是训练一个逻辑回归分类器 区分来自数据分布的样本和来自某些“噪声”分布的样本

    我们可以发现,在词嵌入中,NCE 实际上是负采样。 (关于这两者的区别,请看论文:Notes on Noise Contrastive Estimation and Negative Sampling

    因此,您无需输入噪声分布。而且从引用中,您会发现它实际上是一个逻辑回归:权重和偏差将是逻辑回归所需要的。如果您熟悉 word2vec,那只是添加了一个偏见。

    【讨论】:

      猜你喜欢
      • 2017-09-06
      • 2019-08-06
      • 1970-01-01
      • 2017-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多