Keras多标签分类(年龄+性别)数据增强问题求助
多输出人脸年龄回归+性别分类任务中ImageDataGenerator的适配问题
问题背景
基于TensorFlow Keras实现单输入双输出分支网络,输入为图像,同时完成性别分类(二分类)和年龄回归任务,尝试用ImageDataGenerator做数据增强。但当前实现中,生成器误将年龄识别为分类任务(输出97/90类),导致模型训练无有效输出。使用的数据集为UTK人脸数据集,含4000张训练图、1000张验证图,尺寸均为128×128彩色图像。
问题根源
flow_from_dataframe默认将y_col的内容视为分类标签,自动做one-hot编码,而年龄是连续值,需以原始数值形式传入回归分支- 当前代码中合并了年龄和性别为
labels列,无法让生成器区分两类任务的标签类型 - 模型定义存在重复创建
Model和compile的冗余代码,会覆盖正确的模型配置
解决方案步骤
1. 调整数据集结构
确保DataFrame中年龄(连续值)和性别(0/1二值)分为单独的列,比如df_train和df_test包含image(图像路径)、age(年龄数值)、gender(0/1)三列,而非合并的labels列。
2. 修改数据生成器配置
使用class_mode='raw'让生成器返回原始标签值,同时指定y_col为两个列名:
from keras.preprocessing.image import ImageDataGenerator datagen = ImageDataGenerator( rescale = 1./255, rotation_range = 40, width_shift_range = 0.2, height_shift_range = 0.2, shear_range = 0.2, zoom_range = 0.2, horizontal_flip = True, fill_mode = 'nearest' ) test_datagen= ImageDataGenerator(rescale=1./255.) train_generator=datagen.flow_from_dataframe( dataframe = df_train, x_col="image", y_col=["age", "gender"], # 分开指定年龄和性别列 batch_size=32, seed=42, shuffle=True, target_size=(128, 128), class_mode='raw' # 关键:返回原始数值,不做one-hot编码 ) valid_generator = test_datagen.flow_from_dataframe( dataframe = df_test, x_col = "image", y_col = ["age", "gender"], batch_size = 32, seed = 42, shuffle = True, target_size=(128, 128), class_mode='raw' )
3. 修正生成器Wrapper函数
此时batch_y是形状为(batch_size, 2)的数组,第一列是年龄,第二列是性别,调整Wrapper返回对应分支的标签:
def generator_wrapper(generator): for batch_x, batch_y in generator: # 对应模型输出顺序:gender_out在前,age_out在后 yield (batch_x, [batch_y[:, 1], batch_y[:, 0]])
4. 修正模型定义冗余代码
删除重复的Model创建和compile语句,保留正确的模型构建逻辑:
from keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, Dropout from keras.models import Model input_shape = (128, 128, 3) inputs = Input(input_shape) # 卷积特征提取层 conv_1 = Conv2D(32, kernel_size=(3, 3), activation='relu')(inputs) maxp_1 = MaxPooling2D(pool_size=(2, 2))(conv_1) conv_2 = Conv2D(64, kernel_size=(3, 3), activation='relu')(maxp_1) maxp_2 = MaxPooling2D(pool_size=(2, 2))(conv_2) conv_3 = Conv2D(128, kernel_size=(3, 3), activation='relu')(maxp_2) maxp_3 = MaxPooling2D(pool_size=(2, 2))(conv_3) conv_4 = Conv2D(256, kernel_size=(3, 3), activation='relu')(maxp_3) maxp_4 = MaxPooling2D(pool_size=(2, 2))(conv_4) flatten = Flatten()(maxp_4) # 性别分类分支 gender_dense = Dense(256, activation='relu')(flatten) gender_dropout = Dropout(0.3)(gender_dense) gender_out = Dense(1, activation='sigmoid', name='gender_out')(gender_dropout) # 年龄回归分支 age_dense = Dense(256, activation='relu')(flatten) age_dropout = Dropout(0.3)(age_dense) age_out = Dense(1, activation='relu', name='age_out')(age_dropout) # 定义并编译模型 model = Model(inputs=inputs, outputs=[gender_out, age_out]) model.compile( optimizer='adam', loss={'gender_out': 'binary_crossentropy', 'age_out': 'mae'}, metrics={'gender_out': 'accuracy', 'age_out': 'mae'} )
5. 执行训练
STEP_SIZE_TRAIN=train_generator.n//train_generator.batch_size STEP_SIZE_VALID=valid_generator.n//valid_generator.batch_size history = model.fit( generator_wrapper(train_generator), steps_per_epoch=STEP_SIZE_TRAIN, validation_data=generator_wrapper(valid_generator), validation_steps=STEP_SIZE_VALID, epochs=10, # 可根据需求调整训练轮数 verbose=2 )
关键说明
class_mode='raw'是核心,确保生成器返回原始数值标签,而非one-hot编码的分类矩阵- 模型输出顺序要和Wrapper返回的标签顺序严格对应,否则损失函数会匹配错误
- 年龄回归分支的激活函数用
relu合理(年龄非负),损失函数用mae或mse均适合回归任务
内容的提问来源于stack exchange,提问作者mountainwater
相关产品推荐
相关产品推荐

