使用ResNet网络实现猫狗数据集分类
1.作者介绍姓名:孙路炜 性别男西安工程大学电子信息学院2025级研究生研究方向医学分割电子邮件2441065596qq.com2ResNet-18网络架构介绍2.1 什么是ResNet-18ResNet-18残差网络-18是深度学习和计算机视觉领域里程碑式的经典卷积神经网络架构.在深度学习的早期研究人员发现了一个直观的规律网络越深层数越多模型的表达能力就越强准确率通常也越高。然而当人们尝试把网络堆叠到几十层甚至上百层时却遇到了两个致命问题梯度消失/梯度爆炸 (Vanishing/Exploding Gradients)在传统的神经网络中信号是通过链式法则反向传播的。如果层数太深导数连乘会导致梯度要么变得无限小梯度消失导致前面的层根本无法更新参数要么变得无限大梯度爆炸导致模型崩溃。退化问题 (Degradation Problem)——ResNet 诞生的直接原因科学家们发现即使解决了梯度消失当网络层数达到一定深度后训练集上的准确率反而开始下降这并不是过拟合因为过拟合是训练集表现好、测试集表现差而退化问题是训练集和测试集都很差。2.2 核心架构结构要点卷积层包含 2个卷积操作 3×3 卷积用于提取特征批归一化Batch Normalization加速训练缓解梯度消失激活函数 ReLU引入非线性跳跃连接Skip Connection将输入 x 直接加到卷积层的输出上需保证维度匹配残差块的核心公式2.3 18层权重层的结构脉络为什么是 18 层由 1个初始卷积层 4个 Stage (共 8个残差块每块含2层卷积) 1个全连接输出层组成。渐进式特征提取随着层数加深特征图的空间分辨率逐渐减半112到7而语义深度通道数成倍增加64到512。3.数据集介绍本次实验选用了机器学习领域极具代表性的Kaggle Dogs vs. Cats经典数据集。该数据源自2013年Kaggle平台发起的同名图像分类竞赛因其数据特征丰富、标注规范成为了图像识别与深度学习入门阶段最经典的标准评测基准之一被广泛用于验证各类算法模型的性能。总体规模包含25000张高质量彩色宠物图像样本覆盖了不同品种、拍摄角度、光照条件与背景环境具备良好的多样性和真实场景挑战性。训练集25000张猫Cat与狗Dog样本数量完全一致各含 12,500 张。无类别偏置的特性确保了模型训练过程中的学习公平性与结果评估的客观性。测试集12500张没有标定是猫还是狗。4.代码实现4.1 环境准备与库的导入Python3.10.20 Torchvision0.22.1cu118 pip install numpy matplotlib opencv-python pillow torch torchvision sympy tqdm4.2 数据处理import os import cv2 from torch.utils.data import Dataset class DogCatDataset(Dataset): def __init__(self, root_path, transformNone): self.label_name {Cat: 0, Dog: 1} self.root_path root_path self.transform transform self.get_train_img_info() def __getitem__(self, index): img_name self.train_img_name[index] img_path os.path.join(self.root_path, img_name) # 1. 读取图片 img_bgr cv2.imread(img_path) # 2. 如果读取失败坏图或格式不对 if img_bgr is None: print(f\n⚠️ 警告检测到坏图已自动跳过路径: {img_path}) # 递归读取下一张图直到读到好图为止 next_index (index 1) % len(self.train_img_name) return self.__getitem__(next_index) # 3. 正常读取转换颜色通道 (BGR - RGB) self.img cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # 4. 执行数据预处理 if self.transform is not None: self.img self.transform(self.img) self.label self.train_img_label[index] return self.img, self.label def __len__(self): return len(self.train_img_name) def get_train_img_info(self): all_files os.listdir(self.root_path) # 严格过滤非图片后缀防止把压缩包或者系统隐藏文件读进去 self.train_img_name [ f for f in all_files if f.lower().endswith((.jpg, .jpeg, .png, .bmp)) ] self.train_img_label [0 if cat in imgname.lower() else 1 for imgname in self.train_img_name]4.3 train设置epoch20保存权重以及损失和精确率的折线图import os from tqdm import tqdm import torch from torch.utils.data import DataLoader import torch.nn as nn import torch.optim as optim from torchvision import transforms import torchvision.models as models import matplotlib.pyplot as plt import DogCatDataset def main(): # Step 0: 查看torch版本、设置device print(torch.__version__) device torch.device(cuda if torch.cuda.is_available() else cpu) # Step 1: 准备数据集 train_transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor() ]) train_data DogCatDataset.DogCatDataset( root_pathos.path.join(os.getcwd(), data/newtrain), transformtrain_transform ) train_dataloader DataLoader( datasettrain_data, batch_size8, shuffleTrue, num_workers2, pin_memoryTrue ) # Step 2: 初始化模型 model models.resnet18() # 修改最后全连接层 fc_input_feature model.fc.in_features model.fc nn.Linear(fc_input_feature, 2) # 加载预训练权重 pretrained_weight torch.hub.load_state_dict_from_url( urlhttps://download.pytorch.org/models/resnet18-5c106cde.pth, progressTrue ) del pretrained_weight[fc.weight] del pretrained_weight[fc.bias] model.load_state_dict(pretrained_weight, strictFalse) model.to(device) # Step 3: 损失函数 criterion nn.CrossEntropyLoss() # Step 4: 优化器 LR 0.01 optimizer optim.SGD( model.parameters(), lrLR, momentum0.9 ) # Step 5: 学习率衰减 scheduler torch.optim.lr_scheduler.StepLR( optimizer, step_size10, gamma0.1 ) model.train() MAX_EPOCH 20 loss_list [] acc_list [] for epoch in range(MAX_EPOCH): loss_log 0.0 total_sample 0 train_correct_sample 0 for data in tqdm( train_dataloader, descfEpoch [{epoch1}/{MAX_EPOCH}]): img, label data img img.to(device) label label.to(device) output model(img) optimizer.zero_grad() loss criterion(output, label) loss.backward() optimizer.step() _, predicted_label torch.max(output, 1) total_sample label.size(0) train_correct_sample ( predicted_label label ).sum().item() loss_log loss.item() # 当前epoch指标 epoch_acc train_correct_sample / total_sample epoch_loss loss_log / len(train_dataloader) acc_list.append(epoch_acc) loss_list.append(epoch_loss) print(f\nEpoch [{epoch1}/{MAX_EPOCH}]) print(fAccuracy: {epoch_acc:.4f}) print(fLoss: {epoch_loss:.6f}) scheduler.step() print(train finish!) # Step 7: 保存模型 torch.save(model.state_dict(), ./resnet18_Cat_Dog.pth) print(model saved!) # # Step 8: 可视化训练过程 # epochs range(1, MAX_EPOCH 1) plt.figure(figsize(12, 6)) # Loss曲线 plt.subplot(1, 2, 1) plt.plot( epochs, loss_list, markero, linewidth2, labelTraining Loss ) plt.title(Training Loss) plt.xlabel(Epochs) plt.ylabel(Loss) plt.grid(True) plt.legend() # Accuracy曲线 plt.subplot(1, 2, 2) plt.plot( epochs, acc_list, markero, linewidth2, labelTraining Accuracy ) plt.title(Training Accuracy) plt.xlabel(Epochs) plt.ylabel(Accuracy) plt.grid(True) plt.legend() plt.tight_layout() # 保存图片 plt.savefig( training_curve.png, dpi300, bbox_inchestight ) print(training_curve.png saved!) plt.show() if __name__ __main__: main()4.4测试4.4.1准确率的测试import os import tqdm import torch from torch.utils.data import DataLoader from torch.nn import functional as F import torch.nn as nn from torchvision import transforms import torchvision.models as models import DogCatDataset def main(): #Step 0:查看torch版本、设置device print(torch.__version__) device torch.device(cuda if torch.cuda.is_available() else cpu) #Step 1:准备数据集 test_transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor() ]) test_data DogCatDataset.DogCatDataset(root_pathos.path.join(os.getcwd(), data/newtest), transformtest_transform) test_dataloader DataLoader(datasettest_data, batch_size1, shuffleFalse) #Step 2: 初始化网络 model models.resnet18() #修改网络结构将fc层1000个输出改为2个输出 fc_input_feature model.fc.in_features model.fc nn.Linear(fc_input_feature, 2) #Step 3加载训练好的权重 trained_weight torch.load(./resnet18_Cat_Dog.pth) model.load_state_dict(trained_weight) model.to(device) #Steo 4网络推理 model.eval() correct_sample 0 total_sample 0 with torch.no_grad(): for data in test_dataloader: img, label data img img.to(device) label label.to(device) output model(img) _, predicted_label torch.max(output, 1) correct_sample (predicted_labellabel).cpu().numpy() total_sample 1 #Step 5:打印分类准确率 print(correct_sample/total_sample) if __name__ __main__: main()4.4.2随机选取20张图片进行测试import os import random import torch import torch.nn as nn import cv2 import matplotlib.pyplot as plt from torchvision import transforms import torchvision.models as models # 解决matplotlib中文显示问题 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False def main(): device torch.device(cuda if torch.cuda.is_available() else cpu) # 1. 映射标签含义 label_map {0: 猫 (Cat), 1: 狗 (Dog)} # 2. 搜集测试集下所有的图片路径 test_dir os.path.join(os.getcwd(), data/newtest) if not os.path.exists(test_dir): print(f❌ 找不到测试集文件夹: {test_dir}请检查路径) return all_files os.listdir(test_dir) # 严格过滤图片格式 img_names [f for f in all_files if f.lower().endswith((.jpg, .jpeg, .png, .bmp))] # 修改提示判断条件改为 20 张 if len(img_names) 20: print(f❌ 测试集里面的图片只有 {len(img_names)} 张少于 20 张无法完成挑选) return # 核心修改随机从里面挑选 20 张图片名字 selected_imgs random.sample(img_names, 20) # 3. 初始化并加载训练好的模型 model models.resnet18() fc_input_feature model.fc.in_features model.fc nn.Linear(fc_input_feature, 2) weight_path ./resnet18_Cat_Dog.pth if not os.path.exists(weight_path): print(f❌ 找不到权重文件 {weight_path}请先运行 train.py 进行训练) return model.load_state_dict(torch.load(weight_path, map_locationdevice)) model.to(device) model.eval() # 4. 定义推理专用的图像预处理 test_transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor() ]) # 5. 开始循环预测并画图 # 修改提示调整了画布大小 (15, 12)让 4 行图片能够塞得下且不重叠 plt.figure(figsize(15, 12)) for i, img_name in enumerate(selected_imgs): img_path os.path.join(test_dir, img_name) # 读取原图用来显示OpenCV默认BGR转成RGB给plt画图 orig_img cv2.imread(img_path) if orig_img is None: continue orig_img cv2.cvtColor(orig_img, cv2.COLOR_BGR2RGB) # 自动解析出真实的标签延续你之前的命名规则含cat为0否则为1 true_label_id 0 if cat in img_name.lower() else 1 true_label_name label_map[true_label_id] # 模型推理预处理 img_tensor test_transform(orig_img) # 形状变为 [3, 224, 224] img_tensor img_tensor.unsqueeze(0).to(device) # 增加 Batch 维度变成 [1, 3, 224, 224] # 让模型猜 with torch.no_grad(): output model(img_tensor) _, predicted_id torch.max(output, 1) pred_label_name label_map[predicted_id.item()] # 6. 使用 matplotlib 摆放这 20 张图 # 核心修改改为 4 行 5 列的网格布局 plt.subplot(4, 5, i 1) plt.imshow(orig_img) plt.axis(off) # 隐藏坐标轴 # 如果猜对了用绿色字猜错了用红色字标识 title_color green if predicted_id.item() true_label_id else red plt.title(f真实: {true_label_name}\n预测: {pred_label_name}, colortitle_color, fontsize10) # 修改提示w_pad 和 h_pad 可以防止行与行、列与列之间的文字发生重叠 plt.tight_layout(w_pad1.0, h_pad2.0) print( 正在弹出 20 张图片的可视化检测窗口...) plt.show() if __name__ __main__: main()5结果展示参考链接数据集链接https://gitcode.com/open-source-toolkit/ef827/blob/main/cat_dog.zip