☰
深度学习毕设实战:鞋类图像分类从数据到部署完整指南
2026/10/21 12:16:45 网站建设 项目流程

用Python做深度学习方向的毕业设计,选题真的很讲究。选太大,半年时间连复现都费劲;选太小,又撑不起一篇论文的工作量。我后来完整做完一个基于深度学习的鞋类分类项目,回头来看,这个题目在难度和完成度之间平衡得相当好。它属于标准的图像分类任务,公开数据不难找,类别语义清晰,从数据预处理、迁移学习、模型调参,一路做到可视化分析和Web演示,链路完整,正好覆盖毕设需要的全部技术环节。

很多同学看到“鞋类分类”第一反应是太普通,担心答辩没有亮点。但这恰恰说明这类题目被低估了。图像分类是最能体现深度学习基本功的任务,过拟合怎么解决、类别不均衡怎么处理、模型为什么这么设计,这些答辩必问的问题,你在做项目的过程中都会真实遇到。做完之后,答案不是背出来的,是亲手调出来的。比起拿一个很新但跑不通的模型硬撑场面,把经典题目做到细节完善,反而更稳。

鞋类分类还有一个天然优势是数据。鞋类图像在公开数据集里相当丰富,按运动鞋、皮鞋、凉鞋、拖鞋、靴子、高跟鞋、帆布鞋分个六七类,每类几百到几千张,完全在普通电脑的处理范围内。不用像医疗影像那样动辄几十万张图,也不像某些垂直领域还得自己手工画框标注。整个项目在消费级显卡上就能跑完,时间成本和硬件门槛都很友好。

1. 毕设选题思路与整体方案设计

1.1 为什么选鞋类分类当毕设题目

先说说选题逻辑。毕业设计和平时做练习不一样,它既要体现工作量,又要保证半年内能完成,还要让评审老师觉得“这学生确实掌握了技术”。鞋类分类这种题目,正好落在“天花板不低、入门不难”的位置上。

从技术视角看,它和经典的猫狗分类、交通标志分类同属一个范式,但又有自己的特点。鞋类图像的背景干扰非常大,有白色摄影棚,有户外街拍,有杂乱桌面,有脚穿着的实拍图。模型必须从这些复杂环境中把鞋子本体分离出来,这考验的是特征提取能力。类别之间的差异也有微妙之处,比如凉鞋和拖鞋在结构上有重叠,运动鞋和帆布鞋的轮廓容易混淆,这些细粒度问题比普通的二分类有意思得多。

从工作量角度看,一个完整的毕设需要包含:问题定义、数据准备、模型设计、训练调参、实验对比、可视化分析、论文撰写。鞋类分类项目把这些环节全部覆盖了,没有任何一个环节是可以跳过的。反过来说,如果你选一个特别小众的方向,公开代码和资料少,遇到bug连参考都找不到,那种焦虑感会直接把项目拖垮。

还有一个我比较看重的点:这个方向后续扩展空间大。做完基础分类,你可以加数据增强消融实验,可以做Grad-CAM可视化,可以换成轻量化模型做对比,可以再做一个Web端推理演示。这些扩展每一项都能写进论文,成为区别于其他同学的工作量来源。

1.2 任务定义:先搞清楚解决什么问题

拿到题目别急着敲代码,先自己在纸上把任务定义写清楚。我当时是写成一句话的:给定一张鞋类图像,模型输出每个类别对应的概率,取概率最大的类别作为预测结果。这句话看着简单,但它决定了后面所有动作的方向。

接着定类别数。我选了7类:运动鞋、皮鞋、凉鞋、拖鞋、靴子、高跟鞋、帆布鞋。这个数字不是拍脑袋定的,而是有讲究的。类别太少,比如只有运动鞋和皮鞋,答辩时老师一句“这不就是二分类”,场面会非常尴尬。类别太多,比如按品牌型号细分成几十类,数据量和标注成本都会失控,学生项目根本扛不住。7类在论文篇幅、训练难度和可解释性之间是比较舒服的位置。

