简介这份资源面向希望入门深度学习与小程序开发的开发者提供一套完整的猫狗表情识别实战方案涵盖从数据预处理、模型训练到服务端部署的全流程。包内共492个文件以468张jpg图片构成核心数据集辅以json配置、js与wxml/wxss小程序页面文件、py脚本及txt说明压缩包约30.88MB结构清晰便于按模块查阅。运行01数据集文本生成脚本可读取各类别图片路径与标签02训练脚本完成模型训练并保存本地模型与逐epoch验证损失及准确率日志03脚本则启动flask服务端供小程序调用。数据预处理阶段通过短边补灰边使图片转为正方形并结合旋转、翻转等方式扩增样本帮助读者理解数据增强对模型泛化的作用。已有156人学习适合作为课程设计或练手项目参考。1. 从一堆乱序图片到能跑的小程序这套猫狗表情识别资源到底值不值得拆如果你手头正好有一批猫狗表情图片想快速搭一个能识别的小程序演示又不想从零写数据加载、模型定义和接口服务那这套「小程序版基于 python 深度学习的猫狗表情识别-含图片数据集.zip」就是冲着你来的。它把数据集、训练脚本、Flask 服务端和小程序前端串成了一条完整链路核心依赖是 PyTorch环境用 requirement.txt 锁定。适合两类人一是刚学完 CNN 想找个能跑通的项目练手二是需要快速出 demo 给业务方看的工程师。但别急着解压这套东西的坑集中在环境版本、数据路径和接口联调上下面按「是什么 → 怎么用 → 坑在哪」拆开讲。2. 资源结构与技术栈先看清 01/02/03 三个脚本各自管什么2.1 数据集目录与图片命名规律解压后你会看到一个数据集文件夹里面按类别分子目录存放图片。从项目正文给出的文件名看命名相当随意92.jpg、6_rotated45.jpg、6_flip.jpg、072.jpg混在一起。这说明数据来源不是标准数据集而是人工收集后做过增强的。_rotated45和_flip后缀直接暴露了预处理动作——旋转 45 度和水平翻转。这种命名方式对训练本身没影响因为脚本是按「文件夹名 标签」来读取的但你要心里有数如果后续想自己加图片保持「一个类别一个文件夹」的结构就行文件名随便起。常见做法是先把数据集整理成下面这种结构再跑 01 脚本dataset/ ├── angry/ │ ├── 92.jpg │ ├── 6_rotated45.jpg │ └── 6_flip.jpg ├── happy/ │ ├── 71.jpg │ └── 108.jpg └── sad/ ├── 69.jpg └── 072.jpg这里的关键参数是类别文件夹名它会直接变成模型输出的标签。如果你后面发现预测结果全是同一类先回头检查是不是某个类别文件夹里图片数量太少导致训练时类别严重不平衡。2.2 三个脚本的职责边界这套资源最清晰的地方就是脚本编号即执行顺序01数据集文本生成制作.py遍历数据集目录把每张图片的路径和对应标签写进 txt 文件同时按比例划分训练集和验证集。02深度学习模型训练.py读取 01 生成的 txt用 PyTorch 定义 CNN 模型跑训练循环保存模型权重和 log 日志。03flask_服务端.py加载训练好的模型起一个 Flask 服务对外提供图片上传和识别接口供小程序调用。这个拆分方式很务实每个脚本只干一件事出问题容易定位。比如训练 loss 不下降你只需要盯 02接口返回 500只需要查 03。比起那种一个文件从头写到尾的项目这种结构对新手友好得多。2.3 环境依赖与版本敏感点摘要里明确说了「基于 python pytorch 环境安装」并且提供了 requirement.txt。这里有个血泪经验PyTorch 版本和 torchvision 版本必须匹配否则 import 直接报错。常见做法是先用 conda 建一个干净环境再按 requirement.txt 装conda create -n catdog python3.8 conda activate catdog pip install -r requirement.txt为什么建议 Python 3.8因为很多老版 PyTorch 对 3.9 支持不好而这套资源的代码风格看起来不是最新版。如果你用 3.10 以上可能会遇到torch.load的weights_only参数问题。装完之后跑一句验证import torch print(torch.__version__) print(torch.cuda.is_available())如果输出 False 但你确定有显卡别急着改代码先检查 CUDA 驱动版本和 PyTorch 的 CUDA 版本是否对应。没有显卡也能跑只是训练慢把 02 脚本里的device改成cpu就行。3. 从图片到 txt01 脚本的预处理逻辑与参数调整3.1 读取目录与标签映射01 脚本的核心动作是os.listdir遍历数据集根目录下的每个子文件夹把文件夹名作为类别标签然后给每个类别分配一个数字索引。这个过程通常用一个字典保存比如{angry: 0, happy: 1, sad: 2}。你不需要手动改这个映射但如果类别顺序变了之前训练的模型就不能直接复用因为输出层神经元顺序对不上。代码逻辑大致如下import os data_dir ./dataset classes sorted(os.listdir(data_dir)) class_to_idx {cls: idx for idx, cls in enumerate(classes)} for cls in classes: cls_dir os.path.join(data_dir, cls) for img_name in os.listdir(cls_dir): img_path os.path.join(cls_dir, img_name) label class_to_idx[cls] # 写入 txtimg_path label注意sorted这一步它保证了每次运行生成的类别顺序一致。如果你手动改了文件夹名或者增删了类别记得重新跑 01否则 02 读到的标签和实际类别会错位。3.2 训练集/验证集划分比例01 脚本还会做一件事按比例把数据分成训练集和验证集通常写成两个 txt 文件比如train.txt和val.txt。常见比例是 8:2 或 7:3。这个比例没有绝对标准但如果你的数据集本身很小比如每类只有几十张验证集太小会导致评估结果波动大。我一般会改成 9:1并且加一个随机种子保证每次划分一致import random random.seed(42) random.shuffle(all_samples) split int(len(all_samples) * 0.9) train_samples all_samples[:split] val_samples all_samples[split:]这里random.seed(42)是后悔药没有它你每次跑 01 得到的划分都不一样调参时根本分不清是模型变了还是数据变了。3.3 数据增强与正方形化处理摘要里提到「在较短边增加灰边使得图片变为正方形」和「旋转角度」。这两个操作在 01 里可能只是生成增强后的图片文件也可能是在 02 的 Dataset 类里用 torchvision.transforms 做在线增强。从文件名出现_rotated45和_flip来看更像是离线增强——也就是提前把增强图片存到磁盘。这样做的好处是训练时不用重复计算坏处是数据集体积膨胀。如果你要自己加增强注意旋转角度别太大。45 度旋转对猫狗表情这种细粒度分类来说已经接近极限再大可能把眼睛和嘴巴的位置关系转乱模型学不到有效特征。灰边填充也要注意颜色值常见做法是填 0黑色或 128灰色填 0 可能在卷积时引入边缘伪影填 128 相对温和。4. 训练脚本 02CNN 结构、日志读取与模型保存4.1 模型定义与输入尺寸02 脚本里的 CNN 结构通常不会太深三四层卷积加池化最后接全连接。输入尺寸取决于 01 预处理后的图片大小。如果 01 把图片统一成了 224×224那模型第一层就要接受3×224×224的输入。你可以在 02 开头找到类似transform transforms.Compose([transforms.Resize((224, 224)), ...])的代码这里的 224 就是关键参数。如果显存不够把 224 改成 128 或 96同时模型里的全连接层输入维度也要跟着改。改错会报size mismatch这是新手最常见的翻车点之一。稳妥做法是先用小尺寸跑通再逐步加大。4.2 训练循环与日志记录训练部分一般是标准的两层循环外层 epoch内层 batch。每个 epoch 结束后在验证集上算 loss 和 accuracy写进 log 文件。摘要说「log 日志保存本地里面记录了每个 epoch 的验证集损失值和准确率」这个日志是你判断模型好坏的主要依据。读日志时重点看两个信号验证集 loss 是否持续下降以及训练集和验证集的 accuracy 差距是否过大。如果训练集 accuracy 到 95% 而验证集只有 60%说明过拟合了常见做法是加 Dropout 或减小模型复杂度。如果验证集 loss 先降后升那就是典型的过拟合拐点可以考虑早停。for epoch in range(num_epochs): model.train() for imgs, labels in train_loader: # 前向、反向、优化 ... model.eval() with torch.no_grad(): # 算验证集 loss 和 acc ... print(fEpoch {epoch}, val_loss: {val_loss:.4f}, val_acc: {val_acc:.4f})4.3 模型保存与加载方式训练完成后02 会把模型权重保存到本地通常是.pth文件。保存方式有两种只存state_dict或存整个模型。推荐只存state_dict因为整个模型 pickle 依赖类定义换环境容易加载失败。torch.save(model.state_dict(), catdog_model.pth)03 加载时就要对应地先实例化模型类再load_state_dict。如果你改了模型结构但忘了重新训练加载旧权重会报 key 不匹配。这时候别硬改代码重新跑一遍 02 最省事。5. Flask 服务端 03 与小程序联调接口、跨域与图片编码5.1 Flask 接口定义与请求格式03 脚本起一个 Flask 服务通常提供一个 POST 接口接收小程序上传的图片返回识别结果。接口路径可能是/predict或/upload。请求格式常见两种multipart/form-data直接传文件或者 base64 编码后放在 JSON 里。小程序端用wx.uploadFile时默认是 multipart所以 Flask 这边要用request.files取。from flask import Flask, request, jsonify app Flask(__name__) app.route(/predict, methods[POST]) def predict(): file request.files[image] img Image.open(file.stream).convert(RGB) # 预处理 模型推理 return jsonify({label: pred_label, score: float(score)})注意convert(RGB)这一步小程序上传的图片可能是 RGBA 或灰度不转成 RGB 会在卷积时报通道数不匹配。5.2 跨域与小程序域名配置小程序请求本地 Flask 服务时开发者工具里可以勾选「不校验合法域名」但真机预览必须把服务部署到 HTTPS 域名并配置到小程序后台。如果你只是本地演示用开发者工具就够了。Flask 这边如果遇到跨域问题可以加flask-corsfrom flask_cors import CORS CORS(app)但小程序请求不是浏览器 AJAX跨域限制主要来自小程序平台本身所以 CORS 更多是给网页调试用。真机联调时确保手机和电脑在同一局域网小程序里请求http://192.168.x.x:5000/predict。5.3 图片预处理一致性这是最容易翻车的地方03 里对图片的预处理必须和 02 训练时完全一致。训练时用了Resize((224,224))和ToTensor()推理时也要一模一样。少一个Normalize或者 resize 尺寸不同预测结果就会莫名其妙。我一般会把预处理写成一个函数02 和 03 共用避免手抄出错。def preprocess(image): transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) return transform(image).unsqueeze(0)unsqueeze(0)是加 batch 维度因为模型接受的是N×C×H×W单张图片只有C×H×W。6. 避坑与排查环境、路径、接口联调中最容易翻车的五件事6.1 现象跑 01 报 FileNotFoundError找不到数据集路径原因脚本里的data_dir写的是相对路径而你在别的目录下执行。或者解压后文件夹名多了层嵌套比如dataset/dataset/angry。解决在 01 开头打印os.getcwd()和os.listdir(data_dir)确认路径存在。稳妥做法是用os.path.dirname(__file__)拼绝对路径别依赖当前工作目录。6.2 现象02 训练时 loss 一直是 nan原因学习率太大或者数据没有归一化像素值 0-255 直接进网络导致梯度爆炸。解决先把学习率降到 1e-4 或 1e-3确认ToTensor()之后有没有加Normalize。如果还没有加上 ImageNet 的均值和标准差。另外检查标签有没有越界比如类别数是 3 但标签出现了 3。6.3 现象03 启动后小程序请求超时原因Flask 默认只监听127.0.0.1局域网内手机访问不到。解决启动时改成app.run(host0.0.0.0, port5000)。同时检查电脑防火墙有没有拦 5000 端口。如果用了公司网络可能还开了 AP 隔离手机和电脑互相 ping 不通换手机热点最省事。6.4 现象预测结果永远返回同一个类别原因模型没训练好或者 03 加载的权重文件是旧的或者预处理不一致导致输入分布偏移。解决先用 02 的验证集跑一遍确认模型在验证集上 accuracy 正常。然后检查 03 里加载的.pth文件修改时间是不是最新。最后逐行对比 02 和 03 的预处理代码确保 resize、归一化完全一致。6.5 现象pip install -r requirement.txt 卡在某个包原因requirement.txt 里可能锁定了某个版本的 torch而你的 CUDA 版本不匹配pip 会尝试从源码编译。解决先单独装 PyTorch去官网选对应 CUDA 版本的命令装完再装其他依赖。如果 requirement.txt 里 torch 版本太老可以手动改成兼容版本但要注意 torchvision 也要同步改。7. 进阶技巧用混淆矩阵定位模型到底在哪些类别上翻车训练日志只告诉你整体 accuracy但猫狗表情识别里模型很可能把「生气」和「难过」搞混因为这两个表情在猫狗脸上差异不大。这时候光看 accuracy 没用得看混淆矩阵。做法很简单在 02 训练完后用验证集跑一遍预测把真实标签和预测标签收集起来用 sklearn 画矩阵。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt all_preds [] all_labels [] model.eval() with torch.no_grad(): for imgs, labels in val_loader: outputs model(imgs.to(device)) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annotTrue, fmtd, xticklabelsclasses, yticklabelsclasses) plt.savefig(confusion_matrix.png)拿到矩阵后重点看非对角线上的数字。如果「生气」被预测成「难过」的次数特别多说明这两个类别的特征在模型眼里太接近。常见做法是针对性补充这两类的训练数据或者在损失函数里给这两个类更高权重。我一般还会把错分图片单独导出来看有时候是标注错了有时候是图片本身模糊到人都分不清。另一个实用技巧是给模型加一个置信度阈值。当最高概率低于 0.6 时接口返回「无法识别」而不是硬猜一个类别。小程序端拿到这个结果可以提示用户重新拍照体验比乱返回一个好得多。从那以后我每次跑完训练都强制先看混淆矩阵再决定要不要调参光看 accuracy 太容易自我感觉良好了。希望帮到你。本文还有配套的精品资源点击获取