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

问题描述

我正在使用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函数将返回标量。

像这样定义损失函数是否正确?据我所知,y_truey_pred的形状的第一维是批量大小。我认为损失函数应返回批次中每个样品的损失值。因此,损失函数应该给出形状为(batch_size,)的数组。但是上面的函数为整个批次提供了一个单一的值。

也许上面的例子是错误的?有人可以帮我解决这个问题吗?


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

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

根据LossFunctionWrapper类的源代码,通过LossesContainer._losses方法(从LossFunctionWrapper.__call__()类继承)计算训练批次的总损失值,即返回整个批次的单个损失值。。但是Loss首先调用LossFunctionWrapper.__call__()方法,以获取训练批次中每个样本的损失数组。然后对这些损失进行平均,以得到整个批次的单个损失值。调用LossFunctionWrapper.call()方法提供的损失函数就是在LossFunctionWrapper.call()方法中。

这就是为什么我认为自定义损失函数应该返回一系列损失,并具有单个标量值。此外,如果我们为Model.compile()方法编写了一个自定义Loss类,那么我们的自定义Model.compile()类的call()方法也应该返回一个数组,而不是一个信号值。


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

解决方法

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

,

实际上,据我所知,损失函数的返回值的形状并不重要,即它可以是标量张量或每个样本一个或多个值的张量。重要的是如何将其减小为标量值,以便可以将其用于优化过程或显示给用户。为此,您可以在Reduction documentation中检查缩小类型。

此外,这是compile方法documentation关于loss参数的内容,部分解决了这一点:

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

此外,值得注意的是,TF / Keras中的大多数内置损耗函数通常会在最后一个维度(即[batch_size,.. dN-1])上减小。


对于那些怀疑返回标量值的自定义损失函数是否会起作用的人:您可以运行以下代码段,然后您会看到模型可以正确训练和收敛。

axis=-1
,

<div class="container"> <table border='1' id='theTable'> <thead> <tr> <th>Name</th> <th>Role</th> </tr> </thead> <tbody> <tr> <td>Adam</td> <td>AAA</td> </tr> <tr> <td>Adam</td> <td>BBB</td> </tr> <tr> <td>Adam</td> <td>CCC</td> </tr> <tr> <td>Bert</td> <td>AAA</td> </tr> <tr> <td>Bert</td> <td>CCC</td> </tr> <tr> <td>Cesar</td> <td>BBB</td> </tr> </tbody> </table> <br> <table id='newTable' border='1'> <thead></thead> <tbody></tbody> </table> </div> <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script> <script> $(document).ready(function () { var role_arr = []; $("#theTable td:nth-child(2)").each(function() { if ($.inArray($(this).text(),role_arr) == -1) role_arr.push($(this).text()); }); role_arr.sort() console.log(role_arr); // create thead row and put Roles in it var trow = "<tr>"; trow += '<th>Name</th>'; for (var i=0; i<role_arr.length; i++) { trow +='<th>'+ role_arr[i] +'</th>'; } trow += '</tr>'; $("#newTable").find("thead").append(trow); // create all names array var name_arr = []; $("#theTable td:nth-child(1)").each(function() { if ($.inArray($(this).text(),name_arr) == -1) name_arr.push($(this).text()); }); console.log(name_arr); for (var i=0; i<name_arr.length; i++) { // create an array for each name's roles var row_arr = []; $("#theTable tr:has(td:contains('"+name_arr[i]+"'))").each(function () { //console.log($(this).find('td:nth-child(2)').text()); row_arr.push($(this).find('td:nth-child(2)').text()); }); // create the table body row row var trow = "<tr>"; trow += '<td>'+name_arr[i]+'</td>'; for(var j=0; j<role_arr.length; j++) { if(row_arr.includes(role_arr[j])) { trow += '<td> X </td>'; } else { trow += '<td> - </td>'; } } trow += '</tr>'; $("#newTable").find("tbody").append(trow); } }); </script>取批次的平均值并返回。这就是为什么它是一个标量。

,

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

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

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

关于使集装箱蒙受损失:

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

,

我认为@Gödel发表的问题完全合法,而且是正确的。自定义损失函数应返回每个样本的损失值。并且,@ today提供的解释也是正确的。最后,这完全取决于所使用的 减少量

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

Keras文档中还提到,当人们使用model.fit()时,减少的这些差异是无可争辩的,因为减少是由TF / Keras自动处理的。

最后,还要提到的是,在创建自定义损失函数时,应返回一系列损失(单个样本损失)。它们的减少由框架处理。

链接:

,

由于有多个通道,因此可以增加维数。但是,每个通道的损耗都应只有一个标量值。