具有多个输入的 Tensorflow 2.0 自定义损失函数

Tensorflow 2.0 Custom loss function with multiple inputs

我正在尝试使用以下两个损失函数优化模型

def loss_1(pred, weights, logits):
    weighted_sparse_ce = kls.SparseCategoricalCrossentropy(from_logits=True)
    policy_loss = weighted_sparse_ce(pred, logits, sample_weight=advantages)

def loss_2(y_pred, y):
    return kls.mean_squared_error(y_pred, y)

但是,因为 TensorFlow 2 期望损失函数的形式为

def fn(y_pred, y_true):
    ...

我正在使用 loss_1 的变通方法,我将 predweights 打包到一个张量中,然后在 model.fit and then unpack them in loss_1. This is inelegant and nasty because pred and weights are of different data types and so this requires an additional cast, pack, un-pack and un-cast each time I call model.fit.

此外,我知道 fitsample_weight 参数,这有点像 的解决方案。如果不是因为我使用了两个损失函数并且我只想将 sample_weight 应用于其中一个,这可能是一个可行的解决方案。另外,即使这是一个解决方案,它是否不能推广到其他类型的自定义损失函数。


综上所述,我的问题简明扼要地说:

创建具有任意数量的损失函数的最佳方法是什么 TensorFlow 2 中的参数?

我尝试过的另一件事是传递 tf.tuple 但这似乎也违反了 TensorFlow 对损失函数输入的要求。

在 tf 1.x 中,我们有 tf.nn.weighted_cross_entropy_with_logits 函数,它允许我们通过为每个 class 添加额外的正权重来权衡召回率和准确率。在多标签class化中,应该是一个(N,)张量或者numpy数组。然而,在tf 2.0中,我还没有找到类似的损失函数,所以我自己写了一个带有额外参数的损失函数pos_w_arr

from tensorflow.keras.backend import epsilon

def pos_w_loss(pos_w_arr):
    """
    Define positive weighted loss function
    """
    def fn(y_true, y_pred):
        _epsilon = tf.convert_to_tensor(epsilon(), dtype=y_pred.dtype.base_dtype)
        _y_pred = tf.clip_by_value(y_pred, _epsilon, 1. - _epsilon)
        cost = tf.multiply(tf.multiply(y_true, tf.math.log(
            _y_pred)), pos_w_arr)+tf.multiply((1-y_true), tf.math.log(1-_y_pred))
        return -tf.reduce_mean(cost)
    return fn

不确定你是什么意思,但是当使用 eager tensor 或 numpy 数组作为输入时它不起作用。如果我错了,请纠正我。

在TF2中使用custom training可以轻松解决这个问题。您只需在 GradientTape 上下文中计算双分量损失函数,然后使用生成的梯度调用优化器。例如,您可以创建一个函数 custom_loss,它根据每个参数计算两个损失:

def custom_loss(model, loss1_args, loss2_args):
  # model: tf.model.Keras
  # loss1_args: arguments to loss_1, as tuple.
  # loss2_args: arguments to loss_2, as tuple.
  with tf.GradientTape() as tape:
    l1_value = loss_1(*loss1_args)
    l2_value = loss_2(*loss2_args)
    loss_value = [l1_value, l2_value]
  return loss_value, tape.gradient(loss_value, model.trainable_variables)

# In training loop:
loss_values, grads = custom_loss(model, loss1_args, loss2_args)
optimizer.apply_gradients(zip(grads, model.trainable_variables))

这样一来,每个损失函数都可以采用任意数量的热切张量,而不管它们是模型的输入还是输出。每个损失函数的参数集不必像本例中所示那样不相交。

扩展 Jon 的回答。如果您仍然希望获得 Keras 模型的好处,您可以扩展模型 class 并编写您自己的自定义 train_step:

from tensorflow.python.keras.engine import data_adapter

# custom loss function that takes two outputs of the model
# as input parameters which would otherwise not be possible
def custom_loss(gt, x, y):
    return tf.reduce_mean(x) + tf.reduce_mean(y)

class CustomModel(keras.Model):
    def compile(self, optimizer, my_loss):
        super().compile(optimizer)
        self.my_loss = my_loss

    def train_step(self, data):
        data = data_adapter.expand_1d(data)
        input_data, gt, sample_weight = data_adapter.unpack_x_y_sample_weight(data)

        with tf.GradientTape() as tape:
            y_pred = self(input_data, training=True)
            loss_value = self.my_loss(gt, y_pred[0], y_pred[1])

        grads = tape.gradient(loss_value, self.trainable_variables)
        self.optimizer.apply_gradients(zip(grads, self.trainable_variables))

        return {"loss_value": loss_value}

...

model = CustomModel(inputs=input_tensor0, outputs=[x, y])
model.compile(optimizer=tf.keras.optimizers.Adam(), my_loss=custom_loss)