☰
微信小程序+Python猫狗表情识别:CNN训练与Flask部署全流程
2026/9/24 22:41:12 网站建设 项目流程

简介:面向小程序端的猫狗表情识别工程,基于Python与PyTorch构建,内含完整图片数据集与训练、部署代码,适合有基础深度学习知识、想实现前后端识别应用的开发者和学生。工程覆盖数据集预处理(短边补灰边、旋转扩增)、训练集/验证集文本生成、模型训练、Flask服务端以及小程序前端展示页面,可直观理解从数据准备到服务上线的完整流程。压缩包共492个文件,以468张jpg图片数据集为主,配合3个Python脚本、6个json配置、小程序相关js/wxml/wxss文件以及环境依赖txt,包体仅30.88MB,便于快速下载与运行。目前已吸引156人学习,对于希望复现一个小型深度学习识别项目或作为毕业设计/课程作业参考的读者,是一份轻量且结构清晰的实用素材。

1. 猫狗表情识别不是猫狗分类:这个压缩包到底解决什么问题

看到这个标题的读者,十有八九是想拿猫狗表情识别做一个练手项目或毕业设计。别急着解压那份 zip,先想清楚一个问题:它最终要给谁用。如果只是本地跑一个 predict.py,那标题前半段“小程序版”就多余了;既然压缩包里带了图片数据集和微信小程序端,说明这是一条完整链路:小程序负责拍照和展示,Python 深度学习模型在服务端做推理。

猫狗表情识别比猫狗分类难一个量级。狗的耳朵形态、嘴部结构差异远大于人脸的微笑和愤怒,模型非常容易把“张嘴”当“开心”,把“飞机耳”当“害怕”,这跟做人脸情绪识别完全是两套思路。这套项目适合两类人:想系统走一遍深度学习实战全流程的学生,以及需要一个手机端产品原型的开发者。本文就按数据组织、模型训练、后端接口、小程序对接的顺序把这条链路拆开,包括我踩过的坑和调参经验。

2. 架构怎么拆:微信小程序做前端,Python 后端扛推理

很多初学者拿到这个压缩包,第一个念头是把模型塞进手机里跑。如果你是纯本地实验,那没问题;但一旦涉及微信小程序,这条路基本走不通,下面先说清楚为什么,再给出一套可落地的架构分工。

2.1 为什么不在微信小程序里直接跑深度学习模型

微信小程序的包体积限制是硬约束。主包不能超过 2MB,整个小程序总包一般限制在 20MB 以内,一个 MobileNetV3 的 ONNX 模型文件就要 15MB 左右,ResNet18 更是直接奔着 45MB 去,光模型就把包撑爆了,还不算框架运行时。这是第一道坎。

其次是推理性能。小程序的运行环境是 WebView 加 JSCore,普通的 JS 引擎跑图像预处理都吃力,更别说跑卷积运算。就算用 TensorFlow.js 或 ONNX Runtime Web 勉强推理,用户的手机性能差异极大,中低端安卓机上一张 224x224 的图可能要等两三秒,体验很难接受。

第三个问题是模型更新。深度学习模型大概率要迭代,你今天部署 v1,明天发现猫的“生气”样本不够,要重新训练。模型在服务端,你只需要替换一个文件;模型在客户端,你得发版、过审、等用户更新,周期以天计。所以这个压缩包最合理的架构就是:微信小程序端只做图片采集、压缩、展示和请求,Python 后端负责加载模型、跑推理、返回结构化结果。

角色职责技术选型
微信小程序端拍照/选图、压缩、转 base64、展示结果WXML + JavaScript + wx.request
Python 后端接收图片、预处理、模型推理、返回 JSONFlask / FastAPI + PyTorch
模型文件训练产物,承载分类能力ResNet18 / MobileNetV3 权重文件

数据流一句话就能说清:小程序把图片压缩后转成 base64 字符串,POST 到 Python 后端的/predict接口,后端解码、预处理、送入模型,再返回“类别 + 置信度”的 JSON。第 4 章会给完整代码。

2.2 后端框架选型:Flask 够用,FastAPI 更舒服

