【问题标题】:Keras discrepancy between .evaluate and .predict.evaluate 和 .predict 之间的 Keras 差异
【发布时间】:2021-12-13 04:50:12
【问题描述】:

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

我的问题:

我正在运行一个 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 [==============================] - 30 秒 7 秒/步 - 损失:1.7180 - 准确度: 0.8627

但是,当我运行时:

model.predict(test_dataset)

我有以下混淆矩阵输出:

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

问题:

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

我尝试解决我的问题:

我认为稀疏分类交叉熵可能不正确,因此我对目标标签进行了一次热编码,并改用了分类交叉熵损失。我仍然遇到与上述完全相同的问题。

担忧:

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

编辑:(2021 年 10 月 28 日:上午 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 方法实际上返回了一个 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 输入的图像。然后,在我最后的处理步骤中,我进行了以下验证/测试拆分:

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 总是一致的。但是,(如果我错了,请纠正我)我怀疑从 keras.applications API 导入的 VGG16 实际上是 NOT keras.Sequential() 模型。因此,当我将数据直接输入模型时,我不认为 .predict 和 .evaluate 结果实际上是一致的(我打算将此作为答案发布,但我没有足够的知识或研究来证实这一点我所说的任何内容都是正确的,请有人加入,因为我喜欢学习我几乎一无所知的东西,现在是编辑)。

最后,我通过调用 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 文档中的以下链接获得的:https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image/ImageDataGenerator#flow_from_directory

这为我完成了所有的预处理。然后,当我调用 model.evaluate(test_ds) 时,它返回的结果与我调用 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 模型,我怀疑它不是 sequence() 模型,使用 ImageDataGenerator(...).flow_from_directory( ...) 为模型生成正确的格式以生成与性能指标一致的预测。

TLDR 我没有回答我最初提出的任何问题,但我找到了解决方法。对不起,如果这是任何形式的垃圾邮件。与大多数 Stack Overflow 帖子的性质一样,我希望我在过去几个小时内的动荡对未来的某些人有所帮助。

【问题讨论】:

  • 你需要包含产生混淆矩阵的代码,如果你在那里出错了怎么办?
  • @Dr.Snoopy 经过深思熟虑,我意识到我的数据预处理完全错误。我正在考虑删除这篇文章,但它可能会帮助其他正在做类似任务的人。我在下面给出一个完整的答案。我会在那里输入我所有的想法。感谢您查看我的问题!
  • 我遇到了同样的问题,你帮了我很多!谢谢!

标签: machine-learning keras tensorflow2.0


【解决方案1】:

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

但我认为问题在于验证集的 shuffle 标志。

你从这里改变了:

 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'
 )

【讨论】:

  • 是的!如果您需要保留结果(例如当我输出混淆矩阵时),我绝对建议不要改组您的验证集。事实上,无意中打乱了我的验证集是导致 .predict 和 .evaluate 之间差异的原因(我的同事和我有同样的问题,这就是真正的原因)。我很久以前就知道了(但由于某种原因我没有更新它......)。这绝对是造成差异的主要原因。接受它作为答案。谢谢!
猜你喜欢
  • 2019-07-15
  • 1970-01-01
  • 2020-01-11
  • 2017-12-20
  • 1970-01-01
  • 2020-04-15
  • 1970-01-01
  • 1970-01-01
  • 2020-10-06
相关资源
最近更新 更多