keras中不同批量大小的损失计算

Posted

技术标签:

【中文标题】keras中不同批量大小的损失计算【英文标题】:loss calculation over different batch sizes in keras 【发布时间】:2019-02-09 20:59:49 【问题描述】:

我知道理论上,一个批次的网络损失只是所有单个损失的总和。这反映在用于计算总损失的Keras code 中。相关:

            for i in range(len(self.outputs)):
            if i in skip_target_indices:
                continue
            y_true = self.targets[i]
            y_pred = self.outputs[i]
            weighted_loss = weighted_losses[i]
            sample_weight = sample_weights[i]
            mask = masks[i]
            loss_weight = loss_weights_list[i]
            with K.name_scope(self.output_names[i] + '_loss'):
                output_loss = weighted_loss(y_true, y_pred,
                                            sample_weight, mask)
            if len(self.outputs) > 1:
                self.metrics_tensors.append(output_loss)
                self.metrics_names.append(self.output_names[i] + '_loss')
            if total_loss is None:
                total_loss = loss_weight * output_loss
            else:
                total_loss += loss_weight * output_loss

但是,我注意到,当我用 batch_size=32batch_size=64 训练网络时,每个 epoch 的损失值仍然或多或少相同,只有 ~0.05% 差异。然而,两个网络的准确度保持不变。所以本质上,batch size 对网络没有太大影响。

我的问题是,当我将批量大小加倍时,假设损失确实被求和,损失实际上不应该是以前值的两倍,或者至少更大吗?网络可能通过更大的批大小更好地学习的借口被准确度保持完全相同的事实所否定。

无论批次大小如何,损失或多或少都保持不变的事实让我认为它被平均了。

【问题讨论】:

损失是平均值,而不是单个损失的总和。 能否通过代码确认一下? @enumaris 当我遵循fit() 的代码时,它似乎是平均的,但compile() 似乎是总和。为什么两者都有? 见这里:github.com/keras-team/keras/blob/master/keras/losses.py 所有损失都带有K.mean(),向您表明这是平均值而不是总和。 @enumaris 查看对已接受答案的评论。 【参考方案1】:

您发布的代码涉及多输出模型,其中每个输出可能有自己的损失和权重。因此,不同输出层的损失值被加在一起。但是,如您在 losses.py 文件中所见,单个损失是批次 的平均值。例如这是与二元交叉熵损失相关的代码:

def binary_crossentropy(y_true, y_pred):
    return K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1)

更新:在添加this answer的第二部分(即损失函数)之后,作为OP,我对损失函数定义中的axis=-1感到困惑,我想我自己认为它必须是axis=0 来表示批次的平均值?!然后我意识到,损失函数定义中使用的所有K.mean() 都适用于由多个单元组成的输出层的情况。那么批次的平均损失在哪里?我检查了代码以找到答案:要获得特定损失函数的损失值,a function is called 将真实和预测标签以及样本权重和掩码作为其输入:

weighted_loss = weighted_losses[i]
# ...
output_loss = weighted_loss(y_true, y_pred, sample_weight, mask)

weighted_losses[i] 函数是什么?如您所见,it is an element of list of (augmented) loss functions:

weighted_losses = [
    weighted_masked_objective(fn) for fn in loss_functions]

fn实际上是losses.py文件中定义的损失函数之一,也可能是用户自定义的损失函数。现在这个weighted_masked_objective 函数是什么?已在training_utils.py 文件中定义:

def weighted_masked_objective(fn):
    """Adds support for masking and sample-weighting to an objective function.
    It transforms an objective function `fn(y_true, y_pred)`
    into a sample-weighted, cost-masked objective function
    `fn(y_true, y_pred, weights, mask)`.
    # Arguments
        fn: The objective function to wrap,
            with signature `fn(y_true, y_pred)`.
    # Returns
        A function with signature `fn(y_true, y_pred, weights, mask)`.
    """
    if fn is None:
        return None

    def weighted(y_true, y_pred, weights, mask=None):
        """Wrapper function.
        # Arguments
            y_true: `y_true` argument of `fn`.
            y_pred: `y_pred` argument of `fn`.
            weights: Weights tensor.
            mask: Mask tensor.
        # Returns
            Scalar tensor.
        """
        # score_array has ndim >= 2
        score_array = fn(y_true, y_pred)
        if mask is not None:
            # Cast the mask to floatX to avoid float64 upcasting in Theano
            mask = K.cast(mask, K.floatx())
            # mask should have the same shape as score_array
            score_array *= mask
            #  the loss per batch should be proportional
            #  to the number of unmasked samples.
            score_array /= K.mean(mask)

        # apply sample weighting
        if weights is not None:
            # reduce score_array to same ndim as weight array
            ndim = K.ndim(score_array)
            weight_ndim = K.ndim(weights)
            score_array = K.mean(score_array,
                                 axis=list(range(weight_ndim, ndim)))
            score_array *= weights
            score_array /= K.mean(K.cast(K.not_equal(weights, 0), K.floatx()))
        return K.mean(score_array)
