keras 为什么我的训练损失有规律的峰值?

8nuwlpux  于 2023-10-19  发布在  其他
关注(0)|答案(5)|浏览(156)

我正在训练这个问题底部链接的Keras对象检测模型,尽管我相信我的问题既不与Keras也不与我试图训练的特定模型(SSD)有关,而是与数据在训练过程中传递给模型的方式有关。
这是我的问题(见下图):我的训练损失总体上在减少,但它显示出尖锐的规律性峰值:

X轴上的单位不是训练时期,而是数十个训练步骤。尖峰每1390个训练步骤发生一次,这正好是我的训练数据集上一次完整通过的训练步骤数。
尖峰总是在训练数据集的每次完整通过之后发生,这一事实让我怀疑问题不在于模型本身,而在于训练过程中输入的数据。
我使用batch generator provided in the repository在训练期间生成批处理。我检查了生成器的源代码,它确实在每次通过之前使用sklearn.utils.shuffle对训练数据集进行 Shuffle 。
我感到困惑的原因有两个:
1.训练数据集在每次通过之前被打乱。
1.正如你在this Jupyter notebook中看到的,我使用了生成器的ad-hoc数据增强功能,所以理论上数据集在任何一次传递中都不应该是相同的:所有的扩增都是随机的。
我做了一些测试预测,看看模型是否真的在学习什么,它是!随着时间的推移,预测会变得更好,但当然,模型的学习速度非常慢,因为这些尖峰似乎每1390步就会扰乱梯度。
任何提示,这可能是非常赞赏!我使用的是上面链接的完全相同的Jummyter笔记本用于我的训练,唯一的变量是我改变了批量大小从32到16。除此之外,链接的笔记本包含了我所遵循的确切训练过程。
下面是包含模型的存储库的链接:
https://github.com/pierluigiferrari/ssd_keras

von4xj4u

von4xj4u1#

我自己也想明白了:

TL;DR:

确保您的损失幅度与您的小批量大小无关。

详细解释:

就我而言,这个问题毕竟是Keras特有的。
也许这个问题的解决方案在某个时候会对某人有用。
事实证明,Keras将损失除以小批量大小。这里需要理解的重要一点是,不是损失函数本身对批量大小进行平均,而是平均发生在训练过程中的其他地方。
为什么这很重要呢?
我正在训练的模型SSD使用了一个相当复杂的多任务损失函数,它会自己求平均值(不是通过批量大小,而是通过批量中的真实边界框的数量)。现在,如果损失函数已经将损失除以与批量大小相关的某个数字,然后Keras再除以批量大小 *,那么突然之间,损失值的大小开始取决于批量大小(准确地说,它与批量大小成反比)。
现在,通常数据集中的样本数量不是您选择的批量大小的整数倍,因此epoch的最后一个minibatch(这里我隐式地将epoch定义为对数据集的一次完整传递)最终包含的样本数将少于批量大小。如果损失取决于批处理大小,这会使损失的大小变得混乱,反过来又会使梯度的大小变得混乱。由于我使用的是带有动量的优化器,这个混乱的梯度也会继续影响后续几个训练步骤的梯度。
一旦我通过将损失乘以批量大小来调整损失函数(从而恢复Keras随后除以批量大小),一切都很好:失去的不再是尖峰。

fcy6dtqo

fcy6dtqo2#

对于任何在PyTorch工作的人来说,解决这个特定问题的简单解决方案是在DataLoader中指定删除最后一个批处理:

train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=False, 
                                          pin_memory=(torch.cuda.is_available()), 
                                          num_workers=num_workers, drop_last=True)
i86rm4rw

i86rm4rw3#

我会添加梯度裁剪,因为这可以防止梯度中的尖峰在训练过程中混淆参数。
梯度裁剪(Gradient Clipping)是一种防止深度网络(通常是递归神经网络)中梯度爆炸的技术。
大多数程序允许您添加一个梯度裁剪参数到您的GD为基础的优化。

lkaoscv7

lkaoscv74#

对我来说,问题的解决方案和原因有点不同。
当将批次大小定义为1或样本总数的适当除数(以便最后一个批次仍然是满的)时,仍然会发生问题。

TL;DR;

这是一个度量伪影,由于平均不影响训练。**默认的损失指标是整个当前epoch的平均损失,直到您读取它的点。**您可以通过使用

def on_batch_begin(batch, logs):
    model.reset_metrics()
    return

lambda_callback = tf.keras.callbacks.LambdaCallback(on_batch_begin=on_batch_begin)

在训练时,

model.fit(..., callbacks=[lambda_callback])

请注意,这显然会使所有指标仅报告每个epoch的最后一个训练批次丢失。

说明

当我注意到model.fit()方法中的这个reset_states()调用时,我设法找到了问题所在。我还意识到这不会发生在批处理循环代码或train_step中的每个批处理上,所以我进行了进一步的研究。
查看LossesContainer类,可以看到_total_loss_mean__call__()函数中累积,并按@Alex提到的batch_size加权。这个值随后被减少到一个平均值(因为它是一个Mean示例),最终呈现出我们看到的损失度量。
好消息是,_total_loss_mean实际上并没有被输入到训练过程中,因为它不是LossesContainer__call__()返回的值。
下图显示了使用(灰色)和不使用(粉红色)“修复”的训练实验。它是在MNIST Tensorflow example上完成的。在它,它'有可能看到的平均效果超过实际数据.

bq8i3lrv

bq8i3lrv5#

我在使用tensorflow时遇到了类似的问题。在我的情况下,这个问题与小批量无关。实际的问题是,由于buffer_size设置的限制,tensorflow没有完全重排数据,如这里所述https://www.tensorflow.org/api_docs/python/tf/data/Dataset#shuffle为了完美的重排,buffer_size应该大于或等于数据集的完整大小。

相关问题