基于Xception自定义模型的5折交叉验证各数据拆分MAE获取及结果验证
问题分析与解答
首先直接给你答案:当前输出并不是5个数据拆分对应的平均绝对误差(MAE),而是每个拆分测试子集里单个样本的绝对误差组成的数组,而且你的交叉验证逻辑还存在几个关键问题,导致结果完全不符合预期。
核心问题梳理
1. 错误地仅用单个batch数据做交叉验证
你代码里的X_train, Y_train = next(train_gen)只获取了数据生成器的一个batch数据,而非整个训练集。KFold拆分的只是这一小批样本,完全失去了交叉验证“遍历全数据集拆分评估”的意义,得到的结果没有任何统计价值。
2. 混淆了MAE函数的行为
如果你的mean_absolute_error调用的是tf.keras.losses.mean_absolute_error,它会返回每个样本的绝对误差(形状为(样本数,)的张量);而如果是sklearn.metrics.mean_absolute_error,默认会返回整个测试集的平均误差(标量)。你当前的输出就是前者的结果,不是该拆分的平均MAE。
3. 交叉验证的验证集逻辑错误
循环里你用validation_data=(X_valid, y_valid),这是外部独立验证集,而非KFold拆分出的对应测试子集,违背了“每个拆分用自身测试集评估”的交叉验证原则。
正确实现5折交叉验证的步骤
针对Keras图像模型,正确做法是对整个数据集的索引拆分,为每个fold创建专属的训练/验证生成器,避免一次性加载全量数据(防止OOM)。
修正后的代码示例
import numpy as np from sklearn.model_selection import KFold from sklearn.metrics import mean_absolute_error import tensorflow as tf from tensorflow.keras.applications import Xception from tensorflow.keras.models import Sequential from tensorflow.keras.layers import GlobalMaxPooling2D, Flatten, Dense from tensorflow.keras.optimizers import Adam from tensorflow.keras.preprocessing.image import ImageDataGenerator # 启用GPU内存增长(缓解OOM) physical_devices = tf.config.list_physical_devices('GPU') if physical_devices: tf.config.experimental.set_memory_growth(physical_devices[0], True) print("Num GPUs Available: ", len(physical_devices)) # 全局参数 IMG_SIZE = (224, 224) # 根据你的任务需求调整 boneage_div = 1.0 # 对应你数据预处理的zscore转换系数 batch_size = 32 # 准备训练集的完整索引(假设train_df是包含所有训练样本的DataFrame) train_indices = np.arange(len(train_df)) n_splits = 5 kf = KFold(n_splits=n_splits, shuffle=True, random_state=42) cv_mae_list = [] for fold, (train_idx, val_idx) in enumerate(kf.split(train_indices)): print(f"===== Fold {fold+1}/{n_splits} =====") # 为当前拆分创建独立的训练/验证DataFrame fold_train_df = train_df.iloc[train_idx].reset_index(drop=True) fold_val_df = train_df.iloc[val_idx].reset_index(drop=True) # 初始化数据生成器(可添加数据增强逻辑) core_idg = ImageDataGenerator(rescale=1./255) train_gen = core_idg.flow_from_dataframe( fold_train_df, path_col='path', y_col='boneage_zscore', target_size=IMG_SIZE, color_mode='rgb', batch_size=batch_size, shuffle=True, class_mode='raw' # 回归任务需指定为raw ) val_gen = core_idg.flow_from_dataframe( fold_val_df, path_col='path', y_col='boneage_zscore', target_size=IMG_SIZE, color_mode='rgb', batch_size=batch_size, shuffle=False, # 验证集无需打乱,方便后续匹配标签 class_mode='raw' ) # 每个fold重新初始化模型(避免之前训练的权重干扰) base_model = Xception(input_shape=(*IMG_SIZE, 3), include_top=False, weights='imagenet') base_model.trainable = True # 如需分层微调,可后续修改 model = Sequential([ base_model, GlobalMaxPooling2D(), Flatten(), Dense(16, activation='relu'), Dense(1, activation='linear') ]) # 自定义月份MAE指标 def mae_months(in_gt, in_pred): return tf.keras.metrics.mean_absolute_error(boneage_div * in_gt, boneage_div * in_pred) # 编译模型 adam = Adam(learning_rate=0.0005) model.compile(loss='mse', optimizer=adam, metrics=[mae_months]) # 训练模型 history = model.fit( train_gen, validation_data=val_gen, epochs=10, # 根据你的任务需求调整epochs verbose=1 ) # 在验证集上预测并计算MAE val_preds = model.predict(val_gen, verbose=1) val_true = fold_val_df['boneage_zscore'].values # 计算当前fold的平均MAE(转换为月份) fold_mae = mean_absolute_error(boneage_div * val_true, boneage_div * val_preds) cv_mae_list.append(fold_mae) print(f"Fold {fold+1} MAE (months): {fold_mae:.2f}") # 输出5折交叉验证的最终结果 mean_cv_mae = np.mean(cv_mae_list) std_cv_mae = np.std(cv_mae_list) print(f"\n5-Fold Cross Validation Results:") print(f"Mean MAE (months): {mean_cv_mae:.2f} ± {std_cv_mae:.2f}")
关键说明
- 按索引拆分数据集:通过拆分DataFrame索引为每个fold创建独立子集,确保交叉验证覆盖全量训练数据。
- 每个fold重新初始化模型:避免模型在之前fold的训练权重基础上继续训练,保证每个fold的结果独立可靠。
- 正确计算平均MAE:使用sklearn的
mean_absolute_error计算整个验证集的平均误差,并转换为月份单位,得到每个fold的MAE值。 - 缓解OOM问题:用生成器分批加载数据,配合GPU内存增长设置,有效避免内存溢出。
内容的提问来源于stack exchange,提问作者Bathtub
相关产品推荐
相关产品推荐

