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

loss calculation over different batch sizes in keras

我知道从理论上讲,网络在一批上的损失只是所有个体损失的总和。这反映在用于计算总损失的 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 训练网络时,每个时期的损失值仍然或多或少与只有 ~0.05% 区别。然而,两个网络的准确度保持完全相同。所以基本上,批量大小对网络没有太大影响。

我的问题是,当我将批次大小加倍时,假设损失确实被求和,那么损失实际上不应该是之前的两倍,或者至少更大吗?网络可能在更大的批量大小下学得更好的借口被准确性保持完全相同的事实所否定。

无论批量大小如何,损失都或多或少保持不变这一事实让我认为它正在被平均化。

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

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

更新: 在添加这个答案的第二部分(即损失函数)之后,作为 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 作为后端,calls the tf.reduce_mean() function. Now, when K.mean() is called without an axis argument (the default value of axis argument would be None), as it is called in weighted_masked_objective function, the corresponding call to tf.reduce_mean() computes the mean over all the axes and returns one single value。这就是为什么无论输出层的形状和使用的损失函数如何,Keras 只使用和报告一个单一的损失值(它应该是这样的,因为优化算法需要最小化标量值,而不是向量或张量) .

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

  1. 当然,模型需要一个标量值来优化(即梯度下降)。
  2. 这个重要的值是在batch level上计算的。(如果你设置batch size=1,它是随机梯度下降模式。所以梯度是在那个数据点上计算的)
  3. 在损失函数中,组聚合函数如k.mean(),专门针对多分类等问题激活,在何处获得一个数据点损失,我们需要沿着许多标签对许多标量求和。
  4. 在model.fit打印的损失历史记录中,打印的损失值是每个批次的运行平均值。因此,我们看到的值实际上是按 batch_size* 每个数据点缩放的估计损失。

  5. 请注意,即使我们设置batch size=1,打印的历史记录也可能使用不同的batch interval进行打印。就我而言:

    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.So 的规模,我猜模型采用前 4 个数据点的损失总和。但是,tain 的批量大小不是 4。