aiAGENTINFIDEAS INTO INTERFACES
← 所有文章
AI 工程TRAINING LOG / 2026.10.14

从 24 万张样本到两个轻量模型:见微训练实录

把图片收集、标签审计、去重、训练、校准与部署串起来。这里记录实际完成的版本、可复核的结果,以及仍然没有解决的问题。

2026.10.16 更新 · 修正“开心 → 轻蔑”误判

实验室现已使用两个经过验证的 v2 模型。网页判断阈值仍为 35%,八类输出全部保留。下面先记录这次修正;后面的十节保留 10 月 14 日原始训练实验,便于对照。

复核确认:博客上的权重与本地训练产物完全相同,类别顺序也一致。核心问题出在混合数据的训练损失:240,369 张训练图中有 203,110 张(84.50%)来自不标注轻蔑的来源,旧实现把轻蔑从这些图片的 softmax 分母中删除。对其中 48,343 张标注为开心的图片,即使模型原始轻蔑分数很高,轻蔑输出收到的纠错梯度仍是零。单元测试直接复现了这个现象。

此外,1,840 张轻蔑正样本中的 1,696 张(92.17%)来自第三方 AffectNet 镜像。抽查可见闭嘴微笑、嘴角轻抬等边界含糊的图片。目录与 CSV 一致只能检查内部一致性,不能证明人类标签完全正确;这里把它作为来源集中与标注歧义的风险,没有凭肉眼把测试标签改掉。

修正后,已知标签在完整八类分母中竞争,让开心等已标注样本也能纠正过高的轻蔑分数;标签平滑仍只分配给来源能标注的类别。七类数据的标签未必覆盖所有表情,因此这是一项需要验证的建模假设,不代表“没标注的类别永远不存在”。相同初始化、数据、随机种子和八轮训练的快速版对照中,旧损失在验证集仍有 326 次开心→轻蔑误判;新损失明显减少该错误。我们进一步比较了冻结特征的定向修正,以限制其他类别的变化。

最终快速版采用只训练最后一层轻蔑输出的权重与偏置,冻结特征和其他七类输出;精度版采用全网络小学习率微调。候选只能用 39,018 张验证图选择,要求开心误报轻蔑至少减少一半、开心召回率不下降、总体 Macro-F1 下降不超过 1 个百分点、轻蔑召回率至少保留原来一半;通过条件后按验证 Macro-F1 选择。然后才评估原有 78,689 张外部测试和固定 FER 测试。没有把测试图片加入训练,去重与分组划分完全保持原样。

模型开心→轻蔑张数开心误判率开心召回率轻蔑精确率轻蔑召回率总体准确率
快速版592 → 593.44% → 0.34%70.93% → 72.86%11.00% → 46.46%45.25% → 29.79%51.35% → 52.49%
精度版540 → 753.14% → 0.44%72.18% → 73.45%15.06% → 48.65%53.90% → 35.89%56.95% → 58.21%

上述开心误判率的分母都是 17,204 张标注为开心的外部测试图;表格比较不使用拒判阈值,以免把“更多拒绝回答”当成模型变准。轻蔑精确率和召回率同时列出,暴露减少误报的代价。新模型重新在原校准集上拟合温度,并经过 ONNX 数值一致性与 CPU 速度测试。模型结构、输入分辨率和浏览器 CPU 运行方式保持一致。

这仍不能保证每个人、每种手机光照都同样改善;用户本人的误判视频没有纳入本次评估。可见表情也不等于真实心理状态。下载完整诊断、对照和评估 JSON,或下载 快速版 v2 / 精度版 v2。原版权重与评估仍保留,方便复核和回退。

01 · 这次真正训练了什么

见微 JEV 是一个识别可见面部表情的小型视觉分类系统。输入一张人脸,输出中性、开心、惊讶、悲伤、愤怒、厌恶、恐惧、轻蔑八类分数;分数不足时返回“不确定”。它不生成文字,也不需要调用大语言模型。

名字中的 Jev 来自 TypeSafe 的 System One / Jev 介绍:这里借鉴的是快速、非生成式、结构化决策的产品方向。实际实现采用 MobileNetV3、监督学习和 ONNX,没有使用 TypeSafe Jev 权重,也不是其 RLCD 方法的复现。

