【问题标题】:How to calculate Categorical Cross-Entropy by hand?如何手动计算分类交叉熵?
【发布时间】:2019-09-29 21:00:27
【问题描述】:

当我手动计算二元交叉熵时,我应用 sigmoid 来获得概率,然后使用交叉熵公式计算结果:

logits = tf.constant([-1, -1, 0, 1, 2.])
labels = tf.constant([0, 0, 1, 1, 1.])

probs = tf.nn.sigmoid(logits)
loss = labels * (-tf.math.log(probs)) + (1 - labels) * (-tf.math.log(1 - probs))
print(tf.reduce_mean(loss).numpy()) # 0.35197204

cross_entropy = tf.keras.losses.BinaryCrossentropy(from_logits=True)
loss = cross_entropy(labels, logits)
print(loss.numpy()) # 0.35197204

logitslabels 大小不同时如何计算分类交叉熵?

logits = tf.constant([[-3.27133679, -22.6687183, -4.15501118, -5.14916372, -5.94609261,
                       -6.93373299, -5.72364092, -9.75725174, -3.15748906, -4.84012318],
                      [-11.7642536, -45.3370094, -3.17252636, 4.34527206, -17.7164974,
                      -0.595088899, -17.6322937, -2.36941719, -6.82157373, -3.47369862],
                      [-4.55468369, -1.07379043, -3.73261762, -7.08982277, -0.0288562477, 
                       -5.46847963, -0.979336262, -3.03667569, -3.29502845, -2.25880361]])
labels = tf.constant([2, 3, 4])

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True,
                                                            reduction='none')
loss = loss_object(labels, logits)
print(loss.numpy()) # [2.0077195  0.00928135 0.6800677 ]
print(tf.reduce_mean(loss).numpy()) # 0.8990229

我的意思是如何手动获得相同的结果 ([2.0077195 0.00928135 0.6800677 ])?

@OverLordGoldDragon 的回答是正确的。在TF 2.0 中看起来像这样:

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')
loss = loss_object(labels, logits)
print(f'{loss.numpy()}\n{tf.math.reduce_sum(loss).numpy()}')

one_hot_labels = tf.one_hot(labels, 10)

preds = tf.nn.softmax(logits)
preds /= tf.math.reduce_sum(preds, axis=-1, keepdims=True)
loss = tf.math.reduce_sum(tf.math.multiply(one_hot_labels, -tf.math.log(preds)), axis=-1)
print(f'{loss.numpy()}\n{tf.math.reduce_sum(loss).numpy()}')
# [2.0077195  0.00928135 0.6800677 ]
# 2.697068691253662
# [2.0077198  0.00928142 0.6800677 ]
# 2.697068929672241

对于语言模型:

vocab_size = 9
seq_len = 6
batch_size = 2

labels = tf.reshape(tf.range(batch_size*seq_len), (batch_size,seq_len)) # (2, 6)
logits = tf.random.normal((batch_size,seq_len,vocab_size)) # (2, 6, 9)

loss_object = tf.keras.losses.SparseCategoricalCrossentropy(
    from_logits=True, reduction='none')
loss = loss_object(labels, logits)
print(f'{loss.numpy()}\n{tf.math.reduce_sum(loss).numpy()}')

one_hot_labels = tf.one_hot(labels, vocab_size)

preds = tf.nn.softmax(logits)
preds /= tf.math.reduce_sum(preds, axis=-1, keepdims=True)
loss = tf.math.reduce_sum(tf.math.multiply(one_hot_labels, -tf.math.log(preds)), axis=-1)
print(f'{loss.numpy()}\n{tf.math.reduce_sum(loss).numpy()}')
# [[1.341706  3.2518263 2.6482694 3.039099  1.5835983 4.3498387]
#  [2.67237   3.3978183 2.8657475       nan       nan       nan]]
# nan
# [[1.341706  3.2518263 2.6482694 3.039099  1.5835984 4.3498387]
#  [2.67237   3.3978183 2.8657475 0.        0.        0.       ]]
# 25.1502742767334

