同一验证数据集的准确度在上一次epoch和拟合后存在差异



下面的代码给出了一个以

结尾的日志
Epoch 19/20
1/1 [==============================] - 0s 473ms/step - loss: 1.4018 - accuracy: 0.8750 - val_loss: 1.8656 - val_accuracy: 0.8900
Epoch 20/20
1/1 [==============================] - 0s 444ms/step - loss: 0.5904 - accuracy: 0.8750 - val_loss: 2.1255 - val_accuracy: 0.8700
get_dataset: validation
Found 1000 files belonging to 2 classes.
Using 100 files for validation.
4/4 [==============================] - 1s 81ms/step
eval acc: 0.81
我的问题是:

为什么最后一个历元后的val_accuracy(0.87)与拟合后的eval acc(0.81)不同?

在我的代码中,我尝试使用相同的数据集用于拟合期间的每个epoch的验证以及之后的附加验证。

[更新1,2022-07-19]:

  1. 显然,这两种精度计算并没有真正使用相同的数据。我如何调试实际使用的数据?
    [更新3,2022-07-20:我已经将数据跟踪到TensorFlow中。最后我看到的是,在Model.evaluate(在fit)和Model.predictx.filenames是相等的。我没有设法进一步调试,因为很快在quick_execute__inference_test_function_248219的响应。__inference_predict_function_231438在Python之外求值,参数是带有dtype=resource的张量,其内容我看不到。]
  2. 我故意删除了我的类平衡代码,以保持我的例子小。我知道这会使准确性变得不那么有用,但我现在不关心这个。
  3. 注意,get_dataset('validation')只在拟合开始时调用一次,而不是在每个epoch都调用。
  4. 我现在也设置了max_queue_size=0, use_multiprocessing=False, workers=0(如这里所见,通过关于TensorFlow 1的相关SO问题发现),但这并没有使准确性相等。

)代码:

import tensorflow as tf
from sklearn.metrics import accuracy_score
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.preprocessing import image_dataset_from_directory

inputs = tf.keras.Input(shape=(224, 224, 3))
base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False)
base_output = base_model(inputs)
base_model.trainable = False
out = Flatten(name='flat')(base_output)
out = Dense(1, activation='sigmoid')(out)
model = Model(inputs=inputs, outputs=out)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
def get_dataset(subset):
print('get_dataset:', subset)
return image_dataset_from_directory(
'data-nodup-1000',
labels="inferred",
label_mode='binary',
color_mode="rgb",
image_size=(224, 224),
shuffle=True,
seed=1,
validation_split=0.1,
subset=subset,
crop_to_aspect_ratio=False,
)
model.fit(
get_dataset('training'),
steps_per_epoch=1,
epochs=20,
validation_data=get_dataset('validation'),
max_queue_size=0,
use_multiprocessing=False,
workers=0,
)
val_dataset = get_dataset('validation')
true_class = tf.concat([y for x, y in val_dataset], axis=0)
pred = model.predict(val_dataset)
pred_class = pred >= .5
print('eval acc:', accuracy_score(true_class, pred_class))

[更新2,2022-07-19:我还可以使用

用已弃用的ImageDataGenerator重现该行为
from tensorflow.keras.applications.resnet50 import preprocess_input
from keras_preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
preprocessing_function=preprocess_input,
validation_split=0.1,
)
def get_dataset(subset):
print('get_dataset:', subset)
return datagen.flow_from_directory(
'data-nodup-1000',
class_mode='binary',
target_size=(224, 224),
shuffle=True,
seed=1,
subset=subset,
)

true_class = val_dataset.labels

)

[更新4,2022-07-21:请注意,通过设置shuffle=(subset == 'training')取消激活验证数据的洗牌使两个验证精度相等。然而,这不是一个变通方法,因为验证集只由类1组成,因为flow_from_directory不进行分层。)

我环境:

  • 我使用所有最新的库,如tensorflow 2.9.1和sklearn 1.1.1(通过pip-compile -U)。
  • 文件夹data-nodup-1000包含一个子文件夹,包含113个0类文件,一个子文件夹包含887个1类文件。

我现在已经发现,在TensorFlow 2.9.1model.predict使用数据集的第二次迭代,这是洗牌不同于第一次迭代!当我直接调用model.predict(get_dataset('validation'))!

时,它甚至使用了第二次迭代。因此,true_classpred的条目不匹配。

切换到TensorFlow 2.10.0-rc3和它的tf.keras.utils.split_dataset使精度相等。

下面是更新后的代码:

import tensorflow as tf
from sklearn.metrics import accuracy_score
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.preprocessing import image_dataset_from_directory

inputs = tf.keras.Input(shape=(224, 224, 3))
base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False)
base_output = base_model(inputs)
base_model.trainable = False
out = Flatten(name='flat')(base_output)
out = Dense(1, activation='sigmoid')(out)
model = Model(inputs=inputs, outputs=out)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
dataset = image_dataset_from_directory(
'data-synthetic',
labels="inferred",
label_mode='binary',
color_mode="rgb",
image_size=(224, 224),
shuffle=True,
seed=1,
crop_to_aspect_ratio=False,
)
train_dataset, val_dataset = tf.keras.utils.split_dataset(dataset, right_size=0.1)
model.fit(
train_dataset,
steps_per_epoch=1,
epochs=20,
validation_data=val_dataset,
max_queue_size=0,
use_multiprocessing=False,
workers=0,
)
true_class = tf.concat([y for x, y in val_dataset], axis=0)
pred = model.predict(val_dataset)
pred_class = pred >= .5
print('eval acc:', accuracy_score(true_class, pred_class))

正确地产生:

Epoch 19/20
1/1 [==============================] - 0s 438ms/step - loss: 0.4426 - accuracy: 0.9062 - val_loss: 0.4658 - val_accuracy: 0.8800
Epoch 20/20
1/1 [==============================] - 0s 444ms/step - loss: 2.1619 - accuracy: 0.8438 - val_loss: 0.5886 - val_accuracy: 0.8900
4/4 [==============================] - 1s 87ms/step
eval acc: 0.89

你的数据有几点会导致这种情况:

  • 首先,你的数据高度不平衡(8比1的标签比例),这使得模型相当过拟合,CV估计不准确。
  • 其次,在get_dataset函数中,shuffle被设置为True,因此每次调用get_dataset()时,它都会对数据进行洗牌,并且因为(1)验证集非常小,(2)训练/值分割没有在标签上分层,因此由于这种洗牌,验证指标会发生很大变化。

解决建议:

  1. 在拟合模型之前,只对train和val数据集调用一次get_dataset(),并将它们保存为变量。如果你的数据没有顺序,也许可以设置shuffle=False
  2. (可选)如果可能的话,通过数据增强、过采样/欠采样等技术使你的数据集更加平衡

def get_dataset(subset):
return image_dataset_from_directory(
'data-nodup-1000',
labels="inferred",
label_mode='binary',
color_mode="rgb",
image_size=(224, 224),
shuffle=False,
seed=0,
validation_split=0.1,
subset=subset,
crop_to_aspect_ratio=False,
)
train_dataset = get_dataset('training')
val_dataset = get_dataset('validation')
model.fit(
train_dataset,
steps_per_epoch=1,
epochs=20,
validation_data=val_dataset,
)

true_class = tf.concat([y for x, y in val_dataset], axis=0)
pred = model.predict(val_dataset)
pred_class = pred >= .5
print('eval acc:', accuracy_score(true_class, pred_class))

最新更新