本次提供两个已经完成训练的版本:expanded_fast(扩充快速版)和 expanded_accurate(扩充精度版)。二者使用相同的最终训练集合,区别是网络容量、内部输入尺寸与训练参数。打开实验室即可切换,并使用照片、实时摄像头、多脸选择、阈值调整、历史记录和 JSON 导出。

这篇文章描述的是 2026 年 10 月 14 日完成的灰度 MobileNetV3 版本。彩色高分辨率重训、人脸专用预训练、蒸馏和新一轮抗噪声训练仍是后续实验方向,没有把它们写成已完成的能力。

02 · 数据从哪里来,又有多少真正进入训练

下载任务共 14,531 个,保存约 66.15 GB。预处理后得到 555,376 个候选人脸,合并阶段因重复、标签冲突、评估集保护和分组规则等排除 183,007 个,最终留下 372,369 个样本,分配到五个互不混用的集合。下载文件数、候选图片数和训练图片数是三个不同的数字。

集合数量用途
训练集240,369更新模型参数
验证集39,018选最佳轮次、决定早停
校准集10,933拟合温度与选择拒判阈值
固定 FER 测试集3,360与原模型在同一批图片上比较
多来源外部测试集78,689检查跨来源表现

最终训练来源如下。表中按去重后保留的代表图片计数;一个合并包包含其他数据集的图片时,不会把同一张图再计算一次。

来源实际训练样本
ferplus25,000
rafdb8,772
affectnet12,259
expw55,909
yolo_emotions7,889
mutfer20245,843
ferv39k_16f46,313
caer19,010
caers38,126
cremad16,446
ravdess4,802
AffectNet+ 官方验证包0;3,979 张仅作外部测试

用户补充的 YOLO emotions 和 AffectNet 镜像都经过检查与去重后加入训练。AffectNet+ 官方 Kaggle 包实际只有验证数据:5,500 张中,4,000 张属于八类有效表情,另有 1,500 张无脸、不确定或非表情图片;有效图片去重后保留 3,979 张,仅进入外部测试。不能把这个验证包当作完整 AffectNet+ 训练集。

最终八类训练样本分别为:中性 66,962,开心 58,353,惊讶 22,091,悲伤 32,174,愤怒 27,764,厌恶 16,868,恐惧 14,317,轻蔑 1,840。中性约为轻蔑的 36 倍,样本量不平衡依然存在。本文与网页不分发原始训练图片;数据获取与使用应分别查看各来源的原始说明。

03 · 去重之前,先处理标签与数据泄漏

一个容易忽略的问题是:文件名、目录标签和标注文件不一定互相一致。AffectNet 镜像共有 31,002 张图片,其中 11,377 张的目录类别与 labels.csv 不一致。本次保守保留二者一致的 19,625 张候选,再进行跨来源去重与划分。最终进入训练的是 12,259 张。

另有 4,334 组重名文件,其中绝大多数内容不同。像 ffhq_1.png 这样的名字不能直接作为全局身份。YOLO 合并包还重新编号了 FER 图片,因此通过原图像素内容匹配 FER 来源,不能相信文件名中的数字。

  1. 先保护留出集合:原 FER 测试的 3,360 张像素和标签保持不变;已知原始来源、视频或演员分组遵守留出优先级。
  2. 完全重复:把人脸统一到 48×48 的规范像素表示,计算 SHA-256,并结合原图来源匹配。
  3. 严格近重复:要求 pHash 汉明距离 ≤ 4、dHash 距离 ≤ 6,并同时满足归一化相关系数 ≥ 0.985。只靠感知哈希相近不足以认定重复。
  4. 同原图不同裁剪:对可追溯的同一原始人脸只保留一个代表。训练重复项若主要标签冲突,连已有代表也移除。
  5. 保护整组:重复证据一旦连接视频、演员或来源分组,留出优先级传播到整个关联组,避免同一片段的相邻帧跨集合。

外部测试还排除了原模型训练、验证和校准时已见过的图片,使扩充前后的比较更可信。自动检查覆盖有限且归一化的标签、合法类别掩码、正样本权重、规范像素不重叠、已知来源/组不重叠,以及固定 FER 测试不变。

