Keras构建分块图像训练生成器:超大规模数据集多轮训练异常
问题根因定位
- 训练逻辑设计错误:原代码在
for X,y in monGenerateur循环内调用model.fit,每接收一个批次就启动一次全新的训练流程,自然每次日志都从Epoch 1/10开始,完全违背Keras训练接口的设计逻辑。Keras的model.fit支持接收生成器作为数据源,由fit本身负责控制遍历节奏、计数epoch,不需要用户手动循环遍历生成器。 - 自定义生成器逻辑缺失:原生成器是仅单次遍历DataFrame的有限生成器,没有无限循环结构,一旦跑完全部行就抛出StopIteration信号,fit接收到终止信号就直接结束训练,无法自动进入下一轮epoch。
- 功能逻辑bug:
- 单图读取函数中存在未定义变量
red的笔误,正常运行会触发NameError,实际逻辑应该是对读取到的图像img做resize - 标签提取逻辑
v[3:]和DataFrame实际列索引不匹配,image_path是第0列,后面紧跟OHE标签列,用v[3:]会漏取前两列标签 - 没有实现epoch结束后的洗牌逻辑,原代码仅按原始DataFrame顺序遍历,没有打乱数据顺序的步骤
- 单图读取函数中存在未定义变量
- 数据增强逻辑嵌套层级错误:原代码在每个单独批次外又套了一层
ImageDataGenerator.flow,导致批次数据加载和增强流程割裂,无法实现逐批次在线处理的目标。
修正后的完整实现
1. 基础功能函数修正
先修复单图读取、预处理的原有bug,保持原有功能的同时修正笔误:
import numpy as np import cv2 import matplotlib.pyplot as plt from sklearn.utils import shuffle from tensorflow.keras.preprocessing.image import ImageDataGenerator def open_multilayer_image_aug_hflip(path, SIZE, test=False): fullpath = path if test: img = plt.imread(fullpath) img = cv2.resize(img, SIZE) ni = np.zeros((SIZE[0], SIZE[1], 3), 'uint8') ni[..., 0] = img * 255 else: if fullpath[-3:] == '.png': img = plt.imread(fullpath) # 修复原代码未定义变量red的笔误 img = cv2.resize(img, SIZE) ni = np.zeros((SIZE[0], SIZE[1], 3), 'uint8') ni[..., 0] = img * 255 return ni def preprocess_hflip(size, train_img_names, test=False): x_train = [] for i in train_img_names: x_train.append(open_multilayer_image_aug_hflip(i, size, test)) x_train = np.array(x_train) x_train = x_train.astype('float32') x_train /= 255 return x_train
2. 自定义无限循环生成器实现
核心满足要求:无限输出批次、每轮epoch开始前洗牌、逐批次完成预处理+增强、批次生成后自动清空内存缓存:
# 按需配置训练集在线增强参数 train_augmenter = ImageDataGenerator( horizontal_flip=True, rotation_range=15, zoom_range=0.2 ) def generate_arrays_from_file(df, batchsize, image_size=(128,128), is_test=False): # 无限循环结构,支撑多epoch训练 while True: # 每个epoch开始前对全量数据洗牌 df_shuffled = shuffle(df, random_state=None) inputs = [] targets = [] batchcount = 0 for k, v in df_shuffled.iterrows(): inputs.append(v['image_path']) # 修复原标签提取逻辑,从image_path列后提取所有OHE标签 targets.append(v.iloc[1:].values.astype('float32')) batchcount += 1 # 凑齐完整批次后执行后续流程 if batchcount >= batchsize: # 批次内完成图像读取、resize、归一化 X = preprocess_hflip(image_size, inputs, test=is_test) y = np.array(targets, dtype='float32') # 批次内执行数据增强 if not is_test: X, y = next(train_augmenter.flow(X, y, batch_size=batchsize, shuffle=False)) # 输出批次 yield (X, y) # 清空缓存,避免占用常驻内存 inputs = [] targets = [] batchcount = 0 # 处理数据集尾部不足一个batchsize的残留数据 if len(inputs) > 0: X = preprocess_hflip(image_size, inputs, test=is_test) y = np.array(targets, dtype='float32') if not is_test: X, y = next(train_augmenter.flow(X, y, batch_size=len(inputs), shuffle=False)) yield (X, y)
3. 正确的训练流程调用
直接将生成器传入model.fit,由fit负责完成10轮epoch的遍历控制,不需要手动写循环遍历生成器:
# 初始化训练、验证集生成器 train_generator = generate_arrays_from_file( df=train_df, batchsize=32, # 根据显存调整批次大小 image_size=(128,128), is_test=False ) valid_generator = generate_arrays_from_file( df=test_df, batchsize=32, image_size=(128,128), is_test=True ) # 计算每轮epoch需要的批次量 steps_per_epoch = int(np.ceil(len(train_df) / 32)) validation_steps = int(np.ceil(len(test_df) / 32)) # 启动训练,由fit自动完成10轮epoch、每epoch结束后触发回调 hist = model.fit( train_generator, epochs=10, steps_per_epoch=steps_per_epoch, validation_data=valid_generator, validation_steps=validation_steps, callbacks=[rlr, ckp, es], verbose=2 )
关键约束说明
- 生成器必须保留
while True无限循环结构:Keras的fit依赖生成器持续输出数据,一旦生成器抛出StopIteration就会终止训练,不会自动重启遍历流程。 - 洗牌操作必须嵌入生成器内层:每跑完一轮全量数据后,在进入下一轮遍历前重新打乱DataFrame顺序,不要依赖外部逻辑控制洗牌时机。
- 批次输出后必须清空临时缓存:每次yield批次数据后,立刻清空路径列表、临时图像数组的引用,避免400GB规模的数据集累计占用运行内存。
- 不要在fit外层嵌套生成器遍历循环:Keras的fit会自动按
steps_per_epoch的计数规则完成epoch切换,手动循环会导致每个批次都触发一次独立的fit流程,无法累计训练进度。
内容的提问来源于stack exchange,提问作者Carlos Carvalho
相关产品推荐
相关产品推荐

