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

Strange behaviour of the loss function in keras model, with pretrained convolutional base

我正在尝试在 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 - loss: 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。也许有人遇到过这个问题并找到了解决方案?

But dropout layers usually create opposite effect making loss on evaluation less than loss during training.

不一定!虽然在 dropout 层中一些神经元被丢弃,但请记住输出是根据 dropout 率按比例缩小的。在推理时间(即测试时间)中,dropout 被完全移除,并且考虑到您只训练了一个 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 是更新模型权重后的损失。即使使用的数据相同,计算这些损失值时模型的状态也不相同(另一个问题:为什么反向传播后损失增加了?这仅仅是因为你只训练了一个时期因此权重更新还不够稳定。

为了证实这一点,您还可以使用与验证数据相同的数据批次:

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

你看到validation loss和evaluation loss是完全一样的:因为validation是在epoch结束的时候进行的(也就是模型权重已经更新的时候)。

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

  1. 减去均值并用标准差归一化
  2. 使用运行平均值
  3. 收集平均值和标准差的统计数据
  4. 训练两个附加参数(每个节点两个)。

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

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

  1. 将所有 BatchNormalization 层设置为可训练。在这种情况下,这些层将从您的数据集中收集统计数据,而不是使用预训练的数据(这可能会有很大的不同!)。在这种情况下,您将在训练期间将所有 BatchNorm 层调整到您的自定义数据集。
  2. 将模型分成两部分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**

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

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

更新。我找到了一个很棒的博客 post,其中对这个问题的所有细节进行了讨论。看看here