• 正文
  • 相关推荐
申请入驻 产业图谱

深度学习实战-基于DenseNet121的人脸年龄检测模型

05/25 14:34
425
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

1.项目背景

在数字化浪潮深耕的今天,人脸识别技术早已超越了单纯的“刷脸支付”或“身份比对”,开始向更深层次的属性分析领域进化。其中,人脸年龄检测不仅是计算机视觉中的一个经典课题,更是精准营销、公共安全以及人机交互系统中的核心环节。然而,受限于个体遗传差异、生活环境及面部表情的复杂性,人类的衰老过程呈现出极高的非线性与个体差异化。如何在千变万化的光影与构图中提取出那些真正能够代表“岁月痕迹”的微观特征,如皮肤质感的演变、眼角细纹的分布以及面部骨骼轮廓的微妙平移,成为了深度学习模型必须攻克的难关。

本项目旨在利用 DenseNet121(密集连接网络) 架构,构建一个稳健的自动化年龄区间检测模型。之所以选择 DenseNet,正是看中了其独特的特征重用机制——通过将每一层与之后的所有层直接相连,网络能够最大程度地保留从底层位置信息到高层语义特征的完整流转,这对于捕捉人脸这种精细化生物特征至关重要。实验所依托的数据集涵盖了从 18 岁到 60 岁以上不同族裔、性别的上万张人脸样本,通过标准化的像素预处理与迁移学习策略,我们试图打破传统算法在复杂环境下识别率不高的瓶颈。这不仅是一次模型参数的调优过程,更是一场关于如何让机器“读懂”时光、在像素经纬间精准解构人类生命周期的技术探索。

2.数据集介绍

本实验数据集来源于Kaggle,该数据集包含来自不同年龄段人群的图像,专门针对年龄预测人脸识别任务进行精心挑选。数据集涵盖了多样化的人口统计特征、种族和性别

数据集中的人群年龄组:18-20岁、21-30岁、31-40岁、41-50岁和51-60岁

3.技术工具

Python版本:3.9

代码编辑器:jupyter notebook

4.实验过程

4.1导入数据

在启动人脸识别流水线之前,数据的高效获取与路径映射是第一步。我们利用 kagglehub 直接从云端调取结构化的年龄识别数据集。由于人脸样本按年龄段(Ranges)分布在不同的物理文件夹中,我们通过嵌套循环遍历整个 Dataset 根目录,将每一张人脸图像的相对路径与其对应的年龄标签进行配对。这种手动构建 DataFrame 的方式虽然基础,但却能让我们对数据的底层分布有最直观的掌控,为后续利用 ImageDataGenerator 进行大规模并行加载奠定清晰的索引基础。

# --- 导入自动化数据集下载工具与系统库 ---import kagglehubimport osimport pandas as pd# --- 从 Kaggle 自动下载最新版人脸年龄数据集 ---# 该数据集按年龄区间分类,包含数千张经过预处理的人脸样本dataset_path = kagglehub.dataset_download('rashikrahmanpritom/age-recognition-dataset')# 定位到解压后的数据集核心目录directory = os.path.join(dataset_path, 'Dataset')# 初始化列表,用于存储图像路径及其对应的年龄区间标签images = []ranges = []# --- 递归遍历文件系统,构建路径映射 ---try:    # 遍历 Dataset 下的每一个子文件夹(即年龄区间,如 6-20, 25-30 等)    for foldr in os.listdir(directory):        # 遍历该年龄区间内的所有图像文件        for filee in os.listdir(os.path.join(directory, foldr)):            # 记录相对路径:便于后续配合目录前缀读取            images.append(os.path.join(foldr, filee))            # 记录文件夹名称作为分类标签            ranges.append(foldr)except Exception as e:    # 捕获潜在的 IO 异常,确保路径解析逻辑的健壮性    print(f'数据加载出错: {e}')# --- 封装为 Pandas 数据表,方便后续切分与分析 ---all_df = pd.DataFrame({    'Images': images,    'Ranges': ranges})# 打印数据表摘要,检查映射关系是否正确print(all_df.head())

4.2数据可视化

我们首先利用 Seaborn 绘制了各年龄区间的样本数量柱状图。这一步并非简单的视觉展示,而是为了排查是否存在严重的“数据长尾”现象。在人脸年龄识别中,青年组的样本往往远多于老年或幼年组,如果分布极度不均,模型可能会倾向于将所有模糊样本都预测为“多数类”。通过这张直观的统计图,我们可以决定是否需要在后续步骤中引入类别权重(Class Weights)或过采样策略,以确保 DenseNet121 能够公平地学习到每一个生命阶段的视觉细节。