这个压缩包的 Python 后端部分,最常带的往往是 Flask 版本,原因是它依赖最少、源码最容易看懂,对刚开始接触深度学习落地的读者最友好。Flask 的 wsgi 模型是同步的,一个请求占一个 worker 线程,对于个人项目、毕设演示、内部工具来说完全够用。

我自己的习惯是:如果只是把模型包成接口给小程序调,用 Flask;如果还想加批量预测、流式返回、自动生成 Swagger 文档,那优先 FastAPI。FastAPI 的异步机制在处理并发时更有优势,但代价是引入 pydantic 和 uvicorn,心智负担高一点。

# 常见做法:先建虚拟环境,再装依赖 python -m venv venv source venv/bin/activate # Windows 上是 venv\Scripts\activate pip install flask torch torchvision pillow

装依赖这一步很多人踩坑。PyTorch 的 CPU 版和 GPU 版安装方式不同,如果你只是部署一个识别服务,CPU 版足够;如果你要训练模型,才需要装 CUDA 版。建议到官网按操作系统和 CUDA 版本复制安装命令,不要直接用pip install torch,那会默认装 CPU 版,训练慢到你怀疑人生。

2.3 模型选型:ResNet18 打底,MobileNet 留给手机端

猫狗表情识别是一个典型的图像分类问题,输入一张图片,输出一个类别。这类问题不需要上 Transformer 之类的复杂结构,一个在 ImageNet 上预训练过的 CNN 模型,迁移到猫狗表情数据集上,效果就非常能打。

这个项目里标签组合很关键。最常见的标签设计是四类:

类别名含义
cat_happy猫开心
cat_angry猫生气(或害怕)
dog_happy狗开心(吐舌头、摇尾巴、张嘴)
dog_angry狗生气(龇牙、飞机耳)

注意这里把“猫狗分类”和“表情分类”合并成了一个四分类问题,而不是先做猫狗二分类再做表情二分类。原因很简单:数据集是配套的,四类标签直接对应四个目录,用 ImageFolder 就能加载,训练路径最短。如果你拿到的是猫、狗、表情分开标注的数据集,才需要改成先检测再分类的两段式方案。

模型方面,第 3 章的训练代码我会用 ResNet18,它在 torchvision 里有预训练权重,模型文件约 45MB,放在服务器上没有任何压力。如果你想让模型更轻量,把训练代码里的resnet18换成mobilenet_v3_small,其他部分不用改,权重文件立刻降到 10MB 左右。这个替换成本极低,建议在两台机器上都试一下,对比准确率和推理耗时的差距。

3. 用自带图片数据集训练 CNN:目录结构、数据增强与迁移学习参数

训练是整套系统里最“玄学”的环节,很多读者拿到压缩包直接跑训练脚本,发现 val_acc 卡在 70% 上不去,然后开始怀疑数据有问题。其实大部分情况是目录结构没理解对、增强参数没调、迁移学习的冻结策略不对。这一章把这些事情依次说透。

3.1 数据集的目录结构:先搞清楚标签是什么

解压图片数据集之后,第一件事不是看图片长什么样,而是看目录结构。这套项目常见的数据组织方式是 ImageFolder 格式,也就是每个类别一个目录,目录名就是标签。

# 期望的目录结构(以项目实际解压结果为准) data/ ├── train/ │ ├── cat_happy/ │ ├── cat_angry/ │ ├── dog_happy/ │ └── dog_angry/ └── valid/ ├── cat_happy/ ├── cat_angry/ ├── dog_happy/ └── dog_angry/

如果压缩包里解压出来只有一个总目录,没有划分 train 和 valid,你需要自己按比例切分。我一般写一个split.py,把每个类别的图片随机取 85% 放进 train,15% 放进 valid。这个操作千万别省,动手深度学习的第一步就是建立数据集的信任——不划分验证集,你根本不知道模型训练到什么程度算好,后面的一切调参都是对着空气打拳。

验证集划分还有一个容易忽略的点:同一个来源的相似图片可能被分到 train 和 valid 两边,导致验证集准确率虚高。这个压缩包的图片大多来自互联网爬取,同一视频抽帧的连续帧很可能重复出现,建议划分时用文件名前缀或图片哈希做个简单的去重,能减少一部分“假高分”。

