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

Python 具有预训练卷积基的keras模型中损失函数的奇异行为,python,tensorflow,keras,deep-learning,transfer-learning,Python,Tensorflow,Keras,Deep Learning,Transfer Learning,我试图在Keras中创建一个模型,从图片中进行数值预测。我的模型以densenet121卷积为基础,顶部有两个附加层。除最后两层之外的所有层都设置为layer.trainable=False。我的损失是均方误差,因为这是一项回归任务。在培训期间,我得到了损失:~3,而对同一批数据的评估给出了损失:~30: model.fit(x=dat[0],y=dat[1],批量大小=32) 纪元1/1 32/32[====================================]-0s 11ms/

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

model.fit(x=dat[0],y=dat[1],批量大小=32)
纪元1/1 32/32[====================================]-0s 11ms/步- 损失:2.5571

model.evaluate(x=dat[0],y=dat[1])
32/32[=======================================]-2s 59ms/步 29.276123046875

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

有人认为这种行为可能是由于卷积基()中的
BatchNormalization
层造成的。当然,我的模型中的所有
BatchNormalization
层都被设置为
layer.trainable=False
。也许有人遇到了这个问题并找到了解决办法

但辍学者通常会产生相反的效果,使评估损失小于培训损失


不一定!虽然在辍学层中,一些神经元被丢弃,但请记住,输出是根据辍学率缩小的。在推理时间(即测试时间)中,退出被完全删除,并且考虑到您只训练了一个历元的模型,您看到的行为可能会发生。别忘了,因为你只训练了一个历元的模型,所以只有一部分神经元掉在了退出层,但它们都在推理时出现

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

自己实验:只需将退出层的
trainable
参数设置为
False
,看看是否会发生这种情况


人们可能会感到困惑(正如我所看到的),在一个历元的训练之后,训练损失不等于同一批数据的评估损失。这并不特定于具有
退出
批处理规范化
层的模型。考虑这个例子:

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))
如果您使用上面修改过的行运行上面的代码,您将得到如下输出(显然,精确值可能与您的不同):


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

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

  • 减去平均值并用标准标准进行标准化
  • 使用运行平均值收集平均值和标准差的统计数据
  • 培训两个附加参数(每个节点两个)
  • 当将
    trainable
    设置为
    False
    时,这两个参数冻结,图层也停止收集平均值和标准值的统计数据。但看起来图层仍然在使用训练批的训练时间中执行标准化。很可能是keras中的一个bug,或者他们是出于某种原因故意这么做的。因此,与预测时间相比,训练时间内的前向传播计算是不同的,即使可训练心房肌被设置为假

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

  • 将所有批次标准化层设置为可培训。在这种情况下,这些层将从您的数据集中收集统计信息,而不是使用预训练的数据集(这可能会有很大的不同!)。在这种情况下,您将在培训期间根据自定义数据集调整所有BatchNorm层
  • 将模型分成两部分
    model=model\u base+model\u top
    。之后,使用
    model\u base
    通过
    model\u base.predict()
    提取特征,然后将这些特征输入
    model\u top
    并仅训练
    model\u 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**
    
    这是在一些培训之后——需要等待,直到收集到足够的关于平均数和性病的统计数据

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


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

    您的模型是否包含
    辍学
    批量标准化
    层?如果它有退出层
    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**