如何用Keras在单个epoch中多次调用model.fit进行训练
问题描述
使用Keras训练模型时因数据量过大导致内存不足,希望单个epoch通过多次model.fit调用完成训练,现有代码框架如下:
for epoche in range(nbEpoches): for index_df in range(len(list_of_dataFrames)): dataFrame = load_dataFrame(list_of_dataFrames, index_df) # 仅将当前DF加载到内存 X_train, Y_train, X_test, Y_test = calc_train_arrays(dataFrame) model.fit( X_train, Y_train, validation_data=(X_test, Y_test), # ... 需要配置的参数 batch_size=batch_size, )
其中X_train和X_test是形状为(数千, 35至200, 54+)的numpy数组,必须通过多批次适配GPU显存、动态加载DataFrame适配内存,因此被迫在同一个epoch内多次调用fit。
需求:
- 如何正确配置
model.fit参数实现上述单epoch多fit调用的训练方式? - 使用输出形状为
(batch_size, 35+, 54+)的生成器并指定steps_per_epoch是否可行?
之前的尝试:
- 仅在约2万样本的单个DataFrame上训练,模型泛化性差;
- 每个DataFrame训练一个epoch,模型出现“互相遗忘”的情况。
解决方案
一、单epoch内多次调用model.fit的正确配置
要实现单epoch多fit调用,核心是保留模型优化器状态,让每次fit成为当前全局epoch的一部分,具体配置如下:
核心参数设置
外层循环控制全局epoch,每次内层循环的fit都设置epochs=1,且不重新编译模型(Keras默认会保留优化器的梯度、学习率等状态)。调整后的代码:for epoch in range(nbEpoches): print(f"===== 全局 Epoch {epoch+1}/{nbEpoches} =====") # 遍历所有DataFrame,作为当前全局epoch的训练批次 for index_df in range(len(list_of_dataFrames)): dataFrame = load_dataFrame(list_of_dataFrames, index_df) X_train, Y_train, X_test, Y_test = calc_train_arrays(dataFrame) model.fit( X_train, Y_train, validation_data=(X_test, Y_test), batch_size=batch_size, epochs=1, verbose=1, # 按需设置日志输出级别 shuffle=True # 每个DataFrame内部打乱样本 ) # 全局epoch结束后可统一评估 val_loss, val_acc = model.evaluate(X_total_val, Y_total_val, batch_size=batch_size) print(f"全局 Epoch {epoch+1} 验证损失: {val_loss}, 准确率: {val_acc}")解决“遗忘”问题
之前每个DataFrame单独训练一个epoch导致遗忘,本质是模型在小数据集上过拟合后,权重被下一个数据集的梯度覆盖。现在单epoch遍历所有DataFrame,让模型每个全局epoch都接触全量数据,避免单数据集的权重偏移。验证集优化
建议使用统一的全局验证集,而非每个DataFrame的独立验证集,避免每次fit的验证结果波动过大,更准确反映模型泛化能力。
二、使用生成器+steps_per_epoch的方案(更推荐)
这种方案更贴合Keras设计规范,无需手动循环DataFrame,自动处理动态加载和批次训练:
实现自定义Sequence生成器
继承keras.utils.Sequence(比普通生成器更稳定,支持多进程),动态加载DataFrame并生成批次:from keras.utils import Sequence import numpy as np class DataGenerator(Sequence): def __init__(self, df_paths, batch_size, is_train=True): self.df_paths = df_paths self.batch_size = batch_size self.is_train = is_train # 预建立样本与DataFrame的映射,避免重复加载 self.sample_map = [] for df_idx, path in enumerate(df_paths): # 仅加载DataFrame元数据(如样本数量),不加载完整数据 df_meta = load_df_metadata(path) self.sample_map.extend([(df_idx, i) for i in range(df_meta['num_samples'])]) # 训练集打乱样本顺序 if self.is_train: import random random.shuffle(self.sample_map) def __len__(self): # 计算每个epoch的总步数 return len(self.sample_map) // self.batch_size def __getitem__(self, idx): batch_samples = self.sample_map[idx*self.batch_size : (idx+1)*self.batch_size] current_df = None current_df_idx = -1 X_batch, Y_batch = [], [] for df_idx, sample_idx in batch_samples: if df_idx != current_df_idx: # 释放上一个DataFrame内存,加载当前DataFrame current_df = None current_df = load_dataFrame(self.df_paths[df_idx]) current_df_idx = df_idx # 从DataFrame提取单个样本(根据你的预处理逻辑调整) X, Y = get_single_sample(current_df, sample_idx, self.is_train) X_batch.append(X) Y_batch.append(Y) return np.array(X_batch), np.array(Y_batch)生成器训练代码
# 创建训练/验证生成器 train_gen = DataGenerator(train_df_paths, batch_size=batch_size, is_train=True) val_gen = DataGenerator(val_df_paths, batch_size=batch_size, is_train=False) # 启动训练 model.fit( train_gen, validation_data=val_gen, epochs=nbEpoches, steps_per_epoch=len(train_gen), # Sequence会自动计算,可选省略 use_multiprocessing=True, # 开启多进程加速数据加载 workers=4 )方案优势
- 自动完成全量数据遍历,避免“遗忘”问题;
- 支持多进程加载,提升数据读取效率;
- 内置样本打乱逻辑,每个epoch样本顺序不同,泛化性更好;
- 代码更简洁,无需手动管理
fit调用流程。
三、额外优化建议
- 预处理缓存:将每个DataFrame预处理后的numpy数组保存到磁盘,下次直接读取,减少重复计算;
- 学习率调度:使用
ReduceLROnPlateau回调,根据验证集性能动态调整学习率,缓解过拟合; - 梯度累积:若GPU显存仍不足,可手动累加梯度,每隔N批次更新一次权重(通过自定义训练循环实现)。
内容的提问来源于stack exchange,提问作者vuvu 700
相关产品推荐
相关产品推荐