仍有边界:本流程没有用人脸身份模型给所有网络照片分配人物 ID,因此未知的同人跨来源重叠仍可能存在;大幅改变裁剪或姿态的近重复可能漏检。CAER-S 文件名不暴露父视频 ID,未通过内容关联的同源视频帧也可能跨来源出现。MUTFER 缺少可靠参与者 ID,所以只用于训练。这是一套尽量严格、可审计的去重方案,不是“完全无泄漏”的保证。

04 · 一张图片如何变成模型输入

自然图片先由 YuNet 检测人脸,再裁剪、转灰度、缩放为 96×96 的无符号 8 位像素。训练准备阶段也使用相应来源的人脸框或检测器,剔除过小、过于平坦或无法确认目标人脸的样本。视频来源每个片段抽取三个间隔帧,并降低这些样本的训练权重,因为视频级标签未必适用于每一帧。

原图 / 摄像头
  → YuNet 人脸检测
  → 人脸框四周约 3% 扩展并裁剪
  → 灰度 + 面积重采样到 96×96
  → uint8 [batch, 96, 96]
  → 网络内缩放到 112×112 或 144×144
  → 复制为三通道 + ImageNet 归一化
  → MobileNetV3 → 八类 logits
  → 温度校准 + softmax → 八类分数

这里的三通道是同一灰度图的三份复制,不会恢复已经丢失的颜色或细节。选用灰度和较低分辨率是当时的速度、显存与数据统一化取舍,也是后续提高精度时值得重新评估的环节。

当前版本按检测框裁剪,没有用眼睛关键点把歪头旋正,也没有做多帧概率平滑。训练时使用随机水平翻转、约 ±12° 仿射旋转、0.9–1.1 缩放、约 ±6% 平移和亮度/对比度扰动。训练增强只影响训练图片,验证、校准和推理走确定性的预处理。

05 · 两个模型的训练配方

基础骨干来自 torchvision 的 ImageNet 预训练 MobileNetV3,最后一层改为八类。先有原 FER 模型,再以对应原模型的最佳检查点初始化本次扩充训练;本轮不是从随机参数开始。

配置扩充快速版扩充精度版
骨干MobileNetV3-SmallMobileNetV3-Large
参数量1,526,0564,212,280
数据输入 / 网络内部尺寸96×96 / 112×11296×96 / 144×144
ONNX 文件约 6.12 MB约 16.84 MB
骨干初始学习率0.000150.00010
分类头学习率骨干的 4 倍骨干的 4 倍
最大 / 实际轮次20 / 16,触发早停24 / 24
选中检查点第 10 轮第 21 轮
批量大小192192

优化器为 AdamW,权重衰减 0.01,余弦学习率调度降到初始值的 8%。第一轮只训练分类头并冻结骨干的 BatchNorm 统计,之后解冻骨干。损失使用 soft-target 交叉熵、2% 标签平滑和梯度范数裁剪(最大 5)。来源中若有多人投票分布,就保留软标签,避免强行抹掉标注歧义。

类别不平衡通过逆频率的 0.4 次方加权,最大截到 3 倍;来源权重使用逆频率的 0.35 次方、最大 2 倍,再乘以样本可靠性系数,并归一化到平均 1。这里是损失加权,不是简单复制少数类图片。只有七类的来源对缺失类别使用损失掩码;这避免把“没有标注轻蔑”直接视为“确定不是轻蔑”,但也会减少轻蔑类别的负样本约束,值得后续单独比较。

最佳轮次按验证集选取:对每个来源计算 0.6 × Macro-F1 + 0.4 × Accuracy,再对来源等权平均;其中验证来源内的 Macro-F1 使用该来源实际出现的标签。连续六轮没有改善就早停。校准集和测试集不用于选训练轮次。

训练设备为 NVIDIA GeForce RTX 5080,PyTorch 2.11.0 + CUDA 12.8,使用 BF16 混合精度,随机种子 20261014。在数据已经准备好的前提下,日志记录的扩充训练循环约为快速版 408 秒、精度版 867 秒;不含下载、清洗、导出与评估。这个数字只描述本次硬件和实现,不能当作其他电脑的耗时承诺。cuDNN 自动调优与 BF16 也不保证逐位复现。

06 · 分数、阈值与准确率是不同的东西