# --- 1. 年龄区间样本分布统计 ---import seaborn as snsimport matplotlib.pyplot as plt# 配置清爽的冷色调调色板colors = ['skyblue', 'cyan', 'blue', 'magenta']# 绘制各年龄段的样本计数图plt.figure(figsize=(10, 6))sns.countplot(x='Ranges', data=all_df, palette=colors, hue='Ranges', legend=False)plt.title('各年龄区间样本数量统计 (Count of Age Ranges)')plt.xlabel('年龄区间 (Range)')plt.ylabel('图像总数 (Number of Images)')plt.grid(axis='y', linestyle='--', alpha=0.7) # 增加横向网格线便于观察plt.show()

随后,我们利用 OpenCV 从每个年龄区间中随机抽取一张图像进行横向对比展示。通过这种“全家福”式的排列,我们可以观察到不同年龄段在面部特征上的显著差异:从幼年期的圆润轮廓,到青年期的皮肤质感,再到中老年期逐渐深刻的纹理。这种视觉校验能帮我们确认:数据集中的图像是否经过了良好的对齐处理?光照条件是否统一?这些因素都将直接影响 DenseNet121 内部密集连接层对细微特征的提取效率。

# --- 2. 跨年龄段样本可视化抽检 ---import cv2plt.figure(figsize=(15, 6)) # 拓宽画布以容纳所有年龄段# 遍历所有唯一的年龄标签,每个类别随机抽取一个样本for i, label in enumerate(all_df['Ranges'].unique()):    # 随机采样并获取该样本的完整物理路径    sample = all_df[all_df['Ranges'] == label].sample(1).iloc[0]    img_path = os.path.join(directory, sample['Images'])    # 读取并进行颜色空间转换 (OpenCV 默认 BGR -> RGB)    img = cv2.imread(img_path)    if img is not None:        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)        # 按类别顺序排列子图        plt.subplot(1, len(all_df['Ranges'].unique()), i + 1)        plt.imshow(img)        plt.title(label)        plt.axis('off') # 隐藏坐标轴,聚焦面部特征plt.tight_layout()plt.show()

4.3特征工程

首先,我们利用 train_test_split 将数据集按 8:2 的比例划分为训练集与测试集。在医学或生理特征识别中,维持标签比例的平衡至关重要,因此我们显式设置了 stratify 参数。这能确保每一个年龄区间在训练和测试中的分布保持一致,避免因随机切分导致某些罕见年龄段(如高龄组)在测试集中缺失,从而保证了评估结果的公平性。

为了应对现实场景中人脸识别的复杂性,我们在训练端引入了 ImageDataGenerator。通过随机旋转(20°)、缩放(20%)以及水平翻转,我们模拟了不同拍摄角度和距离下的人脸状态,这种“在线式”的数据增强能让模型在每一轮迭代中都见到略有差异的样本,从而有效抑制过拟合。关键的一点是,我们直接调用了 DenseNet 专属的 preprocess_input 函数,它能将像素值归一化到与 ImageNet 预训练权重一致的数值区间,这对于利用迁移学习加速收敛起到了决定性作用。

from sklearn.model_selection import train_test_splitfrom tensorflow.keras.preprocessing.image import ImageDataGeneratorfrom tensorflow.keras.applications.densenet import preprocess_input# --- 1. 科学切分数据集 ---# 采用分层抽样 (stratify),确保训练集和测试集中的年龄分布完全同步train_df, test_df = train_test_split(    all_df,     test_size=0.2,     random_state=42,     stratify=all_df['Ranges'])# --- 2. 配置训练集数据增强 ---# 模拟面部倾斜、远近变化及镜像翻转,增强模型对姿态的鲁棒性trainimgen = ImageDataGenerator(    rotation_range=20,          # 随机旋转角度    zoom_range=0.2,              # 随机缩放比例    horizontal_flip=True,       # 水平翻转    shear_range=0.2,            # 错切变换    preprocessing_function=preprocess_input # DenseNet 专用归一化)# 构建训练流:将 DataFrame 映射为并行的图像批处理流train_data = trainimgen.flow_from_dataframe(    dataframe=train_df,    directory=directory,    x_col='Images',    y_col='Ranges',    target_size=(224, 224),     # 匹配 DenseNet 标准输入尺寸    color_mode='rgb',    class_mode='categorical',   # 针对多分类任务生成 One-hot 编码    batch_size=16)# --- 3. 配置测试集预处理 ---# 测试阶段仅进行归一化,不进行随机变换,以保证预测结果的可重复性testimgen = ImageDataGenerator(preprocessing_function=preprocess_input)test_data = testimgen.flow_from_dataframe(    dataframe=test_df,    directory=directory,    x_col='Images',    y_col='Ranges',    target_size=(224, 224),    color_mode='rgb',    class_mode='categorical',    batch_size=16,    shuffle=False              # 关闭打乱,确保预测顺序与标签完全对齐)

