基于ResNet的动物图像分类系统:PyTorch训练与Flask部署
简介一套基于残差网络ResNet的动物图像分类系统完整项目使用Python编程语言开发桌面端采用PyQt框架构建图形界面服务端通过Flask轻量级框架与HTML5网页技术提供浏览器访问入口模型训练与推理部分基于PyTorch深度学习库实现。项目面向Python期末大作业场景适合正在学习深度学习和计算机视觉的学生作为课程设计或毕业设计的参考案例。压缩包共27个文件整体大小约41.75兆字节其中包含8个Python源代码文件、1个已训练好的ResNet18权重文件、12张界面与示例图片、1个HTML页面模板及1份项目说明文档方便对照学习。当前已有49人浏览学习。资源完整覆盖了从数据预处理、模型构建、参数调优到前后端服务部署的典型流程特别展示了PyQt与Flask两种交互方式的整合思路能够帮助读者快速掌握从训练模型到落地成应用的全套方法。1. 基于ResNet的动物图像分类一个能直接交作业的完整系统如果你是Python期末大作业不知道做什么、或者正在为“分类模型 界面展示 Web部署”三件套发愁这份基于ResNet的动物图像分类系统资源能直接帮你省掉两周时间。它不是单一的算法文件而是一套完整的项目包PyTorch训练脚本负责出模型PyQt5搭桌面端用于本地演示Flask后端加HTML5前端组成网页版交互界面。三种入口共用同一个ResNet权重训练一次随处展示。你拿到手之后只需要配好环境、跑训练脚本、启动服务就能在本地完成“上传图片→模型推理→返回分类结果”的完整闭环。不管你是期末要交大作业还是想自己练一遍ResNet的完整落地流程这套资源都值得下下来跑一跑。2. 系统架构与运行链路Flask、PyQt和HTML5各负责什么2.1 三种前端入口与PyTorch模型的调用关系这个项目最值得先弄清楚的地方是它的“一鱼三吃”结构。很多课程设计的通病是只能在一个界面上跑换一种交互方式就得重新写一套逻辑但这份资源把界面层和推理层拆开了。核心推理由PyTorch训练的ResNet模型完成模型本身只做一件事接收预处理后的图像张量输出每个类别的概率分布。而PyQt桌面端、Flask Web端、以及浏览器里的HTML5页面都是这个模型的不同客户端。实际运行的时候Flask和PyQt走的是两条独立的调用路径。Flask端是典型的B/S架构浏览器打开HTML5页面上传图片后表单提交到Flask后端路由Flask读取图片、调用ResNet模型进行推理把类别和置信度以JSON格式返回给前端渲染。PyQt端则更直接它作为桌面应用程序内部通过torch.load加载模型权重在本地完成图片预处理和推理结果直接显示在窗口控件上不依赖任何Web服务。这样一来你既能在答辩现场打开网页演示也能在没有浏览器的实验环境下用桌面端兜底。2.2 模型推理的完整数据链路无论从哪个入口发起请求数据都要经过一条固定的处理管线。原始图片先经过PIL或OpenCV读取Resize到224×224像素这是ResNet系列的标准输入尺寸。然后转换为张量除以255做归一化处理再按ImageNet数据集的统计值做标准化——mean和std分别取[0.485, 0.456, 0.406]和[0.229, 0.224, 0.225]。这一步非常关键因为如果直接输入未标准化的图片模型输出的置信度分布会明显异常很多“模型预测不准”的问题其实都出在这里。推理阶段默认使用CUDA加速代码里通常会这样判断设备import torch # 优先使用GPU否则回退到CPU device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing device: {device}) # 加载训练好的ResNet权重 model torch.load(model/best_model.pth, map_locationdevice) model.eval() # 切换到推理模式关闭Dropout和BatchNorm的动态更新model.eval()是新手最容易漏掉的一行。在训练模式下BatchNorm层会持续更新均值和方差如果推理时忘了切换同一个模型在不同图片上的输出会漂移。Flask端每次请求都直接调用封装好的推理函数所以模型初始化只做一次避免每次请求都重复加载权重拖慢响应。3. 环境搭建与依赖清单从零配齐 PyTorch、PyQt5 和 Flask3.1 Python 版本与 PyTorch 的匹配关系先说结论这个项目的依赖组合不算新Python 3.8 或 3.9 是兼容性最稳的选择。PyQt5 对 Python 3.10 以上的支持虽然没问题但 PyTorch 在部分 Windows 环境下的 CUDA 版本与 Python 版本存在对应关系盲目装最新版反而可能翻车。为了前后端都少出幺蛾子我建议用 Anaconda 建一个独立环境别直接装在基础环境里。这能省掉后续大量“两个项目各自的依赖互相打架”的麻烦。创建环境后装依赖核心的安装语句如下# 创建并激活Python 3.8环境 conda create -n animal_classify python3.8 conda activate animal_classify # 安装PyTorch CPU版稳定可靠 pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cpu # 安装桌面端和Web端依赖 pip install pyqt5 flask flask-cors pillow numpy如果你的机器有NVIDIA显卡且配置好CUDA可以把--index-url换成对应的CUDA版本号比如cu117或cu118。但说实话期末大作业的数据量通常不大CPU训练ResNet18也就几十分钟到几小时的事没有必要为了速度快一点去折腾CUDA环境。3.2 项目文件结构与每个文件的作用下载资源包后第一件事是看目录结构。一个规范的期末大作业项目一定包含训练、推理、界面三个独立模块。通常你会看到这些文件文件/目录作用train.py数据集加载、模型训练、验证集评估输出最佳权重model.pyResNet模型的构建支持resnet18/resnet50predict.py单张图片推理脚本供测试和整合调用app.pyFlask应用入口定义路由和推理接口ui_main.pyPyQt5桌面端主窗口实现templates/index.htmlFlask的HTML5前端页面static/CSS和JavaScript资源dataset/训练和验证图片按类别分文件夹model/best_model.pth训练产出的最佳权重文件dataset目录的组织方式直接影响PyTorch的torchvision.datasets.ImageFolder能否直接读取。标准格式是主目录下每个子文件夹代表一个类别文件夹名字就是标签名。比如dataset/train/cat/*.jpg、dataset/train/dog/*.jpg验证集做同样的组织。4. 训练流程与核心实操数据集准备到模型保存4.1 用ImageFolder加载数据并划分训练集数据准备阶段最省事的方式是利用torchvision.datasets.ImageFolder自动按子文件夹名生成标签。训练前把数据集按8:2或7:3的比例切分代码里常用random_split或者手动移动文件。这里给出一个完整的训练脚本框架import torch import torch.nn as nn from torch.utils.data import DataLoader, random_split from torchvision import datasets, transforms, models import os # 数据增广与预处理 train_transform transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 加载全部训练数据并按比例切分 full_dataset datasets.ImageFolder(rootdataset/train, transformtrain_transform) train_size int(0.8 * len(full_dataset)) val_size len(full_dataset) - train_size train_dataset, val_dataset random_split(full_dataset, [train_size, val_size]) # 验证集使用独立的transform val_dataset.dataset.transform val_transform train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4) print(f训练集样本数: {len(train_dataset)}, 验证集样本数: {len(val_dataset)}) print(f类别数: {len(full_dataset.classes)})这里有个常见误操作random_split切分后验证集仍然沿用训练集的transform导致验证时使用了随机裁剪和翻转评估指标虚高或者不稳定。解决办法是在切分后手动把验证集的transform替换成val_transform代码中val_dataset.dataset.transform val_transform做的就是这件事。4.2 加载预训练ResNet并替换全连接层ResNet的核心优势在于残差结构它解决了深层网络梯度消失的问题。对于期末项目直接用models.resnet18(pretrainedTrue)加载ImageNet预训练权重然后替换最后一层全连接输出类别数改为自己的动物种类数即可。这样既能借助预训练模型强大的特征提取能力又不需要从零训练几百个epoch。# 以resnet18为例加载预训练权重并替换分类头 def build_model(num_classes): model models.resnet18(pretrainedTrue) # 冻结前面的特征提取层 for param in model.parameters(): param.requires_grad False # 解冻最后一层残差块做微调 for param in model.layer4.parameters(): param.requires_grad True # 替换全连接层AdaptiveAvgPool后接新的分类层 in_features model.fc.in_features model.fc nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, 256), nn.ReLU(), nn.Linear(256, num_classes) ) return modelpretrainedTrue会自动从网上下载权重文件国内网络环境下经常卡在这一步。如果下载慢或失败先从官网镜像手动下载resnet18-f37072fd.pth放到~/.cache/torch/hub/checkpoints/目录下再设置pretrainedTrue时会直接读取本地文件。这是很多人在第一步就翻车的重灾区。训练时的损失函数用nn.CrossEntropyLoss()优化器用Adam比较省心。微调场景下学习率设置到1e-4左右全连接层因为随机初始化学习率可以高一些。完整训练循环里每个epoch结束后在验证集上计算准确率保存验证集表现最好的那个权重最后在脚本结束前打印一条最佳准确率记录方便答辩时汇报。5. 避坑指南训练到部署最常见的五个“翻车”现场5.1 图片读取报错PIL无法打开损坏文件现象训练到中途抛异常提示OSError: image file is truncated或cannot identify image file。原因数据集里混入了下载不完整的图片或者数据集来源是网络爬虫时图片本身就损坏了。PyTorch默认遇到这种图片直接报错中止训练。解决在自定义Dataset的__getitem__里捕获异常并跳过或者训练前先写脚本全量扫描一遍数据集import os from PIL import Image bad_images [] for root, dirs, files in os.walk(dataset): for f in files: if f.lower().endswith((.jpg, .jpeg, .png)): path os.path.join(root, f) try: img Image.open(path) img.verify() # 验证图片完整性 except Exception: bad_images.append(path) print(f损坏图片数量: {len(bad_images)}) # 删除或人工检查 for p in bad_images: os.remove(p)5.2 Flask启动后界面能开但上传图片一直无响应现象python app.py启动成功浏览器也能打开页面但一上传图片就转圈最后报连接错误。原因Flask默认的app.run()是单线程模式如果推理函数里加载模型或处理大图耗时较长第一个请求阻塞后第二个请求排队等待超过浏览器超时时间就报错。另一个常见原因是Flask启动的端口和防火墙冲突比如某些环境下5000端口被其他程序占用。解决启动时显式开启多线程并把模型加载放在全局作用域而不是函数内部from flask import Flask, request, jsonify import torch app Flask(__name__) # 全局加载一次避免每次请求都重复读权重 model build_model(num_classes10) model.load_state_dict(torch.load(model/best_model.pth, map_locationcpu)) model.eval() # 开启多线程模式禁止缓存 app.run(host0.0.0.0, port5000, threadedTrue)threadedTrue能保证Flask的每次请求都在独立线程中处理前一个推理就算耗时较长也不会阻塞后面的请求。host0.0.0.0允许局域网内其他设备通过IP访问方便答辩时让老师用自己的手机或电脑连到你的服务上体验。5.3 PyTorch版本与CUDA不匹配torch.cuda.is_available()返回False现象代码注释里写torch.cuda.is_available()应该返回True但实际输出False程序自动回退到CPU训练速度慢得无法接受。原因三种可能。一是安装的是CPU版PyTorch二是CUDA驱动版本太旧不兼容PyTorch需要的CUDA工具包版本三是PyTorch版本和显卡驱动不匹配比如安培架构显卡配了老版本PyTorch。解决先用nvidia-smi查看CUDA驱动版本然后到PyTorch官网选择匹配的安装命令。如果显卡是10系或20系装cu117或cu118版本最稳。如果完全不想折腾就老老实实用CPU版做训练只要数据集不超过几千张ResNet18在普通笔记本上也能在半天内完成训练。期末项目追求的是能跑通、有结果、有分析不要在环境上死磕GPU。5.4 Flask路由无法绑定到HTML5页面元素现象前端页面上传按钮点击后没有反应或返回的JSON在页面上一闪而过没有显示在指定的位置。原因前后端没有正确对接。HTML5的form表单提交地址和Flask路由不一致或者前端用了JavaScript的fetch但没有正确处理异步返回。常见新手错误是Flask端返回的JSON里键名是class_name前端代码里却读取result。解决先厘清接口协议。Flask端定义明确的响应格式前端fetch拿到数据后按相同的键名渲染app.route(/predict, methods[POST]) def predict(): file request.files.get(image) if file is None: return jsonify({code: 1, msg: 未接收到图片文件}) # 调用推理函数 class_name, confidence inference(file) return jsonify({code: 0, class_name: class_name, confidence: confidence})相应的前端要按code判断请求是否成功失败时把msg显示在页面红色提示区域而非空白处。这一步是Web端整合里最容易卡壳的位置建议先用Postman直接向http://127.0.0.1:5000/predict发POST请求调试接口确认后端逻辑无误后再处理前端展示。5.5 训练完成后加载权重报错或者分类数目对不上现象报错信息类似size mismatch for fc.3.weight: copying a param with shape torch.Size([256, 512]) from checkpoint, the shape in current model is torch.Size([64, 512])。原因训练时和推理时构建的模型结构不一致。比如训练用了resnet18但推理脚本写成了resnet50或者训练时自定义了全连接层只有2层而推理时直接用了默认的ResNet结构。解决把构建模型的代码单独封装成一个函数训练脚本和推理脚本统一导入这个函数确保模型定义完全一致。同时保存权重时用torch.save(model.state_dict(), best_model.pth)加载时用model.load_state_dict(torch.load(...))而不是直接torch.save(model, ...)。后者会把模型类也序列化进去一旦文件复制到别的目录或换了环境容易因为类的导入路径不同而加载失败。6. 进阶把模型封装成可复用的推理类并验证多端效果做到这一步你手上的资源已经能完成“训练→保存→Flask网页展示→PyQt桌面展示”的完整链路了。但如果你想在答辩时多展现一些工程能力我建议把推理代码进一步重构成一个独立的推理类统一供给Flask和PyQt两端调用显著减少代码冗余。常见的做法是单独维护一个classifier.py文件核心逻辑如下import torch import torchvision.transforms as transforms from PIL import Image class AnimalClassifier: def __init__(self, model_path, class_names, deviceNone): self.device device or torch.device(cuda if torch.cuda.is_available() else cpu) self.model build_model(len(class_names)) # 确保和训练时结构一致 self.model.load_state_dict(torch.load(model_path, map_locationself.device)) self.model.to(self.device) self.model.eval() self.class_names class_names self.transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def predict(self, image): 输入PIL Image或图片路径返回(类别名, 置信度) if isinstance(image, str): image Image.open(image).convert(RGB) elif isinstance(image, Image.Image): image image.convert(RGB) tensor self.transform(image).unsqueeze(0).to(self.device) with torch.no_grad(): outputs self.model(tensor) probabilities torch.softmax(outputs, dim1) confidence, index torch.max(probabilities, dim1) return self.class_names[index.item()], confidence.item()这个封装带来的好处立竿见影。Flask端每收到一个请求只需实例化一次AnimalClassifier然后反复调用predict方法PyQt端更是直接把图片路径传进去就行。两端的预处理逻辑被这个类统一收拢不会再出现“网页端归一化和桌面端归一化不一致”这种隐蔽错误。验证多端一致性有一个很管用的方法准备一组固定的测试图片分别在PyQt端和Flask端跑一遍把输出的类别和置信度打印到终端。如果两端的输出完全一致说明模型加载和预处理链路没有问题如果不一致优先怀疑两端的Normalize参数是否写成了不同值。我就曾经因为Flask端复制代码时少写了一个transforms.CenterCrop(224)导致同一个模型在两个界面上的推理结果完全不同那次的教训让我养成一个习惯凡是涉及多端调用的模型一定用同一张测试图做端到端验证输出不一致就立刻排查预处理链路而不是先去怀疑模型权重有问题。希望这篇笔记能帮你少踩几个类似的坑顺顺利利把项目跑通。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →