简介这份资源是面向计算机、人工智能及相关专业学生与从业者的猫狗识别分类项目源码包可作为毕业设计、课程设计、作业或项目初期立项演示的参考方案也适合具备一定基础的学习者进阶练手。压缩包共16个文件约1.67MB以7个Python脚本为核心涵盖数据读取、CNN与ResNet、Swin Transformer等模型实现及测试代码另含1个pth模型权重、2个md说明文档、1个docx论文与1个txt数据说明结构清晰、便于按模块查阅。项目代码完整、资料齐全包含设计文档与训练日志读者可借此理解从数据预处理、模型搭建到训练评估的完整流程并在此基础上修改扩展实现其他分类功能。目前已有68人学习下载遇到配置或运行问题还可远程交流适合作为机器学习入门与实战的参考案例。1. 猫狗识别项目拆开看一份能跑通的 PyTorch 二分类工程长什么样很多人第一次接触机器学习都是从猫狗识别这个经典任务开始的。它足够简单简单到一台普通笔记本就能跑又足够完整完整到涵盖了数据加载、模型搭建、训练调参、评估部署的全流程。你手上如果拿到一份「基于 Python 机器学习的猫狗识别分类项目源码」里面通常包含四个东西一份可运行的 PyTorch 训练脚本、一份数据集说明、一篇配套论文或技术文档、以及训练好的模型权重文件。这套组合的价值不在于模型有多先进而在于它是一条从零到推理的完整链路适合刚学完 Python 和 PyTorch 基础、想找一个能跑通的项目来验证自己理解的人。我见过太多人卡在环境配置上就放弃了也见过有人跑通了但不知道每个参数在干什么。这篇内容就是把这套项目拆开告诉你每一步在做什么、参数怎么调、哪里容易翻车。2. 从数据集到 DataLoader猫狗图片怎么喂给模型2.1 数据集目录结构与划分策略拿到项目源码后第一件事不是急着跑训练脚本而是先看清楚数据是怎么组织的。常见的猫狗识别数据集有两种来源一种是 Kaggle 上的 Dogs vs. Cats训练集 25000 张猫狗各半另一种是项目自带的精简版可能只有几千张。不管哪种目录结构通常长这样dataset/ ├── train/ │ ├── cat/ │ │ ├── cat.001.jpg │ │ └── ... │ └── dog/ │ ├── dog.001.jpg │ └── ... ├── val/ │ ├── cat/ │ └── dog/ └── test/ ├── cat/ └── dog/这种按类别分文件夹的结构可以直接用torchvision.datasets.ImageFolder加载不需要自己写标签映射。我一般会按 8:1:1 的比例划分训练集、验证集和测试集。如果项目源码里只给了训练集那就自己写个脚本切分注意要固定随机种子否则每次切出来的验证集不一样调参时你会怀疑人生。注意切分数据集时确保每个类别的比例一致猫狗各半的原则在训练集、验证集、测试集里都要保持否则评估指标会失真。2.2 数据增强与归一化的参数怎么设猫狗识别最容易踩的坑是过拟合。训练集准确率冲到 99%验证集卡在 70% 不动这就是典型的过拟合信号。数据增强是性价比最高的缓解手段。下面是我常用的增强配置import torch from torchvision import transforms, datasets from torch.utils.data import DataLoader train_transform transforms.Compose([ transforms.Resize((224, 224)), # 统一尺寸匹配预训练模型输入 transforms.RandomHorizontalFlip(p0.5), # 水平翻转猫狗都适用 transforms.RandomRotation(15), # 小角度旋转模拟拍摄角度变化 transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), # 颜色扰动 transforms.ToTensor(), # 转成张量像素值从0-255变0-1 transforms.Normalize( # 标准化用ImageNet的均值和标准差 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) val_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) train_dataset datasets.ImageFolder(dataset/train, transformtrain_transform) val_dataset datasets.ImageFolder(dataset/val, transformval_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size32, shuffleFalse, num_workers4)这段代码里有几个参数值得展开说。Resize((224, 224))是因为后面要用 ResNet 或 VGG 这类预训练模型它们的全连接层期望的输入尺寸就是 224。RandomHorizontalFlip对猫狗识别几乎无损因为猫狗左右翻转后还是猫狗但对手写数字识别就不能用6 翻转成 9 就出事了。Normalize里的均值和标准差是 ImageNet 统计出来的用预训练权重时必须保持一致自己从头训练的话可以改成数据集的真实统计值。batch_size32是个保守选择显存 8GB 以上可以拉到 64显存不够就降到 16。num_workers4在 Linux 上没问题Windows 上如果报错就改成 0这是 PyTorch 在 Windows 下的老毛病了。2.3 验证集和测试集的处理差异验证集和测试集只做 Resize、ToTensor 和 Normalize不做随机翻转和旋转。原因很简单验证和测试要的是稳定可复现的结果随机增强会让同一个样本每次评估的结果不一样你没法判断模型到底有没有进步。这个细节很多新手会忽略直接把训练集的 transform 套到验证集上结果验证集准确率波动好几个百分点白白浪费调参时间。3. 模型选型与训练脚本ResNet 微调还是从头搭 CNN3.1 两种路线的取舍猫狗识别项目里模型部分通常有两种写法一种是自己搭一个 4 层卷积加全连接的小网络另一种是加载 ResNet18 或 ResNet50 的预训练权重做微调。两种路线我都跑过结论很明确如果你追求快速出结果用预训练模型微调如果你想理解卷积网络到底在学什么从头搭。从头搭的 CNN 长这样import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self, num_classes2): super(SimpleCNN, self).__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), # 输入3通道输出32通道 nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 尺寸减半 nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), nn.Conv2d(128, 256, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((1, 1)), # 全局平均池化输出1x1 nn.Flatten(), nn.Linear(256, 128), nn.ReLU(inplaceTrue), nn.Dropout(0.5), # 丢弃一半神经元防过拟合 nn.Linear(128, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return x这个网络大概 110 万参数在 25000 张图上训练 20 个 epoch 能到 85% 左右的验证准确率。AdaptiveAvgPool2d((1, 1))是个好用的层不管前面特征图多大输出都是 1x1这样就不用算全连接层的输入维度了。Dropout(0.5)放在全连接层之间训练时随机丢弃一半神经元推理时关闭。用预训练模型的话代码更短import torchvision.models as models import torch.nn as nn model models.resnet18(pretrainedTrue) # 冻结前面的卷积层只训练最后的全连接层 for param in model.parameters(): param.requires_grad False # 替换最后的分类头输出2类 model.fc nn.Linear(model.fc.in_features, 2)pretrainedTrue会下载 ImageNet 上训练好的权重这些权重已经学会了提取边缘、纹理、形状等通用特征。冻结前面的层意味着反向传播不会更新它们的参数只训练最后的fc层。这样做的好处是训练快、不容易过拟合缺点是如果猫狗图片和 ImageNet 分布差异大效果会打折扣。我一般会先冻结训练 5 个 epoch然后解冻所有层用更小的学习率再训 10 个 epoch这叫「微调」。3.2 训练循环里的关键参数训练脚本的核心是一个双层循环外层 epoch内层 batch。下面是一个标准的训练循环import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() # 多分类交叉熵二分类也能用 optimizer optim.Adam(model.parameters(), lr1e-3) # Adam优化器学习率0.001 scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) # 每7个epoch学习率乘0.1 for epoch in range(20): model.train() # 切换到训练模式启用Dropout和BatchNorm running_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() # 清空上一轮梯度 outputs model(images) # 前向传播 loss criterion(outputs, labels) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 更新参数 running_loss loss.item() scheduler.step() # 更新学习率 print(fEpoch {epoch1}, Loss: {running_loss/len(train_loader):.4f})CrossEntropyLoss内部已经包含了 Softmax所以模型最后一层不要加 Softmax否则会重复计算。Adam的学习率设 1e-3 是常规操作如果 loss 震荡厉害就降到 1e-4。StepLR每 7 个 epoch 把学习率乘以 0.1目的是后期让模型在小范围内精细调整。model.train()和model.eval()的切换不能忘Dropout 和 BatchNorm 在两种模式下的行为完全不同忘了切会导致验证结果异常。3.3 验证与保存最佳模型训练过程中要定期在验证集上评估保存验证准确率最高的模型权重best_acc 0.0 for epoch in range(20): # ... 训练代码 ... model.eval() # 切换到评估模式 correct 0 total 0 with torch.no_grad(): # 关闭梯度计算节省显存 for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) # 取概率最大的类别 total labels.size(0) correct (predicted labels).sum().item() acc correct / total print(fValidation Accuracy: {acc:.4f}) if acc best_acc: best_acc acc torch.save(model.state_dict(), best_model.pth) # 只保存参数不保存结构torch.no_grad()在验证时一定要加否则会构建计算图显存占用翻倍。torch.max(outputs, 1)返回每行最大值和对应的索引索引就是预测类别。保存模型时用state_dict()而不是整个模型对象这样加载时更灵活不会因为代码结构变化而失败。4. 评估与推理模型到底行不行怎么看4.1 混淆矩阵比准确率更有信息量准确率是个粗糙的指标。如果测试集里猫 900 张、狗 100 张模型全预测成猫也能拿 90% 准确率但这模型没用。混淆矩阵能看出模型在哪个类别上翻车from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) cm confusion_matrix(all_labels, all_preds) print(cm) print(classification_report(all_labels, all_preds, target_names[cat, dog]))confusion_matrix输出一个 2x2 矩阵对角线是正确分类的数量非对角线是误判。如果猫被误判成狗的数量远大于狗被误判成猫的数量说明模型对猫的特征学得不够好可能需要增加猫的样本或调整类别权重。classification_report会输出每个类别的精确率、召回率和 F1 分数比单一准确率全面得多。4.2 单张图片推理的完整流程训练完模型最终要能对单张图片做预测。推理流程和验证类似但多了图片预处理和结果解读from PIL import Image def predict_image(image_path, model, transform, device): model.eval() image Image.open(image_path).convert(RGB) # 确保3通道 image transform(image).unsqueeze(0) # 增加batch维度 image image.to(device) with torch.no_grad(): outputs model(image) probabilities torch.softmax(outputs, dim1) # 转成概率 _, predicted torch.max(probabilities, 1) classes [cat, dog] prob probabilities[0][predicted.item()].item() return classes[predicted.item()], prob # 使用 result, confidence predict_image(test.jpg, model, val_transform, device) print(f预测: {result}, 置信度: {confidence:.4f})unsqueeze(0)是因为模型期望的输入是[batch_size, channels, height, width]单张图片只有三个维度需要在最前面加一个维度变成[1, 3, 224, 224]。torch.softmax把原始输出转成概率分布两个值加起来等于 1。置信度低于 0.7 的时候我一般会人工复核尤其是医疗、工业检测这类场景但猫狗识别里 0.6 以上基本可信。4.3 模型文件怎么存怎么加载项目源码里通常会附带一个.pth或.pt文件这是 PyTorch 的模型权重。加载方式取决于保存时用的是state_dict()还是整个模型# 方式一保存的是state_dict model SimpleCNN(num_classes2) # 先实例化结构 model.load_state_dict(torch.load(best_model.pth, map_locationcpu)) model.eval() # 方式二保存的是整个模型 model torch.load(best_model.pth, map_locationcpu) model.eval()map_locationcpu很重要如果你在 GPU 上训练保存的模型拿到没有 GPU 的机器上加载会报错加上这个参数就能自动映射到 CPU。方式一更推荐因为文件小、加载灵活但需要你保留模型定义的代码。方式二方便但文件大而且依赖当时的代码环境。5. 避坑指南猫狗识别项目里最容易翻车的五个地方5.1 现象训练 loss 不下降准确率卡在 50%原因学习率设太大或者数据标签有问题。猫狗二分类如果标签全错成同一类模型会直接摆烂输出恒定值。解决先把学习率降到 1e-4 试一轮如果 loss 还是不动检查ImageFolder加载后的class_to_idx映射确认猫和狗的文件夹名和标签对应正确。可以用print(train_dataset.class_to_idx)看一眼。5.2 现象验证集准确率比训练集高很多原因数据增强太狠训练集图片被随机变换得面目全非模型在训练时看到的和验证时看到的分布差异太大。另一个可能是验证集太小统计波动大。解决降低增强强度把RandomRotation从 15 度降到 10 度ColorJitter的系数从 0.2 降到 0.1。验证集至少保证每个类别 500 张以上。5.3 现象GPU 显存爆了报 CUDA out of memory原因batch_size太大或者验证时忘了加torch.no_grad()计算图一直在累积。解决先把batch_size减半8GB 显存跑 ResNet18 用 32 一般没问题。检查验证循环里有没有with torch.no_grad():没有的话加上。还可以在训练循环里加torch.cuda.empty_cache()但这是治标不治本根本还是减小 batch 或模型。5.4 现象Windows 上num_workers大于 0 就报错原因Windows 下 PyTorch 的多进程 DataLoader 和 Linux 实现不同容易出BrokenPipeError或卡死。解决把num_workers设成 0数据加载在主进程里做速度慢一点但稳定。或者把训练脚本放到 WSL 里跑Linux 环境下num_workers4没问题。5.5 现象加载预训练模型时下载失败原因pretrainedTrue会从网上下载权重网络不通或速度慢就会超时。解决提前手动下载好权重文件放到~/.cache/torch/hub/checkpoints/目录下或者用model models.resnet18(pretrainedFalse)先建结构再model.load_state_dict(torch.load(resnet18.pth))手动加载。项目源码里如果附带了预训练权重直接用本地的。6. 把模型推到 90% 以上三个我反复验证过的技巧第一个技巧是学习率预热。前 3 个 epoch 把学习率从 1e-5 线性增加到 1e-3然后再按正常策略衰减。这样做的好处是模型初期不会因为学习率太大而震荡后期又能快速收敛。实现方式是用torch.optim.lr_scheduler.LambdaLR写一个简单的线性函数或者用transformers库里的get_linear_schedule_with_warmup。我在猫狗识别上试过预热比不预热平均高 2 到 3 个百分点。第二个技巧是测试时增强TTA。推理时对同一张图片做多次变换——原图、水平翻转、轻微旋转——分别预测后取平均概率。代码不复杂def predict_with_tta(image_path, model, device): model.eval() image Image.open(image_path).convert(RGB) transforms_list [ val_transform, transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p1.0), # 强制翻转 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) ] probs [] for t in transforms_list: img_tensor t(image).unsqueeze(0).to(device) with torch.no_grad(): output model(img_tensor) prob torch.softmax(output, dim1) probs.append(prob) avg_prob torch.mean(torch.stack(probs), dim0) _, predicted torch.max(avg_prob, 1) return predicted.item(), avg_prob[0][predicted.item()].item()TTA 的代价是推理时间翻倍但在离线评估或对精度要求高的场景里很划算。我实测能提升 1 到 2 个百分点尤其是验证集准确率在 88% 左右徘徊的时候TTA 经常能推到 90% 以上。第三个技巧是类别权重平衡。如果猫狗样本比例不是 1:1比如猫 8000 张、狗 4000 张模型会偏向预测猫。这时候在CrossEntropyLoss里加weight参数# 假设猫10000张狗5000张 weights torch.tensor([1.0, 2.0]).to(device) # 狗类的权重是猫的两倍 criterion nn.CrossEntropyLoss(weightweights)权重的计算方式是总样本数除以类别数再除以每类样本数猫的权重 15000/2/10000 0.75狗的权重 15000/2/5000 1.5归一化后大概是 [0.5, 1.0]。加上权重后模型对少数类的召回率会明显提升整体 F1 分数也更均衡。这三个技巧不用全上按需组合。我的习惯是数据不平衡先上类别权重精度不够再加 TTA训练不稳定就加预热。每次只改一个变量跑完对比验证集指标确认有效再叠加下一个。这套流程我跑了不下二十次猫狗识别项目从 85% 推到 93% 大概需要两到三轮迭代。希望帮到你。本文还有配套的精品资源点击获取