You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Keras多标签分类(年龄+性别)数据增强问题求助

多输出人脸年龄回归+性别分类任务中ImageDataGenerator的适配问题

问题背景

基于TensorFlow Keras实现单输入双输出分支网络,输入为图像,同时完成性别分类(二分类)和年龄回归任务,尝试用ImageDataGenerator做数据增强。但当前实现中,生成器误将年龄识别为分类任务(输出97/90类),导致模型训练无有效输出。使用的数据集为UTK人脸数据集,含4000张训练图、1000张验证图,尺寸均为128×128彩色图像。

问题根源

  1. flow_from_dataframe默认将y_col的内容视为分类标签,自动做one-hot编码,而年龄是连续值,需以原始数值形式传入回归分支
  2. 当前代码中合并了年龄和性别为labels列,无法让生成器区分两类任务的标签类型
  3. 模型定义存在重复创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 23:40:36