当前位置: 首页 > news >正文

分享一套锋哥原创的基于PyTorch的猫狗图像识别系统(深度学习+PyQt6+ResNet18+ImageNet+迁移学习)

大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于PyTorch的猫狗图像识别系统(深度学习+PyQt6+ResNet18+ImageNet+迁移学习)

项目介绍

图像分类是计算机视觉领域的基础任务之一,在智能安防、宠物管理、内容审核与教学实验等场景中具有广泛的应用价值。针对传统人工鉴别效率低、主观性强,以及从零训练深度模型对算力与样本量要求较高等问题,本文设计并实现了一套基于PyTorch的猫狗图像识别系统。系统以Python为主要开发语言,采用ResNet18卷积神经网络作为核心分类模型,并基于ImageNet预训练权重开展迁移学习;同时使用PyQt6构建桌面图形界面,实现模型训练、单张图像识别与数据集统计分析等功能的一体化集成。

在方法层面,系统通过冻结骨干网络、仅训练分类头的策略,显著降低CPU环境下的训练成本;结合随机裁剪、水平翻转与色彩抖动等数据增强手段,提升模型泛化能力;训练过程中实时绘制损失与准确率曲线,并自动保存验证集上表现最优的模型权重。实验结果表明,在每类采样约2000张图像、训练5个轮次的设置下,系统最优验证准确率可达99.00%,能够满足本科毕业设计对功能完整性、可演示性与技术深度的要求。

本文从需求分析、总体设计、详细实现到系统测试对课题进行了完整阐述,重点介绍了Python语言特性、PyTorch深度学习框架、ImageNet大规模数据集以及ResNet18残差网络等关键技术,并给出了各功能模块的核心代码说明。研究成果可为同类图像分类桌面应用的设计与教学实践提供参考。

源码下载

链接: https://pan.baidu.com/s/1z05iC6wDJmnsAYApQ0tXXQ?pwd=1234
提取码: 1234

系统展示

核心代码

