keras 模型中损失函数的奇怪行为,具有预训练的卷积基础

Posted

技术标签:

【中文标题】keras 模型中损失函数的奇怪行为,具有预训练的卷积基础【英文标题】:Strange behaviour of the loss function in keras model, with pretrained convolutional base 【发布时间】:2018-12-09 21:58:38 【问题描述】:

我正在尝试在 Keras 中创建一个模型,以根据图片进行数值预测。我的模型有 densenet121 卷积基础,顶部有几个附加层。除了最后两个层之外的所有层都设置为layer.trainable = False。我的损失是均方误差,因为它是一个回归任务。在训练期间我得到loss: ~3,而对同一批数据的评估得到loss: ~30

model.fit(x=dat[0],y=dat[1],batch_size=32)

Epoch 1/1 32/32 [===============================] - 0s 11ms/step - 损失:2.5571

model.evaluate(x=dat[0],y=dat[1])

32/32 [===============================] - 2s 59ms/step 29.276123046875

我在训练和评估期间提供完全相同的 32 张图片。我还使用来自y_pred=model.predict(dat[0]) 的预测值计算了损失,然后使用 numpy 构造了均方误差。结果与我从评估中得到的结果相同(即 29.276123...)。

有人建议这种行为可能是由于卷积基础 (discussion on github) 中的 BatchNormalization 层造成的。当然,我模型中的所有BatchNormalization 层也已设置为layer.trainable=False。也许有人遇到过这个问题并想出了解决方案?

【问题讨论】:

您的模型是否包含DropoutBatchNormalization 层?如果它有 Dropout 层,那么很可能是造成差异的原因。 是的,确实如此。我的模型中有一个可训练的 dropout 层。但是 dropout 层通常会产生相反的效果,使评估的损失小于训练期间的损失。此外,它通常不会产生如此大的数量级差异。 不一定!尽管在 dropout 层中,一些神经元被丢弃了,但请记住,输出会根据 dropout 率进行缩减。在推理时间(即测试时间)中,完全消除了 dropout,并且考虑到您只训练了一个 epoch 的模型,您看到的行为可能会发生。自己试验一下:只需将Dropout 层的trainable 参数设置为False,看看是否会发生这种情况。 【参考方案1】:

但 dropout 层通常会产生相反的效果,使评估损失小于训练损失。

不一定!尽管在 dropout 层中,一些神经元被丢弃了,但请记住,输出是根据 dropout 率缩减的。在推理时间(即测试时间)中,完全消除了 dropout,并且考虑到您只训练了一个 epoch 的模型,您看到的行为可能会发生。不要忘记,由于您只训练了一个 epoch 的模型,因此只有一部分神经元被丢弃在 dropout 层中,但它们在推理时都存在。

如果您继续对模型进行更多时期的训练,您可能会期望训练损失和测试损失(在相同数据上)或多或少相同。

自己实验一下:只需将 Dropout 层的trainable 参数设置为False 看看是否会发生这种情况。


看到在一个时期的训练之后,训练损失不等于同一批次数据的评估损失,可能会感到困惑(就像我一样)。这并不特定于具有DropoutBatchNormalization 层的模型。考虑这个例子:

from keras import layers, models
import numpy as np

model = models.Sequential()
model.add(layers.Dense(1000, activation='relu', input_dim=100))
model.add(layers.Dense(1))

model.compile(loss='mse', optimizer='adam')
x = np.random.rand(32, 100)
y = np.random.rand(32, 1)

print("Training:")
model.fit(x, y, batch_size=32, epochs=1)

print("\nEvaluation:")
loss = model.evaluate(x, y)
print(loss)

输出:

Training:
Epoch 1/1
32/32 [==============================] - 0s 7ms/step - loss: 0.1520

Evaluation:
32/32 [==============================] - 0s 2ms/step
0.7577340602874756

那么,如果它们是在相同的数据(即0.1520 != 0.7577)上计算的,那么为什么损失会不同?

如果你问这个,那是因为你和我一样,没有足够重视:0.1520 是更新模型参数之前的损失(即在进行反向传递或反向传播之前)。而0.7577是模型权重更新后的损失。即使使用的数据相同,计算这些损失值时模型的状态也不相同(另一个问题:那么为什么反向传播后损失增加了?这仅仅是因为您只训练了一个 epoch因此权重更新还不够稳定)。

要确认这一点,您还可以使用与验证数据相同的数据批次:

model.fit(x, y, batch_size=32, epochs=1, validation_data=(x,y))

如果你用上面修改过的行运行上面的代码,你会得到这样的输出(显然你的确切值可能不同):

Training:
Train on 32 samples, validate on 32 samples
Epoch 1/1
32/32 [==============================] - 0s 15ms/step - loss: 0.1273 - val_loss: 0.5344