网络输出 logits 后,在独立的混合来源校准集上拟合一个温度参数:p = softmax(logits / T)。快速版的 T 为 1.24095,精度版为 2.20818。温度是正数,不改变最高分是哪一类;它调整分数的尖锐程度,使分数与经验正确率更相符,但不能保证所有来源都同样校准。

训练产物中的原始阈值按校准集确定。快速版在约 83.16% 阈值下,接受比例只有 14.67%,被接受样本准确率约 90.02%。精度版没有达到预设的 90% 目标,于是使用校准置信度中位数作为明确的回退规则:阈值约 48.18%,接受比例 50%,接受样本准确率约 70.39%。

互动网页根据本次使用偏好,将两个版本的默认判断阈值统一设为 35%,仍可自行调整。这会减少“不确定”,也可能增加明确输出中的错误。它没有重新训练网络,也没有改变每张图最高分的类别。页面上的 80% 分数不等于“这个模型准确率 80%”。

“只看模型愿意回答的样本”的准确率应同时报告覆盖率。例如本项目精度版在固定 FER 测试集上,35% 阈值接受约 80.30% 的样本,接受部分正确率约 87.62%;这些都是该测试集上的条件统计,不能移用于手机摄像头,也不能替代全部样本的准确率。

07 · 扩充数据后的真实收益与退步

下面四个版本在同一组 78,689 张自定义外部测试图片上比较。外部测试使用 PyTorch FP32,固定 FER 测试使用导出的 ONNX 模型;导出数值误差小于 0.000006。外部 Macro-F1 对八类等权,有助于发现少数类是否被总体准确率掩盖。

模型外部准确率外部 Macro-F1FER 准确率CPU 分类中位延迟
原快速版39.06%20.75%81.04%0.47 ms
扩充快速版51.35%45.13%79.85%0.47 ms
原精度版40.73%22.07%83.54%1.17 ms
扩充精度版56.95%52.08%80.71%1.12 ms

扩充精度版的外部准确率从 40.73% 提高到 56.95%,增加 16.22 个百分点;Macro-F1 从 22.07% 到 52.08%。但固定 FER 准确率从 83.54% 下降到 80.71%,ExpW 从 66.86% 下降到 65.81%,FERV 从 32.72% 下降到 29.17%。扩充数据改善了整体跨来源表现,并没有让每个来源都变好。

扩充精度版的八类外部 F1 分别是:中性 56.54%、开心 72.19%、惊讶 52.06%、悲伤 53.38%、愤怒 46.06%、厌恶 51.37%、恐惧 61.54%、轻蔑 23.54%。轻蔑仍是明显短板。常见混淆包括悲伤→中性、愤怒→中性,以及中性与开心互相混淆。

外部测试中的 AffectNet+ 子集准确率为 43.23%,RAF 子集为 78.52%,FERV 视频抽帧为 29.17%。这些差异反映数据分布、标签方式和任务难度不同。视频抽帧使用片段级标签,其中一帧可能没有显示整个片段的主要表情。

本项目经过清洗的混合集合不是论文标准协议。它的 56.95% 不能直接与别人标准 RAF-DB、AffectNet 或七分类实验的数字比较。这里也没有覆盖全部人群、设备、光照与姿态;网页显示的是可见表情分类,不是对人的真实内心感受作判断。

08 · 从 CUDA 训练到 CPU 和浏览器推理

导出模型时,把网络内部缩放、三通道归一化、分类网络、温度除法和 softmax 一起封装进 ONNX(opset 17)。输入仍是 uint8 [batch,96,96],输出为 [batch,8]。这样调用者不用分别实现温度校准或猜测类别顺序。

桌面 Python 版本使用 ONNX Runtime 的 CPUExecutionProvider。预热后,快速版分类中位延迟约 0.47 ms,精度版约 1.12 ms;在固定 640×480 单人脸测试图上,检测、裁剪和分类合计中位延迟约 9.30 / 10.43 ms。前一项不含人脸检测,后一项不含摄像头采集、解码和显示,均不能直接当作手机 FPS。

博客版使用 ONNX Runtime Web 1.30.0 的 WebAssembly CPU 后端,在独立 Web Worker 中运行。两个分类器的 ONNX 文件直接复制自训练产物,没有量化、蒸馏或更换权重。YuNet 只调整输入/输出的动态尺寸声明,以接受与 OpenCV 一样的 32 像素倍数补边;检测权重保持不变。

