ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

EmotionVGGnet情绪识别源码解析:从环境配置到调参避坑指南

EmotionVGGnet情绪识别源码解析:从环境配置到调参避坑指南 简介一个基于VGGNet深度卷积神经网络的Python情绪识别源码包面向具备一定深度学习基础的研究者、算法工程师及高年级学生适用于语音、图像等场景下的情感分类任务完整覆盖从数据预处理、特征提取、模型搭建、训练调优到评估推理的全流程。压缩包共11个文件、约12.38MB其中6个Python脚本分别负责数据构建、配置、网络定义、训练、测试与实时检测2个HDF5文件为不同训练轮次保存的模型权重另有JSON模型配置、PNG可视化结果与README说明文档整体结构便于对照学习。可直接加载权重进行情绪识别测试也可基于完整源码重新训练模型并通过可视化图表直观观察VGGNet在情感任务上的特征提取与分类效果为后续改进或迁移到其他数据集提供了可复用的实验框架。目前已有307人学习下载适合作为深度学习和情感计算领域的实战参考资料。1. 情绪识别项目的入口EmotionVGGnet 这套源码到底能干什么如果你手头正好拿到了 “EmotionVGGnet情绪识别-python源码.zip” 这个压缩包大概率是想在本地跑通一个表情分类模型。它做的事很直接输入一张人脸图像输出一个情绪标签常见的是七分类——angry、disgust、fear、happy、neutral、sad、surprise。这个任务本身不算新但 EmotionVGGnet 这套源码的价值在于它把“VGG 网络 情绪数据集 训练脚本”打包成了一份开箱即用的 Python 工程适合刚入门的同学理解图像分类全流程也适合课程设计或小规模 demo 的快速复现。网上关于这套代码的讨论经常和 python 安装教程、zip 解压、vscode python 环境配置混在一起说明大部分人卡在“跑起来”这一步而不是模型原理。这篇笔记就顺着拿到 zip 之后的真实路径来讲先解压、核对文件、装环境再把数据流和模型结构拆开然后给出一组能收敛的训练参数最后落到实时推理时最容易翻车的细节上。整个过程不需要 GPU 也能跑只是慢一些。适合的人群是刚学完 Python 基础、想动手跑第一个深度学习项目的开发者以及需要快速交付情绪识别演示效果的技术人员。2. 从 zip 到跑通训练EmotionVGGnet 的环境准备与文件核对2.1 解压后先核对这四类文件别急着装依赖拿到任何一个源码 zip我的习惯是先看文件结构再决定怎么装环境。用命令行解压最直接避免图形工具解压时把文件名编码搞坏。Linux 或 macOS 用 unzipWindows 推荐在 PowerShell 里用 Expand-Archive或者装 7-Zip 之后从右键菜单解压。解压之后一个正常的 EmotionVGGnet 工程里通常包含四类东西第一类是模型定义文件名字一般叫 model.py 或者 model_vgg.py里面是 VGG 风格的卷积堆叠加全连接分类头。第二类是训练脚本常见命名是 train.py负责加载数据、定义损失函数、跑 epoch。第三类是数据加载相关代码可能写在 data_loader.py 或 dataset.py 里处理图片读取、标签映射、数据增强。第四类是数据集目录结构一般是 train/ 和 val/ 两个大文件夹每种情绪一个子文件夹子文件夹名字就是标签。这里有个容易出现误判的地方压缩包里的数据集如果缺失训练脚本跑起来会直接报 FileNotFoundError。所以第一步不是 pip install而是先统计数据集目录下的图片数量。如果解压时提示需要密码先不要急着去搜什么 zip 密码移除工具。很多老工程打包时用了压缩工具的“伪加密”功能就是只在 zip 头部标记了加密位实际文件内容没有被加密。这种问题用 7-Zip 打开一次或者用 Python 的 zipfile 库去读很可能直接绕过。import zipfile with zipfile.ZipFile(EmotionVGGnet情绪识别-python源码.zip) as zf: # 先列出所有文件确认结构再决定是否解压 for info in zf.infolist(): print(info.flag_bits, info.filename)这段代码的关键在于打印出来的 flag_bits。正常情况下是 0 或者不带加密标记如果文件被伪加密flag_bits 里会有一个看似加密的位但 zipfile 在读取文件名和目录时并不会真正校验密码。用这种方式先看一眼清单能帮你判断包里到底是什么也避免解压出一个不知道是训练集还是模型权重的混乱目录。2.2 Python 与 PyTorch 环境的最小安装套路跑 EmotionVGGnet 这种图像分类工程最省心的方式是 Python 3.8 到 3.10 之间配 PyTorch。版本太新可能有算子兼容问题太老则装不上最新的 torchvision。如果你电脑上还没装 Python直接去 python.org 官网下载对应系统的安装包安装时记得勾选“Add Python to PATH”这是很多人后面在命令行里敲 python 提示找不到命令的主要原因。装完 Python 之后下一步是建虚拟环境。常见做法是这样python -m venv emotion_env source emotion_env/bin/activate # Windows 下是 emotion_env\Scripts\activate虚拟环境不是可选项是必选项。因为情绪识别这类项目依赖的 torch、torchvision、opencv-python 版本彼此约束直接装进全局环境很容易和别的项目冲突。激活之后再装核心依赖。pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install opencv-python numpy tqdm pillow第一条命令指定了 CPU 版本的 PyTorch如果你的机器没有 NVIDIA 显卡这样能省掉 CUDA 工具链的麻烦。第二条命令里的 opencv-python 负责图片读取和摄像头推理numpy 处理张量转换tqdm 用来显示训练进度条。装完可以验证一下python -c import torch; import cv2; print(torch.__version__, cv2.__version__)如果这条命令没报错说明核心依赖已经就位。这一步常常遇到的问题是 pip 下载速度慢解决办法是换用国内镜像源比如把 pip 的 index-url 指到清华或阿里云的 PyPI 镜像下载速度会快很多。vscode python 环境配置也是同一个思路在编辑器右下角选解释器时定位到你刚创建的 emotion_env 虚拟环境这样调试时用的依赖才不会跑偏。2.3 把数据集路径和标签目录名对准训练才可能开始环境装好之后你面对的下一个坑是路径。这类源码在打包时数据路径通常是写死的相对路径比如./data/train。你解压出来的目录如果不是这个名字或者数据集没放在工程根目录下训练脚本会立刻报错。我的建议是解压后先执行一次目录切换把数据目录的结构固定成源码默认的样子。cd EmotionVGGnet情绪识别-python源码 ls -la data/看到的结果一般是 train/ 和 val/ 或 test/ 两个目录。如果只有一个 train/说明验证集和测试集可能要从 train/ 里按比例切分源码里通常会在 data_loader 里做 train_test_split。这时不要自己去手动整理目录而是先看代码里怎么读取的最常见的是 torchvision 的 ImageFolder 用法它要求目录结构严格是“标签名/图片文件”。另一个容易忽视的问题是标签命名源码里用英文标签比如happy/、sad/千万别改成中文。因为 ImageFolder 会按目录名排序生成类别索引代码里的 class_names 列表顺序一旦和模型输出对不上推理时就会出现“预测 happy 结果打印出来是 sad”这种莫名其妙的情况。3. 训练管线拆解EmotionVGGnet 的数据流与模型结构3.1 数据加载器源码里最常见的 EmotionDataset 写法这类工程的数据加载逻辑很少用复杂的 Dataset 子类多数是 torchvision.datasets.ImageFolder 直接搞定因为它天然支持“目录名即标签”。但如果你想自己控制数据增强流程或者要复现论文里的预处理细节手写一个 Dataset 也是常见操作。下面是一个标准实现逻辑上等价于 ImageFolderimport os from PIL import Image from torch.utils.data import Dataset class EmotionDataset(Dataset): def __init__(self, root_dir, transformNone): self.root_dir root_dir self.transform transform # 目录名当作类别排序保证索引稳定 self.classes sorted(os.listdir(root_dir)) self.class_to_idx {c: i for i, c in enumerate(self.classes)} self.samples [] for c in self.classes: c_dir os.path.join(root_dir, c) for fname in os.listdir(c_dir): if fname.endswith((.jpg, .jpeg, .png)): self.samples.append((os.path.join(c_dir, fname), self.class_to_idx[c])) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, label self.samples[idx] image Image.open(path).convert(RGB) if self.transform: image self.transform(image) return image, label这段代码里有两个值得注意的参数点。class_to_idx是类别到数字的映射它是后续所有输出解释的参照表如果目录里混进了系统生成的隐藏文件比如 macOS 的 .DS_Storeos.listdir会把它们也当成类别目录导致类别数从 7 变成 9训练直接崩。transform参数用来做 resize、归一化和增强如果源码里没有显示传 transform很多情况下图片尺寸不统一会导致后面卷积层形状对不上。实际训练时用的 transform 通常是这样的组合from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((48, 48)), transforms.RandomHorizontalFlip(p0.5), transforms.ToTensor(), transforms.Normalize(mean[0.5, 0.5, 0.5], std[0.5, 0.5, 0.5]) ])Resize 到 48x48 是情绪识别领域的老传统因为 FER2013 数据集原生就是 48x48 灰度图。Normalize 用的 mean 和 std 都是 0.5对应把像素从 0-1 映射到 -1 到 1这是很多 VGG 实现的标准做法。如果你发现原始源码里用的是 224x224 或者更大的尺寸说明作者改用了 VGG16 的标准输入这种改动需要相应增加模型全连接层的维度计算后面会单独讲。3.2 VGG 骨干加分类头EmotionVGGnet 的模型定义EmotionVGGnet 本质上是在 VGG 结构上截断并替换分类器得到的。VGG 的特点是用小卷积核堆叠出足够深的感受野参数多但结构简单特别适合做迁移学习。一个精简版模型定义如下import torch.nn as nn class EmotionVGGnet(nn.Module): def __init__(self, num_classes7): super(EmotionVGGnet, self).__init__() # 三个卷积块每块后面接一个 max pooling self.features nn.Sequential( nn.Conv2d(3, 64, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(64, 64, 3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # 48x48 - 24x24 nn.Conv2d(64, 128, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(128, 128, 3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # 24x24 - 12x12 nn.Conv2d(128, 256, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(256, 256, 3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2, 2), # 12x12 - 6x6 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(256 * 6 * 6, 512), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(512, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x这个结构值得说明的地方有三处。第一处是nn.Flatten()之后的全连接输入维度256 * 6 * 6它不是拍脑袋写的而是由输入尺寸经过三次池化推算出来的48 先变 24再变 12最后变 6。如果你把输入换成了 64x64 或者 224x224这个数字必须跟着改否则模型初始化就会报维度不匹配的错误。第二处是 Dropout 0.5这是对付情绪数据集过拟合的关键因为表情样本的类间差异很小模型很容易死记训练集不抽样正则化准确率上不去。第三处是最后输出 num_classes 个 logits没有接 Softmax这是为了让训练时的 CrossEntropyLoss 直接吃原始分数如果先 Softmax 再接 Loss梯度反而会出问题。3.3 训练循环与 checkpoint 保存的参考写法训练循环是整个源码里最像“黑匣子”的部分。我建议不要直接跑完整脚本而是先理解它每个 epoch 都做了什么。一个常规的训练循环包含四个动作前向计算、反向传播、梯度更新、指标统计外加定期保存模型。def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0.0 correct 0 total 0 for images, labels in loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() avg_loss total_loss / total acc correct / total return avg_loss, acc这段循环里有一个隐藏很久的细节optimizer.zero_grad()必须放在每次前向之前。如果你发现 loss 在训练过程中忽高忽低、不收敛十有八九是没清梯度导致累加。另一个细节是准确率计算用的是torch.max取 logits 最大值对应的索引这必须和类别映射的顺序一致也就是训练数据的目录名排序结果。模型的保存常见做法是每个 epoch 结束都存一份但只保留最优和最后两个torch.save({ model_state_dict: model.state_dict(), optimizer_state_dict: optimizer.state_dict(), epoch: epoch, acc: acc, }, fcheckpoints/emotion_vgg_epoch{epoch:02d}.pt)这里有两个后悔药第一保存 optimizer 的 state_dict这样你想从断点续训时学习率调度器的状态不会丢第二把准确率和 epoch 一起存下来后续回看 checkpoint 时不用猜它是哪个阶段的产物。很多人在训练到一半崩了之后只能从头再来就是因为只存了模型权重没存优化器状态。4. 调参顺序让 EmotionVGGnet 从过拟合到正常收敛4.1 三个优先于一切的参数学习率、batch size、输入尺寸如果源码给定的默认参数训完效果很差先不要怀疑模型结构问题通常出在三个参数上。学习率是最敏感的一个PyTorch 里 Adam 优化器的默认学习率是 0.001但 VGG 类模型配合 Adam 时0.001 经常偏大损失会在某个值附近震荡。我一般先把学习率压到 0.0003 或 0.0001观察 5 个 epoch 的 loss 曲线如果 loss 完全不动再回调到 0.001。Batch size 对情绪识别的影响体现在标签噪声上。表情数据集里有很多标注本来就模糊的样本batch size 太大梯度会被多数类主导少数类比如 disgust 和 fear 样本量少很难学到有效特征。在显存允许的前提下batch size 取 32 到 64 是常见范围CPU 训练时再调小到 16保证一个 epoch 能多更新几次参数。输入尺寸是关键中的关键。前面提到的模型结构是按 48x48 输入设计的如果你感觉到手的数据集是灰度图还需要额外把图片转成三通道再喂给模型否则第一个卷积层的Conv2d(3, 64, ...)会直接报通道数错误。这里顺带说明灰度图转 RGB 最常见做法是复制三份而不是用 PIL 的 mode 转换因为复制三份能保持纹理信息不变。4.2 预训练权重与骨干冻结的迁移学习设置如果你手里的数据集很小比如每个类别只有几百张图从头训 VGG 几乎必然过拟合。这时的首选是迁移学习加载 ImageNet 上预训练的 VGG16 权重冻结骨干卷积层只训练分类头。PyTorch 的 torchvision 里提供了现成的 vgg16 模型加载方式如下import torchvision.models as models model models.vgg16(weightsmodels.VGG16_Weights.IMAGENET1K_V1) # 替换最后一层分类器 num_features model.classifier[0].in_features model.classifier[6] nn.Linear(num_features, 7)这里有一个关键的参数设置classifier[6]是 VGG16 默认分类器中最后一个线性层原来输出 1000 类改成 7 类。如果 EmotionVGGnet 的源码没有使用 torchvision 的预训练权重而是自己定义了整段 VGG 结构那么迁移学习就退化成只初始化骨干部分效果会差不少。迁移学习时冻结骨干采用的方式是for param in model.features.parameters(): param.requires_grad False optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr0.0001)requires_grad设置为 False 之后这些参数不会出现在优化器的更新列表里训练时只更新分类头。这样做的好处是显存占用小训练速度快而且在小数据集上不容易崩。等分类头训了几个 epoch、准确率稳定之后再解冻部分骨干层进行微调把学习率降到 0.00001这是情绪识别任务里最稳妥的调参顺序。4.3 训练时看哪些指标才不会误判模型好坏很多初学同学只看 loss 下降就以为模型在变好这是个大误判。情绪识别里 loss 下降但准确率不动是常态因为数据类别不均衡模型学会大量猜多数类也能把 loss 压低。我自己训练时会同时盯三个指标训练 loss、训练准确率、验证准确率。三个指标两两组合能看出不同问题。训练 loss 低但验证准确率也低是过拟合训练 loss 降不下去是学习率太大或模型容量不够验证准确率波动剧烈是 batch size 太小或数据增强过强。一个比较省心的技巧是把每一轮的验证准确率打印出来并且在验证集上计算每个类别的准确率而不是总的准确率。如果 happy 和 surprise 这两个类别各有 90% 和 40% 的准确率那说明模型把 surprise 的样本大量误判成了 happy这个信息比一个整体数字有用得多。5. EmotionVGGnet 落地避坑五条血泪经验5.1 zip 解压后文件路径莫名多出空白字符导致 FileNotFoundError现象是脚本运行时找不到图片但明明文件就在目录里。用 Python 打印路径后会发现文件名头部或尾部多了制表符或空格。原因是用 Windows 自带的压缩功能解压某些 linux 上生成的 zip 包时路径分隔符和特殊字符被错误处理或者在文件信息里残留了不可见字符。解决方法是解压后立刻用 Python 遍历一遍文件路径做一次 strip 清洗。import os import zipfile with zipfile.ZipFile(EmotionVGGnet情绪识别-python源码.zip) as zf: for name in zf.namelist(): clean name.strip().replace(\\, /) if name ! clean: print(f修复路径: {name!r} - {clean!r})这段代码执行后如果输出列表为空说明路径没毛病一旦有输出直接在解压前处理即可。这一招也能顺带避开“zip 伪加密”的干扰——很多伪加密 zip 在 namelist 阶段不需要密码就能读出文件名你可以先用这个脚本看清包里全貌再决定解压方案。5.2 训练 loss 下降但测试准确率始终卡在 60% 附近这个现象在情绪识别里太常见了。原因是数据类别不均衡而且表情本身就有混淆性——fear 与 surprise 在面部肌肉运动上本来就很接近模型学到的是类别先验而不是视觉特征。解决分两步第一步在数据加载器里给每个 batch 做类别加权采样让每个类别在每个 batch 中出现次数大致相同第二步把损失函数换成带权重版本。from torch.nn import CrossEntropyLoss class_weights torch.tensor([1.0, 2.5, 2.0, 1.0, 1.2, 1.5, 1.0]) criterion CrossEntropyLoss(weightclass_weights)这里的 class_weights 是按类别样本量反比设置的示例实际工程里需要先统计训练集中每个类别的样本数再用总样本数除以每个类别的样本数得到权重。权重的作用是让淡出样本在损失函数里贡献更大的梯度。这个技巧能稳定提升 2-3 个百分点。5.3 OpenCV 读取图像是 BGR 格式导致推理结果在 happy 和 surprise 之间横跳现象是单张图片预测准确摄像头实时预测时结果忽好忽坏。原因出在色彩通道顺序上OpenCV 的cv2.imread默认读出来的是 BGR 排列而模型训练时用的是 RGB。两个通道对调后红色信息跑到了蓝色通道人脸肤色纹理完全错乱。解决方法是每次把帧转换后再送入模型。rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) pil_img Image.fromarray(rgb_frame)只要记住一个原则模型中定义的 Normalize 是什么色彩空间输入就必须是什么色彩空间。如果这条没做对再怎么调学习率都是白费功夫。5.4 显存不足直接 out of memory换 CPU 又慢得离谱现象是 batch size 调成 64 后GPU 显存直接报错。原因很简单VGG 的参数和中间特征图都吃显存特别是输入尺寸从 48 提到 224 之后特征图体积成平方上升。解决方法是先撤到 32 再撤到 16同时注意把验证阶段的模型切到 eval 模式否则 BN 层和 Dropout 层会在验证时产生额外的显存开销。还有一招是开启梯度累加每 4 个 batch 更新一次参数等效于 batch size 翻倍但显存不翻倍。for i, (images, labels) in enumerate(loader): loss criterion(model(images), labels) / 4 loss.backward() if (i 1) % 4 0: optimizer.step() optimizer.zero_grad()这段代码的要点是 loss 除以 4因为累计 4 次反向传播的梯度之后再更新一次梯度数值等效于 batch size 乘 4。loss.backward()默认是累加梯度不是覆盖所以这个写法成立。注意最后一次不足 4 的余数 batch 要单独处理否则梯度会残留到下一个 epoch。5.5 模型加载到一半报错声称 checkpoint 不是模型文件现象是torch.load加载成功但model.load_state_dict报 key 不匹配。原因常见于两种情况要么保存的是整个模型而加载时按 state_dict 读取要么训练时用了 DataParallel 多卡包装参数名前多了module.前缀。解决方式是加载时先打印 key 列表如果看到module.features.0.weight这种前缀就做一个前缀剥离。state torch.load(checkpoints/emotion_vgg_epoch10.pt, map_locationcpu) new_state {} for k, v in state[model_state_dict].items(): if k.startswith(module.): k k[len(module.):] new_state[k] v model.load_state_dict(new_state)这个兼容处理能省掉你半天时间也是分布式训练和单卡训练之间最常遇到的“遗产代码”。6. 最后一步用摄像头把模型用起来并验证它没白训模型训练完之后先做一件事写一个单张图片推理脚本打印每个类别的置信度。这样能确认类别映射和训练时一致而不是直接上摄像头。下面这段代码是常见做法直接读一张验证集图片走完预处理-前向-softmax 全流程。from PIL import Image import torch import torchvision.transforms as transforms def predict_image(model, img_path, transform, label_names): model.eval() img Image.open(img_path).convert(RGB) x transform(img).unsqueeze(0) with torch.no_grad(): logits model(x) prob torch.softmax(logits, dim1) idx prob.argmax(dim1).item() return label_names[idx], prob[0, idx].item() label_names [angry, disgust, fear, happy, neutral, sad, surprise]注意model.eval()这句必须有。它会把 Dropout 层关掉、BN 层切换到全局统计量否则同一个输入每次推理结果都不同看起来就像模型随机抖动。torch.no_grad()是关闭梯度追踪推理阶段不需要保留计算图能省一部分内存。实时摄像头推理是在这个基础上套一层循环。先用 OpenCV 的 Haar 级联检测人脸框再把裁剪出的人脸区域送进模型。千万不要把整帧画面直接送入模型因为模型训练时输入是纯人脸图像背景信息会被当成噪声参与计算。一个简单的人脸框检测加裁剪代码如下import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, scaleFactor1.1, minNeighbors5, minSize(48, 48)) for (x, y, w, h) in faces: face frame[y:yh, x:xw] rgb cv2.cvtColor(face, cv2.COLOR_BGR2RGB) pil Image.fromarray(rgb) label, confidence predict_image(model, pil, transform, label_names) cv2.rectangle(frame, (x, y), (xw, yh), (0, 255, 0), 2) cv2.putText(frame, f{label}: {confidence:.2f}, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(EmotionVGGnet, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里scaleFactor1.1表示每次检测时窗口放大 10%值越小检测越慢但越不容易漏minNeighbors5是抑制误检的参数值越大越保守。如果实时画面里人脸框贴得很准但情绪一直在变先不要怀疑模型多半是光照变化导致人脸区块的像素分布发生了偏移可以让模型接收多个连续帧取置信度平均值而不是每一帧单独出结果。另外一个值得做的验证是把模型的中间特征图可视化出来看卷积层究竟在关注什么。简化做法是把第一个卷积层的权重矩阵直接画成 64 张小图肉眼观察它是否学到了类似边缘、纹理的滤波器。如果这些滤波器看起来全是噪声斑点说明模型训练失败或数据预处理有问题。我自己的习惯是每次换数据集都会做一次这个检查比看测试准确率快得多。说到教训我早期练情绪识别时犯过一个很低级的错在训练时顺手打印了一张 “happy” 预测为 “surprise” 的失败样本发现那张图里人的嘴型确实很像 surprise但周围光照暗得几乎看不到眼睛轮廓。从那以后我坚持在训练时不做过强的随机裁剪尤其是不要随机调整亮度对比度。情绪识别本来就依赖面部肌肉纹理过度增强反而把关键细节抹掉了。调试这类模型稳定输入的预处理比调网络结构更值得投入。希望这篇笔记能帮你少走几段弯路直接把 EmotionVGGnet 跑通并落地。本文还有配套的精品资源点击获取
返回列表