.evaluate 和 .predict 之间的 Keras 差异

Keras discrepancy between .evaluate and .predict

我知道以前有人问过这个问题,但我已经尝试了他们所有的解决方案,但没有一个对我有用。

我的问题:

我正在 运行使用 CNN 对一些图像进行分类,这是一个典型的任务,没什么太疯狂的。我对我的模型进行了以下编译。

model.compile(optimizer = keras.optimizers.Adam(learning_rate = exp_learning_rate),
          loss = tf.keras.losses.SparseCategoricalCrossentropy(),
          metrics = ['accuracy'])

我将其放入我的训练数据集,并按如下方式在我的验证数据集上进行评估:

history = model.fit(train_dataset, validation_data = validation_dataset, epochs = 5)

然后我在单独的测试集上进行了如下评估:

model.evaluate(test_dataset)

结果是:

4/4 [==============================] - 30s 7s/step - loss: 1.7180 - accuracy: 0.8627

然而,当我 运行:

model.predict(test_dataset)

我有以下混淆矩阵输出:

这显然不是 .evaluate 方法告诉我的 86% 的准确率。事实上,它的准确率为 35.39%。为了确保这不是我的测试数据集的问题,我对我的训练和验证数据集进行了模型预测,尽管我进行了训练,但我仍然得到了与这里相似的百分比(~30%),拟合过程中的验证准确度上升到分别为 96%、87%。

问题:

我不知道为什么.predict 和.evaluate 输出不同的结果?那里发生了什么?似乎当我调用 .predict 时,它没有使用我在拟合期间训练的任何权重? (实际上,假设有 3 个 类,这个输出并不比盲目猜测每个标签好多少)。我的拟合权重是否没有转移到我的预测中?我的损失函数是正确的(我将我的数据标记为 tensorflow 希望与 sparse_categorical_crossentropy 一起使用)并且当我通过 'accuracy' 时,它只会采用与我的损失函数相对应的精度。所有这些都应该是一致的。但是为什么.evaluate 和.predict 的结果会出现这样的差异呢?我应该相信哪一个?

我尝试解决我的问题:

我认为稀疏分类交叉熵可能不对,所以我一次性编码了我的目标标签并使用 categorical_crossentropy 损失代替。我仍然遇到与上述完全相同的问题。

顾虑:

如果.evaluate不正确,那不就意味着我在fitting时的训练准确率和验证准确率也不准确吗?那些人不也使用 .evaluate 方法吗?如果真是这样,那我还能相信什么?损失并不能很好地表明我的模型是否运行良好,因为众所周知,最小损失并不意味着良好的准确性(尽管相反的情况通常是正确的,具体取决于我们使用的“好”标准)。如果我的准确性指标不正确,我该如何衡量我的模型的有效性?我真的不知道该看什么了,因为我没有其他方法可以判断我的模型是否正在学习,如果有人可以帮助我了解正在发生的事情,我将不胜感激。我很沮丧。

编辑:(10-28-2021: 12:26 上午)

好的,所以我将提供更多代码来真正解决此问题。

我最初是这样预处理我的数据的:

image_size = (256, 256)
batch_size = 16

train_ds = keras.preprocessing.image_dataset_from_directory(
    directory = image_directory,
    label_mode = 'categorical',
    shuffle = True,
    validation_split = 0.2,
    subset = 'training',
    seed = 24,
    batch_size = batch_size
)

val_ds = keras.preprocessing.image_dataset_from_directory(
    directory = image_directory,
    label_mode = 'categorical',
    shuffle = True,
    validation_split = 0.2,
    subset = 'validation',
    seed = 24,
    batch_size = batch_size
)

其中 image_directory 是一个字符串,其路径包含我的图像。现在您可能会阅读文档,但 image_dataset_from_directory 方法实际上 returns 一个 tf.data.Dataset 对象,其中包含一堆相应的(训练、验证)数据。

我导入了 VGG16 架构来做我的分类,所以我调用了 VGG16 的相应预处理函数,如下所示:

preprocess_input = tf.keras.applications.vgg16.preprocess_input

train_ds = train_ds.map(lambda x, y: (preprocess_input(x), y))

val_ds = val_ds.map(lambda x, y: (preprocess_input(x), y))

这将图像转换为适合作为 VGG16 输入的图像。然后,在我最后的处理步骤中,我做了以下 validation/test split:

