ResNet动物分类实战:从数据清洗到PyQt/Flask双端部署
简介本资源是一个面向计算机专业本科生与深度学习初学者的动物图像识别实践项目基于ResNet残差网络实现端到端的图像分类功能适用于课程设计、毕业设计及AI入门实战训练。项目完整覆盖数据采集百度图片爬虫、预处理均值计算、数据增强、模型微调ResNet18迁移学习重置全连接层适配10类动物、本地GUIPyQt与网页服务Flask双端部署含训练日志、预训练权重resnet18_e_best.pth、HTML前端模板及多张界面截图如主页面.png、show.png等工程结构清晰开箱即用。压缩包共32个文件以8个核心Python脚本train.py、predict.py、myflask.py等、11张PNG/JPG界面与效果示意图、5个备份文件.zbak及1个模型权重.pth为主总大小41.75MB。已有47人学习下载提供从数据生成generate_dataset.py到服务调用的全流程代码与调试支持特别适合理解工业级图像分类系统集成逻辑。1. 这不是调个torchvision.models.resnet18(pretrainedTrue)就能跑通的动物分类器你手头有一堆猫、狗、熊猫、狮子的图片想快速搭一个能本地运行、带界面、点选上传就出结果的动物识别工具——但直接套用 PyTorch 官方 ResNet 预训练模型大概率会在predict.py里卡在RuntimeError: size mismatch或者 GUI 点击后返回None。原因很实在ResNet 原生输出是 1000 类 ImageNet 标签而你的数据集只有 12 种动物从images/目录结构和generate_dataset.py中class_names [...]可推断且图像尺寸、归一化参数、输入通道顺序全都不匹配。这个项目真正价值在于它把「预训练模型迁移」这件事做成了闭环从百度爬图 → 自动清洗 → 计算定制均值方差 → 替换分类头 → Flask PyQt 双端部署 → 支持单图/批量预测。它不追求 SOTA 指标而是让一个没跑过train.py的人改两行路径就能在自己笔记本上看到预测结果雪豹 (置信度: 0.92)。适合课程设计、毕设原型验证、或想亲手拆解 ResNet 迁移流程的初学者——你不需要懂反向传播推导但得知道nn.Linear(512, 12)里的12是怎么来的。2. 数据准备爬虫不是重点清洗和标准化才是 ResNet 能认准动物的关键2.1 百度图片爬取逻辑与隐性约束spider.py并非通用爬虫它依赖百度图片搜索页的 DOM 结构截至 2023 年底有效。核心逻辑是构造 URLurl fhttps://image.baidu.com/search/index?tnbaiduimageword{keyword}pn{start}其中keyword来自generate_dataset.py中硬编码的动物列表如雪豹,金丝猴pn控制翻页。关键限制在于反爬策略请求头必须包含User-Agent和Referer: https://www.baidu.com/否则返回空 JSON图片过滤spider.py会跳过宽高比 0.5 或 2.0 的图片避免极端长条/正方形干扰并剔除尺寸 200×200 的低质图存储规范每类动物存入images/{class_name}/子目录文件名格式为{class_name}_{index}.jpg这直接影响后续ImageFolder加载时的class_to_idx映射。提示若爬取失败先检查spider.py第 32 行time.sleep(random.uniform(0.5, 1.5))是否被注释——百度对高频请求会返回 403此延时是必要缓冲。2.2calc_mean.py为什么不能直接用 ImageNet 的 [0.485, 0.456, 0.406]ResNet 预训练模型要求输入图像按特定均值/方差归一化。但 ImageNet 统计值[0.485, 0.456, 0.406]针对自然场景通用图像而你的动物数据集存在明显偏差白色背景占比高百度图常带白底动物主体多居中边缘信息少光照条件集中室内拍摄/网页截图。calc_mean.py通过遍历images/下所有图片计算真实数据分布# calc_mean.py 关键片段 for img_path in all_img_paths: img Image.open(img_path).convert(RGB) img transforms.ToTensor()(img) # 转为 [C, H, W]值域 [0,1] pixel_sum img.sum(dim[1,2]) pixel_cnt img.numel() // 3 mean pixel_sum / pixel_cnt # 输出形如 tensor([0.721, 0.689, 0.654])运行后生成dataset_mean_std.txt内容为mean: [0.721, 0.689, 0.654] std: [0.215, 0.223, 0.231]必须将此值填入utils.py的get_transforms()函数def get_transforms(): mean [0.721, 0.689, 0.654] # ← 替换此处 std [0.215, 0.223, 0.231] # ← 替换此处 train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomHorizontalFlip(), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(meanmean, stdstd) # ← 归一化参数生效点 ]) return train_transform, val_transform若跳过此步模型因输入分布偏移训练 loss 会震荡剧烈验证准确率长期卡在 30% 以下。2.3generate_dataset.py如何让 DataLoader 正确识别 12 个动物类别该脚本核心任务是构建ImageFolder兼容的数据结构并生成class_names.txt# generate_dataset.py 片段 class_names [雪豹, 金丝猴, 大熊猫, 藏羚羊, 亚洲象, 东北虎, 朱鹮, 扬子鳄, 中华鲟, 白鳍豚, 穿山甲, 黑颈鹤] # ← 实际类别数决定模型输出维度 # 创建 images/ 下的子目录 for name in class_names: os.makedirs(fimages/{name}, exist_okTrue) # 读取 spider.py 下载的原始图按关键词移动到对应目录 # ...移动逻辑 # 生成 class_names.txt 供 predict.py 加载 with open(class_names.txt, w, encodingutf-8) as f: for name in class_names: f.write(name \n)关键细节class_names.txt的行序必须与images/子目录名严格一致因为ImageFolder按目录名排序生成class_to_idx大熊猫对应索引2而非字典序train.py中dataset datasets.ImageFolder(rootimages, transformtrain_transform)依赖此结构若新增类别必须同步修改class_names列表、创建新目录、更新class_names.txt否则nn.Linear(512, len(class_names))维度报错。3. 模型训练与 ResNet 结构改造从预训练权重到 12 分类的三步手术3.1resnet18_e_best.pth的加载机制与权重冻结策略train.py加载预训练 ResNet18 的方式并非简单model models.resnet18(pretrainedTrue)而是显式加载并冻结前 6 个残差块# train.py 片段 model models.resnet18(pretrainedFalse) # ← 注意pretrainedFalse model.load_state_dict(torch.load(resnet18_e_best.pth)) # ← 加载已训练权重 # 冻结前6层layer1-layer3只训练layer4和fc for name, param in model.named_parameters(): if layer4 not in name and fc not in name: param.requires_grad False为什么这么做resnet18_e_best.pth是作者在自有数据集上微调后的权重比 ImageNet 预训练权重更适配动物特征冻结layer1-layer3占总参数 85%可防止小数据集每类约 200 张导致底层特征提取器过拟合layer4包含高级语义特征如毛发纹理、面部轮廓需微调以区分相似物种如雪豹 vs 东北虎fc层完全替换适配 12 分类任务。3.2 全连接层改造nn.Linear(512, 12)的数学依据ResNet18 最后一层全局平均池化输出维度为 512model.fc.in_features 512这是由网络结构决定的固定值# 查看原模型 fc 层 print(model.fc) # Linear(in_features512, out_features1000, biasTrue) # 改造为 12 分类 model.fc nn.Linear(512, 12) # ← 关键改造参数量对比层级原 ImageNet 参数量新动物分类参数量fc512 × 1000 1000 513,000512 × 12 12 6,156改造后仅增加 6k 参数训练速度快且避免因输出维度不匹配导致RuntimeError: mat1 and mat2 shapes cannot be multiplied。3.3 训练配置与收敛监控train.py中不可跳过的参数train.py使用torch.optim.Adam优化器学习率策略为分段衰减optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr0.001) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5) # 每5轮降半关键超参说明filter(lambda p: p.requires_grad, model.parameters())仅优化layer4和fc的可训练参数避免冻结层被意外更新step_size5因数据集小总计约 2400 张训练轮次设为 205 轮一衰减可防过拟合criterion nn.CrossEntropyLoss(label_smoothing0.1)标签平滑缓解类别不平衡如‘大熊猫’样本多‘白鳍豚’少日志写入logs/目录events.out.tfevents.*文件可用 TensorBoard 可视化tensorboard --logdirlogs --port6006观察Loss/train和Accuracy/val曲线若val准确率在第 12 轮后停滞说明已收敛。4. 双端部署PyQt 本地 GUI 与 Flask 网页服务的协同逻辑4.1myflask.pyFlask 服务如何与 ResNet 模型联动myflask.py启动一个轻量 HTTP 服务核心是predict_image()函数app.route(/predict, methods[POST]) def predict_image(): if file not in request.files: return jsonify({error: No file uploaded}) file request.files[file] img_bytes file.read() img Image.open(io.BytesIO(img_bytes)).convert(RGB) # 复用 utils.py 中的预处理管道 transform utils.get_transforms()[1] # 获取验证 transform img_tensor transform(img).unsqueeze(0) # 添加 batch 维度 with torch.no_grad(): output model(img_tensor) probs torch.nn.functional.softmax(output, dim1) confidence, pred_idx torch.max(probs, 1) class_names utils.load_class_names() # 读取 class_names.txt result { class: class_names[pred_idx.item()], confidence: float(confidence.item()) } return jsonify(result)注意三点transform必须与训练时的val_transform一致get_transforms()[1]否则归一化失配unsqueeze(0)添加 batch 维度因模型期望输入形状为[N, C, H, W]torch.no_grad()关闭梯度计算提升预测速度并节省显存。4.2window.pyPyQt 界面如何调用本地模型而非 Flaskwindow.py是独立桌面应用不依赖 Flask 服务直接加载模型# window.py 片段 self.model models.resnet18(pretrainedFalse) self.model.load_state_dict(torch.load(resnet18_e_best.pth)) self.model.eval() # 设置为评估模式 # 预处理复用 utils.get_transforms()[1] self.transform utils.get_transforms()[1] # 图片上传后执行 def predict(self): img Image.open(self.current_img_path).convert(RGB) img_tensor self.transform(img).unsqueeze(0) with torch.no_grad(): output self.model(img_tensor) probs torch.nn.functional.softmax(output, dim1) confidence, idx torch.max(probs, 1) class_names utils.load_class_names() self.result_label.setText(f{class_names[idx.item()]} ({confidence.item():.2f}))优势无网络延迟离线可用风险若resnet18_e_best.pth路径错误界面点击无响应——此时需检查window.py第 42 行torch.load(resnet18_e_best.pth)的相对路径是否指向项目根目录。4.3templates/index.html前端如何与 Flask API 通信网页端通过 AJAX 调用/predict接口!-- templates/index.html 片段 -- input typefile idimageInput acceptimage/* button onclickuploadAndPredict()上传并预测/button div idresult/div script function uploadAndPredict() { const fileInput document.getElementById(imageInput); const file fileInput.files[0]; const formData new FormData(); formData.append(file, file); fetch(/predict, { method: POST, body: formData }) .then(response response.json()) .then(data { document.getElementById(result).innerHTML 预测结果${data.class}置信度${data.confidence.toFixed(2)}; }) .catch(error console.error(Error:, error)); } /script调试技巧浏览器开发者工具 Network 标签页查看/predict请求状态码若返回500 Internal Server Error检查myflask.py中model加载是否成功print(model)应输出ResNet(结构acceptimage/*限制上传类型避免用户误传 PDF 导致Image.open()报错。5. 预测可靠性验证如何判断你的 ResNet 模型真的“认识”动物5.1predict.py命令行预测的黄金标准用法predict.py是脱离 GUI 的最小验证单元用于快速测试模型泛化能力python predict.py --image_path images/雪豹/雪豹_1.jpg --model_path resnet18_e_best.pth其内部逻辑强制执行三重校验图像完整性Image.open()后检查img.mode RGB非 RGB 图自动转换尺寸合规性transforms.Resize((256,256))确保短边缩放至 256再CenterCrop(224)截取中心区域置信度阈值若最高概率 0.6输出低置信度警告可能为未见过的动物或模糊图像。输出示例加载模型: resnet18_e_best.pth 处理图像: images/雪豹/雪豹_1.jpg 预测结果: 雪豹 (置信度: 0.942) Top-3 预测: 1. 雪豹 (0.942) 2. 东北虎 (0.031) 3. 金丝猴 (0.012)注意Top-3排序基于torch.topk(probs, 3)可直观发现模型混淆点如雪豹与东北虎常互判提示需增强这两类的训练样本。5.2 混淆矩阵分析用train.py的验证集输出诊断模型弱点train.py在每轮验证后生成confusion_matrix.png需取消第 187 行注释# plot_confusion_matrix(...)。该图横轴为真实标签纵轴为预测标签颜色深度代表频次。重点关注对角线外的亮色块如藏羚羊行中雪豹列颜色深说明模型易将藏羚羊误判为雪豹二者都有白色斑纹整行/整列暗淡如白鳍豚行全暗表明该类样本极少或质量差模型几乎无法识别。改进方案对混淆严重的类别如雪豹/东北虎在images/中补充更具区分性的图片侧脸、幼体、栖息地背景对样本稀少的类别如白鳍豚启用transforms.RandomRotation(15)增强数据多样性。5.3 Grad-CAM 可视化让 ResNet “指出它看到了什么”utils.py内置grad_cam函数可生成热力图揭示模型关注区域# 在 predict.py 中添加 from utils import grad_cam cam grad_cam(model, img_tensor, target_layermodel.layer4[-1]) # 指定 layer4 最后一层 save_cam_image(img, cam, gradcam_result.jpg)解读热力图红色区域 模型决策依据如雪豹预测时高亮眼部斑纹、肩部黑点若红色分散在背景如天空、草地说明模型未聚焦动物主体需检查数据清洗是否剔除了过多背景干扰若热力图全黑检查target_layer是否正确ResNet18 的layer4是Sequential需指定具体BasicBlock。此技术不改变预测结果但提供可解释性证据——当导师问“为什么判为雪豹”你能展示热力图而非只说“模型算出来的”。本文还有配套的精品资源点击获取