3.2 数据增强的 transforms:表情识别对局部特征更敏感

猫狗表情识别的难点在于,表情差异往往体现在嘴部、耳朵、眼睛这些局部区域,而猫狗的品种差异会带来巨大的整体外观变化。如果只做简单的 Resize,模型很容易学会“识别品种”而不是“识别表情”,因为品种特征比表情特征更容易学。

# dataset.py from torch.utils.data import DataLoader from torchvision import datasets, transforms # 训练集增强:尺度、翻转、旋转、颜色抖动 train_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) # 验证集只做 Resize 和标准化,不做随机增强 valid_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) train_dataset = datasets.ImageFolder("data/train", transform=train_transforms) valid_dataset = datasets.ImageFolder("data/valid", transform=valid_transforms) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True, num_workers=4) valid_loader = DataLoader(valid_dataset, batch_size=32, shuffle=False, num_workers=4)

RandomRotation 我建议只给 10 到 15 度。猫狗照片大多是水平视角,旋转角度太大,会把地面、天花板拍进来,反而让模型学到不真实的角度特征。ColorJitter 的亮度对比度扰动也是同理:给 0.2 是模拟手机在不同光线下的成像差异,给到 0.5 以上,图片颜色严重失真,模型学到的颜色分布和真实场景脱节。

Resize 到 224x224 是 ResNet 系列的标准输入,这个值不要随便改。改小了模型的分辨率不够,表情细节丢失;改大了会增加显存占用,而且 ImageNet 预训练权重是在 224 分辨率上学习的,迁移时硬用 384 分辨率,效果往往不升反降。

3.3 迁移学习训练:冻结与解冻的四个必调参数

训练部分直接上代码。这里用 PyTorch 加载 ImageNet 预训练的 ResNet18,把最后一层换成 4 分类输出,并冻结前三个 block,只训练最后一个 block 和全连接层。这是迁移学习的标准做法。

# train.py 核心片段 import torch import torch.nn as nn import torchvision.models as models # 加载 ImageNet 预训练权重 model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) num_classes = len(train_dataset.classes) # 4 model.fc = nn.Linear(model.fc.in_features, num_classes) # 冻结前三个 block,只训练 layer4 和 fc for name, param in model.named_parameters(): if "layer4" not in name and "fc" not in name: param.requires_grad = False criterion = nn.CrossEntropyLoss() # 只把 requires_grad=True 的参数传给优化器 optimizer = torch.optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4, ) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)

这里重点说四个参数。第一个是lr=1e-4:迁移学习微调时,预训练权重已经很接近最优解,学习率太大一步就把学好的特征冲没了,1e-4 是我试过最稳的起点。第二个是step_size=5, gamma=0.5:每 5 个 epoch 学习率减半,让训练后期在小步长下精细收敛。第三个是batch_size=32:对 224x224 的 ResNet18,8GB 显存能跑 32,16GB 显存可以试 64,再大收益有限。第四个是冻结策略:这里冻结到layer4之前,如果你的数据集很小(几百张),可以冻结更多层;数据集够大(几千张),可以只冻结layer3之前,让更多层参与微调。

训练 epoch 数我一般设置在 25 到 30 之间,每 5 个 epoch 在验证集上算一次准确率,保存验证集准确率最高的权重为best_model.pth。注意保存时用torch.save(model.state_dict(), "best_model.pth"),别整个 model 直接存,后者在加载时和类定义耦合紧密,换个文件名或目录就报错。

3.4 训练完别急着部署:先看损失曲线的形态

训练脚本跑完,如果验证集准确率到了 88% 以上,可以进入部署阶段。如果卡在 80% 以下,先别调模型结构,回去看两个东西。

第一个是训练集和验证集的准确率差距。如果训练集 95%、验证集 75%,这是典型的过拟合,对应的做法是增强数据增强的强度、加入 Dropout、或者把模型换成参数量更小的 MobileNetV3。第二个是 loss 曲线是不是“锯齿状”剧烈震荡,这说明学习率太大,把初始学习率从 1e-4 降到 3e-5,震荡通常会缓解。