val_batches = tf.data.experimental.cardinality(val_ds)
test_dataset = val_ds.take(val_batches // 3)
validation_dataset = val_ds.skip(val_batches // 3)

然后我开始缓存和预取我的数据:

AUTOTUNE = tf.data.AUTOTUNE

train_dataset = train_ds.prefetch(buffer_size = AUTOTUNE)
validation_dataset = validation_dataset.prefetch(buffer_size = AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size = AUTOTUNE)

问题:

问题出现在上面的方法中。我仍然不确定 .evaluate 是否是我模型准确性的真实指标。但是我意识到当我的神经网络是 keras.Sequential() 模型时,.evaluate 和 .predict 总是重合。但是,(如果我错了请纠正我)我怀疑的是 VGG16,当从 keras.applications API 导入时,实际上是 NOT a keras.Sequential() 模型。因此,当我将数据直接输入模型时,我不认为 .predict 和 .evaluate 结果实际上是一致的(我打算 post 这个作为答案,但我没有足够的知识也没有研究以确认我所说的任何内容都是正确的,有人请插话,因为我喜欢学习我知之甚少甚至一无所知的东西,这是暂时的编辑)。

最后,我通过调用 Image_Data_Generator() 而不是 image_dataset_from_directory() 解决了我的问题,如下所示:

train_datagen = ImageDataGenerator(
    preprocessing_function = preprocess_input,
    width_shift_range = 0.2,
    height_shift_range = 0.2,
    shear_range = 0.2,
    zoom_range = 0.2,
    horizontal_flip = True
)

val_datagen = ImageDataGenerator(
    preprocessing_function = preprocess_input
)


train_ds = train_datagen.flow_from_directory(
    train_image_directory,
    target_size = (224, 224),
    batch_size = 16,
    seed = 24,
    shuffle = True,
    classes = ['class1', 'class2', 'class3'],
    class_mode = 'categorical'
)

test_ds = val_datagen.flow_from_directory(
    test_image_directory,
    target_size = (224, 224),
    batch_size = 16,
    seed = 24,
    shuffle = False,
    classes = ['class1', 'class2', 'class3'],
    class_mode = 'categorical'
)

(注意:我是根据来自 tensorflow 文档的以下 link 得到的:https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image/ImageDataGenerator#flow_from_directory

这就完成了我所有的预处理。然后,当我调用 model.evaluate(test_ds) 时,它 returns 与我调用 model.predict_generator(test_ds) 时的结果完全相同。在对预测输出进行一些小的处理后,我将以下代码用于我的混淆矩阵:

Y_pred = model.predict(test_ds)
y_pred = np.argmax(Y_pred, axis=1)

cf = confusion_matrix(test_ds.classes, y_pred)
sns.heatmap(cf, annot= True, xticklabels = class_names,
           yticklabels = class_names)
plt.title('Performance of Model on Testing Set')

这消除了混淆矩阵和model.evaluate(test_ds).

结果的差异

要点:

如果您将图像加载到分类模型中,并且您的损失和准确度匹配,但您的预测与损失、准确度之间存在差异,请尝试以各种可能的方式进行预处理。我通常使用 image_dataset_from_directory() 方法对所有 keras.sequential() 模型预处理我的图像,但是,对于 VGG16 模型,我怀疑它不是 sequential() 模型,使用 ImageDataGenerator(. ..).flow_from_directory(...) 为模型生成正确的格式以生成与性能指标一致的预测。

TLDR 我没有回答我原来的任何问题,但我找到了解决方法。抱歉,如果这是垃圾邮件。正如大多数 Stack Overflow post 的本质一样,我希望我在过去几个小时里的混乱能在未来帮助别人。

我遇到了同样的问题。即使使用 ImageDataGenerator,它仍然保持这种奇怪的行为。

不过我觉得问题出在验证集的shuffle flag上

您从这里更改了它:

 val_ds = keras.preprocessing.image_dataset_from_directory(
     directory = image_directory,
     label_mode = 'categorical',
     shuffle = True,
     validation_split = 0.2,
     subset = 'validation',
     seed = 24,
     batch_size = batch_size
 )

到这里:

 test_ds = val_datagen.flow_from_directory(
     test_image_directory,
     target_size = (224, 224),
     batch_size = 16,
     seed = 24,
     shuffle = False,
     classes = ['class1', 'class2', 'class3'],
     class_mode = 'categorical'
 )