在 Tensorflow 中到底应该如何完成张量掩码和索引?

我已经使用 TF 两年了,在每个项目中,我都会弹出许多无意义的错误来进行屏蔽,这些错误通常没有帮助,并且不能表明实际问题是什么。或者更糟糕的是,结果是错误的,但没有错误。我总是使用虚拟数据在训练循环之外测试代码,这很好。但在训练中(调用 fit),我不明白 TensorFlow 到底期望什么。仅举一个例子,有经验的人可以告诉我为什么这段代码不适用于二进制交叉熵,结果是错误的并且模型不收敛,但在这种情况下没有错误:


class MaskedBXE(tf.keras.losses.Loss):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

    def call(self, y_true, y_pred):

        y_true = tf.squeeze(y_true)

        mask = tf.where(y_true!=2)

        y_true = tf.gather_nd(y_true, mask)

        y_pred = tf.gather_nd(y_pred, mask)

        loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)

        return tf.reduce_mean(loss)

虽然这可以正常工作:


class MaskedBXE(tf.keras.losses.Loss):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

    def call(self, y_true, y_pred):

        mask = tf.where(y_true!=2, True, False)

        y_true = y_true[mask]

        y_pred = y_pred[mask]

        loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)

        return tf.reduce_mean(loss)

对于一个绝对的例子来说,情况恰恰相反。我无法使用掩码作为索引,如 y_pred[mask] 或 y_pred[mask[0]],或使用 tf.squeeze() 等。但使用 tf.gather_nd() 是有效的。我总是尝试所有我认为可能的组合,我只是不明白为什么如此简单的事情会如此困难和痛苦。Pytorch也是这样吗?如果您知道 Pytorch 没有类似的烦人细节,我很乐意切换。


编辑 1:它们在训练循环之外正常工作,或者更准确地说是图形模式。


y_pred = tf.random.uniform(shape=[10,], minval=0, maxval=1, dtype='float32')

y_true = tf.random.uniform(shape=[10,], minval=0, maxval=2, dtype='int32')


# first method

class MaskedBXE(tf.keras.losses.Loss):

    def __init__(self, **kwargs):

        super().__init__(**kwargs)

        

    def call(self, y_true, y_pred):

        y_true = tf.squeeze(y_true)

        mask = tf.where(y_true!=2)

        y_true = tf.gather_nd(y_true, mask)

        y_pred = tf.gather_nd(y_pred, mask)

        loss = tf.keras.losses.binary_crossentropy(y_true, y_pred)

        return tf.reduce_mean(loss)


    def get_config(self):

        base_config = super().get_config()

        return {**base_config}


# instantiate

mbxe = MaskedBXE()

print(f'first snippet: {mbxe(y_true, y_pred).numpy()}')


慕尼黑的夜晚无繁华
浏览 83回答 1
1回答

万千封印

我认为问题在于您在第一个版本中无意中进行了广播操作,这给您带来了错误的结果。如果您的批次(?, 1)由于tf.squeeze操作而具有 shape ,则会发生这种情况。注意本例中的形状import tensorflow as tf# Make random y_true and y_pred with shape (10, 1)tf.random.set_seed(10)y_true = tf.dtypes.cast(tf.random.uniform((10, 1), 0, 3, dtype=tf.int32), tf.float32)y_pred = tf.random.uniform((10, 1), 0, 1, dtype=tf.float32)# firsty_t = tf.squeeze(y_true)mask = tf.where(y_t != 2)y_t = tf.gather_nd(y_t, mask)tf.print(tf.shape(y_t))# [7]y_p = tf.gather_nd(y_pred, mask)tf.print(tf.shape(y_p))# [7 1]loss = tf.keras.losses.binary_crossentropy(y_t, y_p)first_loss =  tf.reduce_mean(loss)tf.print(tf.shape(loss), summarize=-1)# [7]tf.print(first_loss, summarize=-1)# 0.884061277# secondmask = tf.where(y_true!=2, True, False)y_t = y_true[mask]tf.print(tf.shape(y_t))# [7]y_p = y_pred[mask]tf.print(tf.shape(y_p))# [7]loss = tf.keras.losses.binary_crossentropy(y_t, y_p)tf.print(tf.shape(loss), summarize=-1)# []second_loss = tf.reduce_mean(loss)tf.print(second_loss, summarize=-1)# 1.15896356在第一个版本中, 和y_t都y_p被广播到 7x7 张量中,因此交叉熵基本上是“全部对全部”计算的,然后求平均值。在第二种情况下,仅计算每对对应值的交叉熵,这是正确的做法。如果您只是删除tf.squeeze上面示例中的操作,结果就会得到纠正。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python