深度学习图像分类实战:基于PyTorch的鞋类分类毕设全流程解析

发布时间:2026/10/21 12:17:21
深度学习图像分类实战:基于PyTorch的鞋类分类毕设全流程解析
1. 这个毕设题目选得聪明但坑也不少每年毕业季我都会被问到同一个问题“老师深度学习方向的毕设到底选什么题目能顺利做完又不至于太水”后来某高校一个朋友让我帮他把关他的毕设开题题目就是“基于Python深度学习的鞋类分类”我当时第一反应是这题目很适合作为进入视觉分类领域的第一块跳板它的技术栈完整覆盖了数据准备、迁移学习、模型训练到评估展示难度曲线又足够平缓不至于让人卡在某个数学推导上直接放弃。鞋类分类本质上是一个细粒度图像分类问题。它和猫狗分类、花草识别这些通用分类不太一样鞋子品类之间的差异往往集中在鞋头形状、鞋跟高度、鞋帮轮廓、材质反光这些局部细节上。比如运动鞋和帆布鞋整体轮廓都是圆头低帮但鞋底厚度、鞋面纹理、系带方式完全不同高跟凉鞋和无跟凉鞋最大的区别可能只在鞋跟那几厘米的侧面剪影。这种“大框架很像、局部细节决定身份”的特性正好能逼着你把数据增强、模型微调、混淆矩阵分析这些环节真正做深而不是简单套一套代码就完事。很多学生做到后面会发现真正的难点不是让代码跑起来而是让模型学会区分那些长得像的类别。这篇文章我就用自己实际辅导过的一段完整流程作为主线从题目拆解、数据集整理、模型选型、训练调参到最后的答辩素材准备把每一步怎么判断、怎么做、为什么这样做讲清楚。适合正在准备相关方向毕设、或者想系统走一遍深度学习图像分类全流程的朋友参考有基础的可以直接跳到你关心的章节看纯新手的建议从头往后读。2. 把“鞋类分类”拆成三个问题数据、算力、算法边界2.1 题目拆解约等于一次“带约束的行业落地”拿到“基于Python深度学习的鞋类分类”这个题目你先别急着打开代码编辑器。我习惯让学生做的第一件事是把题目翻译成三句大白话我们需要拿到一批包含多种鞋类图片的数据我们要训练一个模型让它对没见过的新图片输出正确的鞋类标签我们要用足够有说服力的方式证明这个模型确实可靠。这三句话对应到实际操作里就是数据采集清洗、模型训练调优、指标体系构建三个模块任何一个模块做不好整篇论文都会出现短板。环境依赖方面主流做法就是Python配合深度学习框架我这次用的是PyTorch选它的原因后面会单独说。算法层面通常跑不出一条从零训练的卷积神经网络路线绝大多数人会选择基于ImageNet预训练权重做迁移学习这也是目前图像分类任务里性价比最高的一条路——你不需要几十万的标注数据和几周的GPU时间几千张样本图加上一张消费级显卡就能做出不错的效果。这个题目的“边界”也很重要。鞋类分类和通用物体分类不同它要求的不是一个粗粒度的“这是不是鞋”的判断而是“这是哪一类鞋”的细分判断这就决定了模型的最后分类层数量、数据集的类别结构都必须仔细设计。很多学生在这里翻车比如拿到的数据集类别数量不一致、同一种鞋在不同来源的命名规则不同直接导致训练脚本报错。2.2 一个容易被低估的约束条件算力和时间算力是很多人写开题报告时不重视、实际做起来才痛苦的变量。如果你在本地用CPU训练一个ResNet50一个epoch跑完可能就需要十几分钟而一个像样的迁移学习流程至少要跑20到30个epoch再算上反复调参的时间CPU方案的体验会很差。我平时给的建议是优先用可用的GPU环境没有的话就尽量选轻量级的骨干网络比如ResNet18或MobileNetV3不要一上来就堆ResNet152很多人的显存和耐心都撑不住。时间因素同样关键。毕设的实际周期往往比想象中短前期要写开题报告、中期要出阶段性成果、后期还要预留答辩材料制作时间真正留给调参的时间窗口普遍只有一到两周。所以整个技术路线要偏保守用成熟稳定的预训练模型用公开可复现的数据集把精力放在特征分析和结果解释上而不是折腾花哨的新网络结构。学校里做研究也要追求“结果可控”这是选择题目后第一件要想明白的事。3. 数据准备这个环节决定了你后面50%的成败3.1 数据集结构设计与类别梳理鞋类分类现在网上能找到一个公开的鞋类图片数据集里面按照品类划分成了13个大类包括运动鞋、皮鞋、帆布鞋、凉鞋、靴子、高跟鞋、拖鞋这些常见类型。我让学生先别急着跑模型而是先把数据集目录结构整理清楚。PyTorch的ImageFolder机制要求每个类别一个文件夹文件夹名字就是类别标签目录结构和预期不一致会直接影响加载。举个例子这份数据集里同一个大类下可能存在风格或视角上的差异比如“靴子”里既有短靴也有过膝长靴、既有侧面视角也有俯拍视角这些差异如果没有在数据划分阶段做好均匀分配训练集和验证集的效果会出现明显波动。我的做法是先统计每个类别的样本数然后按类别分层划分训练集、验证集和测试集比例大约在7:2:1左右。注意这里一定是“分层划分”如果用随机划分某些样本少的类别很可能在训练集或测试集里出现数量异常最终得出的准确率也会有偏差。3.2 图像清洗与基础预处理原始数据不能直接喂给模型。鞋子图片通常尺寸不一致有的商家图分辨率很高有的用户拍摄图非常模糊甚至还有少量图片带着水印文字、尺码标注、背景抠图残留的痕迹。我在预处理阶段做的最核心一件事是统一输入尺寸常用的是224x224这是ResNet系列默认接收的大小。原始图片在resize过程中如果直接拉伸鞋子的长宽比会变形影响模型学习形状特征所以建议先做等比缩放再居中裁剪这样能保留更多有效信息。另外为了保证模型在不同光照和背景下都能稳定工作我还加了一个简化的背景干扰排除步骤优先保留主体占比大的图片丢弃明显的纯背景图或信息残缺图。这一步不是必须的但做了之后测试集上的准确率确实更稳定。全部整理完后单类别样本数尽量控制在几百张以上类别太少时要考虑是否需要借增强来补充这方面后面会说。3.3 数据增强少样本和过拟合的救星鞋类数据集整体规模不大单类别样本数可能只有几百张到一千张直接训练很容易出现过拟合。数据增强在这里的作用不只是“把数据变多变花”更重要的是模拟出实际场景里的各种变化让模型学会“不管鞋子在图片里是正着放、斜着放、背景换掉都能认出来”。我实际用到的增强策略随机水平翻转、随机旋转正负10度以内、随机仿射变换、颜色抖动亮度、对比度、饱和度微调、随机裁剪后缩放回原尺寸。水平和垂直翻转要注意一点鞋类图片左右对称水平翻转安全垂直翻转一般不用因为会改变鞋子的“摆放姿态”在部分类别上反而会引入不合理样本。另外如果训练集中某些样例带有较大透明度或反光效果可以适当加入少量随机擦除Random Erasing模拟鞋面被部分遮挡的情况增强模型的鲁棒性。增强幅度要适度。过强反而会让模型学不到有效特征甚至导致训练集损失迟迟降不下去。我自己踩过的判断标准是增强后的图片人眼仍然能轻松认出是什么鞋如果连人都认不出来了这个增强强度就该回调。4. 模型选型和训练策略为什么预训练迁移学习是首选4.1 从零训练卷积网络为什么不划算很多新手拿到图像分类项目第一反应是自己搭一个卷积神经网络比如几层卷积加池化再加全连接。这个思路在玩具数据集上没问题但放到13类鞋分类上效果会迅速触顶。原因不复杂卷积网络从零训练需要大量标注数据来学习底层纹理、边缘、颜色分布这些基础特征而鞋类数据集规模远达不到这个量级。模型很容易陷入“在训练集上记得住、在测试集上认不出”的状态。ImageNet预训练权重相当于替你把“看世界的基础能力”练好了它已经知道什么是边缘、什么是纹理、什么是物体轮廓我们要做的只是让它把这些能力迁移到“区分不同鞋子”这个具体任务上。这在工业界也几乎是最常见的选择——用预训练模型做迁移再用相对较小的数据量微调成本低、效果好、可解释性强。4.2 骨干网络对比ResNet18、ResNet50还是EfficientNet在动手之前我让学生先做了个小表格对比这步花不了多少时间但能让选型理由充足地写进开题报告骨干网络参数量推理速度效果水平迁移学习显存占用适用情况ResNet18较小快良好低CPU可接受入门推荐ResNet50中等中等优秀中有入门级GPU推荐EfficientNet-B0较小快良好偏上低追求性价比EfficientNet-B4较大慢优秀较高算力充足可选MobileNetV3最小极快中等偏上极低强调部署场景我最终选择的是ResNet50作为主干理由是它在参数量和效果之间平衡得最好ResNet的残差结构在训练稳定性上也比一些深层VGG类网络强很多整个训练过程不容易跑飞。如果你只有CPU环境建议用ResNet18效果差距在鞋类这种难度不算极端的数据集上不会特别大但训练时间会显著缩短。在PyTorch里加载预训练模型只需要一行代码把分类头替换成自己任务对应的类别数就可以了import torchvision.models as models import torch.nn as nn model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) # 替换最后一层全连接13个类别 in_features model.fc.in_features model.fc nn.Linear(in_features, 13)4.3 冻结与微调先让特征提取层稳住再动分类头迁移学习里最关键的策略问题是哪些层要冻结、哪些层要训练。常见的做法分两种一种是把整个骨干网络参数全部冻结只训练最后新加的分类层另一种是骨干网络全部参与训练从头到尾微调一遍。前者速度快、不容易过拟合但效果上限有限后者拟合能力强但训练时间更长对数据量和正则化要求更高。我这次采用了一个中间策略第一轮训练时冻结骨干网络的所有层只训练新替换的分类头用较大的学习率快速训练几个epoch让新分类头先学会利用预训练特征然后解冻骨干网络的最后几个残差块用较小的学习率继续训练。这样可以兼顾效率和精度也避免了一开始就全量微调导致特征被破坏的问题。具体实现时可以设置参数的requires_grad属性来控制哪些层参与训练# 先冻结全部参数 for param in model.parameters(): param.requires_grad False # 解冻最后一层分类头 for param in model.fc.parameters(): param.requires_grad True # 第一轮只优化分类头 optimizer torch.optim.Adam(model.fc.parameters(), lr0.001) # 第二轮解冻部分骨干层 for param in model.layer4.parameters(): param.requires_grad True optimizer torch.optim.Adam([ {params: model.layer4.parameters(), lr: 0.0001}, {params: model.fc.parameters(), lr: 0.0001} ], weight_decay1e-4)这样的两阶段做法在调整参数时也更直观第一轮如果分类头已经能稳到一定的准确率说明特征提取没问题第二轮微调后准确率还能继续上升说明任务确实需要一些更加个性化的特征。5. 训练参数、损失函数与完整代码框架5.1 训练脚本的工程结构如果把整个项目比作一份外卖订单训练脚本就是厨房里负责炒菜的灶台前面所有数据准备都是为了给灶台备好菜。我习惯把工程拆成几个文件每个文件只负责一件事这样后期调试和写论文时都方便对照数据加载和增强逻辑放一个文件模型定义放一个文件训练主循环放一个文件评估代码单独再放一个文件。不要想着把所有代码写到一个脚本里跑完就算否则后面定位问题、补充实验对比时你会非常痛苦。5.2 核心训练代码稳定可跑的最小实现训练主循环其实没有太多神秘的地方PyTorch生态已经很成熟主要是注意几个容易出错的小点模型和数据的device一致、每个epoch结束要记得验证、保存模型参数而不是保存整个模型对象。下面是一个整理过的最小训练框架import torch import copy from torch.utils.data import DataLoader from tqdm import tqdm def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 correct 0 total 0 for images, labels in tqdm(loader, descTraining): images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() return total_loss / len(loader), 100.0 * correct / total def evaluate(model, loader, criterion, device): model.eval() total_loss 0 correct 0 total 0 with torch.no_grad(): for images, labels in loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) total_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() return total_loss / len(loader), 100.0 * correct / total有了这个基础框架之后换数据集、换模型、改超参数都很方便这也是我推荐同学们把训练代码模块化的重要原因后续要加学习率调度、早停机制都只需要在对应位置插入几行代码。5.3 损失函数的选择为什么交叉熵就够了鞋类分类是标准的单标签多分类问题一张图对应一个类别交叉熵损失是最自然的选择多数情况下没有理由换成其他损失。PyTorch里nn.CrossEntropyLoss()自带Softmax计算你只需要把模型的原始输出丢给它不需要额外对输出做Softmax。不过有两个细节值得注意。第一个是类别不均衡问题当出现某个类别样本数远多于另一个类别时模型会倾向于把所有图片都预测成大类。调整策略可以是按类别频率设置权重把损失里小类的loss权重调高from sklearn.utils.class_weight import compute_class_weight import numpy as np classes np.array(list(train_dataset.class_to_idx.values())) sample_labels train_dataset.targets class_weight compute_class_weight(class_weightbalanced, classesclasses, ysample_labels) class_weight torch.tensor(class_weight, dtypetorch.float32).to(device) criterion torch.nn.CrossEntropyLoss(weightclass_weight)第二个细节是标签平滑Label Smoothing。如果数据集中存在少量误标注的图片硬标签会在训练中放大大类的置信度而标签平滑相当于给标签留了一点容错空间通常能带来1%到3%的准确率提升。PyTorch里在构造交叉熵损失时传一个label_smoothing0.1参数就能开启多数项目都适用。5.4 优化器、学习率与训练轮数几组可以直接参考的参数优化器我尝试过SGD和Adam。SGD配合momentum在微调阶段表现稳健Adam在前期收敛速度快。考虑到毕设的实验周期不会太长我选择的主优化器是AdamW它的收敛稳定配合weight decay实现起来更顺手。学习率调度我用了余弦退火它能在训练后期逐步缩小步长让模型在最优解附近精细调整比学习率固定不变或少次step decay更平滑。如果你希望有个公式化的起点可以直接参考这套参数批量大小32、初始学习率0.0001微调阶段、weight_decay 1e-4、训练总epoch为25到30轮。这组参数在ResNet50加13类鞋分类的配置下表现正常如果你的显卡很好可以增大批量到64配合同步增大一点学习率如果训练不稳定就把学习率再调低一半。不要迷信某个具体数值观察损失曲线及时调整才是关键。6 训练过程中的真实坑点从90%验证集跌到78%测试集说起6.1 一个经典的翻车现场验证集很高测试集很低训练进行到一半时我让学生跑了一下测试集发现准确率只有78%左右而验证集上已经快到90%了。这个数字很能迷惑新手因为从验证集上看训练一切正常至少说明模型没有“没学会”。但验证集和测试集的准确率大幅差值直接指向了一点训练过程和验证过程存在某种程度的信息泄漏或者模型依赖了不该依赖的背景信息。排查思路是这样的第一步检查数据划分。如果验证集和训练集来自同一个目录、只是随机抽样拆分那么验证集的分布和训练集高度重合模型在验证集上表现好并不代表泛化能力好。第二步检查数据集内部结构。鞋类数据集中的商详图经常会带纯色背景而部分用户上传的照片场景多样如果某一类在训练集中很多图片都是白底商详图测试集却主要是真实场景照片模型就会学到“白色背景运动鞋”这种错误规则。第三步才是最让人头疼的需要实际去看看那些预测错误的样本确认模型到底在做规则记忆还是规则理解。6.2 过拟合证据从损失曲线到预测行为在B同学我带的另一个学生的项目里他当时通过绘制训练损失和验证损失的曲线很快就看出问题训练损失一路下降到0.2以下验证损失却在epoch 8附近开始反弹。这就是教科书级的过拟合曲线但在鞋类数据集里这种反弹有时候没那么明显因为验证集整体准确率还在涨只有细分到某些类别时才能看到端倪。我还推荐过一个更直接的判断方法把训练集输入模型看它的预测置信度。如果模型对训练样本的置信度普遍超过0.99而对测试样本的置信度明显下降很多说明模型更多是在“背答案”。一个健康的模型应该能在不熟悉的图片上保持信心同时又不至于对错误分类给出极高的错误置信度。这种置信度分析可以直接做成论文里的可视化和分析图。6.3 针对过拟合的四个修复动作发现问题之后我建议的顺序是先调整数据再调整模型约束最后才动结构。在鞋类这个项目里有几个具体动作很有效第一是重新做严格分层划分保证训练、验证、测试三个集合中各类别比例一致如果必要直接把验证集和测试集从不同来源的图片中抽出来。第二是增强强度上调10%到20%尤其是随机裁剪和颜色抖动让模型不再依赖固定背景或固定构图。第三是开启随机擦除目的前面说过是为了让模型学会通过局部特征判断类别。第四是加大weight_decay从较小的值逐步升到1e-3当损失产生明显波动时说明正则化已经过强需要回调。还有一个很容易被忽视的点增强策略不能用于验证集和测试集只能用于训练集。有人图省事把所有的图片在进入模型前都统一做随机翻转这等于让验证集的每一轮评估都在“变题目”最后得到的结果没有可比性。我在代码里用transforms区分了训练集和验证集的数据处理流程验证集只做resize和normalize不做任何随机操作。6.4 调整后的效果从78%到91%经过上述几步修改测试集准确率从78%稳定升到91%左右这个过程大概用了三轮实验。第一轮只修数据划分方案准确率升到84%第二轮增强强度微调加梯度裁剪升到88%第三轮把冻结策略换成“先冻结再解冻后几层”最后到了91%。这说明该项目的性能瓶颈并不在于模型结构不够复杂而在于数据带来的虚假规律干扰。对于绝大多数类似的分类项目这个结论都适用先查数据再调模型顺序反了你可能调一星期都找不到方向。7. 评估可视化与论文素材分数之外答辩真正看的“证据”7.1 混淆矩阵看每一类到底“像谁”准确率只是一个数字放在毕设答辩里说服力不足。混淆矩阵才是真正能展示“模型哪里分得清、哪里容易混淆”的硬材料。鞋类分类结果里运动鞋和帆布鞋混在一起、高跟鞋和凉鞋偶尔分不清这类混淆信息用矩阵一眼就能看到。我习惯把混淆矩阵绘制成带颜色深度的热力图纵轴是真类别横轴是预测类别。答辩时你指着矩阵里偏离对角线的亮点说明“模型主要混淆了哪两类原因可能是它们的轮廓特征接近后续可以怎么做”这种逻辑线比单纯报一个准确率高很多。计算混淆矩阵可以用sklearn.metrics.confusion_matrix数据准备好之后画图很顺手。除了混淆矩阵类别级别的精确率、召回率和F1分数也要单独整理成表。13个类别的F1如果大都在85%以上只有一两个类别偏低那这个项目的评分天然具备“偏科分析”的素材。如果所有类别F1都很平均说明模型在各类别上表现稳定也是加分项。7.2 模型可视化注意力热力图和错误样本分析现在做视觉类毕设不做可解释性分析总觉得少了点什么。ResNet这类模型不好直接可视化卷积核但可以用Grad-CAM生成注意力热力图把模型分类时重点关注的区域叠加到原图上。具体效果就是模型判断一双运动鞋时热力图的高亮区域集中在鞋底和鞋面装饰线附近而不是背景或者标签水印上这样的图放进答辩PPT会非常加分它证明模型从语义上理解鞋子结构。Grad-CAM的实现网上有很多现成封装要注意的是选择目标层一般取最后一个残差块的输出太浅的层可视化结果比较碎太深的层则分辨率低。错误样本分析也是一样挑选三到五张预测错误的测试图片每张图配上“真实类别、预测类别、置信度、人工分析可能原因”四行信息这类素材在答辩时常常能提前堵住评委“这个模型为什么错了”的追问。7.3 把实验记录整理成论文素材毕设论文不是把代码注释拿来抄一遍。根据实际的实验流程我当时帮学生整理了这样几条主线迁移学习与随机初始化的对比实验记录两者在训练损失、验证准确率上的差异这可以直接支撑“为什么要用预训练模型”的背景不同骨干网络ResNet50和EfficientNet-B0的效果对比表包含准确率、F1、推理时间和占用显存用来支撑选型理由数据增强前后测试集准确率变化这组数据能直接证明增强的有效性。需要注意的是实验记录最好从一开始就写下来。很多同学把训练脚本跑完就结束了觉得结果已经有了等写论文时才发现当时改了哪些参数完全没记住。我建议在项目目录下建一个“实验日志”的文档每次训练前写下参数设置和预期训练后记录实际效果和发现的问题写论文时你只需要把这些内容串起来远比事后回忆靠谱。7.4 模型导出顺便加一个简单Web演示页如果时间充裕我强烈建议把训练好的模型导出做一个极简的Web演示页面。PyTorch模型可以用torch.onnx.export导出成ONNX格式再借助任意前端框架比如Flask或Streamlit包一层接口页面放一个上传按钮用户传一张鞋子的照片页面返回分类结果和各类别置信度。演示时真实上传一张测试集之外的图片模型输出运动鞋89%置信度评委的观感会比看一页实验结果好很多。而且这个工作本身也能写进论文的“系统设计与实现”章节让毕设的完整性再上一个台阶。模型的导出代码不复杂model.eval() dummy_input torch.randn(1, 3, 224, 224, devicedevice) torch.onnx.export( model, dummy_input, shoe_classifier.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}}, opset_version11 )这样一个20余行代码的模块换来的是“具备可用性”的评价既有前瞻性又不用过多精力。8 一点个人经验毕设重点不是刷分而是讲好一条完整的验证链看着代码和文档从零搭起来到最后测试集准确率91%、混淆矩阵和热力图素材齐备我心里很清楚这个项目真正值钱的不是那91%而是过程中反复打磨出的那一整条验证链从数据是怎么来的、如何清洗划分到为什么选这个模型、训练参数怎么调的再到最终评估指标怎么计算、误差如何分析每一步都有据可依每一步都能回答“为什么这样做”。如果接下来你也准备做类似方向我最后想分享几个具体的小招式不要等到全部训练结束了才开始写文档训练间隙顺手记录过程和实验结果会让你后期轻松很多不要把所有样本一次性打个数据集包分层划分好训练、验证、测试后再打包后续所有实验都用同一份划分防止同一个数据在不同轮次之间反复横跳不要以为跑通了模型就万事大吉花两三个小时做一个简单的Web演示页能给答辩带来非常高的印象分。我自己总结的一个核心建议是毕设不是前沿论文不用追求刷分冲到99%评委想看的是你能不能在有限资源里做出一个有人类意义、有逻辑、有展示度的完整项目。把这条验证链讲清楚就算准确率只有85%也是一份让人信服的毕设作品。