tensorflow Keras中的自定义损失函数是返回批次的单个损失值还是返回训练批次中每个样本的损失数组?

xpszyzbs  于 2022-10-29  发布在  其他
关注(0)|答案(9)|浏览(279)

我在StackOverflow上问了一个关于自定义损失函数的返回值的问题。但是我没有得到一个明确的答案。
在tensorflow网站上的这个指南中,我发现了一个自定义损失函数的例子:

def custom_mean_squared_error(y_true, y_pred):
        return tf.math.reduce_mean(tf.square(y_true - y_pred))

这个自定义损失函数中的reduce_mean函数将返回一个标量。但是我认为自定义损失函数应该为训练批中的每个示例返回一个损失数组,而不是一个单一的损失值。
根据Model类的源代码,自定义损失函数用于构造LossFunctionWrapper对象。我阅读了loss模块的源代码。我认为是LossFunctionWrapper.__call()__方法负责获取训练批次的平均损失值。LossFunctionWrapper.__call()__方法首先调用LossFunctionWrapper.call()方法,为训练批中的每个示例获取一个损失数组。
另外,在losses模块的源码中,MeanAbsoluteError类使用mean_squared_error函数构造了一个LossFunctionWrapper类,我们可以看到mean_squared_error函数返回的是K.mean(math_ops.squared_difference(y_pred, y_true), axis=-1),是一个数组,而不是一个单值,我想我们的自定义损失函数应该就是这样的。
那么,为什么tensorflow网站上的指南中的自定义损耗函数会返回一个标量呢?这样定义一个自定义函数是不对的吗?

vm0i2vca

vm0i2vca1#

@lambdaphy keras中的自定义损失函数返回单个值,如上面的示例所示,我们使用tf.math.reduce_mean。reduce_mean的简单示例如下。

import tensorflow as tf
a = tf.ones([1,100],tf.int32)
reduce_m = tf.math.reduce_mean(a) 
print(reduce_m)       # output tf.Tensor(1, shape=(), dtype=int32)

上述数组areduce_mean为1,如输出所示。
对于Keras模型,我们需要定义custom_loss函数来为每个批次提供标量损失。对于custom_training,您可以根据需要在自定义模型中定义损失函数。谢谢!

bvhaajcl

bvhaajcl2#

我知道在训练模型的时候,我们需要一个每个批次的标量损失。但是根据源代码,自定义损失函数并不负责获得这个标量损失。你能检查一下Model.compile()Model.fit()Mode.train_step(),(源代码)Loss.__call()__Loss.call()LossFunctionWrapper.call()方法(source code)来查看一个训练批的损失值的计算过程?
我阅读了这些代码,发现Loss.__call__()方法调用Loss.call()方法(其在子类中实现,例如LossFunctionWrapper)以获得训练批次中每个示例的损失阵列,则Loss.__call__()方法调用compute_weighted_loss()函数以获取LossFunctionWrapper.call()方法是如何得到一个损失数组的呢?阅读源代码,我们可以看到它使用self.fn来得到这些损失,而self.fn是我们提供给Model.compile()方法的损失函数。
下面是Loss.__call__()的源代码:

def __call__(self, y_true, y_pred, sample_weight=None):
       """
       .................(omitted)
       Returns:
            Weighted loss float `Tensor`. If `reduction` is `NONE`, this has
            shape `[batch_size, d0, .. dN-1]`; otherwise, it is scalar. (Note `dN-1`
            because all loss functions reduce by 1 dimension, usually axis=-1.)
       .................(omitted)
       """
        graph_ctx = tf_utils.graph_context_for_symbolic_tensors(
                y_true, y_pred, sample_weight)
        with K.name_scope(self._name_scope), graph_ctx:
        ag_call = autograph.tf_convert(self.call, ag_ctx.control_status_ctx())
        losses = ag_call(y_true, y_pred)
        return losses_utils.compute_weighted_loss(
                      losses, sample_weight, reduction=self._get_reduction())

下面是Loss.call()的源代码:

def call(self, y_true, y_pred):
    """Invokes the `Loss` instance.
        Args:
            y_true: Ground truth values. shape = `[batch_size, d0, .. dN]`, except
            sparse loss functions such as sparse categorical crossentropy where
            shape = `[batch_size, d0, .. dN-1]`
            y_pred: The predicted values. shape = `[batch_size, d0, .. dN]`
       Returns:
           Loss values with the shape `[batch_size, d0, .. dN-1]`.
    """
        NotImplementedError('Must be implemented in subclasses.')

Loss.call()方法只是Loss的子类必须实现的一个接口,但是我们可以看到这个方法的返回值是Loss values,形状为[batch_size, d0, .. dN-1]
现在我们来看LossFunctionWrapper类,LossFunctionWrapperLoss的子类,在它的构造函数中,我们应该提供一个损失函数,这个损失函数存储在LossFunctionWrapper.fn中,下面是LossFunctionWrapper.call()的源代码,它实现了Loss.call()方法:

def call(self, y_true, y_pred):
    """Invokes the `LossFunctionWrapper` instance.
        Args:
             y_true: Ground truth values.
             y_pred: The predicted values.
        Returns:
            Loss values per sample.
    """
         if tensor_util.is_tensor(y_pred) and tensor_util.is_tensor(y_true):
             y_pred, y_true = tf_losses_util.squeeze_or_expand_dimensions(y_pred, y_true)
         ag_fn = autograph.tf_convert(self.fn, ag_ctx.control_status_ctx())
         return ag_fn(y_true, y_pred,**self._fn_kwargs)

看到了吗?这里调用了我们提供的损失函数,它的任务是返回每个样本的损失值
另外,作为一个例子,我们可以看到MeanSquaredError类是如何定义的,它就是一个使用mean_squared_error函数作为损失函数的LossFunctionWrapper类(即LossFunctionWrapper.fn=mean_squared_error),mean_squared_error函数的源代码也在losses模块中定义:

def mean_squared_error(y_true, y_pred):
    """Computes the mean squared error between labels and predictions.
        After computing the squared distance between the inputs, the mean value over
        the last dimension is returned.
       `loss = mean(square(y_true - y_pred), axis=-1)`
        Standalone usage:
        >>> y_true = np.random.randint(0, 2, size=(2, 3))
        >>> y_pred = np.random.random(size=(2, 3))
        >>> loss = tf.keras.losses.mean_squared_error(y_true, y_pred)
        >>> assert loss.shape == (2,)
        >>> assert np.array_equal(
                 ...     loss.numpy(), np.mean(np.square(y_true - y_pred), axis=-1))
       Args:
          y_true: Ground truth values. shape = `[batch_size, d0, .. dN]`.
          y_pred: The predicted values. shape = `[batch_size, d0, .. dN]`.
       Returns:
         Mean squared error values. shape = `[batch_size, d0, .. dN-1]`.
    """
    y_pred = ops.convert_to_tensor_v2(y_pred)
    y_true = math_ops.cast(y_true, y_pred.dtype)
    return K.mean(math_ops.squared_difference(y_pred, y_true), axis=-1)

我们可以看到它返回的是一个数组,而不是标量(K.mean()中的axis=-1),其返回值的第一维是batch_size
根据Model.compile()Model.fit()的源代码,当我们提供一个自定义损失函数时,这个函数被用来构造一个LossFunctionWrapper对象,就像MeanSquaredError对象使用mean_squared_error函数构造一个LossFunctionWrapper对象一样。这就是为什么我认为自定义损失函数应该返回一个损失数组。因为获得训练批次的标量损失值不是损失函数的任务,所以Loss.__call()__应该做这项工作。

7kjnsjlb

7kjnsjlb3#

基于以上的分析,我们可以看到,当我们定义自定义损失CLASS时,我们应该实现call()方法,而这个call()方法应该返回一个数组,而不是标量。但是在tensorflow 指南中,我们也可以看到自定义损失类的例子,就在自定义损失函数的例子下面:

class CustomMSE(keras.losses.Loss):
    def __init__(self, regularization_factor=0.1, name="custom_mse"):
        super().__init__(name=name)
        self.regularization_factor = regularization_factor

    def call(self, y_true, y_pred):
        mse = tf.math.reduce_mean(tf.square(y_true - y_pred))
        reg = tf.math.reduce_mean(tf.square(0.5 - y_pred))
        return mse + reg * self.regularization_factor

model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=CustomMSE())

y_train_one_hot = tf.one_hot(y_train, depth=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)

我们可以看到call()方法的这个实现返回了一个标量。我认为这个行为也是错误的。

svmlkihl

svmlkihl4#

@lambdaphy谢谢您的问题。自定义损失函数需要为每个样本返回一个损失值。示例需要更新以反映这一点。

34gzjxbg

34gzjxbg5#

如果你有兴趣做改变,请随时给我一个公关。

zpgglvta

zpgglvta6#

谢谢你的回复。我很乐意为你做贡献,但是我对使用github打开pull请求不太熟悉。另外我的母语不是英语,所以我可能不能胜任编辑文档。
如果您可以编辑文档,请帮助更改它。谢谢。

0ejtzxu1

0ejtzxu17#

似乎我们仍在等待文档更改。

8tntrjer

8tntrjer8#

大多数指南都遵循tf文档,它为一批训练数据返回单个值。我遵循compute_weighted_loss的源代码,其中LossFunctionWrapper表示批轴的平均值,并发现如果自定义损失函数返回一个值而不是一个值数组,我们会遇到两个问题:

  1. sample_weights自变量将是无意义,因为wo确实意味着第一。
    1.当使用tf.distribute.Strategy进行训练时,基于不正确的分母对损失进行平均。
    @lambdaphy你怎么看?
cl25kdpy

cl25kdpy9#

大多数指南都遵循tf文档,它为一批训练数据返回单个值。我遵循compute_weighted_loss的源代码,其中LossFunctionWrapper表示批轴的平均值,并发现如果自定义损失函数返回一个值而不是一个值数组,我们会遇到两个问题:

  1. sample_weights自变量将是无意义,因为wo确实意味着第一。
    1.当使用tf.distribute.Strategy进行训练时,基于不正确的分母对损失进行平均。
    @lambdaphy你怎么看?
    是的,你说得对。损失函数对整个批次返回单个损失值在某些情况下会导致问题。
    文件尚未更正。
    这份文件中有许多模棱两可的地方。

相关问题