评价指标上,我不建议只盯准确率。准确率确实直观,但类别分布一旦不均匀就会骗人。比如数据里40%是运动鞋,模型什么都不学,全部预测成运动鞋也有40%的准确率。所以我在实验里同时统计了准确率、宏平均F1和混淆矩阵。宏平均F1对少数类更敏感,能反映出模型在“凉鞋”“高跟鞋”这些样本量偏少的类别上的真实表现。

还要把技术路线提前定好。我当时的完整流程是:数据获取和数据清洗,数据增强和预处理,迁移学习训练,评估可视化,最后做部署演示。每个阶段都有独立产出,写论文时也方便按章节组织内容。这条路线是图像分类毕设的通用框架,但每一环的细节做得好不好,才是分数拉开差距的地方。

1.3 技术选型:为什么是PyTorch加ResNet50

框架层面的选择,我建议用PyTorch,除非导师有硬性要求。原因不复杂:PyTorch是动态图,报错堆栈能直接定位到代码行,调试体验对新手极端友好。网上开源的图像分类项目代码也以PyTorch居多,训练到某一步报错,顺手能搜到大量解决方案。对毕设党来说,把时间花在框架本身的“反人类”报错上太不划算。

模型层面,我对比过三条路线:ResNet50、MobileNetV3和EfficientNet-B0。一开始我也纠结选哪个,后来按“训练成本、论文可写性、答辩能讲明白”三个维度做了个权衡,列成了下面这张表:

骨干网络参数量级单张图推理成本精度上限答辩友好度
ResNet50约25M中等较高高,残差结构经典好讲
MobileNetV3约5M低中高中,深度可分离卷积得讲透
EfficientNet-B0约5M低高中,复合缩放和EMA是细节绊脚石

最终选了ResNet50。原因很实际:数据量只有几千张时,ResNet50搭配预训练权重能快速收敛,显存压力适中。残差连接是深度学习核心考点,答辩时从梯度消失、恒等映射到网络退化,老师怎么问都能接住。MobileNetV3轻量但前期收敛更慢,对新手不友好;EfficientNet上限高,但训练细节多,更适合放到论文展望里当延伸方向,不适合当主力模型。

这里的核心逻辑是:小数据量下不要盲目追求大模型或新模型,优先选结构经典、预训练权重丰富、答辩好讲的模型。很多同学一上来就选最新最强的结构,数据量撑不起,调参时间也不够,最后效果反而难看。

2. 数据准备与处理:决定项目天花板的第一步

2.1 数据获取和清洗:别急着训练

我当时整理的数据规模大约6000张图、7个类别,来源是几个公开数据集的组合,还补充了一部分自己采集的样本。这里要重点提醒:公开数据集不是拿来就能用,清洗不到位,后期所有工作都白做。

第一轮是去重。同一双鞋在多个数据源里可能重复出现,有的只是分辨率不同。如果重复图同时出现在训练集和验证集里,模型会把“见过这张图”当成“学会了这一类”,验证集精度虚高。我去重用的是感知哈希的方式,计算每张图的指纹,再按汉明距离找近似重复,把重复项删掉。这个问题非常隐蔽,如果不处理,后面的结果再好都有水分。

第二轮是删坏图。下载的图片里总有一部分是损坏的,读取时报错或直接读成黑图。最保险的办法是写脚本批量检测:用PIL或OpenCV读图,读取失败或文件大小低于某个阈值的直接删除。这个步骤看似低级,但操作时你会发现,一张坏图就能让整个训练循环崩溃,排错的成本比删图的成本高得多。

第三轮是清理错误标注。公开数据的标签质量参差不齐,有些鞋的标签模棱两可:低帮帆布鞋可能被标成运动鞋,皮质凉鞋可能被标成皮鞋。我专门抽了一轮时间看图片和标签,把明显不符合类别边界的样本重新归并。标注清洗是数据工程里最枯燥的活儿,但它直接决定分类器的上限。模型可以反复调,数据错了只能从头再来,这个道理越早明白越省时间。

清洗完成后,我还统计了每个类别的样本数量,把分布情况记在笔记里。这一步看起来不起眼,但后面写数据处理章节、解释类别不均衡的改进实验时,这些数字就是最好的论据。

