下面的代码给出了一个以
结尾的日志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)不同?
[更新1,2022-07-19]:
- 显然,这两种精度计算并没有真正使用相同的数据。我如何调试实际使用的数据?
[更新3,2022-07-20:我已经将数据跟踪到TensorFlow中。最后我看到的是,在Model.evaluate
(在fit
)和Model.predict
x.filenames
是相等的。我没有设法进一步调试,因为很快在quick_execute
中__inference_test_function_248219
的响应。__inference_predict_function_231438
在Python之外求值,参数是带有dtype=resource
的张量,其内容我看不到。] 我故意删除了我的类平衡代码,以保持我的例子小。我知道这会使准确性变得不那么有用,但我现在不关心这个。 - 注意,
get_dataset('validation')
只在拟合开始时调用一次,而不是在每个epoch都调用。 - 我现在也设置了
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_class
和pred
的条目不匹配。
切换到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)训练/值分割没有在标签上分层,因此由于这种洗牌,验证指标会发生很大变化。解决建议:
- 在拟合模型之前,只对train和val数据集调用一次
get_dataset()
,并将它们保存为变量。如果你的数据没有顺序,也许可以设置shuffle=False
。 - (可选)如果可能的话,通过数据增强、过采样/欠采样等技术使你的数据集更加平衡
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))