为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕尼黑的夜晚无繁华 2023-06-27 17:27:10
我已经使用 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 methodclass 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}# instantiatembxe = MaskedBXE()print(f'first snippet: {mbxe(y_true, y_pred).numpy()}')
查看完整描述

1 回答

?
万千封印

TA贡献1891条经验 获得超3个赞

我认为问题在于您在第一个版本中无意中进行了广播操作,这给您带来了错误的结果。如果您的批次(?, 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)


# first

y_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


# second

mask = 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上面示例中的操作,结果就会得到纠正。


查看完整回答
反对 回复 2023-06-27
  • 1 回答
  • 0 关注
  • 81 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信