我在实操中遇到过 loss 一直在 1.3 左右死活不降的情况,反复检查才发现数据增强里加了RandomGrayscale(p=0.5),一半图变成灰度,颜色特征被严重破坏。这类翻车很隐蔽,排查时先逐步注释增强项,把增强强度降到零,确认模型能过拟合训练集,再一项项加回来。

4. 把模型接到小程序:Flask 接口与 wx.request 的完整链路

模型训练完毕,剩下的工作就是把模型从“训练产物”变成“可被小程序调用的服务”。这一章给出一套能直接跑通的最小实现,涉及后端接口、小程序端请求、以及真机调试时的域名配置。

4.1 后端推理接口:接收 base64,返回类别和置信度

推理接口的核心就三件事:接收图片数据、预处理、返回结构化结果。这里选择 base64 字符串而不是 multipart 文件上传,原因有两个:小程序端读取本地文件转 base64 非常方便,不需要引入上传组件;后端解析 base64 不需要处理临时文件,减少磁盘 IO 和清理逻辑。

# app.py import base64 import io import torch from flask import Flask, request, jsonify from PIL import Image from torchvision import transforms app = Flask(__name__) # 加载训练好的权重,模型结构要和训练时保持一致 model = models.resnet18(weights=None) model.fc = nn.Linear(model.fc.in_features, 4) model.load_state_dict(torch.load("best_model.pth", map_location="cpu")) model.eval() classes = ["cat_angry", "cat_happy", "dog_angry", "dog_happy"] # 注意:推理时的预处理不能包含随机增强 infer_transforms = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) @app.route("/predict", methods=["POST"]) def predict(): data = request.get_json() if not data or "image" not in data: return jsonify({"error": "missing image"}), 400 try: img_bytes = base64.b64decode(data["image"]) img = Image.open(io.BytesIO(img_bytes)).convert("RGB") except Exception: return jsonify({"error": "invalid image"}), 400 tensor = infer_transforms(img).unsqueeze(0) with torch.no_grad(): logits = model(tensor) prob = torch.softmax(logits, dim=1) idx = torch.argmax(prob, dim=1).item() return jsonify({ "label": classes[idx], "confidence": round(float(prob[0][idx]), 4), }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)

代码里有两处细节值得注意。第一处是img.convert("RGB"):很多手机拍出来的图片可能是带透明通道的 PNG,PIL 读进来是四通道,直接送进模型会报通道数不匹配,convert 统一成三通道是从根源上规避这个错误。第二处是model.eval():PyTorch 的 BatchNorm 和 Dropout 在训练和推理时行为不同,不调用 eval 模式,预测结果会有随机性,同一个输入两次输出不一样。

confidence用round限制到 4 位小数,是为了让返回的 JSON 更干净,也给前端一个明确的数据格式。输出的 label 我建议用英文标识,因为小程序端可以根据这个字符串做中文本地化,减少前后端耦合。

4.2 小程序端请求:chooseMedia、压缩、转 base64

小程序端的调用链路是:用户选图或拍照 → 压缩 → 读文件转 base64 → wx.request POST → 渲染结果。这里给出核心的 JS 代码。

// pages/index/index.js Page({ data: { resultText: '', confidence: 0, previewPath: '', }, async onChooseImage() { const res = await wx.chooseMedia({ count: 1, mediaType: ['image'], sizeType: ['compressed'], }); const filePath = res.tempFiles[0].tempFilePath; this.setData({ previewPath: filePath }); const base64 = this.fileToBase64(filePath); this.predict(base64); }, fileToBase64(filePath) { const fs = wx.getFileSystemManager(); const buffer = fs.readFileSync(filePath); return wx.arrayBufferToBase64(buffer); }, predict(base64) { wx.showLoading({ title: '识别中' }); wx.request({ url: 'http://127.0.0.1:5000/predict', method: 'POST', header: { 'Content-Type': 'application/json' }, data: { image: base64 }, success: (resp) => { if (resp.statusCode === 200) { this.setData({ resultText: resp.data.label, confidence: resp.data.confidence, }); } else { wx.showToast({ title: '识别失败', icon: 'none' }); } }, fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), complete: () => wx.hideLoading(), }); }, });

wx.chooseMedia是当前推荐的选图接口,它替代了老旧的wx.chooseImage,支持拍照和相册二选一,返回的tempFiles数组里每个元素带tempFilePath临时路径。sizeType: ['compressed']是关键参数,让微信先做一次有损压缩,虽然只是压缩到 720p 级别,但能让后端的解码和推理压力小一个档次。

fileToBase64里用了wx.getFileSystemManager().readFileSync同步读取,配合wx.arrayBufferToBase64转成字符串。这里有个小坑:大图转出来的 base64 字符串可能有几百 KB,微信小程序的wx.request对请求体大小有限制,超过 1MB 会被直接拒绝。所以小程序端压缩是必选动作,不是可选优化。

4.3 域名与真机调试:开发工具和线上环境的三点差异

这是初学者翻车率最高的区域,单独拿出来说。在微信开发者工具里,默认情况下wx.request只能请求合法域名,但开发模式下有一个开关可以绕过这个限制。

提示:在开发者工具的“详情 → 本地设置”中勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”,开发阶段才能请求http://127.0.0.1:5000这样的本地地址。这个开关只在开发工具有效,真机预览上依然会拦截。

真机上的情况更严苛。手机访问电脑上的后端服务,需要电脑和手机在同一局域网,后端启动时app.run(host="0.0.0.0")让它监听所有网卡,然后手机请求电脑的局域网 IP,比如http://192.168.1.100:5000/predict。但真机预览模式下,微信对“不校验域名”的约束依然生效,非 https 域名大概率被拦。

所以正式上线时有两件事必须做:一是把后端部署到一台有公网 IP 的服务器上,配置 HTTPS 证书;二是在微信小程序管理后台的“开发管理 → 服务器域名”中添加你的 HTTPS 域名到 request 合法域名列表。注意这里有个前提——域名备案,且必须用 443 端口,不能自定义端口。如果你只是本地演示,卡在局域网阶段就够了;如果要做给外人看,服务器加备案是绕不开的。

5. 训练到上手机的避坑清单:现象、原因、解决办法

这一章是血泪经验的汇总。以下五类问题我在不同项目里反复见到,覆盖了数据、训练、部署三个阶段。

5.1 训练 acc 虚高,一上真实照片就翻车

现象:验证集准确率 92%,用小程序的摄像头对着自家猫狗拍,识别结果完全离谱。

原因:验证集图片和训练集图片来自同一个数据源,光线、角度、清晰度高度相似。真实照片的光线条件和手机压缩画质是训练集里没有的分布。这是最常见的模型过拟合变体——模型记住了数据源的风格,而不是表情的本质。

解决:划分验证集时不要用随机抽样,尽量按图片来源分组,确保验证集来源不和训练集重叠。更简单的方法是留出 20 张真实拍摄的照片,训练结束后单独做一个 sanity check。这一步不能省,它相当于部署前最后一道质检。

5.2 训练时 loss 降到 0.3 以下,但 val_acc 连续 10 个 epoch 不涨

现象:训练集 loss 一路走低,验证集准确率却停在 75% 左右。

原因:模型进入过拟合区间,训练集学得太好,泛化能力没有跟上来。在表情识别这个场景里,最常见的原因是数据增强太弱,模型很快就“背”下了训练集里的图片分布。

解决:先把训练集和验证集准确率的差距记下来。差距大于 15%,说明过拟合严重。把RandomRotation从 10 度调到 20 度,加上RandomResizedCrop模拟不同构图的图片,再把ColorJitter的饱和度扰动从 0.2 提到 0.3。增强强度每调整一次,重新训练一轮,观察差距是否缩小。

5.3 加载模型报“Missing key(s) in state_dict”

现象:后端启动时报错,提示加载 best_model.pth 时缺少 key 或参数尺寸不匹配。

原因:训练时用torch.save(model.state_dict(), ...)保存,但加载前用torch.load直接读取整个对象,或者用 TensorFlow 的方式去读 PyTorch 权重。另一个常见原因是训练时改过num_classes,加载时忘记同步修改model.fc的输出维度。

