毕业论文中有一部分需要对人脸进行识别,识别出人脸的情绪,所以经过思考采用深度模型来处理,具体采用EfficientNetV2-M预训练模型以及许多优化方案,让识别成功率接近70%。实验基于 FER-2013数据集(7 类情绪、训练 28,709、public test 3,589),训练硬件为 Colab 上的 A100,后续在 MacBook Pro 用于情绪分析。
一、问题背景与目标
任务说明
人脸表情识别(Facial Expression Recognition, FER)属于计算机视觉中的细粒度分类任务,其目标是根据人脸图像判断所表达的情绪类型(如愤怒、厌恶、恐惧、开心、悲伤、惊讶与中性)。与通用图像分类不同,表情识别更依赖局部、细微的面部变化:眼角、眉间、嘴角的微小位移或皱褶往往是判别不同情绪的关键。FER-2013 是学术与工程界常用的基准数据集之一,原始图像为 48×48 灰度人脸,包含 7 类情绪,训练集 28,709 张,public test 3,589 张。该数据集的特点带来几个挑战:
-
低分辨率导致信息稀疏:原始 48×48 图像难以保留能区别细微表情的纹理特征,深度模型难以直接从低分辨率中学到稳定判别特征。
-
类别不平衡:某些情绪(如 Disgust、Fear)样本较少,训练过程中容易被多数类主导,导致召回率很低。
-
拍摄条件复杂:光照、遮挡、姿态、表情混合等现实因素使得建模更困难。
-
标签噪声与主观性:人为标注存在主观性,近似表情样本可能被不同标注者归入不同类别。
本项目目标
在模型能在低性能的笔记本运行的前提下,通过一系列工程与建模优化把 FER-2013 的性能尽可能提升,实现从基础浅层 CNN 到 SOTA-like 的改进,并在实验中验证各项策略的贡献。追求的指标不仅是总体准确率(accuracy),更关注对少样本类(如 Disgust、Fear)的召回与 F1 指标的提升。
核心思路 核心思路是(1)扩大输入分辨率并选用更有表现力的 backbone(EfficientNetV2-S → EfficientNetV2-M),(2)引入局部注意力模块(CBAM)聚焦关键面部区域,(3)采用强增强策略(Albumentations + MixUp + CutMix + CoarseDropout)提升泛化,及(4)使用稳健训练策略(AdamW + warmup + CosineLR + AMP + 梯度累积)增强收敛稳定性与效率,来显著提升在 FER-2013 上的分类性能。实验在 Google Colab 的 A100 GPU 上完成,并在本地(MacBook Pro 2018)进行结果可视化与分析。
结果显示:在相同训练流程与正则化下,将模型从 EfficientNetV2-S(输入 384)升级到 EfficientNetV2-M(输入 480),验证集准确率由约 56.1% 提升至 68.4%,同时少样本类别(如 Disgust、Fear)的精度与召回显著改进。
二、数据与预处理
图像尺度与细节
由于表情信息常常是微表情或局部特征,直接把 48×48 的图片送入深模型很难学习到稳定特征。工程上有两种常见做法:
-
放大分辨率:在训练前将图像等比/非等比放大到 224、384、480 等更高分辨率,允许模型在前几层提取更多纹理与细节(但注意随之而来的计算量与显存消耗)。
-
超分辨率 / 预处理:在数据预处理阶段应用单图像超分辨(若有),或采用细粒度增强保留边缘信息。
对齐(Alignment)
对齐是工程中一项基础但极重要的操作:通过检测面部关键点(例如 dlib 的 68 点或 MTCNN)将眼角和嘴角等几何位置标准化到固定参考位置。对齐能显著降低姿态带来的变异性,使模型更关注表情差异,而非姿态差异。
标准化(Normalization)
当使用 ImageNet 预训练权重时,通常对 RGB 三通道做 ImageNet 均值/方差归一化(mean/std),以便预训练特征能够良好迁移。若不使用预训练权重,也应按本数据集统计量做归一化。
标签清洗与分割
-
建议把训练集划分出稳定的 validation 子集(例如 80/20 或按给定 split),测试集(public test)仅用于最终评估,避免调参泄露测试信息。
-
若存在噪标签,可用半自动方法(embedding 聚类、低置信度样本人工检查)进行清洗;这对提升少样本类表现尤其有效。
三、为什么加入注意力模块(CBAM):原理与工程意义(详细解释 + 极简示例)
直观理解
注意力机制的核心思想是:不是所有特征或像素对分类同等重要。CBAM(Convolutional Block Attention Module)先在通道维度上学习"哪些通道更重要"(哪些滤波器学到的响应是有效的),再在空间维度上学习"图像的哪些位置更重要"(比如眼睛、嘴角)。这与人工观察一致:判定表情时,人类会自然关注眼、眉、口的变化。
为什么对 FER 特别有效
-
表情信息稀疏且局部:CBAM 能放大局部强信号并抑制背景噪声;
-
数据噪声与遮挡:当部分面部被遮挡时,CBAM 能自动转移注意力到未遮挡但有信息的区域;
-
与大模型结合:注意力模块与强 backbone(如 EfficientNetV2)协同,能把更多计算资源聚焦到关键区域,提高最终分类性能。
工程上的使用策略(无繁琐代码)
-
把 CBAM 插入 backbone 的后端(最后 stage 之后),或在每个 stage 之后插入轻量 CBAM;
-
如果担心计算开销,先只在最后 stage 插入;若显存与计算充足,可在多个位置插入;
-
CBAM 的超参数(reduction ratio、kernel_size)对速度与效果有折中,常用 reduction=8,kernel=7。
**极简示例
scss
# 在模型中:feat = backbone(images); feat = CBAM(feat); pooled = GAP(feat); logits = head(pooled)
四、数据增强与正则化(Albumentations、MixUp、CutMix、CoarseDropout)
为什么需要增强与正则化
增强是提升泛化最直接的方法。表情识别尤其依赖对亮度、对比、轻微旋转与遮挡的鲁棒性。MixUp/CutMix 等混合策略则通过软标签或局部替换,使决策边界更平滑,减少过拟合并改善少样本类表现。
具体策略与注意事项
-
基础增强(速度优先):随机水平翻转、亮度/对比度扰动(±25%)、小角度旋转(±15°)、少量缩放与平移。使用 Albumentations 可以在 CPU 端高效并行执行这些变换。
-
遮挡模拟(CoarseDropout):随机遮挡小块(模拟口罩、遮挡),训练出在局部信息缺失时仍能判断的模型。但遮挡尺寸与概率需谨慎,过强会破坏情绪语义。
-
MixUp(输入与标签线性混合):通过混合两个样本图像与其标签,训练时用混合标签计算损失,有助于提高模型对噪声的鲁棒性。MixUp 在表情任务中能提升总体泛化,但对可解释性有影响(输出变软)。
-
CutMix(局部替换):把一张图像的一块区域替换为另一张图,标签按覆盖面积加权。对少样本类有很大帮助,因其生成的合成样本更真实。
-
MixUp/CutMix 与损失函数:因生成了软标签,训练时需要用能接收软标签的损失实现(或者使用按比例加权的交叉熵)。若使用标准 CrossEntropy,需要做标签混合的手动实现。
实践调参建议
-
MixUp α 参数建议从 0.2--0.4 试起;CutMix 的 bbox 大小不要超过图像面积的 25%(表情语义敏感)。
-
在少样本类非常稀缺时,可对这些类做更高概率的 Mix/Cut 采样(即"偏向式"增强)。
-
若原始图像为灰度(FER-2013),在转换成三通道供预训练模型使用时,应先复制通道或用简单色彩扩展,再做色彩增强要谨慎(过强色彩变换对灰度图意义有限甚至有害)。
五、损失与类别不平衡处理(Label smoothing、Focal、类权重)
概念回顾
-
Label Smoothing:把硬标签(例如 one-hot 的 1/0)用小量平滑为软标签,例如把 1 变为 0.9,把 0 变为 0.01/(K-1)。这能抑制过度自信、提升泛化。
-
Focal Loss:在 CrossEntropy 基础上对易分类样本衰减权重,从而让模型聚焦难样本(少样本或边界样本)。
-
类权重:直接在交叉熵中把少样本类别的损失放大(权重为类别频率的倒数或类似策略)。
为什么重要
FER-2013 与许多现实数据一样存在类别分布不均。若训练中不处理,模型会倾向预测多数类(例如 Happy/Neutral),导致少样本类几乎被忽视(precision/recall 极低)。上述方法能从不同角度缓解该问题:label smoothing 提升泛化、focal 关注难例、类权重直接补偿频率差。
实践建议
-
先开 label smoothing(0.05--0.1),观察泛化;
-
若少样本类仍然很差,尝试 focal loss(γ≈2),或把 class weight 加入 CrossEntropy;
-
同时结合数据层面的 oversampling(在训练集中人为增加少样本类的出现频率)通常更直观且有效,但需注意过拟合风险。
六、backbone选择与分辨率对比:为什么 V2-M 明显优于 V2-S
核心观察
实验中,EfficientNetV2-S(384 输入)表现约 56% accuracy,而 EfficientNetV2-M(480 输入)达 ~68% accuracy。这样的差距并非偶然,背后有几点机制:
-
信息量与分辨率的匹配:面部微小表情在高分辨率上更显著;小模型即使看到更高分辨率也可能因容量不够无法有效利用这些信息;但中等或大模型能学习更细工的滤波器来捕捉这些细节。
-
网络深度与感受野:更大模型具有更深的层和更宽的通道,可以在不同尺度上融合局部与全局信息(对表情识别尤为关键)。
-
预训练迁移效果:若基于 ImageNet 预训练,较大的模型常常有更好的通用视觉表示作为起点,微调时更快收敛并获得更高泛化。
工程权衡
-
若算力充足:优先尝试更高分辨率 + 更大模型(如 V2-M);
-
若算力有限:可采用 knowledge distillation(先用大模型训练,再蒸馏到小模型);或先冻结 backbone 多层,仅训练 head,再逐步解冻微调。
七、训练工程化细节:加速、显存与稳定(AMP、梯度累积、数据流水线)
训练慢但显存低的常见原因
-
数据读取与增强成为瓶颈(CPU 端变换太慢、I/O 等待);
-
batch 太小,导致 GPU 并行度不足;
-
不使用混合精度导致计算效率未最大化。
解决策略(流程化)
-
数据流水线优化 :把增强放在 Dataset 中并行执行,提升 DataLoader 的
num_workers
,启用pin_memory
。若 CPU 成为瓶颈,考虑使用 NVIDIA DALI 把增强搬到 GPU。 -
增大 batch(在显存允许下):batch 太小会让 GPU 空闲;若显存不足,则使用梯度累积(把多步的小 batch 的梯度累积再一次性更新,相当于虚拟增加 batch)。
-
混合精度(AMP):启用 NVIDIA 的自动混合精度可减少显存使用并加速训练。配合 GradScaler 可自动处理数值稳定性。
-
冻结与逐层解冻:初始训练只训练新加的头部和少量层,保证快速收敛;当稳定后再解冻 backbone 进行低学习率微调。
-
显存碎片化处理 :必要时重启进程、清理缓存或设置
PYTORCH_CUDA_ALLOC_CONF
等参数来减少碎片化影响。
八、评估指标与错误分析(如何读懂实验结果并定位问题)
必须关注的指标
-
整体准确率 (accuracy):总体趋势指标,但受类别不平衡影响大;
-
宏平均 F1/macro-F1:对少样本类友好,能反映总体公平性;
-
类级别 precision/recall/F1:用于诊断具体某类问题(例如 Disgust 为 0 表明模型无法识别该类或该类样本质量极差);
-
混淆矩阵:直观显示哪个类被误判为哪个类(例如 Sad 被大量判为 Neutral),便于针对性改进数据或模型。
错误样本复查建议
-
抽取混淆矩阵中错误最多的类别对(如 Sad→Neutral),把这些图像可视化并人工审查:检测标签噪声、低质量图像或拍摄角度问题。
-
基于审查结果采取对应措施:纠正标签、增强少样本类别、或设计更强的局部增强(例如只增强嘴部、眼部等)来强化差异。
九、部署与保存
保存策略
-
训练过程保存 checkpoint(包含模型参数、优化器状态、scheduler 状态),便于断点续训与调参回溯。
-
训练结束保存最佳权重(按 validation 指标选择),并导出模型为便于推理的格式(例如 ONNX 或 TorchScript)。
部署建议
-
若目标是低延迟服务:提取 embedding 并部署为服务端检索服务,或把模型导出为 TensorRT/ONNX 并启用 FP16/INT8 加速;
-
若目标是边缘设备:可做后训练量化或采用蒸馏到小模型,然后转换为适配格式(如 TFLite、ONNX + quantization);
-
对于离线/本地推理(如在 MacBook 上),可采用 TorchScript 或导出为 ONNX 后使用适合平台的运行时。
十、下一步与进阶方向
-
针对性扩样:为 Disgust、Fear 等少样本类额外收集或合成数据,或在 embedding 空间进行 SMOTE 式过采样。
-
模型集成:把 EfficientNetV2-M 与 ConvNeXt / Swin 等不同类型模型做 soft-vote 融合;
-
TTA(Test Time Augmentation):测试时做多次变换取平均,提高鲁棒性;
-
蒸馏:把大模型能力蒸馏给轻量模型,便于部署;
-
更细致的 attention 设计:例如把 CBAM 与面部关键点引导结合,只在关键区域放大特征,减少背景干扰。
极简关键代码
下面仅给出两个极简示例:MixUp 的损失合成示意与 AMP+梯度累积的训练核心逻辑。其余实现以文字说明为主。
MixUp 损失合成示意(伪代码很短)
python
# mixed_x, y_a, y_b, lam = mixup_data(inputs, targets, alpha=0.4)
logits = model(mixed_x)
loss = lam * criterion(logits, y_a) + (1 - lam) * criterion(logits, y_b)
AMP + 梯度累积(训练主循环的核心)
python
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
accum_steps = 2
optimizer.zero_grad()
for step, (imgs, labels) in enumerate(train_loader):
imgs, labels = imgs.to(device), labels.to(device)
with autocast():
logits = model(imgs)
loss = criterion(logits, labels) / accum_steps
scaler.scale(loss).backward()
if (step + 1) % accum_steps == 0:
scaler.step(optimizer); scaler.update()
optimizer.zero_grad()
总结
-
核心思想:表情识别的好坏在很大程度上取决于"能否把局部、细微、且噪声下的表情特征学出来"。
-
工程套路:高分辨率输入 + 有力 backbone(容量与输入匹配)+ 注意力模块(聚焦关键区域)+ 强增强(MixUp/CutMix/CoarseDropout)+ 稳定训练策略(AdamW + warmup + cosine + AMP)。
-
实际收益:依据实验,采用上述组合后,从 EfficientNetV2-S 到 EfficientNetV2-M 的升级以及相应的正则化策略,使得验证/测试准确率与各类 F1 指标有明显提升(例如整体从 ~56% 提升到 ~68%)。