简介:这份资源面向深度学习入门者与进阶学习者,提供基于卷积神经网络识别猫咪的完整论文与源码,可作为毕业设计、课程设计、大作业或工程实训的参考方案。项目从图像分块入手,将猫咪照片拆解为3×3像素的重叠图块,经单层神经网络提取特征后排列成三维数值表达,再通过池化层压缩空间维度、降低计算负担并抑制过拟合,完整呈现了卷积网络处理图像的核心思路。压缩包共89个文件,约14.35MB,包含65张jpg猫咪样本图、4个py脚本、2个h5模型文件、2份docx论文文档,以及xml分类器、yml配置、txt说明和md文档等,覆盖数据、代码、模型与论文多个环节。目前已有201人学习下载,适合希望理解CNN图像识别流程、快速搭建实验环境并对照论文复现的读者参考。
1. 从一张猫图说起:卷积神经网络识别猫咪到底在做什么
你拍了一张自家猫的照片,想用程序判断它是不是猫。这件事听起来简单,但让机器"看懂"一张图,背后是一整套从像素到语义的映射。卷积神经网络识别猫咪,本质上是用多层卷积核在图像上滑动提取特征,从边缘、纹理到耳朵轮廓、胡须走向,逐层抽象,最后输出"猫/非猫"的概率。这个方向之所以常年霸榜入门项目,是因为它麻雀虽小五脏俱全:数据采集、预处理、模型搭建、训练调参、评估部署,一个都不少。适合谁?刚学完 CNN 卷积神经网络原理、想找一个能跑通全流程的练手项目的人;也适合要写课程设计或小论文、需要一份可复现源码和实验记录的学生。下面我按自己带人做这个项目的顺序,把论文框架怎么搭、源码怎么写、参数怎么调、坑在哪,一次讲清楚。
2. 先想清楚识别任务的定义:二分类、多分类还是检测
2.1 三种任务形态决定你的网络结构
很多人一上来就搭网络,结果发现标签对不上。识别猫咪至少有三种常见定义,选错了后面全白做。
第一种是二分类:输入一张图,输出"是猫"或"不是猫"。数据集只需要猫图和非猫图两类,网络最后一层用 1 个 sigmoid 神经元。这是最省事、最适合入门和写小论文的形态。
第二种是多分类:比如猫、狗、鸟、兔若干类,输出是 softmax 的 N 维概率。数据集要按类别分文件夹,最后一层神经元数等于类别数。
第三种是目标检测:图里可能有多只猫,还要框出位置。这就不是单纯 CNN 了,常见做法是 YOLO 系列或 DETR 这类检测框架,输出的是边界框加类别。如果你只是想做"识别猫咪"这个标题,二分类或多分类就够了,检测属于另一个量级的工作量。
我一般建议:课程设计或小论文选二分类,数据集好凑、训练快、指标好解释;想体现工作量再上多分类。检测留给有 GPU 且时间充裕的人。
2.2 数据集怎么凑、怎么划分
二分类的数据集构造有个实用技巧:正样本用猫图,负样本用"非猫"的任意图(狗、风景、物品都行),负样本多样性越高,模型越不容易走捷径。常见做法是每类 1000 到 3000 张,按 8:1:1 划分训练、验证、测试。
目录结构建议这样组织,后面写 DataLoader 会省很多事:
dataset/ ├── train/ │ ├── cat/ # 约 1600 张 │ └── not_cat/ # 约 1600 张 ├── val/ │ ├── cat/ # 约 200 张 │ └── not_cat/ # 约 200 张 └── test/ ├── cat/ # 约 200 张 └── not_cat/ # 约 200 张划分时要注意:同一只猫的不同照片尽量别同时出现在训练集和测试集,否则测试准确率会虚高,这就是数据泄漏。我踩过这个坑,测试集 99% 结果换一批真图直接掉到 70%。
提示:负样本里别放太多和猫高度相似的图(比如其他猫科动物),除非你就是要做难例。入门阶段先让模型学得动。
3. 用 PyTorch 搭一个能跑通的猫咪识别网络
3.1 数据加载与预处理代码
先写数据管道。图像统一缩放到 224×224,这是大多数预训练网络的输入尺寸,方便后面迁移学习。
import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms # 训练集做增强,验证测试集只做缩放和归一化 train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), # 随机水平翻转,猫左右对称,安全 transforms.RandomRotation(15), # 小角度旋转,增强泛化 transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], # ImageNet 均值 [0.229, 0.224, 0.225]) # ImageNet 标准差 ]) val_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) train_set = datasets.ImageFolder('dataset/train', transform=train_tf) val_set = datasets.ImageFolder('dataset/val', transform=val_tf) train_loader = DataLoader(train_set, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_set, batch_size=32, shuffle=False, num_workers=4) print(train_set.classes) # ['cat', 'not_cat'],索引即标签逻辑说明:ImageFolder 会按子文件夹名自动生成标签,所以文件夹命名必须和类别一致。训练集用 RandomHorizontalFlip 和 RandomRotation 做数据增强,验证集绝不能用增强,否则评估结果不稳定。Normalize 用的均值和标准差是 ImageNet 统计值,如果你用预训练权重就必须保持一致。
参数说明:batch_size 设 32 是显存和收敛速度的折中,显存不够降到 16 或 8;num_workers 在 Windows 上如果报错就设 0;shuffle 训练集必须开,验证集必须关。
3.2 网络结构:自己搭还是用预训练
从零搭一个四层卷积网络,适合理解原理、写论文时画结构图:
import torch.nn as nn class CatCNN(nn.Module): def __init__(self, num_classes=2): super().__init__() self.features = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 224->112 nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 112->56 nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2), # 56->28 nn.Conv2d(128, 256, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),# 28->14 ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d(1), # 任意尺寸压成 1x1 nn.Flatten(), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): return self.classifier(self.features(x))逻辑说明:四层卷积逐层把通道从 3 提到 256,空间尺寸从 224 降到 14,再用全局平均池化压成向量,最后全连接分类。AdaptiveAvgPool2d(1) 的好处是输入尺寸不固定也能跑,省去手动算 flatten 维度。Dropout 0.5 放在全连接前,抑制过拟合。
参数说明:卷积核统一 3×3、padding=1 是保持尺寸的经典配置;通道数 32/64/128/256 逐层翻倍是常见节奏;num_classes 二分类设 2,多分类改成你的类别数。
如果你追求准确率、数据量又不大,直接用预训练模型微调,效果通常碾压从零训练:
from torchvision import models def build_pretrained(num_classes=2): model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) for p in model.parameters(): # 先冻结主干 p.requires_grad = False model.fc = nn.Linear(model.fc.in_features, num_classes) # 换分类头 return model逻辑说明:冻结主干只训练新的全连接层,几十张图也能收敛。等分类头稳定后,可以解冻最后几个 block 做小学习率微调,这一步常能再涨几个点。
参数说明:weights 用默认预训练权重;解冻微调时学习率要降到 1e-4 甚至更低,否则会把预训练特征冲垮。
4. 训练循环、评估指标与论文里该放什么
4.1 训练脚本与关键超参
import torch from torch import optim device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = build_pretrained(num_classes=2).to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1) best_acc = 0.0 for epoch in range(20): model.train() for imgs, labels in train_loader: imgs, labels = imgs.to(device), labels.to(device) optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() # 验证 model.eval() correct = total = 0 with torch.no_grad(): for imgs, labels in val_loader: imgs, labels = imgs.to(device), labels.to(device) pred = model(imgs).argmax(1) correct += (pred == labels).sum().item() total += labels.size(0) acc = correct / total scheduler.step() if acc > best_acc: best_acc = acc torch.save(model.state_dict(), 'best_cat.pth') print(f'epoch {epoch+1}, val_acc={acc:.4f}, best={best_acc:.4f}')逻辑说明:每个 epoch 先训练后验证,验证时切 eval 模式并关掉梯度。只保存验证集上最好的权重,避免最后一轮过拟合反而变差。StepLR 每 7 个 epoch 把学习率乘 0.1,帮助后期收敛。
参数说明:lr=1e-3 适合只训练分类头;如果全网络微调,改成 1e-4。epoch 数 20 是起点,看验证曲线决定加不加。criterion 用 CrossEntropyLoss,它内部含 softmax,别在网络里再加一层。
4.2 论文里真正该写的指标和图表
写论文不是把代码贴上去。审稿人和老师看的是:你怎么定义问题、数据怎么来、模型结构图、训练曲线、混淆矩阵、和基线对比。二分类至少给这几个指标:
| 指标 | 含义 | 论文里怎么用 |
|---|---|---|
| Accuracy | 总体正确率 | 主结果,但类别不平衡时会骗人 |
| Precision | 预测为猫里真猫比例 | 误报代价高时重点看 |
| Recall | 真猫里被找出的比例 | 漏报代价高时重点看 |
| F1 | 精确率和召回率的调和 | 综合指标,推荐主推 |
| 混淆矩阵 | 四格分布 | 直观展示错在哪类 |
训练曲线(loss 和 acc 随 epoch 变化)几乎必放,能体现收敛过程。结构图用画图工具画,别直接截代码。论文框架怎么搭:引言讲任务意义,相关工作讲 CNN 发展和迁移学习,方法讲你的网络和数据,实验讲指标和对比,结论收在发现上。别把"我用了 Adam"当成创新点,创新点可以是数据增强策略、结构微调或对比实验设计。
注意:测试集只能用来做最终评估,调参全程用验证集。用测试集调参再报测试准确率,是论文里最常见的硬伤。
5. 识别猫咪项目里最容易翻车的几个地方
5.1 准确率虚高,换图就废
现象:验证集 98%,自己拍几张猫图测,一半判错。 原因:训练集和验证集来自同一批图源,背景、光照高度相似,模型学的是背景不是猫。 解决:划分数据时按图源分组,验证集用完全不同的来源;加更强的数据增强(颜色抖动、随机裁剪);负样本多样化。
5.2 loss 不下降或变成 nan
现象:训练几个 batch 后 loss 变 nan,或者一直卡在 0.69(二分类随机水平)。 原因:学习率太大、归一化参数和预训练权重不匹配、标签越界。 解决:先把 lr 降到 1e-4 试;确认 Normalize 用的是 ImageNet 那组值;检查类别索引是否从 0 开始且小于 num_classes。
5.3 显存爆了(CUDA out of memory)
现象:跑几个 batch 就报 OOM。 原因:batch_size 太大、图像分辨率太高、没关梯度累积。 解决:batch_size 减半;分辨率从 224 降到 128 先跑通;验证和推理时用with torch.no_grad();及时del中间变量。
5.4 训练集准确率上不去
现象:训练集本身都学不会,acc 卡在 60%。 原因:网络太浅或太深、学习率不合适、数据标签错乱。 解决:先用预训练模型验证数据管道没问题;打印几个 batch 的图和标签肉眼核对;从零训练时确认卷积层没有把通道数写错。
5.5 推理速度慢到没法用
现象:单张图预测要好几秒。 原因:没切 eval 模式、没关梯度、模型太大。 解决:推理前model.eval()加torch.no_grad();导出 ONNX 或 TorchScript;换更小的骨干如 mobilenet。
6. 把模型真正用起来:推理封装与一个提点技巧
训练完只是半成品,能对着一张新图给出结果才算闭环。下面这段推理封装我一般直接放进项目:
from PIL import Image def predict(img_path, model, classes=('cat', 'not_cat')): model.eval() tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) img = Image.open(img_path).convert('RGB') # 强制三通道,防灰度图翻车 x = tf(img).unsqueeze(0).to(device) with torch.no_grad(): prob = torch.softmax(model(x), dim=1) conf, idx = prob.max(1) return classes[idx.item()], conf.item() print(predict('my_cat.jpg', model))逻辑说明:推理管道必须和验证集完全一致,Resize、ToTensor、Normalize 一个都不能少,顺序也不能变。convert('RGB') 是为了防止 PNG 带透明通道或灰度图导致通道数不匹配。softmax 把 logits 转成概率,方便设置信度阈值。
参数说明:classes 顺序必须和 ImageFolder 的 classes 一致,否则标签会反。置信度低于 0.6 时建议输出"不确定",别硬判。
一个提点技巧:测试时增强(TTA)。对同一张图做原图、水平翻转、轻微裁剪三个版本,分别预测后取平均概率,通常能涨 1 到 2 个点,代价是推理慢三倍。论文里可以作为一个小改进点写。
我自己的习惯是:任何识别项目,先把数据管道和评估脚本写扎实,再动网络结构。模型换来换去涨的那点分,往往不如把数据泄漏堵住、把测试集做干净来得实在。这个猫咪识别项目跑通一遍,你对 CNN 从原理到落地的理解会比看十篇教程都深。希望帮到你。
本文还有配套的精品资源,点击获取