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

vecaoik1  于 2022-12-23  发布在  其他
关注(0)|答案(7)|浏览(154)

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

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

此自定义损失函数中的reduce_mean函数将返回标量。
这样定义损失函数对吗?据我所知,y_truey_pred形状的第一维是批量大小。我认为损失函数应该返回批量中每个样品的损失值。因此损失函数应该给予(batch_size,)形状的数组。但上述函数给出整个批量的单个值。
也许上面的例子是错误的?谁能在这个问题上给予我一些帮助?

为什么我认为loss函数应该返回数组而不是单个值?

我看了Model类的源代码。当你提供一个损失函数(请注意,它是函数,而不是损失)到Model.compile()方法,该损失函数用于构造LossesContainer对象,这个传递给LossesContainer类的构造函数的损失函数再次被用于构造LossFunctionWrapper对象,其存储在LossesContainer._losses中。

**根据LossFunctionWrapper类的源代码,通过LossFunctionWrapper.__call__()方法计算训练批次的整体损失值(继承自Loss类),也就是说,它返回整个批次的单个损失值。**但是LossFunctionWrapper.__call__()首先调用LossFunctionWrapper.call()方法,以获得训练批次中每个样本的损失数组。然后将这些损耗进行最终平均,得到整个批次的单个损耗值,正是在LossFunctionWrapper.call()方法中调用了提供给Model.compile()方法的损耗函数。

这就是为什么我认为自定义loss函数应该返回一个loss数组,而不是一个标量值。此外,如果我们为Model.compile()方法编写一个自定义Loss类,那么我们自定义Loss类的call()方法也应该返回一个数组,而不是一个信号值。
我在github上打开了一个issue,确认了自定义损耗函数需要返回每个样本一个损耗值,这个例子需要更新以反映这一点。

whitzsjs

whitzsjs1#

实际上,据我所知,损失函数的返回值的形状并不重要,即它可以是标量Tensor,也可以是每个样本一个或多个值的Tensor。重要的是如何将其还原为标量值,以便在优化过程中使用或显示给用户。为此,您可以在Reductiondocumentation中检查还原类型。
此外,下面是compile方法文档对loss参数的说明,部分地解决了这一点:

损失:字符串(目标函数的名称)、目标函数或tf.keras.losses.Loss示例。请参见tf.keras.losses。目标函数是具有签名loss = fn(y_true,y_pred)的任何可调用函数,其中y_true =形状为[batch_size, d0, .. dN]的标准真值,稀疏损失函数除外,例如稀疏分类交叉熵,其中shape = [batch_size, d0, .. dN-1]y_pred = shape = [batch_size, d0, .. dN]的预测值。它返回加权损失浮动Tensor。如果使用自定义Loss示例,并且将减少设置为NONE,则返回值具有[batch_size, d0, .. dN-1]的形状,即每样本或每时间步长的损耗值;如果模型有多个输出,您可以通过传递字典或损失列表来对每个输出使用不同的损失。模型将最小化的损失值将是所有单个损失的总和。

此外,值得注意的是,TF/Keras中的大多数内置损失函数通常在最后一个维度(即axis=-1)上被减小。
对于那些怀疑返回标量值的自定义损失函数是否有效的人:你可以运行下面的代码片段,你会看到模型会正确地训练和收敛。

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)
gijlo24d

gijlo24d2#

我在github上打开了一个issue。确认了自定义损耗函数需要返回每个样本一个损耗值。这个例子需要更新以反映这一点。

zbdgwd5y

zbdgwd5y3#

我认为@Gödel提出的问题是完全合法和正确的。自定义损失函数应该返回每个样本的损失值。而且,@today提供的解释也是正确的。最终,这一切都取决于使用的*减少的类型。
因此,如果使用类API创建损失函数,则自定义类中会自动继承reduction参数,使用其默认值"sum_over_batch_size(其简单地平均给定批中的所有损失值)。其它选项是"sum",其计算总和而不是平均,并且最后一个选项是"none",其中返回损失值的数组。
Keras文档中还提到,当使用model.fit()时,这些减少的差异是不尊重的,因为TF/Keras会自动处理减少。
最后,我们还提到,当创建一个自定义损失函数时,应该返回一组损失(单个样本损失),它们的减少由框架处理。
链接:

  • https://keras.io/api/losses/
  • Checkout CategoricalCrossentropy Class: https://keras.io/api/losses/probabilistic_losses/#categoricalcrossentropy-class
toe95027

toe950274#

tf.math.reduce_mean取批处理的平均值并返回,这就是为什么它是一个标量。

fquxozlt

fquxozlt5#

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

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

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

关于制作损耗容器:

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

hk8txs48

hk8txs486#

由于存在多个通道,因此可以增加维度......但是,每个通道应仅具有损耗的标量值。

hmae6n7t

hmae6n7t7#

Tensorflow文档忽略了这一点,但这一点在Keras documentation上得到了明确的说明和澄清。
请注意,这是tf.keras. loss.mean_squared_error等损失函数与tf.keras. loss.MeanSquaredError等违约损失类示例之间的重要区别:函数版本不执行归约,但默认情况下类示例执行归约。
它还指出:
默认情况下,损失函数为每个输入样本返回一个标量损失值

相关问题