Evaluation:
32/32 [==============================] - 0s 89us/step
0.5344240665435791

您会看到验证损失和评估损失完全相同:这是因为验证是在 epoch 结束时执行的(即当模型权重已经更新时)。

【讨论】:

我会试试这个。但我并没有只训练我的模型 1 个 epoch。我在我的问题中写的只是一个测试阶段。我在训练模型一段时间后成功了。在训练期间,损失从 ~1000 下降到 ~1,并且非常稳定。只有在那之后我才开始评估。与 model.fit() 的那一行我只是为了测试目的。 @AndreyKiteGorin 阅读了我在帖子中添加的注释。我无法确认我的说法... 是的,我明白了。我会试试看,看看会发生什么。感谢您调查问题 这一切都是正确的。但这与我的情况无关,因为我在已经对模型进行了很长时间的训练并且看到损失在训练期间变得稳定之后才执行此单个训练步骤。问题是,我在训练期间看到的这种稳定损失与我在评估中得到的损失非常不同。抱歉,如果我对问题的描述令人困惑。【参考方案2】:

看来我找到了解决方案。正如我所建议的,问题出在 BatchNormalization 层上。他们做树的东西

    减去均值并按标准标准化 使用移动平均值收集平均值和标准差的统计数据 训练两个附加参数(每个节点两个)。

当将trainable 设置为False 时,这两个参数会冻结,并且层也停止收集均值和标准差的统计信息。但看起来该层在训练期间仍执行归一化使用训练批次。很可能这是 keras 中的一个错误,或者他们出于某种原因故意这样做。因此,与预测时间相比,训练期间前向传播的计算是不同的即使可训练属性设置为 False

我能想到两种可能的解决方案:

    将所有 BatchNormalization 层设置为可训练。在这种情况下,这些层将从您的数据集中收集统计数据,而不是使用预训练的数据(这可能会有很大的不同!)。在这种情况下,您将在训练期间将所有 BatchNorm 层调整为您的自定义数据集。 将模型分成两部分model=model_base+model_top。之后,使用model_base 提取model_base.predict() 的特征,然后将这些特征输入model_top 并仅训练model_top

我刚刚尝试了第一个解决方案,它看起来很有效:

model.fit(x=dat[0],y=dat[1],batch_size=32)

Epoch 1/1
32/32 [==============================] - 1s 28ms/step - loss: **3.1053**

model.evaluate(x=dat[0],y=dat[1])

32/32 [==============================] - 0s 10ms/step
**2.487905502319336**

这是经过一些训练后得到的——需要等到收集到足够的均值和标准差统计数据。

我还没有尝试过第二种解决方案,但我很确定它会起作用,因为训练和预测期间的前向传播将是相同的。

更新。我发现了一篇很棒的博客文章,其中已经详细讨论了这个问题。看看here

【讨论】:

其实我不知道我在这里缺少什么:3.1053是训练期间最后一个epoch的损失吗?如果是这样的话,应该和同一个数据上的评估损失一样吧?但它不是:3.1053 != 2.4879。为什么? 是的,一个是经过一步训练(1 epoch,batch_size=data_set_size)和另一个评估。它们不同的原因有两个:1-您之前在关于 dropout 层的评论中提到的那个 2-在现在设置为可训练的 BatchNormalization 层中没有收集到足够的统计信息。但现在合乎逻辑的是,有 dropout 的损失比没有的要高。我正在进一步训练模型,并将看到这种差异的动态 澄清一下,你在这里看到的这个训练步骤我只是为了测试目的而执行的。真正的训练是在超过 300000 个样本的大数据集上单独进行的。 没关系。如果它说这批的损失等于 3(在这种情况下我们只有一个批),那么如果我们立即评估该批上的模型,损失应该正好是 3,对吧?或者也许我在这里遗漏了一些东西...... 在训练过程中完成了 dropout,但在评估过程中没有。 BatchNorm 层在评估前向传播和训练期间的计算方式也不同。所以这么小的损失差异并不奇怪。奇怪的是我一开始的数量级差异。

以上是关于keras 模型中损失函数的奇怪行为,具有预训练的卷积基础的主要内容,如果未能解决你的问题,请参考以下文章

使用自定义损失函数编译 Keras 模型时出现 TypeError

使用预训练 vgg19 tensorflow,Keras 在 CNN 自动编码器中定义自定义损失(感知损失)

使用 keras 使用预训练的 VGG 实现感知损失

Keras 模型的验证损失与损失函数输出不匹配

如何使用 PyTorch 在预训练模型上添加新层? (给出了 Keras 示例。)

如何在 Keras 模型中使用 TensorFlow 的采样 softmax 损失函数?