喀拉斯邦不同批次大小的损失计算

Jon*_*han 3 python loss deep-learning keras tensorflow

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

            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
Run Code Online (Sandbox Code Playgroud)

但是,我注意到,当我使用a batch_size=32和a 训练网络时batch_size=64,每个时期的损失值仍然或多或少地相同,只有a~0.05%有所不同。但是,两个网络的准确性都完全相同。因此,从本质上讲,批量大小对网络没有太大影响。

我的问题是,如果我将批处理量加倍,并假设损失确实在被累加,那么损失实际上不应该是以前的两倍,或者至少更大吗?精度保持不变的事实否定了网络可能以更大的批量学习得更好的借口。

无论批次大小如何,损失都大致相同,这一事实使我认为这是平均水平。

tod*_*day 5

您发布的代码涉及多输出模型,其中每个输出可能都有自己的损失和权重。因此,将不同输出层的损耗值相加。但是,如您在losss.py文件中看到的,单个损失是整个批次平均数。例如,这是与二进制交叉熵损失有关的代码:

def binary_crossentropy(y_true, y_pred):
    return K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1)
Run Code Online (Sandbox Code Playgroud)

更新:在添加了此答案的第二部分(即损失函数)之后,作为OP,我axis=-1对损失函数的定义感到困惑,我自己想这必须axis=0表明批次的平均值吗? !然后我意识到,K.mean()对于包含多个单元的输出层,在损耗函数的定义中全部使用了。那么整个批次的平均损失在哪里?我检查了代码以找到答案:要获取特定损失函数的损失值,将调用一个函数该函数将真实和预测的标签以及样本权重和掩码作为其输入:

weighted_loss = weighted_losses[i]
# ...
output_loss = weighted_loss(y_true, y_pred, sample_weight, mask)
Run Code Online (Sandbox Code Playgroud)

这个weighted_losses[i]功能是什么?您可能会发现,它是(增强的)损失函数列表的一个元素

weighted_losses = [
    weighted_masked_objective(fn) for fn in loss_functions]
Run Code Online (Sandbox Code Playgroud)

fn实际上是loss.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
Run Code Online (Sandbox Code Playgroud)

如您所见,首先在行中计算每个样本损失score_array = fn(y_true, y_pred),然后最后返回损失的平均值,即return K.mean(score_array)。因此可以确认所报告的损失是每批次中每个样本损失的平均值。

请注意K.mean(),在使用Tensorflow作为后端的情况下,请调用tf.reduce_mean()函数。现在,当K.mean()被称为没有一个axis参数(默认值axis参数会None),因为它是所谓的weighted_masked_objective功能,相应的调用tf.reduce_mean() 计算平均值在所有的轴,并返回一个单一的值。这就是为什么无论输出层的形状和所使用的损失函数如何,Keras都只使用和报告单个损失值的原因(应该这样,因为优化算法需要最小化标量值,而不是矢量或张量) 。