大家好我是Java1234_小锋老师分享一套锋哥原创的基于PyTorch的猫狗图像识别系统(深度学习PyQt6ResNet18ImageNet迁移学习)项目介绍图像分类是计算机视觉领域的基础任务之一在智能安防、宠物管理、内容审核与教学实验等场景中具有广泛的应用价值。针对传统人工鉴别效率低、主观性强以及从零训练深度模型对算力与样本量要求较高等问题本文设计并实现了一套基于PyTorch的猫狗图像识别系统。系统以Python为主要开发语言采用ResNet18卷积神经网络作为核心分类模型并基于ImageNet预训练权重开展迁移学习同时使用PyQt6构建桌面图形界面实现模型训练、单张图像识别与数据集统计分析等功能的一体化集成。在方法层面系统通过冻结骨干网络、仅训练分类头的策略显著降低CPU环境下的训练成本结合随机裁剪、水平翻转与色彩抖动等数据增强手段提升模型泛化能力训练过程中实时绘制损失与准确率曲线并自动保存验证集上表现最优的模型权重。实验结果表明在每类采样约2000张图像、训练5个轮次的设置下系统最优验证准确率可达99.00%能够满足本科毕业设计对功能完整性、可演示性与技术深度的要求。本文从需求分析、总体设计、详细实现到系统测试对课题进行了完整阐述重点介绍了Python语言特性、PyTorch深度学习框架、ImageNet大规模数据集以及ResNet18残差网络等关键技术并给出了各功能模块的核心代码说明。研究成果可为同类图像分类桌面应用的设计与教学实践提供参考。源码下载链接: https://pan.baidu.com/s/1z05iC6wDJmnsAYApQ0tXXQ?pwd1234提取码: 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, parentNone): 初始化训练线程 :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}, flr{self.lr}, subset{self.subset_per_class}/类) # 加载数据 self._emit_log(正在加载数据集...) train_loader, val_loader, dataset_info create_dataloaders( batch_sizeself.batch_size, img_sizeself.img_size, subset_per_classself.subset_per_class, val_splitself.val_split, ) self._emit_log(f数据集加载完成: 训练集 {dataset_info[train_size]} 张, f验证集 {dataset_info[val_size]} 张) # 构建模型 self._emit_log(正在构建 ResNet18 模型...) model build_model(num_classes2, freeze_backboneself.freeze_backbone) model.to(device) # 优化器与损失函数 criterion nn.CrossEntropyLoss() optimizer optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lrself.lr ) scheduler optim.lr_scheduler.StepLR(optimizer, step_size3, gamma0.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( fEpoch {epoch}: train_loss{train_loss:.4f}, train_acc{train_acc:.2%}, fval_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, encodingutf-8) as f: json.dump(history, f, ensure_asciiFalse, indent2) 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_classes2) 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, dim1)[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, dim1)[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