""" 模型训练模块 使用 QThread 在后台执行训练,通过信号与 UI 通信 """ import json import os from typing import Optional import torch import torch.nn as nn import torch.optim as optim from PyQt6.QtCore import QThread, pyqtSignal import config from src.dataset import create_dataloaders from src.model import build_model, save_model from src.utils import format_datetime class TrainThread(QThread): """ 后台训练线程 在独立线程中执行模型训练,避免阻塞 UI """ # 信号定义 log_signal = pyqtSignal(str) # 日志消息 epoch_done_signal = pyqtSignal(dict) # 每轮训练完成 batch_progress_signal = pyqtSignal(int, int) # 批次进度 (current, total) finished_signal = pyqtSignal(bool, str) # 训练结束 (success, message) def __init__(self, parent=None): """ 初始化训练线程 :param parent: 父对象 """ super().__init__(parent) self._stop_flag = False # 训练参数 self.epochs = config.DEFAULT_EPOCHS self.batch_size = config.DEFAULT_BATCH_SIZE self.lr = config.DEFAULT_LR self.img_size = config.DEFAULT_IMG_SIZE self.subset_per_class = config.DEFAULT_SUBSET_PER_CLASS self.val_split = config.DEFAULT_VAL_SPLIT self.freeze_backbone = config.DEFAULT_FREEZE_BACKBONE def set_params( self, epochs: int = None, batch_size: int = None, lr: float = None, img_size: int = None, subset_per_class: int = None, val_split: float = None, freeze_backbone: bool = None, ): """ 设置训练超参数 :param epochs: 训练轮数 :param batch_size: 批次大小 :param lr: 学习率 :param img_size: 图像尺寸 :param subset_per_class: 每类子集数量 :param val_split: 验证集比例 :param freeze_backbone: 是否冻结骨干 """ if epochs is not None: self.epochs = epochs if batch_size is not None: self.batch_size = batch_size if lr is not None: self.lr = lr if img_size is not None: self.img_size = img_size if subset_per_class is not None: self.subset_per_class = subset_per_class if val_split is not None: self.val_split = val_split if freeze_backbone is not None: self.freeze_backbone = freeze_backbone def stop(self): """请求停止训练""" self._stop_flag = True self._emit_log("正在停止训练...") def _emit_log(self, message: str): """ 发送带时间戳的日志 :param message: 日志内容 """ timestamp = format_datetime() self.log_signal.emit(f"[{timestamp}] {message}") def run(self): """执行训练主流程""" try: self._stop_flag = False device = torch.device(config.DEVICE) self._emit_log(f"使用设备: {device}") self._emit_log(f"训练参数: epochs={self.epochs}, batch_size={self.batch_size}, " f"lr={self.lr}, subset={self.subset_per_class}/类") # 加载数据 self._emit_log("正在加载数据集...") train_loader, val_loader, dataset_info = create_dataloaders( batch_size=self.batch_size, img_size=self.img_size, subset_per_class=self.subset_per_class, val_split=self.val_split, ) self._emit_log(f"数据集加载完成: 训练集 {dataset_info['train_size']} 张, " f"验证集 {dataset_info['val_size']} 张") # 构建模型 self._emit_log("正在构建 ResNet18 模型...") model = build_model(num_classes=2, freeze_backbone=self.freeze_backbone) model.to(device) # 优化器与损失函数 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr=self.lr ) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.5) # 训练历史 history = { "train_loss": [], "val_loss": [], "train_acc": [], "val_acc": [], } best_val_acc = 0.0 total_batches = len(train_loader) # 训练循环 for epoch in range(1, self.epochs + 1): if self._stop_flag: self._emit_log("训练已被用户停止") self.finished_signal.emit(False, "训练已停止") return self._emit_log(f"--- 第 {epoch}/{self.epochs} 轮 ---") # 训练阶段 model.train() train_loss, train_correct, train_total = 0.0, 0, 0 for batch_idx, (images, labels) in enumerate(train_loader): if self._stop_flag: self._emit_log("训练已被用户停止") self.finished_signal.emit(False, "训练已停止") return images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() * images.size(0) _, predicted = outputs.max(1) train_correct += predicted.eq(labels).sum().item() train_total += labels.size(0) self.batch_progress_signal.emit(batch_idx + 1, total_batches) train_loss /= train_total train_acc = train_correct / train_total # 验证阶段 model.eval() val_loss, val_correct, val_total = 0.0, 0, 0 with torch.no_grad(): for images, labels in val_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) val_loss += loss.item() * images.size(0) _, predicted = outputs.max(1) val_correct += predicted.eq(labels).sum().item() val_total += labels.size(0) val_loss /= val_total val_acc = val_correct / val_total scheduler.step() # 记录历史 history["train_loss"].append(round(train_loss, 4)) history["val_loss"].append(round(val_loss, 4)) history["train_acc"].append(round(train_acc, 4)) history["val_acc"].append(round(val_acc, 4)) self._emit_log( f"Epoch {epoch}: train_loss={train_loss:.4f}, train_acc={train_acc:.2%}, " f"val_loss={val_loss:.4f}, val_acc={val_acc:.2%}" ) # 发送 epoch 完成信号 self.epoch_done_signal.emit({ "epoch": epoch, "train_loss": train_loss, "val_loss": val_loss, "train_acc": train_acc, "val_acc": val_acc, }) # 保存最优模型 if val_acc > best_val_acc: best_val_acc = val_acc save_model(model) self._emit_log(f"验证准确率提升至 {val_acc:.2%},已保存最优模型") # 保存训练历史 with open(config.HISTORY_PATH, "w", encoding="utf-8") as f: json.dump(history, f, ensure_ascii=False, indent=2) self._emit_log(f"训练完成!最优验证准确率: {best_val_acc:.2%}") self.finished_signal.emit(True, f"训练完成,最优验证准确率: {best_val_acc:.2%}") except Exception as e: self._emit_log(f"训练出错: {str(e)}") self.finished_signal.emit(False, f"训练出错: {str(e)}")
""" 模型推理预测模块 加载训练好的模型,对单张图片进行猫/狗分类 """ import os from typing import Tuple, Optional import torch import torch.nn.functional as F from PIL import Image import config from src.model import load_model from src.dataset import get_predict_transform class Predictor: """ 猫狗图像分类预测器 封装模型加载与单图推理逻辑 """ def __init__(self, model_path: str = None): """ 初始化预测器 :param model_path: 模型权重路径 """ self.model_path = model_path or config.BEST_MODEL_PATH self.model = None self.transform = get_predict_transform(config.DEFAULT_IMG_SIZE) self.device = torch.device(config.DEVICE) self._loaded = False def load(self) -> bool: """ 加载模型 :return: 是否加载成功 """ try: self.model = load_model(self.model_path, num_classes=2) self._loaded = True return True except Exception as e: print(f"[预测] 模型加载失败: {e}") self._loaded = False return False def is_model_available(self) -> bool: """ 检查模型文件是否存在 :return: 模型是否可用 """ return os.path.exists(self.model_path) def predict(self, image_path: str) -> Tuple[str, float, dict]: """ 对单张图片进行预测 :param image_path: 图片路径 :return: (预测类别中文名, 置信度, 各类别概率字典) """ if not self._loaded: if not self.load(): raise RuntimeError("模型未加载,请先训练模型") if not os.path.exists(image_path): raise FileNotFoundError(f"图片不存在: {image_path}") # 加载并预处理图片 image = Image.open(image_path).convert("RGB") input_tensor = self.transform(image).unsqueeze(0).to(self.device) # 推理 self.model.eval() with torch.no_grad(): outputs = self.model(input_tensor) probabilities = F.softmax(outputs, dim=1)[0] # 解析结果 pred_idx = probabilities.argmax().item() confidence = probabilities[pred_idx].item() label = config.CLASS_NAMES.get(pred_idx, "未知") prob_dict = { config.CLASS_NAMES[i]: probabilities[i].item() for i in range(len(config.CLASS_NAMES)) } return label, confidence, prob_dict def predict_from_pil(self, image: Image.Image) -> Tuple[str, float, dict]: """ 对 PIL Image 对象进行预测 :param image: PIL Image 对象 :return: (预测类别中文名, 置信度, 各类别概率字典) """ if not self._loaded: if not self.load(): raise RuntimeError("模型未加载,请先训练模型") image = image.convert("RGB") input_tensor = self.transform(image).unsqueeze(0).to(self.device) self.model.eval() with torch.no_grad(): outputs = self.model(input_tensor) probabilities = F.softmax(outputs, dim=1)[0] pred_idx = probabilities.argmax().item() confidence = probabilities[pred_idx].item() label = config.CLASS_NAMES.get(pred_idx, "未知") prob_dict = { config.CLASS_NAMES[i]: probabilities[i].item() for i in range(len(config.CLASS_NAMES)) } return label, confidence, prob_dict
http://www.jsqmd.com/news/1229366/

