在 Tensorflow 中实现自定义损失函数会导致“ValueError:必须在循环之前定义‘输出’。”

Posted

技术标签:

【中文标题】在 Tensorflow 中实现自定义损失函数会导致“ValueError:必须在循环之前定义‘输出’。”【英文标题】:Implementing custom loss function in Tensorflow leading to "ValueError: 'outputs' must be defined before the loop." 【发布时间】:2021-10-04 10:56:15 【问题描述】:

我正在开发我的第一个 Python 机器学习项目 - 使用 TensorFlow 尝试使用 Moby Hyphenator II 数据集对单词进行音节化。

我将此视为多标签分类问题,其中单词及其音节按以下格式编码:

T e n - s o r - f l o w
0 0 1   0 0 1   0 0 0 0

在阅读 this guide 作为起点时,我看到作者使用了一个自定义函数——他们在 PyTorch 中用均方根误差对加权二元交叉熵进行平均:

def bce_rmse(pred, target, pos_weight = 1.3, epsilon = 1e-12):
    # Weighted binary cross entropy
    loss_pos = target * torch.log(pred + epsilon)
    loss_neg = (1 - target) * torch.log(1 - pred + epsilon)
    bce = torch.mean(torch.neg(pos_weight * loss_pos + loss_neg))

    # Root mean squared error
    mse = (torch.sum(pred, dim = 0) - torch.sum(target, dim = 0)) ** 2
    rmse = torch.mean(torch.sqrt(mse + epsilon))

    return (bce + rmse) / 2

我已经尝试通过以下方式在 TensorFlow 中实现这一点:

def weighted_bce_mse(y_true, y_prediction):
    # Binary crossentropy with weighting
    epsilon = 1e-12
    positive_weight = 4.108897148948174
    loss_positive = y_true * tf.math.log(y_prediction + epsilon)
    loss_negative = (1 - y_true) * tf.math.log(1 - y_prediction + epsilon)
    bce_loss = np.mean(tf.math.negative(positive_weight * loss_positive + loss_negative))
    
    # Mean squared error
    mse = tf.keras.losses.MeanSquaredError()
    mse_loss = mse(y_true, y_prediction)

    averaged_bce_mse = (bce_loss + mse_loss) / 2
    return averaged_bce_mse

这样做时,我收到错误 ValueError: 'outputs' must be defined before the loop.,我不确定为什么在构建和编译模型之前定义此函数。

我正在使用 Keras 功能 API,我的编译和适配阶段是:

model.compile(optimizer="adam", loss=weighted_bce_mse, metrics=["accuracy"], steps_per_execution=64)
history = model.fit(padded_inputs, padded_outputs, validation_data=(validation_inputs, validation_outputs), epochs=10, verbose=2)

【问题讨论】:

outputs 定义在哪里? @kkgarg 我正在使用 Keras 功能 API,如前所述,输出定义为:model = tf.keras.models.Model(inputs=inputs, outputs=x) 我确定问题不在模型构建中,因为它可以与任何其他功能完美配合我以前使用过的损失函数(二元交叉熵或均方误差)——该错误仅出现在此自定义损失函数中。有趣的是,当我取二元交叉熵的平均值和不加权的均方误差时,该模型也有效,所以我相信加权是导致问题的具体原因。 新的损失函数似乎没有问题。你能粘贴错误堆栈和完整代码吗? @kkgarg 是的!这是error stack 和relevant code。 谢谢!我对在 Stack Overflow 上提问很陌生,所以我不知道这是最佳做法,但以后会继续这样做。 【参考方案1】:

如前所述,显示的错误与自定义损失函数无关。您显示的代码还有许多其他错误,例如未正确导入tf.keras.layers。修复这些错误后,请参阅下面的代码并在以下版本上进行测试(工作正常):

tensorflow 2.4.1
numpy 1.19.5
python 3.9.6
import tensorflow as tf

# Custom loss function - mean of binary crossentropy and mean squared error
def mean_weighted_bce_mse(y_true, y_prediction):
    # Binary crossentropy with weighting
    epsilon = 1e-12
    positive_weight = 4.108897148948174
    loss_positive = y_true * tf.math.log(y_prediction + epsilon)
    loss_negative = (1 - y_true) * tf.math.log(1 - y_prediction + epsilon)
    bce_loss = np.mean(tf.math.negative(positive_weight * loss_positive + loss_negative))
    
    # Mean squared error
    mse = tf.keras.losses.MeanSquaredError()
    mse_loss = mse(y_true, y_prediction)
 
    averaged_bce_mse = (bce_loss + mse_loss) / 2
    return tf.math.reduce_mean(averaged_bce_mse, axis=-1)
 
 
inputs = tf.keras.Input(shape=(15,))
x = tf.keras.layers.Embedding(64, 64, mask_zero=True)(inputs)
 
