简介:这份资源是基于机器学习的猫狗识别算法完整源码包,面向计算机、人工智能、大数据、数学及电子信息等相关专业的学生与开发者,可用于课程设计、期末大作业、毕业设计或自学参考。压缩包共12个文件,约359KB,包含2个Python脚本作为核心算法实现,4张jpg与4张png图片用于数据集样本及训练过程可视化,另有README说明文档与gitignore配置文件,结构精简、便于快速上手。内容涵盖模型训练、预测流程及损失曲线、结果对比等可视化输出,能帮助读者理解图像分类任务的完整实现思路与调试方法。目前已有329人学习下载,适合具备一定编程基础、希望借助现成代码完成项目实践或对照学习机器学习流程的读者参考使用。
1. 猫狗识别源码拆开看:一份能跑通的机器学习项目该长什么样
很多人拿到「基于机器学习的猫狗识别算法源码.zip」这类压缩包,第一反应是解压、找train.py、直接python train.py,然后被一堆ModuleNotFoundError和路径报错劝退。我见过太多这样的场景:一个在机器学习课程设计选题里被反复选中的题目,最后卡在环境配置而不是算法本身。猫狗识别本质是一个二分类图像任务,输入是猫或狗的照片,输出是 0/1 标签,核心链路是「数据读取 → 特征提取 → 模型训练 → 推理验证」。它适合刚入门机器学习、想找一个完整项目练手的人,也适合需要交课程设计、想快速复现一份可解释代码的人。但源码包不等于能跑通的项目,真正决定成败的是数据组织方式、模型选型和训练参数这三件事。下面我按自己复现这类项目的顺序,把每个环节拆开讲清楚。
2. 数据准备与目录结构:源码跑不起来,八成卡在这一步
2.1 猫狗数据集的组织方式与常见坑
猫狗识别最常用的公开数据集是 Kaggle 的 Dogs vs. Cats,原始结构是train/下混放cat.0.jpg、dog.0.jpg这种命名,没有按类别分文件夹。而绝大多数源码默认的读取方式是ImageFolder风格,要求目录长这样:
dataset/ ├── train/ │ ├── cat/ │ │ ├── cat.001.jpg │ │ └── ... │ └── dog/ │ ├── dog.001.jpg │ └── ... ├── val/ │ ├── cat/ │ └── dog/如果你直接拿原始 Kaggle 数据丢进去,ImageFolder会把train/当成一个类别,训练出来的模型永远输出同一个结果,准确率卡在 50% 左右——这是最隐蔽的翻车方式,因为代码不报错,只是学不到东西。
我一般会先写一个整理脚本,把混放的文件按文件名前缀分到对应类别目录,同时切出验证集:
import os import shutil import random src_dir = "raw_train" # 原始混放目录 dst_dir = "dataset" # 整理后目录 val_ratio = 0.2 # 验证集比例 random.seed(42) # 固定随机种子,保证可复现 for split in ["train", "val"]: for cls in ["cat", "dog"]: os.makedirs(os.path.join(dst_dir, split, cls), exist_ok=True) files = [f for f in os.listdir(src_dir) if f.endswith(".jpg")] random.shuffle(files) split_idx = int(len(files) * (1 - val_ratio)) for i, fname in enumerate(files): cls = "cat" if fname.startswith("cat") else "dog" split = "train" if i < split_idx else "val" shutil.copy(os.path.join(src_dir, fname), os.path.join(dst_dir, split, cls, fname))这段脚本做了三件事:建目录、打乱顺序、按比例复制。val_ratio设 0.2 是常见做法,数据量小于 5000 张时可以降到 0.1,避免验证集太小导致指标抖动。random.seed(42)不是可有可无的,没有它每次跑出来的划分不同,你没法判断模型变好是因为改了参数还是换了数据。
提示:整理完后用
find dataset -type f | wc -l确认文件总数和原始数据一致,少文件通常是文件名大小写或扩展名不统一导致的。
2.2 图像预处理与数据增强的参数怎么设
数据整理好之后,读取环节要统一尺寸和归一化。猫狗图片原始尺寸参差不齐,直接送进网络会报维度错误。常见做法是缩放到 224×224 或 128×128,前者适配 ResNet 这类预训练模型,后者适合自己搭的小 CNN,训练更快。
from torchvision import transforms train_tf = transforms.Compose([ transforms.Resize((128, 128)), # 统一尺寸 transforms.RandomHorizontalFlip(), # 随机水平翻转,增强泛化 transforms.RandomRotation(10), # 小角度旋转,模拟拍摄角度差异 transforms.ToTensor(), # 转成张量,像素值归到 [0,1] transforms.Normalize([0.5]*3, [0.5]*3) # 归一化到 [-1,1] ]) val_tf = transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), transforms.Normalize([0.5]*3, [0.5]*3) ])训练集用增强,验证集不用,这是铁律。RandomHorizontalFlip对猫狗识别几乎无副作用,因为左右翻转后猫还是猫。RandomRotation(10)角度别开太大,超过 20 度会出现大量黑边,反而引入噪声。Normalize的均值和方差设 0.5 是简化写法,如果追求更高精度,可以先用脚本统计训练集的真实均值和方差再填进去。
3. 模型选型与训练:从自己搭 CNN 到迁移学习
3.1 自己搭一个小 CNN 的最小可用结构
如果课程设计要求「自己实现」,通常不允许直接调预训练模型。这时候搭一个 3 层卷积的小网络就够了,猫狗二分类在几千张图上能到 80% 以上准确率。
import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier = nn.Sequential( nn.Flatten(), nn.Linear(128 * 16 * 16, 256), nn.ReLU(), nn.Dropout(0.5), nn.Linear(256, 2) # 二分类输出 2 个 logits ) def forward(self, x): return self.classifier(self.features(x))输入 128×128 经过三次池化变成 16×16,通道数 128,所以全连接层输入是128*16*16。Dropout(0.5)放在全连接之间,是防止过拟合最省事的手段。输出维度写 2 而不是 1,配合CrossEntropyLoss使用,比用 Sigmoid + BCELoss 更稳定,梯度不容易消失。
3.2 迁移学习:什么时候该用预训练权重
自己搭的 CNN 在数据量少于 2000 张时容易过拟合,验证准确率上不去。这时候换成 ResNet18 加载 ImageNet 预训练权重,只训练最后的全连接层,通常能把准确率拉高 5 到 10 个百分点。
import torchvision.models as models import torch.nn as nn model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) for param in model.parameters(): param.requires_grad = False # 冻结主干 model.fc = nn.Linear(model.fc.in_features, 2) # 替换分类头冻结主干后只训练fc层,显存占用小、收敛快。如果数据量超过 5000 张,可以解冻最后两个 stage 做微调,学习率调到 1e-4 量级。注意weights参数在新版 torchvision 里替代了旧的pretrained=True,写错会直接报 TypeError。
3.3 训练循环里必须盯住的三个量
训练脚本写完后,真正决定结果的是 loss、学习率和 batch size 的配合。我一般用 Adam,学习率 1e-3,batch size 32,跑 20 个 epoch 看趋势。
import torch from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder train_ds = ImageFolder("dataset/train", transform=train_tf) val_ds = ImageFolder("dataset/val", transform=val_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=2) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.fc.parameters(), lr=1e-3) for epoch in range(20): model.train() for imgs, labels in train_loader: optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() # 每个 epoch 后在验证集上评估 model.eval() correct = total = 0 with torch.no_grad(): for imgs, labels in val_loader: preds = model(imgs).argmax(dim=1) correct += (preds == labels).sum().item() total += labels.size(0) print(f"epoch {epoch}, val_acc {correct/total:.4f}")shuffle=True只给训练集,验证集必须关掉,否则评估结果不可复现。num_workers在 Windows 上设 0 更稳,设大了容易卡死。如果训练 loss 一直不降,先检查学习率是不是太大,把 1e-3 降到 1e-4 试一轮;如果训练 loss 降但验证 loss 上升,说明过拟合,加 Dropout 或减少全连接层参数量。
4. 推理与验证:模型训完之后怎么确认它真的能用
4.1 单张图片推理的完整流程
训练完保存权重后,推理脚本要保证和训练时用完全相同的预处理,否则预测结果会莫名其妙地差。
from PIL import Image import torch model.load_state_dict(torch.load("best.pth", map_location="cpu")) model.eval() img = Image.open("test.jpg").convert("RGB") tensor = val_tf(img).unsqueeze(0) # 加 batch 维度 with torch.no_grad(): logits = model(tensor) prob = torch.softmax(logits, dim=1) pred = logits.argmax(dim=1).item() print(f"预测类别: {['cat','dog'][pred]}, 置信度: {prob[0][pred]:.4f}")convert("RGB")不能省,有些图片是 RGBA 或灰度,直接送进网络会报通道数不匹配。unsqueeze(0)是补 batch 维度,单张图也要凑成[1,3,128,128]。map_location="cpu"在没 GPU 的机器上加载权重时必须加,否则会报 CUDA 相关错误。
4.2 用混淆矩阵判断模型是不是偏科
只看准确率容易被骗。如果验证集里猫 500 张、狗 500 张,模型全预测成狗也有 50% 准确率。这时候要看混淆矩阵:
| 真实 \ 预测 | 猫 | 狗 |
|---|---|---|
| 猫 | 460 | 40 |
| 狗 | 55 | 445 |
对角线是预测对的,非对角线是错的。如果猫那一行错得明显多,说明模型对猫的特征学得不够,可能是猫的图片里背景干扰大,或者训练集里猫的样本偏少。我一般会把这个矩阵打印出来,比准确率更能说明问题。
5. 避坑与排查:源码复现时最容易踩的五个坑
5.1 报错CUDA out of memory但显存明明够
现象:batch size 设 32 就爆显存,降到 8 才能跑。原因通常是验证阶段没有加torch.no_grad(),导致计算图一直累积。解决:所有推理代码块外面套with torch.no_grad():,验证和测试阶段都要加。
5.2 训练准确率 99% 但验证准确率 50%
现象:训练集上几乎全对,验证集上跟瞎猜一样。原因一般是数据泄漏——整理数据时把同一张图的副本分到了训练集和验证集,或者验证集用了训练集的增强变换。解决:检查文件是否有重名,验证集只用Resize + ToTensor + Normalize,不加任何随机变换。
5.3 预测结果永远是同一个类别
现象:不管输入什么图,输出都是 cat 或都是 dog。原因通常是ImageFolder读到的目录结构不对,所有图片被当成一个类。解决:用print(train_ds.classes)确认类别列表是['cat','dog']而不是['train'],用print(len(train_ds))确认样本数正常。
5.4 加载权重时报Missing key(s) in state_dict
现象:load_state_dict报一堆 key 不匹配。原因一般是保存时用了torch.save(model, path)存整个模型,加载时又用load_state_dict。解决:统一用torch.save(model.state_dict(), path)保存,加载时先实例化同结构模型再load_state_dict。如果换了模型结构,strict=False可以跳过不匹配的层,但要确认跳过的不是你需要的层。
5.5 推理速度慢到无法接受
现象:单张图推理要好几秒。原因通常是没设model.eval(),Dropout 和 BatchNorm 还在训练模式,或者每次推理都重新加载模型。解决:推理前调model.eval(),模型只加载一次,循环里复用。如果还慢,检查是不是在 CPU 上跑了大模型,换成 ResNet18 或缩小输入尺寸。
6. 把准确率再往上推一档:三个我常用的调优技巧
第一个技巧是学习率预热加余弦退火。直接上 1e-3 有时候前期震荡大,前 3 个 epoch 用 1e-4 预热,之后按余弦曲线降到 1e-6,验证准确率通常能稳 1 到 2 个点。代码上就是在 epoch 循环里手动改optimizer.param_groups[0]['lr'],不需要额外库。
第二个技巧是测试时增强(TTA)。推理时把同一张图水平翻转一次,两次预测概率取平均,对猫狗这种左右对称的类别几乎稳赚。实现上就是把val_tf和翻转后的val_tf各跑一遍,softmax 结果相加再 argmax。代价是推理时间翻倍,但离线评估值得做。
第三个技巧是错误样本分析。把验证集里预测错的图单独存到一个文件夹,肉眼过一遍,你会发现很多是标注本身就模棱两可的图,或者猫狗同框的图。把这些图从验证集里剔除或重新标注,比调参更能提升指标。我自己的习惯是每轮训练后都导出错误样本,看三遍再决定下一步改什么。这套流程跑下来,猫狗识别从源码到可用模型,快的话一个下午就能走通,慢的话卡在数据整理上,但坑就那几个,踩过一次就记住了。希望帮到你。
本文还有配套的精品资源,点击获取