资讯动态

猫狗分类实战:从自建CNN到迁移学习ResNet18全流程解析

发布时间:2026/9/6 18:23:27 来源:尧图企业网站定制
简介这是一份关于“猫狗分类实验”的深度学习项目总结PDF面向计算机视觉初学者、图像分类任务研究者及需要撰写实验报告的高校学生系统梳理了从数据集处理、CNN模型构建到过拟合缓解、迁移学习与模型评估的完整流程。文档为单个PDF文件大小仅551KB内容聚焦实验核心结论与Keras实现细节便于快速查阅与对照复现。目前已有1254人学习使用。文档详细描述了包含25000张猫狗图像的数据集划分、图片尺寸与背景差异的处理思路讲解了Conv2D卷积层、MaxPooling2D池化层、Flatten与Dense全连接层的结构设计同时针对小样本训练易过拟合的问题给出了Dropout、L2正则化和数据增强等解决方案并介绍了通过冻结预训练卷积基进行微调使验证精度达到约96%的实验结果。此外还附带了模型构建的关键代码与排错反思适合作为课设总结、竞赛复盘或入门CV图像二分类任务的参考模板。1. 项目拆解为什么是猫狗分类1.1 任务定义与目标猫狗分类是图像分类领域最经典的入门级任务常被看作“Hello World”级别的计算机视觉项目。它足够简单不需要像目标检测那样输出边界框也不用像语义分割那样处理像素级掩码只要给出一张图片让模型判断“这是猫”或“这是狗”输出一个二分类概率即可。但别小看这个任务。我在实际做这个实验时发现它几乎涵盖了深度学习图像处理全流程数据集怎么组织、图片怎么加载、模型怎么搭、训练怎么调参、结果怎么评估。把这些都搞明白后面再去做更复杂的检测、分割任务很多套路是相通的。对初学者来说这是性价比极高的练手项目对有经验的人来说这也是快速验证新想法、测试新框架的好载体。我的实验目标定得很直接在公开的猫狗分类数据集上从零搭建和训练一个分类模型最终在验证集上达到90%以上的准确率同时把整个训练流程跑通包括数据读取、增强、模型保存、单图推理和可视化评估。1.2 两条技术路线怎么选关于模型方案我测试了两种主流思路这里先给结论小规模CNN适合打基础迁移学习适合快速出效果。第一种方案是自建一个浅层卷积神经网络CNN结构比较朴素几层卷积池化加全连接层没有用太花哨的模块。这个方案的优点是依赖少、训练快、逻辑好理解网络每一层的输入输出尺寸都能手工算出来非常适合理解卷积、池化、全连接这些基本概念。缺点是准确率有上限如果只用简单结构不做数据增强和调参验证集准确率大概率卡在85%左右。第二种方案是迁移学习也就是把在大规模数据集比如ImageNet上预训练好的模型拿过来替换掉最后一层全连接分类器然后只微调少量参数。我用了ResNet18作为主干网络。从实测效果看哪怕只冻结主干、训练最后一层验证集准确率也能轻松到95%以上训练时间还更短。如果是为了学习原理建议两条路线都跑一遍。先自建CNN理解机制再用迁移学习体验工业级效果提升。如果只是急着交付一个结果直接上迁移学习更省事。我在本次实验中两种都做了对比后面会详细说参数和踩坑过程。2. 数据准备与预处理2.1 数据集的获取与目录规划数据集方面我用的是经典的Dogs vs. Cats公开图片集里面包含大量标注好的猫狗照片。如果没有现成数据也可以用爬虫按关键词抓取后手工清洗但公开数据集能省去不少时间和精力。拿到原始图片后第一步是整理目录结构。深度学习框架里的ImageFolder工具要求按类别建子文件夹结构是这样的data/ ├── train/ │ ├── cat/ │ │ ├── cat.0.jpg │ │ ├── cat.1.jpg │ │ └── ... │ └── dog/ │ ├── dog.0.jpg │ ├── dog.1.jpg │ └── ... ├── val/ │ ├── cat/ │ └── dog/ └── test/训练、验证、测试三套数据一定要分开并且保证类别比例一致。我按8:1:1的比例划分原始数据也就是约80%用于训练10%用于验证10%用于测试。划分时还要注意一个容易踩的坑原始数据可能是按类别顺序排列的直接按比例截取会导致训练集和验证集分布差异大。我使用sklearn里的train_test_split函数先打乱索引再划分保证每个子集里猫狗比例大致相同。然后是图片读取与尺寸统一。数据集中图片尺寸五花八门有大有小有横有竖。模型输入要求固定尺寸我统一缩放为224×224。这里可以用PIL的Image.open读取调用resize方法转换。如果只有一张小图可以先放大再居中裁剪避免图片内容严重变形。2.2 数据增强数据增强是提升模型泛化能力最有效的手段之一。简单说就是通过在训练时对图片做随机变换让模型每轮“看到”的图片都不太一样从而降低过拟合风险。我用的增强策略有四个。一是随机水平翻转猫狗图片左右翻转后语义不变这对模型是天然的正则化二是随机旋转角度控制在正负10度以内超过这个范围图片内容容易失真三是随机裁剪先缩放到一定比例再随机裁出224×224区域模拟不同构图四是色彩抖动轻微调整亮度、对比度、饱和度让模型对光线变化更鲁棒。注意增强只应该施加在训练集上验证集和测试集只做缩放和归一化。为什么因为增强的目的是让模型在训练时见过更多变体而验证集需要尽量还原真实分布如果验证集也做随机变换评估结果会不稳定。归一化这一步很多人忽略但它非常重要。ImageNet预训练模型通常配套一组固定的均值和标准差分别是mean[0.485, 0.456, 0.406]std[0.229, 0.224, 0.225]。加载预训练权重后输入数据必须使用同一组归一化参数否则模型产出的特征分布和预训练时不匹配效果会大幅打折。2.3 数据加载细节数据加载这块我用的是PyTorch的DataLoader。除了常见的batch_size设置有几个参数值得注意。第一是num_workers。这个参数控制数据加载的子进程数。我之前一直设为0数据加载成了训练瓶颈GPU经常处于空闲状态训练速度很慢。后来改成CPU核心数的一半比如8核CPU就设4训练速度明显提升。第二是pin_memory设为True可以把数据直接放到锁页内存GPU拷贝更快。这两个参数在数据量大、加载耗时长的场景下效果很明显。还有一个容易被忽视的问题图片解码非常耗时尤其是几千张图在每轮都要重新从磁盘读取并解码。如果显存足够可以把整个数据集预先加载进内存直接在内存里做增强和搬运速度能快很多倍。如果数据集太大放不进内存可以先压缩成较小尺寸或使用缓存机制。以下是常用的DataLoader配置from torch.utils.data import DataLoader train_loader DataLoader( train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue ) val_loader DataLoader( val_dataset, batch_size32, shuffleFalse, num_workers2, pin_memoryTrue )这里的batch_size选32不是说越大越好而是综合了显存容量和训练稳定性之后的折中方案。后面在常见问题部分我会详细展开batch_size的影响。3. 模型搭建与训练调参3.1 自建小CNN结构设计与尺寸推导自建CNN我用的结构是三层卷积加两层全连接整体模型非常直观适合用来对照学习。第一层是卷积层输入通道3输出通道32卷积核大小3×3padding为1经过ReLU激活后跟一个2×2最大池化。输入224×224×3的图像后这一层输出尺寸是112×112×32。第二层卷积输出64个通道尺寸变成56×56×64。第三层卷积输出128个通道尺寸变成28×28×128再接一个池化得到14×14×128。从卷积层出来的特征图拉平后是一个长度为25088的向量经过两个全连接层后输出2个类别得分。这里最常出问题的地方就是全连接层输入维度算不对。我的建议是不要凭空口算直接在代码里加一行测试输出用随机张量过一次网络打印出展平后的大小再填到全连接层里。代码结构如下import torch import torch.nn as nn class CatDogCNN(nn.Module): def __init__(self, num_classes2): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), 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), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): x self.features(x) x self.classifier(x) return xDropout层我放在了全连接层之间比例设为0.5。深层的全连接层参数量占整个模型的大部分最容易过拟合Dropout可以随机失活一半神经元强制网络学习更加冗余的特征表示。3.2 迁移学习ResNet18微调策略迁移学习走的是另一条路。我使用torchvision里预训练好的ResNet18把最后一层全连接替换成新的二分类输出层。关键在于要不要冻结主干参数我对比了两种策略。策略一是完全冻结主干只训练新的全连接层。这种情况下模型相当于一个固定的特征提取器前向传播速度很快参数更新量少但效果受限于预训练特征和猫狗分类任务的匹配程度实测准确率约在93%到95%区间。策略二是不冻结主干对整个网络进行微调使用较小的学习率比如1e-4。这样可以让主干网络在预训练特征的基础上继续适配猫狗数据准确率可以提升到97%以上代价是训练时间和显存占用都更高。我最后选的是策略二因为实验目的是尽可能拿到更好的效果。做法是先用较大的学习率单独训练新的全连接层几个轮次再统一降低学习率微调全网络这样新层和老层之间不会因为学习速率差异出现“老层被带偏”的问题。参考实现片段如下import torchvision.models as models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) num_ftrs model.fc.in_features model.fc nn.Linear(num_ftrs, 2)3.3 训练配置与关键超参数损失函数我用的CrossEntropyLoss这是分类任务的标准选择它内部已经做了Softmax计算和交叉熵计算不需要在模型输出层额外加Softmax。优化器选择了Adam初始学习率1e-3权重衰减设为1e-4。关于优化器的选择Adam的自适应学习率机制让调参压力小很多对初学者特别友好。如果后续追求更高的精度可以换成带动量的SGD配合余弦退火学习率计划往往能在后期追平甚至反超Adam。训练轮次设置为20轮。这里有个经验性判断如果用自建CNN20轮内验证集准确率会经历快速上升期大约在前5轮就能冲到80%以上之后进入平台期涨幅变慢如果用迁移学习通常5轮左右就能接近95%继续训练到20轮主要是为了压榨最后一点性能。训练过程中需要实时监控两个关键指标训练集准确率和验证集准确率。两者之间的差距就是过拟合程度的直接反映。我习惯在每个epoch结束时打印当前学习率、训练损失、训练准确率、验证损失、验证准确率这五条信息一眼就能看出训练状态是否健康。学习率调度我用的是余弦退火PyTorch里对应CosineAnnealingLR把T_max设为20让学习率在训练过程中从初始值平滑下降到接近0。相比固定学习率余弦退火的好处是前期收敛速度快后期能精细收敛整体效果更稳定。保存模型的逻辑也要注意。不能只在最后一轮保存而应该保存验证集准确率最高的一次权重。我的做法是每次验证时对比当前准确率和历史最佳值如果更优就覆盖保存。这样即使后面几轮过拟合导致验证准确率下降也不会弄丢最好的结果。4. 常见问题与排查实录4.1 过拟合训练集99%验证集只有70%这是我在自建CNN时碰到的最典型问题。训练集准确率一路飙升到99%验证集却卡在70%左右上不去典型的过拟合表现。模型参数太多、训练数据相对不足、没有正则化手段三点叠加必然导致这类问题。我按如下顺序排查和解决。首先是数据增强随机翻转、旋转、裁剪这套组合拳打上去之后验证集准确率能立刻提升几个百分点。然后是Dropout我在全连接层之间加了一层比例0.5的Dropout效果非常显著。再然后是早停机制也就是在验证集准确率连续多轮不提升时停止训练避免模型在后期的过拟合把权重越带越偏。最后是降低模型复杂度把第三层卷积的通道数从256降到128。如果不考虑速度还可以在损失函数中给权重加L2正则化也就是设置weight_decay参数这次实验取1e-4防止权重绝对值过大。这一套组合下来验证集准确率从70%提升到了85%左右虽然比不上迁移学习但对于自建CNN来说已经是可接受的结果。4.2 学习率与收敛问题Loss不降、震荡明显训练过程中有一段时间我发现Loss在高位反复震荡准确率也忽上忽下大概率是学习率设置不当。如果学习率过大参数更新步长就会越过最优点导致左右横跳如果学习率过小训练又太慢长时间看不到优化效果。排查思路是先设定一个初始学习率然后观察前几个epoch的Loss变化曲线。如果Loss快速下降说明学习率合适如果剧烈震荡不下降就降低学习率到原来的十分之一再试如果Loss下降得像乌龟爬就适当调大。那次震荡问题的实际原因是我把batch_size调大后忘了同步调整学习率。经验法则很简单batch_size增大为原来的n倍学习率通常也要相应放大到原来的sqrt(n)倍或n倍。因为更大的batch意味着梯度估计更稳定可以走更大的步长。反之减小batch_size时要缩小学习率否则梯度噪声变大配合大学习率极容易发散。如果遇到前期Loss完全不降的情况还有一个通用的排查技巧先拿一小批数据比如16张图片训练几个批次看看模型能不能把Loss降下来。如果连训练集都过拟合不了说明模型结构或数据管道可能存在bug这种问题越早发现越省时间。4.3 显存不足、训练速度慢与类别不均衡显存不足最常见的报错是CUDA out of memory。最直接的解决方案是减小batch_size从32降到16甚至8这是代价最小的做法。如果还想保持大batch可以使用梯度累积也就是把多个小batch的梯度累加起来后再更新一次参数效果上接近大batch训练。另外可以用torch.cuda.amp混合精度训练把部分计算从32位浮点降到16位显存占用几乎减半速度还更快。我当时打开混合精度后显存占用从接近极限降到了安全范围训练速度还有明显提升。训练速度慢除了前面提过的num_workers和pin_memory配置外在推理时还可以用torch.no_grad()上下文管理器避免PyTorch为推理过程构建计算图从而节省内存并加速。另外模型处于eval模式时一定要调用model.eval()这会关闭Dropout层和BatchNorm层的训练逻辑。如果不调用推理结果会不一致而且速度更慢。类别不均衡在猫狗数据集里一般不会出现因为两类样本数量基本持平。但如果自己收集数据正确做法是先用训练集统计每个类别的样本数量再计算加权交叉熵损失给少样本类别更高的权重。同时在评估阶段不要只看准确率还得看召回率、精确率和F1分数。对于二分类问题用sklearn.metrics里的classification_report一行代码就能输出所有关键指标。下面是我在排查和调优过程中整理的速查表供参考现象可能原因排查顺序与解决方案训练集准确率高、验证集低过拟合加数据增强 → 加Dropout → 调低模型复杂度 → 早停训练Loss震荡不降学习率太大降低学习率调整batch_size时同步调整学习率显存不足batch过大减小batch_size → 梯度累积 → 混合精度训练加载极慢num_workers0增加子进程数 → 开启pin_memory → 数据预加载到内存推理结果和训练不一致忘了eval模式调用model.eval()推理时加torch.no_grad()验证集指标波动大数据划分不均衡用分层抽样划分数据集确保每类比例一致这次实验跑下来我最大的体会是调参本身不是玄学而是建立在观察和假设验证基础上的系统工程。每一个问题都有迹可循关键是要建立一套可靠的监控机制训练过程中把指标记录下来画成曲线哪里出了问题一眼就能看出来。刚开始做猫狗分类时我也曾为了几个精度而在各种参数组合里盲目尝试后来把版本记录和曲线画出来才真正找到每一步调整的方向。如果你也在做类似实验建议先固定一个baseline再一项一项加改进每次只改一个变量这样你才知道哪个操作真正拉高了准确率。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价