解决:严格复用 4.1 节的加载方式——先构建模型结构,再load_state_dict。如果模型结构和训练时有任何改动,比如把 ResNet18 换成了 ResNet34,权重就作废了,必须重新训练。这个坑的排查思路是打印 state_dict 的 key 列表,和模型结构的 key 列表逐项比对。

5.4 小程序真机请求报“404”或“request:fail”

现象:开发者工具里一切正常,真机上请求直接失败,报错信息是request:fail或者提示域名不在合法列表。

原因:开发者工具勾选了“不校验合法域名”,掩盖了真实问题。真机上微信强制校验收到的每一项请求。另外,后端没有监听所有网卡时,host="127.0.0.1"只能在同一台机器上访问,手机自然连不上。

解决:用app.run(host="0.0.0.0", port=5000)启动后端,确保手机和电脑在同一局域网。用http://局域网IP:5000而不是127.0.0.1测试。真机联调时,如果依然失败,在小程序后台临时把 IP 加到 request 合法域名,或者退回到开发者工具模拟器继续调试。注意:将局域网 IP 加入合法域名只在开发阶段可行,微信审核时不会接受 IP 地址,线上务必使用备案过的 HTTPS 域名。

5.5 服务端响应慢:一张图要两秒

现象:后端接口返回时间在 1.5 到 2 秒,小程序端体验很差,误以为服务卡死。

原因:模型推理本身耗时是一部分,更多时间浪费在图片解码和预处理上。PIL 打开大图、Resize(224, 224)对特别大的图做插值,再加上 PyTorch 的 CPU 推理,每一环都在累积耗时。

解决:把推理前的图片压缩放在小程序端,限制在后端再检查一遍。Image.open之后先做一次img.thumbnail((512, 512)),减少后续 Resize 的插值计算量。如果服务器有 GPU,把模型to("cuda")推理耗时能降到 50ms 级别;没有 GPU 就转 ONNX Runtime 加载,多数机器上比 PyTorch 原生 CPU 推理快 20% 到 40%。

6. 用混淆矩阵验收模型:找出最容易混的两类表情

很多读者训练完只看一个 val_acc 就收工了,这是不够的。val_acc 是一个平均值,它不会告诉你模型到底在哪些类别上犯糊涂。猫狗表情识别里最典型的混淆是“狗开心”和“狗生气”——两种表情都可能张开嘴、露出牙齿,单一图片上人都不一定能快速判断,模型混淆它们再正常不过。

建议训练完做一次完整测试集评估,直接输出混淆矩阵:

# evaluate.py import torch import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix y_true, y_pred = [], [] with torch.no_grad(): for images, labels in valid_loader: outputs = model(images) preds = torch.argmax(outputs, dim=1) y_true.extend(labels.tolist()) y_pred.extend(preds.tolist()) cm = confusion_matrix(y_true, y_pred) disp = ConfusionMatrixDisplay( confusion_matrix=cm, display_labels=valid_loader.dataset.classes ) disp.plot(cmap="Blues") plt.savefig("confusion_matrix.png", dpi=150)

拿到混淆矩阵后,看两个数值。第一个是“对角线占比”,也就是每个类别的召回率,如果dog_happy的召回率只有 60%,说明这个类别的特征没有学全。第二个是“最大混淆项”,找到矩阵里最大的非对角线数值,比如dog_angry被预测成dog_happy的比例高达 25%,那你要么去补充“生气的狗”样本,要么接受这个边界情况并渲染给用户。

针对混淆率高的类别对,我一般会做两件事。一是收集更多这两个类别的样本,尤其是中间态样本,比如“张嘴但不一定生气”的照片,让决策边界更清晰。二是在后端加置信度阈值,当最高概率低于某个值(比如 0.75)时,返回“不确定,请换一张更清晰的照片”,这比硬给一个错误标签更体面。这个阈值不要拍脑袋定,用验证集上不同阈值的精确率召回率曲线去选。

我的习惯是每调整一次数据或模型结构,就把混淆矩阵存档成 PNG,并记录下来对应的类别名和阈值,一段时间后回看,能清楚看到哪次改动真正解决了问题。这套方案做下来,即使只依赖压缩包自带的图片集,也能把四类表情的准确率稳定在 85% 以上的水平。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询