浏览器先下载模型,之后照片和相机像素只在本机内存与工作线程间传递。当前模型加载完后,同一页面的识别不需要向服务器发送图片。首次打开、刷新或首次切换未缓存模型仍可能需要联网;网站正常访问日志和模型下载记录不属于图片推理上传。

浏览器的图像解码、缩放和重采样与 OpenCV 可能有细微舍入差异,因此“使用同一权重”不等于“整条图像链逐位相同”。移植时应分别验证:固定灰度输入的概率一致性,以及真实照片从检测到分类的行为。网页端没有完成全量测试集重测,本文准确率仍明确指桌面评估协议。

摄像头预览独立播放,优先请求 60 FPS;识别目标默认 30 次/秒,可调整到最高 60 次。两项都是目标,实际数值由硬件和处理能力决定。页面分别统计画面帧率与识别频率,每次只处理一帧,避免排队;暂停时冻结最近完成识别的对应画面,关闭相机时停止设备轨道。浏览器 CPU 也能使用,不要求独立 GPU。

09 · 如何复核和复现

项目里的流程脚本按“下载 → 预处理 → 去重合并 → 验证划分 → 训练 → 导出校准 → 外部比较 → 速度测试”运行。下面展示关键命令;需要先准备相同来源的数据、原 FER 检查点和项目依赖,不能只凭两行命令从空目录复现全部结果。

python prepare_expanded_sources.py --source affectnet
python merge_expanded_data.py
python validate_expanded_data.py

python train.py --run expanded_accurate --arch large --size 144 \
  --epochs 24 --batch-size 192 --lr 0.0001 --patience 6 \
  --class-balance 0.4 --selection source_macro --missing-class-weight 0 \
  --data-dir data/expanded --init-checkpoint runs/accurate/best.pt

python train.py --run expanded_fast --arch small --size 112 \
  --epochs 20 --batch-size 192 --lr 0.00015 --patience 6 \
  --class-balance 0.4 --selection source_macro --missing-class-weight 0 \
  --data-dir data/expanded --init-checkpoint runs/fast/best.pt

python evaluate_export.py --run expanded_accurate
python evaluate_export.py --run expanded_fast
python compare_expanded_models.py

命令使用类 Unix 的反斜杠换行示意;Windows PowerShell 可将每条命令写在一行。项目保存了随机种子、硬件、参数、训练历史、划分内容指纹和最佳轮次。复现实验不仅要使用同名数据集,还要确认清洗规则、保留图片和划分内容相同。

类别顺序固定为 neutral, happy, surprise, sad, angry, disgust, fear, contempt。读取输出时应使用元数据顺序;网页为便于阅读调整了分数条的展示顺序,不应据此重新解释模型输出数组。

10 · 下一步如何验证精度提升

第一步值得试验的是从原始彩色图片重新制作更高分辨率的人脸,并让训练与推理使用一致的关键点对齐;单纯把现有 96×96 灰度图放大不会恢复细节。第二步是比较人脸专用预训练骨干,再针对轻蔑等薄弱类别审计难例、来源差异和不明确标签。第三步才是把更强教师的知识蒸馏给小模型,验证是否能保留 CPU 速度。

摄像头应用还需要单独的视频评估:短时信息融合可能减少抖动,也会增加响应延迟。对自己常用的手机,可以收集另一天、另一次拍摄的标注样本,按人物或拍摄场次隔离验证,不能把相邻帧随机切成训练与测试后就宣布提升。

这些改进应逐项做对照:用验证集选方案,再用未参与训练和调参的留出数据验收,同时记录全部样本准确率、Macro-F1、各类别召回、拒判覆盖率与实际端到端延迟。提高阈值、降低阈值或者让画面更流畅,都不能代替模型精度的独立证据。

实现参考:torchvision MobileNetV3、OpenCV YuNet、ONNX Runtime Web 部署文档。页面随附 ONNX Runtime 许可与 YuNet 许可;这些依赖的许可不替代各训练数据来源的条款。

TRY BOTH MODELS

把训练结果,放回真实画面。

打开双模型表情实验室 →