【问题讨论】:

    标签: python tensorflow artificial-intelligence


    【解决方案1】:

    SparseCategoricalCrossentropyCategoricalCrossentropy,它采用 integer 标签,而不是 one-hot。来自source code的例子,下面两个是等价的:

    scce = tf.keras.losses.SparseCategoricalCrossentropy()
    cce = tf.keras.losses.CategoricalCrossentropy()
    
    labels_scce = K.variable([[0, 1, 2]]) 
    labels_cce  = K.variable([[1,    0,  0], [0,    1,  0], [0,   0,   1]])
    preds       = K.variable([[.90,.05,.05], [.50,.89,.60], [.05,.01,.94]])
    
    loss_cce  = cce(labels_cce,   preds, from_logits=False)
    loss_scce = scce(labels_scce, preds, from_logits=False)
    
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        sess.run([loss_cce, loss_scce])
    
    print(K.get_value(loss_cce))
    print(K.get_value(loss_scce))
    # [0.10536055  0.8046684  0.0618754]
    # [0.10536055  0.8046684  0.0618754]
    

    至于如何'手工',我们可以参考Numpy backend

    np_labels = K.get_value(labels_cce)
    np_preds  = K.get_value(preds)
    
    losses = []
    for label, pred in zip(np_labels, np_preds):
        pred /= pred.sum(axis=-1, keepdims=True)
        losses.append(np.sum(label * -np.log(pred), axis=-1, keepdims=False))
    print(losses)
    # [0.10536055  0.8046684  0.0618754]
    
    • from_logits = Truepreds 是模型输出之前将其传递给softmax(所以我们将其传递给softmax)
    • from_logits = Falsepreds 是模型输出传递给softmax(所以我们跳过这一步)

    总之,手动计算:

    1. 将整数标签转换为单热标签
    2. 如果 preds 是模型输出之前 softmax,我们计算它们的 softmax
    3. pred /= ... 在计算日志之前规范化预测;这样,高概率。在零标签上预测惩罚在一个标签上的正确预测。如果from_logits = False,则跳过这一步,因为softmax 会进行标准化。见this snippetFurther reading
    4. 对于每个观察/样本,计算元素级负 log(以 e 为底)仅在哪里 label==1
    5. 对所有观测值取平均损失

    最后,分类交叉熵的数学公式是:

    • i 迭代 N 观察结果
    • c 迭代 C
    • 1indicator function - 在这里,类似于二元交叉熵,除了对长度 -C 向量进行操作
    • p_model [y_i \in C_c] - 预测的观察概率i 属于类c

    【讨论】:

    • 谢谢。我试图在TF 2.0 中重复您的代码:我的第一个和第三个标签丢失相同,但第二个是错误的。你知道这是为什么吗?我更新了帖子。
    • @dereks 粘贴时错过了一行,我的错 - 请参阅更新的答案
    • @dereks 它们是分开的 - batch_size 是您提供给模型的 独立序列(例如句子)的数量,vocab_size 是您的字符数/单词(特征维度),seq_len 是每个序列(句子/单词)的字符/单词数。 vocab_size 是否包含单词/字符取决于模型设计 - 一些模型是 word-level,其他模型是 character-level - 无论如何,这是一个单独的问题。跨度>
    • 好的,谢谢。当我知道它是如何工作的时,我删除了我之前的评论。
    • @dereks 公平的问题,本来应该解释的——更新(总结)。不是完整的解释,您可能想阅读categorical crossentropy - 但确实表明零标签预测不仅仅是“丢失”
    猜你喜欢
    • 1970-01-01
    • 2022-01-09
    • 2021-10-14
    • 2019-07-13
    • 1970-01-01
    • 2020-01-03
    • 2021-07-17
    • 2016-06-28
    • 2017-07-20
    相关资源
    最近更新 更多