return weighted

如您所见,首先在score_array = fn(y_true, y_pred) 行中计算每个样本的损失,然后在最后返回损失的平均值,即return K.mean(score_array)。这样就证实了报告的损失是每批中每个样本损失的平均值。

注意K.mean(),如果使用Tensorflow作为后端,callstf.reduce_mean()函数。现在,当在没有axis 参数的情况下调用K.mean() 时(axis 参数的默认值为None),因为它在weighted_masked_objective 函数中被调用,对应的调用tf.reduce_mean() computes the mean over all the axes and returns one single value .这就是为什么无论输出层的形状和使用的损失函数如何,Keras 只使用和报告一个单一的损失值(应该是这样的,因为优化算法需要最小化一个标量值,而不是向量或张量) .

【讨论】:

嗯,但这与我在这个问题中注意到的不太清楚:***.com/questions/52034983/… 它不凝胶的原因是axis=-1。因此,当预测本身是图像时,axis=-1 只是图像的一个维度,在这种情况下,它并没有真正取平均值。 @Jonathan 我和你一样怀疑。请参阅我的更新答案。 @Jonathan K.mean 调用 tf.reduce_mean。现在,当调用不带axis 参数的K.meanaxis 的默认值为None)时,就像在weighted_masked_objective 中一样,tf.reduce_mean 计算所有轴的平均值并返回只有一个单一的价值。我已经更新了我的答案以反映这一点。 谢谢,这确实回答了这个问题。但这仍然让我想知道每个样本的多个输出的损失是如何组合的。看看这个例子:medium.com/nanonets/…。每个不同的标签都可以被认为是为每个输入预测的多个输出。不知何故,这些损失正在加起来。它们似乎没有被求和或平均。【参考方案2】:

我想在这个页面总结一下精彩的答案。

    当然,模型需要一个标量值来优化(即 Gradient Decent)。 这个重要的值是在批次级别计算的。(如果你设置批次大小=1,它是随机梯度下降模式。所以梯度是在那个数据点上计算的) 在损失函数中,k.mean() 等组聚合函数专门用于解决多分类等问题,在哪里获得一个数据点损失,我们需要沿多个标签对多个标量求和。

    在 model.fit 打印的损失历史中,打印的损失值是每批的移动平均值。所以我们看到的值实际上是按 batch_size*per datapoint 缩放的估计损失。

    请注意,即使我们将批量大小设置为 1,打印的历史记录也可能使用不同的批量间隔进行打印。就我而言:

    self.model.fit(x=np.array(single_day_piece),y=np.array(single_day_reward),batch_size=1)
    

打印的是:

 1/24 [>.............................] - ETA: 0s - loss: 4.1276
 5/24 [=====>........................] - ETA: 0s - loss: -2.0592
 9/24 [==========>...................] - ETA: 0s - loss: -2.6107
13/24 [===============>..............] - ETA: 0s - loss: -0.4840
17/24 [====================>.........] - ETA: 0s - loss: -1.8741
21/24 [=========================>....] - ETA: 0s - loss: -2.4558
24/24 [==============================] - 0s 16ms/step - loss: -2.1474

在我的问题中,单个数据点的损失不可能达到 4.xxx 的规模。所以我猜模型会损失前 4 个数据点的总和。但是,tain 的批大小不是 4。

【讨论】:

以上是关于keras中不同批量大小的损失计算的主要内容,如果未能解决你的问题,请参考以下文章

自定义 keras 损失函数二元交叉熵给出不正确的结果

在 CNN 的 keras 自定义损失函数中操作数据

使用Tensorflow后端的Keras LSTM RNN中令人费解的训练损失与纪元...行为的任何原因

为啥用于预测的 Keras LSTM 批量大小必须与拟合批量大小相同?

制作自定义 Keras 层时不能使用未知的输入尺寸(批量大小)

在 Keras 中拟合模型时,批量大小和 epoch 数应该有多大?