x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128, return_sequences=True))(x)
x = tf.keras.layers.Dropout(0.2)(x)
x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128, return_sequences=True))(x)
x = tf.keras.layers.Dropout(0.2)(x)
x = tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(128, return_sequences=True))(x)
x = tf.keras.layers.Dropout(0.3)(x)
 
 
x = tf.keras.layers.Conv1D(64, kernel_size=1)(x)
x = tf.keras.layers.Dropout(0.2)(x)
x = tf.keras.layers.Conv1D(64, kernel_size=1)(x)
x = tf.keras.layers.Dropout(0.2)(x)
x = tf.keras.layers.GlobalMaxPool1D()(x)
x = tf.keras.layers.Dropout(0.5)(x)
 
x = tf.keras.layers.Dense(32, activation="relu")(x)
x = tf.keras.layers.Dense(15, activation="sigmoid")(x)
 
 
model = tf.keras.models.Model(inputs=inputs, outputs=x)
model.compile(optimizer="adam", loss=mean_weighted_bce_mse, metrics=["accuracy"], steps_per_execution=64)
 
# history = model.fit(padded_inputs,
#                     padded_outputs,
#                     validation_data=(validation_inputs, validation_outputs),
#                     epochs=20,
#                     batch_size=8)

【讨论】:

我想我在创建 pastebin 时出错了,所以损失函数的最后一行应该只是 return averaged_bce_mse,这会影响解决方案吗? 没有你之前提到的那种错误,虽然没有深入研究损失函数本身。上述解决方案有效吗? 不幸的是,我仍然收到与ValueError: 'outputs' must be defined before the loop. 相同的错误;我收到了this error stack。【参考方案2】:

在下面的代码行中:

model.compile(optimizer="adam", loss=mean_weighted_bce_mse, metrics=["accuracy"], steps_per_execution=64)
history = model.fit(padded_inputs,
                padded_outputs,
                validation_data=(validation_inputs, validation_outputs),
                epochs=20,
                batch_size=8)

输入数据的长度是多少? steps_per_execution 应该是len(input_data)/Batch_size。 删除steps_per_execution 并再次检查。

【讨论】:

删除steps_per_execution 会导致调用model.fit 时出现不同的错误;新的错误是TypeError: Input 'y' of 'Mul' Op has type float32 that does not match type int32 of argument 'x'.,我认为这意味着错误确实与我编写的自定义损失函数有关。 好吧是steps_per_execution=len(input_data)/Batch_size?...在您发布的错误堆栈跟踪中,发生错误的第一个点是batch_size=8 对吗?... steps_per_execution 是 64 以加快运行时间,因为这意味着在一个 tf.function 调用中传递了 64 个批次,而不是仅 1 个 - 它与输入数据的长度和批次大小无关想想,你指的是steps_per_epoch吗?我还发布了我对这个问题的解决方案,问题是我使用了np.mean 而不是tf.math.reduce_mean,并且没有将y_predictiony_true 转换为数据类型tf.float32 太棒了!...你修好了...我以前遇到过这个问题,并且由于steps_per_execution而发生,所以我问你这个问题【参考方案3】:

我发现错误源于我在自定义损失函数中使用的操作:

bce_loss = np.mean(tf.math.negative(positive_weight * loss_positive + loss_negative))

此行使用 np.mean 导致错误 - 将其替换为 tf.math.reduce_mean 以及通过 tf.casty_truey_prediction 转换为 tf.float32 解决了问题:

# Custom loss function - mean of binary crossentropy and mean squared error
def mean_weighted_bce_mse(y_true, y_prediction):
    y_true = tf.cast(y_true, tf.float32)
    y_prediction = tf.cast(y_prediction, tf.float32)

    # Binary crossentropy with weighting
    epsilon = 1e-12
    positive_weight = 4.108897148948174
    loss_positive = y_true * tf.math.log(y_prediction + epsilon)
    loss_negative = (1 - y_true) * tf.math.log(1 - y_prediction + epsilon)
    bce_loss = tf.math.reduce_mean(tf.math.negative(positive_weight * loss_positive + loss_negative))
    
    # Mean squared error
    mse = tf.keras.losses.MeanSquaredError()
    mse_loss = mse(y_true, y_prediction)

    averaged_bce_mse = (bce_loss + mse_loss) / 2
    return averaged_bce_mse

【讨论】:

以上是关于在 Tensorflow 中实现自定义损失函数会导致“ValueError:必须在循环之前定义‘输出’。”的主要内容,如果未能解决你的问题,请参考以下文章

在具有条件的 keras 中实现自定义损失函数

用条件在keras中实现自定义丢失函数

如何在 QT 中实现自定义模型的 removeRow() 函数?

Keras:如何在损失函数中使用层的权重?

Flink中实现自定义ProcessFunction实现定时器侧输出

如何在Canvas中实现自定义路径动画