相关文章:

  • 【小程序计算机毕业设计案例】校园图书资源互通驿站小程序的设计与实现 图书自助借还与分享驿站管理平台(程序+文档+讲解+定制)
  • 2026年杭州余杭顺畅管道疏通:一个十年本地师傅的服务标准化实践 - 资讯报道
  • 如何快速部署本地AI模型:5个实用场景完整指南
  • MCSManager完整指南:5分钟快速搭建专业游戏服务器集群
  • SLAM数学基石:向量与基础矩阵原理及C++实战
  • 2026年AI视频总结准确率如何实测对比,结果出乎意料黑马胜出
  • 计算机小程序毕设实战-基于 SpringBoot 的高校餐饮点餐服务系统 学生食堂线上点餐取餐管理小程序【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 如何用专业工具重塑音乐歌词管理的工作流
  • 仅限首批200家企业开放:基于AST语义树的AI-SQL可信生成协议v2.1(含审计日志与回滚凭证)
  • 2026阿里贵金属回收排名 TOP5 国家资质黄金回收、铂金回收、白银回收,上门回收无套路靠谱 联系方式推荐 - 中安检金银铂钻回收
  • iOS-Tech-Weekly中的Swift编程精华:从基础到高级的完整学习路线
  • Carnac键盘记录工具详解:提升屏幕录制和演示效率的10个技巧
  • 2026亳州闲置物资厂房打包回收排名 TOP5 整厂拆除回收物资废料,工厂设备批量高价回收一站式服务 联系方式推荐 - 信誉隆金银铂奢回收
  • 2026广州LV、爱马仕、香奈儿名包回收口碑测评!十一区正规门店五星红榜出炉 - 好物测评局
  • AI视频创作的范式转变:从模型堆叠到工作流融合
  • 2026巴彦淖尔奢侈品回收排名 TOP5 国家资质 名表 + 名包 + 钻石回收、劳力士 + LV + 香奈儿回收 无套路 联系方式推荐 - 中业金奢再生回收中心
  • 苏州百达翡丽全国线下维修售后服务体系全攻略|各省市出行指引完整披露(2026 年 7 月最新) - 百达翡丽售后服务官网
  • 上下文工程:AI Agent开发的系统化基础设施
  • Nacos服务发现与配置管理核心架构与实践指南
  • 提升PDF处理效率:Signature PDF压缩功能的高级使用方法
  • 【AI SQL生成技术白皮书】:20年DBA亲授企业级SQL自动生成落地的7大避坑指南
  • 实战指南:如何高效部署容器化MMO服务器
  • 生产级机器学习系统:从模型部署到可信决策的工程实践
  • 3分钟上手Roo Code:如何在VS Code中部署你的专属AI开发团队
  • OptiScaler终极指南:如何免费提升游戏画质和帧率
  • 无锡亨得利钟表维修保养服务中心地址和服务电话: 400-901-0695解析|全国门店信息正式通告(2026年7月更新版) - 卡地亚中国售后中心
  • 2026-07-20 增城本地民生实用资讯|黄金回收避坑指南,本地靠谱实体店汇总 - 得天独厚
  • OData.NET实体数据模型(EDM)完全指南:从基础到高级应用
  • 2026阿里闲置物资厂房打包回收排名 TOP5 整厂拆除回收物资废料,工厂设备批量高价回收一站式服务 联系方式推荐 - 信誉隆金银铂奢回收
  • 如何快速部署Submitty?从安装到运行的完整教程