2.2 数据划分与目录结构:先想好怎么验证

数据划分我坚持三个集合的原则:训练集、验证集、测试集。训练集更新参数,验证集用于调参和选模型,测试集是模型完全没见过的数据,只在全部实验结束后用来做终极评估。比例取8比1比1,6000张图的话,就是训练集约4800张,验证集和测试集各约600张。

目录结构直接按PyTorch的ImageFolder约定组织,后面可以省掉很多读取代码:

data/ ├── train/ │ ├── sports/ │ ├── leather/ │ ├── sandals/ │ ├── slippers/ │ ├── boots/ │ ├── heels/ │ └── canvas/ ├── val/ └── test/

划分的时候有一个非常常见的坑:直接用随机切分,会导致每个类别在训练集和验证集中的比例不一致,尤其小类别可能全被丢进验证集。我用train_test_split时特意加了stratify参数,按类别比例分层抽样。这一步很小,但能保证验证集的评估更可靠。

我习惯在划分之后再做一次复查:随机抽几个文件夹看看图片是否对应正确类别,确认目录层级没有放错。这种低级错误一旦发生,训练跑完才发现,整个实验就废了。检查一遍只需要几分钟,但能避免几天的返工。

2.3 数据增强和预处理:让模型学会看鞋

鞋类图像的拍摄环境差异很大,影棚白底、户外街拍、杂乱桌面都有。如果不做增强,模型很容易学会背景信息而不是鞋子的形态,训练时背景拟合得再好,换到新场景就拉胯。数据增强是这里花小钱办大事的核心手段。

我采用的增强组合是这样的:

  • 随机水平翻转:鞋类没有强方向性,翻转不破坏语义
  • 随机旋转正负15度:模拟不同拍摄角度
  • 随机亮度、对比度、饱和度抖动:模拟不同光照环境
  • 随机缩放裁剪:模拟不同拍摄距离和尺度变化

这里必须强调一个经验:增强不是越狠越好。早期我试过把旋转角度开到30度,训练集里出现大量鞋底朝上、角度诡异的图,验证集却全是正常角度,模型反而学乱了。几何变换的强度要和真实数据分布匹配,颜色抖动可以大胆一点,旋转和裁剪要克制。

预处理里归一化最容易被人忽略。预训练模型的权重是在特定像素分布上学习到的,输入数据如果不按同样的均值和标准差做标准化,预训练特征就会失真。我用的是预训练权重配套的ImageNet统计均值和标准差,和模型权重严格对齐。

代码上用torchvision.transforms组合起来:

from torchvision import transforms train_transform = transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) val_transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])

提示:增强永远只作用于训练集,验证集和测试集一旦加入随机变换,评估结果就带上随机噪声,模型真实水平会被掩盖。

还有一个小细节:RandomResizedCrop默认会随机裁剪到目标尺寸,如果原始图片本身就有大量多余背景,适当把裁剪尺度的下限调高一点,比如设成0.5而不是默认的0.08,能避免模型学到太多局部碎片。这些参数没有标准答案,要根据自己数据集的特点微调。

3. 模型搭建与训练实战

3.1 迁移学习:不重复造轮子

从零训练一个ResNet50,在ImageNet量级的数据上要跑好几周,学生阶段的算力根本不现实。正确做法是用官方预训练权重初始化骨干网络,只把最后一层全连接层换成适配7分类的新结构。

为什么迁移学习在小数据上有效?因为预训练模型前面几十层已经学会了边缘、纹理、轮廓这种通用特征,这些特征和具体任务无关,迁移到鞋类分类完全成立。模型真正需要更新的,主要是后面负责高层语义组合的部分,比如把“鞋头圆润”“鞋底厚实”这样的特征组合成“运动鞋”和“皮鞋”的判别信息。这就是为什么我们保留前面,只换掉最后分类器的原因。

import torchvision.models as models # 加载预训练权重 model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1) # 替换全连接层适配7分类 num_features = model.fc.in_features model.fc = torch.nn.Linear(num_features, 7)

这里有个版本更新的坑:旧教程里常用的pretrained=True在新版本里已经改成传weights参数。而且用weights选择预训练版本时,要注意输入数据的预处理和对应权重保持一致,不然数据分布对不上,表现会莫名其妙地差。

还要留意model.fc.in_features这一步。不同版本的ResNet结构可能微调,但model.fc.in_features能自动拿到全连接层的输入维度,比手动写死数字更稳。手动写死的话,一旦换了骨干网络,这里就会成为隐藏bug。

3.2 训练策略:先冻结后微调

训练刚开始时,不建议把所有层都放开。骨干网络的参数如果在前几轮梯度更新中剧烈变化,原来学到的良好特征会被冲乱。我采用的是两阶段策略。

第一阶段,冻结骨干网络所有参数,只训练新接上去的全连接层。因为可训练参数少,学习率可以给大一点,用Adam优化器设1e-3。这个阶段收敛很快,我当时大约跑了10个epoch,验证集准确率就到了85%左右。

第二阶段,解冻骨干网络最后几层,学习率降到1e-4,做小步长微调。这一步是让深层特征真正贴合鞋类数据。解冻的范围要克制,先只解冻最后一个卷积块,效果不够再往前扩一层。盲目全套解冻,在小数据集上几乎必然过拟合。

优化器方面,我全程用的Adam,这对新手是最稳的选择。我也试过第二阶段换成SGD加Nesterov动量,最终精度确实高了,但学习率稍微没调好就震荡得很厉害。如果时间不充裕,全程用Adam加学习率衰减,已经足够出一个好看的结果。

损失函数没有悬念,多分类用交叉熵:

criterion = torch.nn.CrossEntropyLoss()

PyTorch里的CrossEntropyLoss已经内置了Softmax,模型最后一层输出不用再手动过Softmax。这个点新手经常搞混,以为要自己加Softmax再算loss,结果梯度异常又找不到原因。

3.3 训练循环与模型保存:细节决定上限

核心训练循环写成训练加验证的双循环结构。每个epoch结束都在验证集上评估一次,保存验证集准确率最高时刻的权重,而不是保存最后一个epoch。训练后期模型容易过拟合,最后一个epoch的权重往往不是最优的,只看最后结果会丢掉早前更好的模型。

best_acc = 0.0 for epoch in range(epochs): model.train() train_loss, train_correct = 0.0, 0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() * images.size(0) train_correct += (outputs.argmax(1) == labels).sum().item() model.eval() val_correct = 0 with torch.no_grad(): for val_images, val_labels in val_loader: val_images, val_labels = val_images.to(device), val_labels.to(device) val_outputs = model(val_images) val_correct += (val_outputs.argmax(1) == val_labels).sum().item() val_acc = val_correct / len(val_dataset) print(f"Epoch {epoch+1}: train_acc={train_correct/len(train_dataset):.4f}, val_acc={val_acc:.4f}") if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "best_model.pth")

学习率调度建议用ReduceLROnPlateau,验证集准确率连续3个epoch不提升就把学习率除以10。前期用大学习率快速下降,后期用小学习率精细找最优点,非常实用。有些同学用固定学习率跑完全程,到了后期loss在平缓区域来回弹,精度上不去,就是缺少这一层调度。

还有一个经验:保存模型时把类别列表、预处理参数、训练日期一起存成JSON,与权重文件放同一目录。隔几天回来写论文时,你不会记得pth文件对应的类别顺序到底是什么,有这个配置文件能省掉很多排查时间。这个细节不写进论文,但能极大提升开发效率。

4. 模型评估、优化与毕设加分项

4.1 评价指标:别把鸡蛋都放在准确率一个篮子里

训练完成后,我先看测试集总体准确率,但这个数字只是起点。更重要的是逐类指标。用sklearn.metrics里的classification_report,能把每一类的精确率、召回率、F1分数一次性打出来。

鞋类分类里最典型的混淆是“凉鞋”和“拖鞋”,还有“运动鞋”和“帆布鞋”。我们人眼觉得差异明显,但模型只依赖像素,鞋面开口大小、鞋底厚度这些细微差异确实容易混淆。混淆矩阵能把这类问题直接暴露出来。分类报告里如果某类召回率明显低于其他类,那就是重点要分析的对象,论文里可以直接从这一发现切入写优化实验。

我加了一块可视化:从测试集随机抽一批图,把真实标签、预测标签、置信度画在图上,预测错的用红字标出。这种图放在论文实验章节非常直观,也比光放准确率数字更有说服力。答辩老师看到预测错误的样例,通常会追问“为什么错”,正好顺势展开混淆分析和改进方案。

这里贴一组我实际记录的消融对比数据:

实验设置测试准确率宏平均F1
从零训练ResNet5080.4%79.6%
迁移学习+冻结训练91.2%90.7%
迁移学习+微调+学习率调度93.6%93.1%

迁移学习的优势一目了然。这种对比实验是毕业论文里几乎必有的章节,建议一开始就把基线实验跑好,后面所有改进都跟它对照。没有基线,任何优化结论都站不住脚。

4.2 关键优化:类别不均衡和置信度阈值

如果分类报告显示某类召回率低,多半是样本量不足。我的做法是两步并行。训练层面用类别权重交叉熵,CrossEntropyLoss(weight=class_weight),权重按各类样本数倒数归一化,让少数类的损失贡献更大。数据层面,对少数类加大增强强度,翻转和随机遮挡多来几次,相当于把样本多样性“薅”出来。

还有一个不太起眼但很有价值的优化,是推理阶段的置信度阈值调整。默认情况是取最大概率对应的类别,但模型对某些类别的概率天然偏低。我尝试在验证集上为每个类别搜索单独的置信度阈值,校准之后,整体F1还能再提升一到两个点。这个技巧对毕设来说是加分项,因为体现的不是“会调包”,而是理解模型输出的不确定性。

做阈值校准的时候注意:要在验证集上确定阈值,再拿到测试集上验证,不能在测试集上调参,否则属于信息泄漏。这一点在论文里最好也写清楚,懂行的老师看到这层描述会加分。

4.3 可视化与交互演示:让答辩老师眼前一亮

毕设区别于普通课设,重点不是跑出了个数字,而是展示你懂模型。我做了两件性价比极高的事:Grad-CAM热点图和一个简单的Web演示。

Grad-CAM能把模型判断依据可视化,显示成鞋图上的热力图。实现上并不复杂,选最后一层卷积特征图,把梯度反传得到类别激活图,再叠加到原图上。我给几个预测正确的样本做了可视化,发现热点集中在鞋头、鞋跟和鞋面材质区域,这直接说明模型学到的是鞋类特征,而不是背景或光线。答辩时讲这个,比空口说“模型效果很好”有说服力得多。

Web演示我用Flask做的最简实现:上传一张鞋图,后端调用训练好的模型做预测,返回类别和置信度。整个后端逻辑不到100行,核心就是load_model加predict两个动作。但放一个真实可用的交互页面在大屏幕上,老师提问的方向会从“原理拷打”变成“这里怎么实现的”,回答难度直线下降。

这部分不是硬性要求,但建议至少做一个能传图的命令行工具或网页。毕业设计强调工程闭环,从数据到训练再到推理演示,链条越完整,评分越不容易被挑毛病。

5. 常见调试问题与避坑经验

5.1 训练不收敛或loss异常

训练一开始loss就不降,或者中途变成NaN,大概率是以下几个原因之一:学习率太大,尤其第一阶段换了新分类器之后梯度可能很大;没有正确切换model.train()和model.eval(),BatchNorm统计量混乱;或者数据加载带入异常值。

排查顺序建议从简到繁:先打印第一个batch的loss,确认数值合理;再逐epoch打印loss观察趋势;最后查transforms和数据读取。我遇到最离谱的一次,是把归一化的std参数写成了mean,输入数据分布完全错了,loss直接起飞。当时排查了很久模型代码,最后才发现是transforms里一行小问题。所以遇到反常现象,先回头检查预处理环节,往往能省下大把时间。

5.2 过拟合:训练集狂涨,验证集不动

几千张数据配几千万参数的模型,过拟合几乎是必然。当训练准确率逼近99%而验证集卡在90%附近时,我会依次尝试这样几招:

  • 增强强度加大,特别是颜色抖动和随机遮挡
  • 解冻的层数减少,冻结更多参数等于限制模型容量
  • 分类器前加Dropout,配合0.5的丢弃率很有效
  • 权重衰减调大一点,给参数加正则约束
  • 早停,验证集准确率连续多个epoch不提升就停止训练

我项目里最终靠“增强加减少解冻层数加早停”,把训练集和验证集的差距从8个点压到3个点以内。这组调参记录本身也是论文里很有价值的实验分析素材,记得保留中间曲线,不要只留最终结果。

5.3 显存不足和环境配置

多数同学的训练设备是单张消费级显卡,显存8GB上下。ResNet50在batch_size=32、输入224x224时往往逼近显存上限。遇到CUDA out of memory,最直接的办法是把batch_size降到16或8。数据量不大时,batch小一点对最终精度影响不大,还可以顺便缓解BatchNorm在小batch上的波动。

想进一步压缩显存,可以开混合精度训练。PyTorch的torch.cuda.amp能把显存占用和训练时间都压一截,我在后期实验里实测训练速度提升30%左右。第一次使用建议先在单个epoch上跑通,再放到完整训练循环里,避免引入新的报错。

环境配置的坑一言难尽。强烈建议用conda新建独立环境,不要直接在系统Python里裸装框架。最经典的问题是PyTorch版本和显卡驱动支持的CUDA版本不匹配,代码能装但一运行就报错。顺序一定不能反:先看显卡驱动支持的CUDA版本,再选对应PyTorch的安装命令。

5.4 数据加载慢和低级陷阱

图像读盘太慢会拖慢整个训练节奏。数据量到了几千上万张时,考虑在DataLoader里调大num_workers,常规4个线程就够,开太大线程切换开销反而更大。如果图片文件体积很大,可以提前统一缩放到256x256再存成缓存格式,速度提升会非常明显。

还有两个低级但非常耗时的坑。一个是在训练使用DataLoader时忘了设置shuffle=True,训练数据顺序固定,模型反复看同一条路径的数据,验证指标的波动模式也很奇怪。另一个是在model.eval()里忘了包torch.no_grad(),评估阶段还在记录梯度图,显存和耗时都翻倍。这些细节写完代码要反复检查,低级错误往往最费时间。

5.5 答辩被问概率最高的几个问题

积累一些答辩高频问题,提前准备好答案,会稳很多:

  • 为什么用迁移学习而不从零训练?因为样本量不足以支撑随机初始化下的深层网络训练,迁移学习提供了更好的初始点,也显著缩短收敛时间。
  • 数据增强会不会导致信息泄漏?不会,增强只作用于训练集,验证和测试始终用原始分布。
  • 模型主要分错哪几类,为什么?凉鞋和拖鞋的混淆来自鞋体结构相似,对策是补样本和损失函数加权。
  • 换MobileNetV3会怎样?查阅对照实验结果:训练和推理更快,轻量化骨干的精度通常略低,需要更多epoch才能收敛。
  • 模型部署到真实环境怎么办?讲清楚推理流程、模型体积、单张图耗时和阈值校准即可,这对应的是工程落地能力。

这些问题基本不是故意刁难,只要你把实验记录完整保存,每个超参数、每条曲线、每次消融实验都有据可查,答起来就不用慌。

我在做完这个项目之后最大的感受是,毕业设计的价值不在于模型结构有多新,而在于能否把“为什么这样设计”和“遇到问题怎么解决”讲清楚。鞋类分类看起来是个常见题目,但亲手把数据清洗、模型训练、评估可视化、部署演示完整走一遍之后,你会发现每个环节都有值得挖掘的细节。最后再分享一个小建议:全过程的实验记录,哪怕失败的参数组合,也都按日期存好。写论文时这些“弯路”反而是最有说服力的分析素材,答辩现场拿一条失败曲线讲排查思路,比任何一个漂亮数字都更能体现真实工程能力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询