资讯动态

基于Python与CNN的猫体型三分类识别系统实现

发布时间:2026/10/21 22:09:56 来源:尧图企业网站定制
这个标题我特别眼熟几乎每年都会在毕业设计的选题清单里看到一版类似的。用一句话翻译过来就是用Python搭一个卷积神经网络CNN让模型拿到一张猫的照片后判断这只猫的体型属于偏瘦、正常还是偏胖。很多同学第一反应是“这能算课题吗”但把任务拆开以后你会发现它完整覆盖了数据标注、图像预处理、模型设计、迁移学习、训练调参、指标评估这整条链路当毕设完全撑得住而且后续稍微改一改也能迁移到狗、牛羊的体况评分场景里。这篇文章我就按这个题目的完整流程来讲从任务定义、数据准备到模型选型、训练细节再到最后评估和扩展方向。每个环节我都会说清楚“为什么这么做”尽量让没系统学过深度学习的同学也能照着复现出来。1. 这个题目的本质一个典型的三分类图像任务1.1 “猫的体型识别”不是玄学从BCS评分到三分类很多同学拿到题目时第一反应是懵体型这东西怎么定义是不是得看猫胖不胖、肚子有没有下垂其实兽医临床上已经有了一套相对成熟的判断标准叫体况评分Body Condition Score简称BCS一般是9分制1分是严重消瘦5分是理想体态9分是重度肥胖。实际判断时会看腰线、腹部是否下垂、肋骨能不能摸到、尾巴根部有没有脂肪堆积等。放到计算机视觉任务里我们不可能让模型去“摸”猫只能靠图像上的视觉特征。所以最稳妥的做法是把9分制简化成三分类1~3分映射为“偏瘦”4~5分映射为“正常”6~9分映射为“偏胖”。这样一来问题就变成了一个标准的图像三分类任务卷积网络的输出层只需要三个神经元接softmax得到概率分布再接交叉熵损失就能训练。这个简化过程很重要它决定了数据标注和模型输出的口径。如果你直接把9个分数当成9个类别去做数据量不够的情况下模型基本学不动而且相邻分数之间本身就很模糊标注员也难以稳定区分“6分”和“7分”。三分类则给了人工标注足够的容错空间也让模型的任务边界更清晰。1.2 为什么深度学习方案能胜任对比传统手工特征可能有人会问判断体型是不是用传统图像处理就够了比如先把猫从背景里分割出来再算一下身体的长宽比、腹部轮廓曲率、轮廓填充度之类的几何特征。这类方法在纯色背景、正面标准姿势下确实能跑通一旦照片变成家庭环境里的随手拍背景杂乱、姿势千奇百怪、毛发长短不一手工规则就非常脆弱。CNN方案的核心优势是端到端。我们不手工设计特征而是让网络从训练数据中自动学习“什么样的轮廓特征代表偏瘦、什么样的纹理特征代表偏胖”。卷积层通过局部感受野提取边缘、纹理、形状等底层特征逐层抽象出“腰线是否明显”“腹部是否下垂”“整体是否圆润”这类高层语义信息。这跟你写一堆if-else规则不一样只要数据够多网络能自己学到泛化性更好的判断依据。1.3 两条技术路线自建小CNN和迁移学习怎么选在实际项目中路线基本分两条。一条是从零自建一个小型CNN结构自己写卷积层、池化层、全连接层都手搭出来优点是结构完全透明答辩时每个参数都能讲清楚来龙去脉很适合用来解释卷积原理缺点是参数量小、数据不够时精度往往一般。另一条是迁移学习用预训练好的ResNet18、MobileNetV3等模型做特征提取只替换最后的全连接分类头优点是收敛快、精度高是现在绝大多数图像分类项目的主流做法。我个人的建议是毕设不要只走一条路。先用自建小网络把全流程跑通让每个环节都心里有数再切换到迁移学习刷一轮指标把两种方案的结果做成对比这样既有原理讲解又有实验深度答辩时的素材也丰富得多。两种方案的对比我整理成了一张表对比项自建小CNN迁移学习ResNet18等结构透明性极高每层可解释中特征层来自预训练模型小数据集表现一般容易欠拟合好预训练特征通用性强训练成本低几十分钟到几小时中微调也需要一定轮次答辩友好度高方便讲原理较高方便讲工程思路推荐用法跑通流程、可视化特征刷指标、做最终效果2. 数据准备整个项目里最容易被低估的一步2.1 数据从哪来公开数据集和自建采集两手准备猫体型识别不是一个大热门的公开任务所以很难直接找到一个现成的“猫体况分类数据集”。通用的做法是两个来源结合一是先从深度学习竞赛平台上常见的宠物图像数据集中筛选猫科照片这类数据量大、背景多样适合作为基础素材二是自行采集和标注身边朋友的猫、宠物店的猫、网上可合法使用的开放图片都可以在确认授权的前提下纳入。重点在于确认授权问题。我接触过一些项目直接用爬虫抓了几千张图就开训最后准备写毕业论文时才发现版权和授权说不清楚只能把数据部分重写一遍非常折腾。稳妥起见自建部分尽量用自己拍摄或明确标注可自由使用的图库公开部分要记录好来源和许可信息这在毕设查重和后续公开时会少很多麻烦。2.2 标注规范把“胖瘦”变成可操作的标准标注这件事看起来简单实际是项目最影响效果的一环。我给一个可以抄作业的标注规范优先选择猫的侧面站立照要求身体完整可见躯干没有被遮挡尾巴不遮住腹部正面照和趴卧照单独处理不要和侧面照混在一起标注因为姿态差异会直接影响轮廓特征照片里最好只有一只主体猫多只猫混在一起会给模型引入大量无关信息。另一个容易犯的错是只按“看起来胖不胖”凭感觉标。更好的做法是先让标注员理解BCS三分类判据比如偏瘦类主要看腰线凹进、肋骨轮廓明显正常类看腰线略收、腹部平滑偏胖类看腰线消失、腹部下垂呈梨形。有条件的话让两个人分别标一批再对不一致的样本讨论修正能明显提升标注稳定性。类别平衡也要提前控制。实际喂养环境下正常体型的猫大概占多数偏瘦和偏胖的相对少一些如果训练集里“正常”占了70%模型很容易学成“无脑输出正常”。建议在采集时有意识地控制三个类别比例在1:1:1附近实在不平衡后面训练时再配合类别权重来兜底。2.3 预处理和数据增强不要让猫在输入阶段就已经“变形”图像预处理里有一个关键细节不要简单粗暴地把所有图Resize成正方形。直接拉伸会改变猫的宽高比而体型判断恰恰依赖身体的比例关系拉成方图之后原本偏瘦的猫可能在视觉上变“宽”了模型学到的东西就是错的。正确做法是等比缩放后做边缘填充也就是常说的Letterbox方式把图片放到统一的224×224画布里多余部分填充灰色或黑色。数据增强方面常用的组合是随机水平翻转、随机旋转正负15度、随机亮度对比度扰动、随机裁剪缩放。不要轻易用垂直翻转猫不会头朝下站着这个增强会引入不符合现实的样本。分类任务里也可以用一点随机擦除模拟猫被遮挡的场景但擦除面积不要超过30%否则会破坏整体轮廓信息。3. 模型设计卷积层、特征图和参数量怎么定3.1 自建小CNN重复的Conv-BN-ReLU-Pool结构如果你打算先自建一个小模型练手我建议按这个思路搭输入层是224×224×3的RGB图像经过几组卷积块提取特征。每个卷积块内部是卷积层、批归一化BatchNorm、ReLU激活、最大池化四件套。卷积负责提取局部特征BN负责稳定训练分布ReLU负责引入非线性池化负责下采样、扩大感受野。为什么要用BN因为深层网络训练时每层输入分布会随着前层参数变化而漂移BN在每个mini-batch内做归一化把数据拉回稳定范围这样可以用更大的学习率、收敛更快。ReLU激活函数则比较简单正区间梯度恒为1能缓解梯度消失。3.2 特征图尺寸变化计算手算一遍心里就有底自建网络的设计不是随便叠层数特征图的尺寸变化要能算清楚。卷积层的输出尺寸公式是W_out (W_in − F 2P) / S 1其中F是卷积核大小P是paddingS是步长。以一个224×224×3的输入为例16通道的3×3卷积加padding1输出尺寸保持224×224经过2×2最大池化后变成112×112。按这个逻辑往下走特征图尺寸减半通道数翻倍这是经典的“通道数增加、空间尺寸缩小”设计模式。我整理了一个示例参数表方便你对照着算网络层输入尺寸卷积核/padding/stride输出尺寸通道数Conv1 BN ReLU Pool224×224×33×3, p1, s1112×11232Conv2 BN ReLU Pool112×112×323×3, p1, s156×5664Conv3 BN ReLU Pool56×56×643×3, p1, s128×28128Conv4 BN ReLU Pool28×28×1283×3, p1, s114×14256Conv5 BN ReLU Pool14×14×2563×3, p1, s17×7512全局平均池化7×7×512—1×1512全连接分类层512—3—最后用全局平均池化把7×7变成1×1再接三个神经元的全连接层参数量会远小于直接把7×7×512拉平成一个大向量的做法也不容易过拟合。3.3 迁移学习实践ResNet18替换分类头如果你想用迁移学习代码上只需要做一件小事加载预训练模型把最后一层全连接替换成输出3类的新全连接层。PyTorch里类似这样import torch.nn as nn import torchvision.models as models model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, 3)这里有个新版PyTorch的细节要注意旧代码里常见的是pretrainedTrue新版本已经推荐用weights参数来指定权重版本不然会报警告而且以后维护也不方便。另外一个实用策略是分两阶段微调先冻结特征提取层只训练新加的全连接分类头用一个稍大的学习率跑几个epoch让分类头先适应新任务然后解冻整个网络把学习率调小到原来的十分之一端到端微调效果通常比一上来就全量训练要稳。3.4 损失函数和优化器为什么是交叉熵和AdamW分类任务里最标准的损失函数是交叉熵CrossEntropyLoss。它做的事情是对正确类别对应的预测概率取负对数。如果模型对真实类别的预测概率很接近1损失就很小如果预测概率很小损失就非常大。这种梯度信号比均方误差更适合分类问题因为梯度的大小和“错误程度”直接挂钩。优化器方面我推荐AdamW而不是普通Adam。Adam本身对学习率不太敏感、收敛很快但权重衰减的写法在原生Adam里会和梯度相关项耦合导致正则化效果不稳定AdamW把权重衰减独立出来直接在参数更新时减去一个固定比例正则效果更干净。实际项目里设weight_decay1e-4就能有效抑制过拟合。4. 训练流程与调参记录从超参数到损失曲线4.1 数据划分和超参配置按猫个体分不按照片分数据集划分有一个特别容易踩的坑如果同一只猫不同姿势的照片既出现在训练集又出现在测试集模型等于“见过”这只猫测试准确率会被严重虚高。正确的划分方式是按照“猫个体”来分最好先给每只猫一个编号然后把同一编号的所有照片分到同一个集合里尽量保证训练集和测试集里是不同个体。超参数方面我给一套可以直接作为起点的配置输入尺寸224×224Batch Size 32或64初始学习率1e-3迁移学习微调阶段用1e-4~3e-4优化器AdamW权重衰减1e-4训练轮次30到50早停patience设为10个epoch。训练集、验证集、测试集按7:1.5:1.5划分验证集用来做早停和调参测试集最后才碰。4.2 训练过程观察loss下降和过拟合信号训练时要盯的曲线主要是两条训练集损失和验证集损失。理想情况是训练损失一路下降验证损失先下降后在一个平台期稳定。如果训练损失还在降验证损失却开始反弹那就是典型的过拟合信号这时候应该停下来把模型回滚到验证损失最低的那个checkpoint而不是等最后一个epoch的权重。PyTorch里做early stopping可以手动保存验证集表现最好的状态类似这样best_val_loss float(inf) for epoch in range(num_epochs): train_one_epoch() val_loss evaluate() if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth)用早停恢复最佳权重后再用测试集评估一次这样得到的准确率才是可信的最终结果。4.3 实用技巧组合多尺度、类别权重和测试期增强训练技巧不需要太多有三招在实践中性价比很高。第一招是余弦退火学习率调度让学习率按余弦曲线从初始值衰减到接近0比固定学习率在后期更容易收敛到更优的局部最小点。第二招是类别不均衡时给损失函数加权重比如偏胖样本少就在计算交叉熵时把偏胖类别的权重调高一点PyTorch的CrossEntropyLoss(weight...)直接支持。第三招是测试期的简单增强把同一张图和它的水平翻转都过一遍模型两个概率取平均再预测通常能带来1到2个百分点的提升。多尺度训练也是一个可选项比如每一轮随机让模型看到大小在200到260之间缩放的图片再裁剪到224相当于免费扩充了数据。但这个操作会显著增加训练时间毕设项目里如果时间不是特别充裕可以先不加。5. 评估结果与常见问题排查5.1 评估指标准确率之外还要看混淆矩阵很多同学做完项目只报一个整体准确率比如“模型最终准确率87%”这在答辩时很容易被追问。更严谨的做法是报告每类别的精确率Precision、召回率Recall、F1值以及一个混淆矩阵。假设测试集有400张图某次实验的结果形态大致可以是这样真实/预测偏瘦正常偏胖召回率偏瘦11814884.3%正常12131986.2%偏胖769588.0%从这个表格可以很直观地看到误判主要发生在相邻类别之间“正常”和“偏瘦”之间、以及“正常”和“偏胖”之间的混淆最严重这是符合直觉的等模型能力提升到一定程度后再往上扣准确率就会非常吃力。这种分析性表述比单纯报一个数字有说服力得多。5.2 典型翻车场景与排查指南我把实际项目里比较高发的几类问题整理成了速查表现象可能原因排查与解决训练准确率卡在60%左右不动类别严重不均衡、模型欠拟合检查各类别样本数给loss加类别权重增加模型容量loss变成NaN学习率过大、数据里有NaN像素调低学习率检查数据预处理管道确认归一化正确验证集指标高、测试集指标低按照片划分导致同个体泄漏、过拟合验证集改为按猫个体编号划分数据降低正则强度别用验证集反复调参对长毛猫系统性误判训练集中长毛猫数量不足单独补充长毛猫样本或做分层抽样确保毛发长度分布平衡对“趴卧”照片全部输出正常训练集里趴卧姿势占比过高去掉趴卧姿势或单独建类模型对姿势敏感的锅别让体型背5.3 一个容易被低估的坑长毛猫和拍摄角度这个项目里最容易出问题的不是网络结构而是长毛猫。一只毛量蓬松的猫即使体重在正常范围视觉上也很容易被判断成偏胖。应对方法有三个层次第一层次是采集数据时保证长毛和短毛样本均衡模型需要见过足够多的“长毛但体型正常”的例子才能学会不把毛发量当胖第二层次是在评估时按毛发长度分别统计准确率如果长毛组明显低于短毛组就说明模型还没学到该学的东西第三层次是给网络加辅助输入比如额外标注毛发长度做成双分支网络或者用传统图像处理估计毛发覆盖程度再作为额外特征拼进去。拍摄角度的影响也一样。侧面照是最标准的判断角度但很多数据集里混杂了大量正面照、斜上方俯拍照这些角度下腰线信息天然缺失。如果这类非标准照片很多模型就会被迫找其他特征来猜结果往往不理想。我的建议是标注时给每张照片加一个“视角”标签训练时先只用侧面照训练一个主模型再用其他角度照片微调或者在数据增强阶段固定只保留侧面主导的照片。6. 从毕设到落地后面还能怎么扩展6.1 轻量化和边缘部署如果想把项目从“能跑”变成“能演示”一个很自然的扩展是模型轻量化。训练阶段可以用大一点的网络但演示的时候可以换成MobileNetV3这类轻量网络或者把训练好的模型导出成ONNX格式再用量化手段把权重复制成8位整数。这样模型体积可以从几百MB缩到几十MB在普通笔记本CPU上也能做到几十毫秒推理一张图。导出ONNX在PyTorch里非常方便一句话就行。真正需要注意的反而是输入预处理的一致性训练时用的是Letterbox加归一化导出推理时要保证输入的图片也走完全一样的流程否则模型在部署时表现会和训练时有很大偏差。这点我在不少项目里见过人踩说起来小事排查起来很费时间。6.2 从“分类”到“检测识别”体型识别本质上是在“猫已经出现在画面里”的前提下工作的。真实应用场景里照片可能是一整面墙、一个房间猫在里面只占据很小一块。这时候可以把框架升级成两阶段先用目标检测模型把猫框出来再把框出来的区域送入体型分类网络。检测可以用现成的轻量检测模型训练成本可控和体型分类串起来就成了一条能落地的推理链路。这个扩展方向对毕设很友好因为它不需要改分类模型的核心原理只是加了一个上游模块。答辩时你可以讲清楚整个链路检测负责定位分类负责判断两部分的误差会叠加所以检测框质量对最终结果有直接影响。这种系统级思考很加分。6.3 多模态融合图像之外再给一点结构化信息纯图像能获取的信息其实有限很多体型判断需要知道猫的品种、年龄、是否绝育、体重历史记录。绝育后的猫更容易发胖长毛猫视觉上自带“膨胀”效果老年猫肌肉流失更多。如果手上能拿到这些结构化信息把它们和图像特征拼在一起再接一个简单的分类头往往比只用图像精度更高同时还让项目显得更有思考深度。具体实现也不复杂图像经过CNN得到特征向量结构化信息经过一个几层的MLP编码成另一个向量两个向量拼接后过全连接层输出三类概率。数据组织上只需要每只猫对应一条结构化的标签表。做这个方向的时候要特别注意结构化信息不能泄露测试时拿不到的数据比如未来体重变化、是否已经知道诊断结果之类的字段。最后分享一点实操体会这类毕设项目我最深的体会就是前期数据工作偷懒后期模型再怎么调都补不回来。见过不少同学上来先把模型搭好跑通然后发现训练集里偏胖样本没几张、长毛猫全是同一个姿势回头再补数据工期全部浪费。宁可第一周把标注规范、类别平衡、按个体划分这三件事做扎实后面训练就是顺水推舟。还有一个小技巧训练过程中把损失曲线截图、混淆矩阵和每个类别的错误示例都留存好。答辩时把这些东西整理出来比背概念讲理论有用得多。最后再提醒一下如果你的机器只有CPU那就用轻量网络跑小尺寸输入先把流程走通再去想办法用预训练权重刷高指标别一上来就开一个ResNet101的训练任务等着你的是漫长的黑夜。

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

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

免费获取报价 →
↑