我已经使用TF已有2年了,在每个项目中,我都会冒出很多无意义的错误来掩盖信息,这些错误通常无济于事,也没有指出实际上是什么错误。或更糟糕的是,结果是错误的,但没有错误。我总是在训练循环之外使用伪数据测试代码,这很好。但是在训练(称合适)中,我不明白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没有类似的烦人细节,我们很乐意切换。
编辑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()}')
# second method
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)
def get_config(self):
base_config = super().get_config()
return {**base_config}
# instantiate
mbxe = MaskedBXE()
print(f'second snippet: {mbxe(y_true, y_pred).numpy()}')
第一个片段:1.2907861471176147
第二段:1.2907861471176147
编辑2:在以@jdehesa的建议方式在图形模式下打印损失后,它们有所不同,它们不应这样做:
class MaskedBXE(tf.keras.losses.Loss):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def call(self, y_true, y_pred):
# first
y_t = tf.squeeze(y_true)
mask = tf.where(y_t!=2)
y_t = tf.gather_nd(y_t, mask)
y_p = tf.gather_nd(y_pred, mask)
loss = tf.keras.losses.binary_crossentropy(y_t, y_p)
first_loss = tf.reduce_mean(loss)
tf.print('first:')
tf.print(first_loss, summarize=-1)
# second
mask = tf.where(y_true!=2, True, False)
y_t = y_true[mask]
y_p = y_pred[mask]
loss = tf.keras.losses.binary_crossentropy(y_t, y_p)
second_loss = tf.reduce_mean(loss)
tf.print('second:')
tf.print(second_loss, summarize=-1)
return second_loss
第一:
0.814215422
第二:
0.787778914
第一:
0.779697835
第二:
0.802924752
。。。
我认为问题是您无意中在第一个版本中执行广播操作,这会给您带来错误的结果。如果批次(?, 1)
由于tf.squeeze
操作而具有形状,则会发生这种情况。注意此示例中的形状
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的张量,因此交叉熵基本上是计算“所有VS一切”,然后取平均值。在第二种情况下,仅对每对对应的值计算交叉熵,这是正确的做法。
如果仅tf.squeeze
在上面的示例中删除了该操作,则结果将得到纠正。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句