我做了什么:
- 我使用Keras的
fit_generator()
训练一个预训练的CNN。每轮训练后会生成评估指标(loss, acc, val_loss, val_acc
)。在模型训练完成后,我使用evaluate_generator()
生成评估指标(loss, acc
)。
我期待的结果:
- 如果我训练模型一轮,我希望
fit_generator()
和evaluate_generator()
获得的指标是相同的。它们都应该基于整个数据集来计算指标。
我观察到的结果:
我不理解的地方:
- 为什么
fit_generator()
的准确率与evaluate_generator()
的准确率不同
我的代码:
def generate_data(path, imagesize, nBatches): datagen = ImageDataGenerator(rescale=1./255) generator = datagen.flow_from_directory\ (directory=path, # 目标目录的路径 target_size=(imagesize,imagesize), # 找到的所有图像将被调整到的尺寸 color_mode='rgb', # 图像将被转换为具有1、3或4个通道的颜色模式 classes=None, # 类别子目录的可选列表 class_mode='categorical', # 返回的标签数组类型 batch_size=nBatches, # 数据批次的大小 shuffle=True) # 是否打乱数据 return generator
[…]
def train_model(model, nBatches, nEpochs, trainGenerator, valGenerator, resultPath): history = model.fit_generator(generator=trainGenerator, steps_per_epoch=trainGenerator.samples//nBatches, # 总步骤数(样本批次) epochs=nEpochs, # 训练模型的轮数 verbose=2, # 详细模式。0 = 静默,1 = 进度条,2 = 每轮一行 callbacks=None, # 训练过程中应用的keras.callbacks.Callback实例 validation_data=valGenerator, # 在每轮结束时评估损失和模型指标的生成器或元组 validation_steps= valGenerator.samples//nBatches, # 在每轮结束前从验证数据生成器中产出的步骤数(样本批次) class_weight=None, # 可选字典,将类别索引(整数)映射到权重(浮点数)值,用于加权损失函数 max_queue_size=10, # 生成器队列的最大大小 workers=32, # 使用基于进程的线程时启动的最大进程数 use_multiprocessing=True, # 是否使用基于进程的线程 shuffle=False, # 是否在每轮开始时打乱批次的顺序 initial_epoch=0) # 开始训练的轮数 print("%s: 模型已训练。" % datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) # 保存模型 modelPath = os.path.join(resultPath, datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + '_modelArchitecture.h5') weightsPath = os.path.join(resultPath, datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + '_modelWeights.h5') model.save(modelPath) model.save_weights(weightsPath) print("%s: 模型已保存。" % datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) return history, model
[…]
def evaluate_model(model, generator): score = model.evaluate_generator(generator=generator, # 生成元组的生成器 steps= generator.samples//nBatches) # 在停止前从生成器中产出的步骤数(样本批次) print("%s: 模型已评估:" "\n\t\t\t\t\t\t 损失:%.3f" "\n\t\t\t\t\t\t 准确率:%.3f" % (datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), score[0], score[1]))
[…]
def main(): # 创建模型 modelUntrained = create_model(imagesize, nBands, nClasses) # 准备训练和验证数据 trainGenerator = generate_data(imagePathTraining, imagesize, nBatches) valGenerator = generate_data(imagePathValidation, imagesize, nBatches) # 训练并保存模型 history, modelTrained = train_model(modelUntrained, nBatches, nEpochs, trainGenerator, valGenerator, resultPath) # 在验证数据上评估 print("%s: 模型评估(valX, valY):" % datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) evaluate_model(modelTrained, valGenerator) # 在训练数据上评估 print("%s: 模型评估(trainX, trainY):" % datetime.now().strftime('%Y-%m-%d_%H-%M-%S')) evaluate_model(modelTrained, trainGenerator)
更新
我找到了一些报告此问题的网站:
- Keras的批量归一化层出现问题
- Keras模型中预训练卷积基的损失函数表现异常
- model.evaluate() 在训练数据上的损失与训练过程中的损失不同
- 历史记录和评估之间的准确率不同
- ResNet:训练期间100%的准确率,但使用相同数据的预测准确率为33%
我尝试了一些建议的解决方案,但到目前为止没有成功。acc
和loss
在fit_generator()
和evaluate_generator()
中仍然不同,即使使用相同的生成器生成的相同数据进行训练和验证。我尝试了以下方法:
- 在整个脚本中或在向预训练层添加新层之前静态设置学习阶段
K.set_learning_phase(0) # 测试 K.set_learning_phase(1) # 训练
- 解冻预训练模型中的所有批量归一化层
for i in range(len(model.layers)): if str.startswith(model.layers[i].name, 'bn'): model.layers[i].trainable=True
- 不添加未经训练的dropout或批量归一化层
# 创建预训练基础模型 basemodel = ResNet50(include_top=False, # 排除原始模型中的最终池化和全连接层 weights='imagenet', # 在ImageNet上进行预训练 input_tensor=None, # 作为模型图像输入的可选张量 input_shape=(imagesize, # 形状元组 imagesize, nBands), pooling=None, # 模型的输出将是最后一个卷积层的4D张量输出 classes=nClasses) # 用于对图像进行分类的类别数量 # 创建新的未经训练的层 x = basemodel.output x = GlobalAveragePooling2D()(x) # 全局空间平均池化层 x = Dense(1024, activation='relu')(x) # 全连接层 y = Dense(nClasses, activation='softmax')(x) # 逻辑层确保概率总和为1 # 创建结合预训练基础模型和新未经训练层的模型 model = Model(inputs=basemodel.input, outputs=y) # 冻结预训练层的权重 for layer in basemodel.layers: layer.trainable = False # 定义学习优化器 learningRate = 0.01 optimizerSGD = optimizers.SGD(lr=learningRate, # 学习率。 momentum=0.9, # 参数,加速SGD在相关方向上的速度并抑制振荡 decay=learningRate/nEpochs, # 每轮更新的学习率衰减 nesterov=True) # 是否应用Nesterov动量 # 编译模型 model.compile(optimizer=optimizerSGD, # 随机梯度下降优化器 loss='categorical_crossentropy', # 目标函数 metrics=['accuracy'], # 模型在训练和测试期间评估的指标 loss_weights=None, # 不同模型输出的损失贡献的标量系数 sample_weight_mode=None, # 样本加权 weighted_metrics=None, # 在训练和测试期间通过sample_weight或class_weight加权评估的指标 target_tensors=None) # 模型的目标张量,将在训练期间提供目标数据
- 使用不同的预训练CNN作为基础模型(VGG19, InceptionV3, InceptionResNetV2, Xception)
from keras.applications.vgg19 import VGG19 basemodel = VGG19(include_top=False, # 排除原始模型中的最终池化和全连接层 weights='imagenet', # 在ImageNet上进行预训练 input_tensor=None, # 作为模型图像输入的可选张量 input_shape=(imagesize, # 形状元组 imagesize, nBands), pooling=None, # 模型的输出将是最后一个卷积层的4D张量输出 classes=nClasses) # 用于对图像进行分类的类别数量
请告诉我是否有我遗漏的其他解决方案。
回答:
我现在设法获得了相同的评估指标。我做了以下更改:
- 如@Anakin所建议,在
flow_from_directory()
中设置seed
def generate_data(path, imagesize, nBatches): datagen = ImageDataGenerator(rescale=1./255) generator = datagen.flow_from_directory(directory=path, # 目标目录的路径 target_size=(imagesize,imagesize), # 找到的所有图像将被调整到的尺寸 color_mode='rgb', # 图像将被转换为具有1、3或4个通道的颜色模式 classes=None, # 类别子目录的可选列表 class_mode='categorical', # 返回的标签数组类型 batch_size=nBatches, # 数据批次的大小 shuffle=True, # 是否打乱数据 seed=42) # 打乱和变换的随机种子 return generator