4.4构建模型

我们加载了在 ImageNet 上预训练的 DenseNet121 作为特征提取基座。由于前 100 层主要负责识别通用的点、线、面等基础视觉特征,我们通过 trainable = False 将其权重锁定,这样既能利用预训练模型的强大泛化能力,又能显著降低训练初期的计算开销。而随后的深层网络则保持开启,以便模型能针对人脸衰老特有的生物学标志进行“深度调优”。在基座之上,我们放弃了传统的全连接铺平方式,转而采用 GlobalAveragePooling2D 来降维,这能有效减少参数量并抑制过拟合。随后,我们构建了一个由 256、128、64 个神经元组成的级联全连接层,每一层都配备了 Dropout (0.2) 随机失活机制。

这种“漏斗式”的架构设计能够将复杂的卷积特征逐步压缩、提炼,最终映射到 4 个年龄区间的概率分布上。同时,我们引入了 EarlyStopping 机制,确保在验证集损失不再下降时自动“锁死”最佳权重,防止模型在训练集上出现过度拟合。

import tensorflow.keras.applications as apfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropoutfrom tensorflow.keras.models import Sequentialfrom tensorflow.keras.optimizers import Adamimport tensorflow.keras.callbacks as cb# --- 1. 加载预训练 DenseNet121 基座 ---# 不包含原有的 1000 类分类头,输入尺寸统一为 224x224base_model = ap.DenseNet121(weights='imagenet', include_top=False, input_shape=(224,224,3))# 策略性冻结:前 100 层锁定权重,保留基础特征提取能力for layer in base_model.layers[:100]:    layer.trainable = False# --- 2. 搭建自定义分类网络 ---Model = Sequential([    base_model,                 # 密集连接特征提取层    GlobalAveragePooling2D(),    # 全局平均池化,降低模型复杂度        # 多层级稠密网络,逐步提取高层语义特征    Dense(256, activation='relu'),    Dropout(0.2),               # 随机失活,增强泛化能力       Dense(128, activation='relu'),    Dropout(0.2),        Dense(64, activation='relu'),    Dropout(0.2),        # 输出层:针对 4 个年龄区间进行 Softmax 概率预测    Dense(4, activation='softmax')])# --- 3. 配置回调函数与编译参数 ---# 早停机制:如果 10 轮内验证集 Loss 没有改善,则停止训练并恢复最优参数callbacky = cb.EarlyStopping(    monitor='val_loss',     patience=10,     restore_best_weights=True)# 编译模型:采用 Adam 优化器和多分类交叉熵损失Model.compile(    optimizer=Adam(learning_rate=0.001),     loss='categorical_crossentropy',     metrics=['accuracy'])# 打印模型拓扑结构,确认参数规模Model.summary()

4.5训练模型

由于我们设置了 EarlyStopping 回调函数,虽然预设的上限是 50 个 Epoch,但系统会实时监控验证集损失(val_loss)。如果模型在连续 10 轮内无法进一步挖掘出更深层的年龄特征,训练将提前终止,并自动回滚到表现最稳健的那一版权重。这种策略在处理人脸图像时尤为有效,因为它能防止模型过分“迷信”训练集中的特定人脸(过拟合),从而确保其在面对陌生面孔时依然具备高精度的年龄区间判断力。

# --- 启动人脸年龄识别模型训练 ---# 执行拟合:通过数据生成器并行输送增强后的图像流# 设定最大轮次为 50,实际训练时长将由 EarlyStopping 动态调控history = Model.fit(    train_data,                 # 训练集数据流(带动态增强)    epochs=50,                  # 最大迭代次数    validation_data=test_data,  # 验证集数据流(仅标准化)    callbacks=[callbacky]       # 挂载早停机制,守护最优权重)

4.6模型评估

我们首先调用 Model.evaluate 对测试集进行全量扫描。这一步会输出模型在未见数据上的最终准确率(Accuracy)和损失值(Loss)。对于年龄检测而言,由于 20-30 岁与 30-40 岁之间的视觉界限本身就存在模糊地带,一个稳健的模型应当在保证高准确率的同时,拥有较低的泛化误差。随后,我们利用 predict 生成预测概率,并通过 np.argmax 锁定模型认为最可能的年龄区间。通过绘制“中国红”风格的混淆矩阵热力图,我们可以清晰地洞察模型的识别逻辑:对角线上的数值代表了预测正确的样本数,而偏离对角线的热点则暴露了模型最容易混淆的年龄段。例如,模型是否经常将保养较好的“30-40岁”误判为“20-30岁”?这种误判规律能为我们后续调整数据增强策略或引入更精细的特征注意力机制提供直接依据。

# --- 1. 执行测试集基准评估 ---# 返回测试集上的 Loss 与 Accuracy 指标test_loss, test_acc = Model.evaluate(test_data)print(f"测试集最终准确率: {test_acc:.4f}")# --- 2. 构建混淆矩阵分析 ---from sklearn.metrics import confusion_matriximport numpy as npimport matplotlib.pyplot as pltimport seaborn as sns# 获取测试集全量预测概率,并转化为类别索引preds = Model.predict(test_data)preds_classes = np.argmax(preds, axis=1)#获取测试集的真实类别标签true_classes = test_data.classes# 生成混淆矩阵cm = confusion_matrix(true_classes, preds_classes)# 绘制热力图展示分类性能分布plt.figure(figsize=(8, 6))sns.heatmap(cm, annot=True, fmt='d', cmap='Reds', # 使用红色调,突出误判重灾区            xticklabels=list(test_data.class_indices.keys()),            yticklabels=list(test_data.class_indices.keys()))plt.title('人脸年龄识别混淆矩阵 (Age Detection Confusion Matrix)')plt.xlabel('模型预测年龄区间 (Predicted)')plt.ylabel('真实年龄区间 (Truth)')plt.show()

4.7模型预测

我们从测试集中随机抽取 10 个从未参与过训练的样本,通过 preds_classes 获取模型对应的年龄区间索引。在展示界面中,我们通过色彩逻辑(绿色代表一致,红色代表偏离)来增强视觉反馈。通过这组随机抽样,我们可以观察到一个有趣的现象:当模型判定正确时,通常对应的都是面部光影清晰、特征明显的标准样本;而当出现红色标记时,往往是因为样本本身处于两个年龄段的交界模糊区,或者是受到发型、饰品等非生理特征的干扰。这种点对点的预测演示,将冷冰冰的概率值转化为了可感知、可解释的医疗影像辅助分析逻辑。

# --- 获取类别标签映射表 ---class_labels = list(test_data.class_indices.keys())# 从原始测试集 Dataframe 中随机抽取 10 个样本进行“盲测”sample_df = test_df.sample(10)plt.figure(figsize=(20, 10))for i, (idx, row) in enumerate(sample_df.iterrows()):    # 拼接完整路径并读取图像    img_path = os.path.join(directory, row['Images'])    img = cv2.imread(img_path)    if img is None: continue    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)    # 获取该样本的真实年龄标签名称    true_label_name = row['Ranges']    # 精确锁定该样本在 preds_classes (预测列表) 中的对应索引位置    original_index = test_df.index.get_loc(idx)    pred_class_index = preds_classes[original_index]    pred_label_name = class_labels[pred_class_index]    # 构建 2x5 的展示网格    plt.subplot(2, 5, i + 1)    plt.imshow(img)    plt.axis('off')    # 动态配色:预测准确显示绿色,误判显示红色    color = "green" if true_label_name == pred_label_name else "red"    plt.title(f"真实年龄: {true_label_name}n预测年龄: {pred_label_name}",               color=color, fontsize=12)plt.suptitle("人脸年龄检测模型 - 测试集随机抽样预测展示", fontsize=18)plt.tight_layout()plt.show()

5.总结

本实验依托于 Kaggle 提供的多维度人脸年龄数据集,通过 DenseNet121 深度密集连接网络,成功构建了一个能够跨越性别、种族及人口统计学特征的年龄区间检测模型。该数据集涵盖了从 18 岁到 60 岁以上多个关键生命阶段的视觉样本,为模型理解面部衰老的非线性演变提供了坚实的底层数据支撑。

在实验性能方面,模型展现出了良好的特征提取与分类稳定性,最终在验证集上取得了 0.7863 的准确率,损失值稳定在 0.54 左右。通过对混淆矩阵的深入复盘可以发现,模型在 60-98 岁高龄组及 25-30 岁青年组表现出了极强的判别力,分别正确识别了 492 及 401 个样本。尽管在 42-48 岁这类过渡年龄段存在一定的特征混淆,但整体预测逻辑高度符合面部生物学演变的连续性规律。

实验证明,DenseNet121 的特征重用机制能有效捕捉到皮肤纹理与面部轮廓的细微差异,为实现复杂环境下的自动化人脸属性分析提供了可靠且高效的技术范式。

相关推荐