Keras 中的自定义损失函数 return 是批量的单个损失值还是训练批次中每个样本的一系列损失?

Should the custom loss function in Keras return a single loss value for the batch or an arrary of losses for every sample in the training batch?

我正在学习 tensorflow(2.3) 中的 keras API。在tensorflow网站上的这个guide中,我找到了一个自定义损失函数的例子:

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

此自定义损失函数中的 reduce_mean 函数将 return 一个标量。

这样定义损失函数对吗?据我所知,y_truey_pred 形状的第一个维度是批量大小。我认为损失函数应该 return 批次中每个样本的损失值。所以损失函数应该给出一个形状为 (batch_size,) 的数组。但是上面的函数为整个批次给出了一个值。

也许上面的例子是错误的?谁能帮我解决这个问题?


p.s。 为什么我认为损失函数应该return一个数组而不是单个值?

我阅读了Modelclass的源代码。当您向 Model.compile() 方法提供损失函数(请注意它是 function,而不是损失 class)时,这个损失函数用来构造一个LossesContainer对象,存储在Model.compiled_loss中。这个传递给 LossesContainer class 的构造函数的损失函数再次用于构造一个 LossFunctionWrapper 对象,该对象存储在 LossesContainer._losses.

根据LossFunctionWrapperclass的源码,训练batch的整体损失值是通过LossFunctionWrapper.__call__()方法计算的(继承自Loss class),即return是整个batch的单个损失值。但是LossFunctionWrapper.__call__()首先调用LossFunctionWrapper.call()方法得到一个训练批次中每个样本的损失数组。然后对这些损失进行最终平均以获得整批的单个损失值。正是在LossFunctionWrapper.call()方法中调用了提供给Model.compile()方法的损失函数。

这就是为什么我认为自定义损失函数应该 return 一系列损失,而不是单个标量值。另外,如果我们为Model.compile()方法写一个自定义Lossclass,那么我们自定义Lossclass的call()方法也应该return 一个数组,而不是一个信号值。


我在 github 上打开了一个 issue。已确认自定义损失函数需要 return 每个样本一个损失值。该示例需要更新以反映这一点。

tf.math.reduce_mean 取批次的平均值,returns 取平均值。这就是为什么它是标量。

Tensorflow网站上给出的损失函数绝对正确。

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

在机器学习中,我们使用的loss是单个训练样例的loss之和,所以应该是一个标量值。 (因为对于所有示例,我们都使用单个网络,因此我们需要单个损失值来更新参数。)

关于制作损失容器:

当使用并行计算时,制作容器是一种更简单可行的方法来跟踪计算的损失指数,因为我们使用批次进行训练而不是整个训练集。

我在 github 上打开了一个 issue。已确认自定义损失函数需要 return 每个样本一个损失值。该示例将需要更新以反映这一点。

其实,据我所知,损失函数的return值的形状并不重要,即它可以是标量张量,也可以是每个样本一个或多个值的张量。重要的是它应该如何减少为标量值,以便它可以用于优化过程或显示给用户。为此,您可以在 Reduction documentation.

中检查缩减类型

此外,这里是 compile 方法 documentationloss 参数的说明,部分解决了这一点:

loss: String (name of objective function), objective function or tf.keras.losses.Loss instance. See tf.keras.losses. An objective function is any callable with the signature loss = fn(y_true,y_pred), where y_true = ground truth values with shape = [batch_size, d0, .. dN], except sparse loss functions such as sparse categorical crossentropy where shape = [batch_size, d0, .. dN-1]. y_pred = predicted values with shape = [batch_size, d0, .. dN]. It returns a weighted loss float tensor. If a custom Loss instance is used and reduction is set to NONE, return value has the shape [batch_size, d0, .. dN-1] ie. per-sample or per-timestep loss values; otherwise, it is a scalar. If the model has multiple outputs, you can use a different loss on each output by passing a dictionary or a list of losses. The loss value that will be minimized by the model will then be the sum of all individual losses.

此外,值得注意的是 TF/Keras 中的大多数 built-in 损失函数通常在最后一个维度上减少(即 axis=-1)。


对于那些怀疑 return 标量值的自定义损失函数是否有效的人:您可以 运行 以下代码段,您会发现模型会正确训练和收敛。

import tensorflow as tf
import numpy as np

def custom_loss(y_true, y_pred):
    return tf.reduce_sum(tf.square(y_true - y_pred))

inp = tf.keras.layers.Input(shape=(3,))
out = tf.keras.layers.Dense(3)(inp)

model = tf.keras.Model(inp, out)
model.compile(loss=custom_loss, optimizer=tf.keras.optimizers.Adam(lr=0.1))

x = np.random.rand(1000, 3)
y = x * 10 + 2.5
model.fit(x, y, epochs=20)

维度可以因为多个通道而增加...但是,每个通道应该只有一个标量值用于损失。

我认为@Gödel 提出的问题是完全合法和正确的。自定义损失函数应该 return 每个样本的损失值。而且,@today 提供的解释也是正确的。最后,这完全取决于使用的减少的种类。

因此,如果使用 class API 创建损失函数,则自定义 class 中会自动继承缩减参数。使用其默认值“sum_over_batch_size”(这只是给定批次中所有损失值的平均值)。其他选项是“sum”,它计算总和而不是求平均值,最后一个选项是“none”,其中一个数组的损失值是 returned.

Keras 文档中还提到,当使用 model.fit() 时,这些缩减差异是不敬的,因为缩减随后由 TF/Keras 自动处理。

最后,还提到在创建自定义损失函数时,应该return编辑一组损失(单个样本损失)。它们的减少由框架处理。

链接: