
用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比16000张图的话就是训练集约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(p0.5), transforms.RandomRotation(15), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.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(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) # 替换全连接层适配7分类 num_features model.fc.in_features model.fc torch.nn.Linear(num_features, 7)这里有个版本更新的坑旧教程里常用的pretrainedTrue在新版本里已经改成传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(fEpoch {epoch1}: 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(weightclass_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_size32、输入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时忘了设置shuffleTrue训练数据顺序固定模型反复看同一条路径的数据验证指标的波动模式也很奇怪。另一个是在model.eval()里忘了包torch.no_grad()评估阶段还在记录梯度图显存和耗时都翻倍。这些细节写完代码要反复检查低级错误往往最费时间。5.5 答辩被问概率最高的几个问题积累一些答辩高频问题提前准备好答案会稳很多为什么用迁移学习而不从零训练因为样本量不足以支撑随机初始化下的深层网络训练迁移学习提供了更好的初始点也显著缩短收敛时间。数据增强会不会导致信息泄漏不会增强只作用于训练集验证和测试始终用原始分布。模型主要分错哪几类为什么凉鞋和拖鞋的混淆来自鞋体结构相似对策是补样本和损失函数加权。换MobileNetV3会怎样查阅对照实验结果训练和推理更快轻量化骨干的精度通常略低需要更多epoch才能收敛。模型部署到真实环境怎么办讲清楚推理流程、模型体积、单张图耗时和阈值校准即可这对应的是工程落地能力。这些问题基本不是故意刁难只要你把实验记录完整保存每个超参数、每条曲线、每次消融实验都有据可查答起来就不用慌。我在做完这个项目之后最大的感受是毕业设计的价值不在于模型结构有多新而在于能否把“为什么这样设计”和“遇到问题怎么解决”讲清楚。鞋类分类看起来是个常见题目但亲手把数据清洗、模型训练、评估可视化、部署演示完整走一遍之后你会发现每个环节都有值得挖掘的细节。最后再分享一个小建议全过程的实验记录哪怕失败的参数组合也都按日期存好。写论文时这些“弯路”反而是最有说服力的分析素材答辩现场拿一条失败曲线讲排查思路比任何一个漂亮数字都更能体现真实工程能力。