
简介本资源是一套完整的基于深度学习的舌苔图像检测毕业设计实现方案面向人工智能方向本科生及初阶深度学习实践者聚焦中医智能辅助诊断中的关键图像识别任务。压缩包共109个文件涵盖26个核心Python训练与推理脚本、6个预训练及微调后的PyTorch模型.pth、7张典型舌苔样本图.jpg及5个配置与标注结构化文件.json另有TensorBoard日志文件.tfevents记录完整训练过程便于复现实验与分析收敛曲线整体体积105.39MB结构清晰含数据预处理、模型定义、训练调度、可视化评估等完整模块。目前已有827人学习下载提供可直接运行的端到端代码流程、模型权重、测试样例及基础文档.docx适合用于课程设计、毕设参考或轻量级医学图像识别入门实践。 整理网盘的时候翻到了当年那个“留档.zip”解压一看里面躺着三百多行代码、四个版本的训练日志、十二个模型权重文件还有一份写满了涂改痕迹的论文初稿。这个基于深度学习的舌苔检测毕设算是我真正从深度学习门外走到门内的一个完整项目。今天把这套东西的选题思路、数据准备、模型训练、踩坑记录和留档整理全部梳理一遍给正在做或者打算做医学图像方向、尤其是做舌诊相关项目的同学一个能直接抄作业的参考。先说结论舌苔检测这个题目作为本科毕设难度适中踩坑可控而且答辩时故事性好讲——它有明确的应用场景中医舌诊数字化、有清晰的技术路线图像分类或目标检测、有现成的公开数据可以起步最后还能用热力图可视化把“黑盒”问题变成加分项。但如果你以为随便从网上下个数据集丢进ResNet就能跑出好结果那大概率会在数据清洗和类别不均衡这两关上翻车。下面我把整个项目从0到1的完整经历拆开讲。1. 选题阶段为什么舌苔检测是“性价比很高”的毕设方向1.1 题目背后的事件驱动逻辑在做这个项目之前我对深度学习的了解停留在吴恩达课程和几篇入门博客的层面。选题时我列了几个方向车辆检测、人脸表情识别、垃圾图片分类最后导师给了一个词——“舌苔”。当时我第一反应是这东西听起来有点偏门但仔细调研后发现它背后的逻辑其实非常顺。舌苔是中医望诊里非常重要的信息载体舌苔的颜色、厚薄、润燥直接对应身体状态。传统中医看舌苔靠的是医生肉眼观察经验依赖强、主观性强、难以量化。而深度学习方法天然适合做图像特征提取把“看舌苔”这件事变成“图像分类”或者“物体检测”正好是卷积神经网络最擅长的任务。放到大背景里这也属于中医数字化、智能化这个大趋势的一环评委听起来不陌生也容易理解。作为毕设题目它有几个天然优势。第一数据门槛相对可控。舌苔图像不像医疗CT、病理切片那样涉及极高级别的隐私和伦理审批网上有公开的中医舌象数据集可以拿来起步后期可以自己补充拍摄。第二技术路线清晰。即便只做“舌苔分类”比如区分薄白苔、黄苔、腻苔等常见类型也能完整覆盖深度学习项目的数据处理、模型训练、评估、可视化全流程。第三有故事可讲。中医舌诊本身有千年理论支撑深度学习是当下热点两者结合既有文化厚度又有技术新颖性答辩时不容易冷场。1.2 任务定义先想清楚是“分类”还是“检测”这里有一个很多初学者容易忽略的点你在选题时说的“舌苔检测”到底是指语义上的“检测任务”通过目标检测算法定位舌头和舌苔区域还是习惯上把“识别/分类”也笼统叫做“检测”我最初就被这个模糊表述坑过——以为要用YOLO系列做目标检测后来跟导师聊完才确认其实他想要的核心功能是把一张舌象图片自动分类到对应的舌苔类别。这两种任务的工作量和复杂度差别很大。目标检测需要画包围框标注数据标注成本高图像分类只需要给整张图打标签适合数据量少、起步快的项目。我最终的做法是先做分类验证可行性再用一个轻量检测模型做舌体区域定位作为分类的前置处理。这样整个项目的技术栈更丰富又不会让工作量失控。如果你也在做类似题目建议先跟导师确认清楚任务边界避免方向跑偏。1.3 可行性预研动手前必须回答的三个问题在正式开工前我做了三天调研核心就是回答三个问题有没有数据——查到了公开的中医舌象数据集另外导师联系了本地中医院采集了一部分真实临床图片。数量上勉强够用后面再配合数据增强解决。算力够不够——实验室有一块GTX 1080Ti训练ResNet级别的模型完全够用。没有GPU的同学也可以考虑用云平台按小时租成本可以控在几百块以内。我现在的水平能不能完成——当时的我会用PyTorch写简单CNN、懂基本的训练循环但不熟悉迁移学习、数据增强策略、模型调优。评估下来这些技能是可以通过项目过程补齐的难度曲线可承受。这三个问题想清楚后我就确定这个题目可以做而且过程不会太痛苦。2. 数据准备舌象数据集从哪来、怎么洗、怎么标2.1 数据来源与合规性处理我的数据由三部分构成公开数据集约4000张合作医院提供的脱敏舌象图片约800张自己用手机按照固定拍摄规范补拍约200张。别小看后面这200张它们在答辩时是很好的加分素材——“真实环境补充数据”既能说明你对数据采集有完整认知也能用来测试模型的泛化能力。先说明一下公开数据集的坑。部分公开舌象数据集存在水印、标注不一致、图片尺寸混乱等问题。我花了一个周末才把这些数据过滤到可用状态。具体做了这几件事去掉所有带明显水印、拼接痕迹或分辨率极低的图片。去掉标注与图片内容明显不符的样本比如标注是黄苔但图片里舌头颜色明显偏正常的。统一图片格式全部转成JPG避免PNG带透明通道在后续处理中产生异常。按来源目录管理数据方便后续分析模型在“公开数据”和“自采数据”上的效果差异。数据合规方面要特别说一句。医院提供的临床图片全部做了去标识化处理不包含患者姓名、病历号等任何个人信息。这在医学图像相关的毕设里是底线问题答辩评委很可能问到务必提前准备好说明。2.2 分类体系的确定别贪多先做最常见四类舌苔在中医里的分类非常细什么白苔、黄苔、灰黑苔、厚腻苔、剥苔、裂纹舌等等如果全做数据量和标注成本都会失控。我最后把任务收敛为四分类类别说明样本占比处理后薄白苔正常或轻症常见表现38%黄苔热证相关27%厚腻苔湿浊、食积相关22%剥苔舌苔部分或全部脱落13%四分类的好处很明显各类别数据量相对均衡虽然仍有不均衡但可控、类别间视觉差异较大、模型区分难度适中。既能让模型收敛到不错的精度又不需要把中医诊断理论学透适合毕设的时间节奏。如果你想让项目显得更有深度可以在四分类之上再加一个“舌体区域检测”任务作为辅助这样就有两个技术点可以写进论文和答辩PPT。我当时是把分类作为主任务检测作为辅助模块后面还会细讲。2.3 数据清洗的两小时实战流程数据清洗看起来不产生直接成果但跳过的后果很严重——脏数据会直接转化为模型精度的天花板。我清洗时的操作流程如下写一个Python脚本遍历所有图片输出尺寸、通道数、文件大小把异常项筛出来。用OpenCV检测模糊度拉普拉斯方差低于阈值的图片单独放在“模糊候选”文件夹人工二次确认后删除。肉眼快速扫每一张缩略图把明明是舌头特写但包含大量嘴唇、牙齿、口腔背景的图片标记出来——这类图片不是不能用但后期需要统一裁剪策略。检查是否有近似重复图片保留质量更高的一张。清洗前4000多张清洗后剩下约3400张可用。再综合自采数据最终训练集约3200张验证集400张测试集400张。比例大致是8:1:1。划分时我特别注意了按来源分层抽样——比如自采的200张不能全落在测试集里否则训练集就看不到这种分布了。提示数据划分时一定要做分层划分尤其当数据来自多个来源时。如果不分层比如来自医院的图片全部进了测试集模型在测试集上的表现会严重偏低你会被这个假象误导以为模型效果很差而盲目调参。2.4 标注策略从Excel打标到LabelImg的演进分类任务的标注相对简单我最初用Excel管理文件名和类别映射后来觉得不直观就改用文件夹命名——每个类别的图片放在对应文件夹里代码读目录名作为标签。这样做的好处是后期调整类别时逻辑清晰不容易出现标注错位。对于舌体区域检测我用LabelImg工具画包围框标注目标就是“舌体”。这个过程大概花了两天画了800多张。经验是标注框不要画得太紧贴舌缘稍微留一点点边因为舌头边缘往往和嘴唇颜色接近标注过于贴边会干扰检测模型的学习。2.5 数据增强你的免费训练集扩充器舌象数据有一个特点真实拍摄环境下光照差异巨大。有的照片舌头偏亮有的偏暗有的带一点黄色调。如果模型只在原始数据上训练遇到不同光照条件的输入很容易“失明”。我的增强策略包括随机水平翻转概率0.5随机旋转±15度随机亮度调整0.8~1.2倍随机对比度调整0.8~1.2倍随机色相偏移±5度随机裁剪后resize回原尺寸这组增强在PyTorch里用torchvision.transforms就能组合实现。有个细节舌苔分类中颜色是核心特征所以色相偏移幅度不能太大否则“黄苔”可能被增强成“薄白苔”的样子等于给数据引入了错误标签。我实测±5度是安全范围超过±8度验证集精度就会掉1到2个点。另外我在训练阶段和验证/测试阶段使用了不同的transform。验证和测试阶段只做resize、归一化不做任何随机增强。这一点很容易漏如果验证集也做了随机增强评估结果会有抖动不利于判断模型真实水平。3. 模型选型与训练细节ResNet打底EfficientNet提精度3.1 为什么迁移学习是毕设项目的“安全牌”很多初学者上来就自己设计一个几层的小卷积网络然后从零训练。我一开始也想这么干被导师拦住了。原因很简单从零训练的小网络在几千张舌象数据上很难学到足够强的特征精度通常到80%就顶天了而且非常容易过拟合。迁移学习的思路是拿一个在大规模数据集比如ImageNet120万张图片上预训练好的模型把它的前几层卷积层看作通用的“特征提取器”只替换最后的全连接分类层来适配我们的类别数然后用我们的舌象数据去“微调”整个网络。这样做等于让模型站在了巨人的肩膀上特征提取能力是现成的我们需要做的只是让模型“适应”舌象这种特定图像分布。3.2 网络结构对比我用过的三个模型我在项目里先后尝试了ResNet18、ResNet50和EfficientNet-B3。下表是它们在同一个验证集上的结果对比训练轮数相同、优化策略相同模型参数量验证集准确率单epoch训练耗时显存占用ResNet1811M91.2%约3分钟约2GBResNet5025M93.5%约6分钟约4GBEfficientNet-B312M94.1%约7分钟约4.5GB考虑到训练时间差别不大最终我选了EfficientNet-B3作为主模型。ResNet18虽然快但精度瓶颈太明显ResNet50训练时间和显存需求都更高精度提升却没比EfficientNet-B3多。EfficientNet系列通过神经结构搜索得到的复合缩放策略在同等参数量下特征提取效率更高用在舌苔这种纹理细密的图像上效果确实更好。如果算力有限我建议最低从ResNet50起步不要用ResNet18——前期省的时间后面会在调参上加倍还回来。3.3 训练配置优化器、学习率与损失函数的实用设置训练细节决定了模型最终能不能收敛到理想精度。我最终的配置如下优化器AdamW初始学习率1e-4权重衰减1e-4学习率调度先用5个epoch做warmup从1e-5升到1e-4再用余弦退火降到1e-6损失函数CrossEntropyLoss加label smoothingsmoothing系数0.1Batch size32总训练轮数60轮输入尺寸训练时使用随机crop后的224x224为了保留舌苔纹理细节我把EfficientNet-B3的输入提高到320x320重新训练了一次精度又涨了约0.8个百分点这里解释几个关键选择。AdamW相比SGD的优势是收敛稳定、对学习率不敏感对初学者更友好SGD想跑出好效果需要精细调节学习率和动量新手很容易卡住。label smoothing的作用是防止模型对训练集过于自信强迫模型输出不那么极端的概率分布从而增强泛化能力。在小数据集上这个trick带来的提升非常明显。分词和推理阶段我用的是PyTorch的DataLoader设num_workers4并在训练循环里手动把model.train()和model.eval()切换好。一个常见的坑是忘记在评估时写torch.no_grad()导致显存爆炸。我在第一次跑验证集时就踩了这个直接OOM。3.4 微调策略先冻结后解冻的两阶段训练我用了两阶段微调这也是迁移学习里常见的做法。第一阶段冻结backbone的所有层只训练分类头训练10个epoch学习率1e-3第二阶段解冻所有层用1e-4的初始学习率全量微调。这样做的原因是如果一开始就全量微调随机初始化的分类头梯度幅度很大会把预训练权重冲坏导致loss异常震荡。经验之谈第一阶段不需要跑太久只要分类头收敛、loss下降开始变得平缓就可以切第二阶段。判断标准是训练集准确率超过90%或者loss低于某个预设阈值我设的是0.6。第二阶段才是精度真正提升的时期所以要给足训练轮数。4. 训练过程中的高频故障过拟合、类别不均衡与Loss震荡的排查链路4.1 问题一验证集Loss先降后升典型的过拟合信号这是训练过程里最先遇到的问题。大约在第35轮开始训练集loss持续下降但验证集loss开始回升验证集准确率出现小幅波动。这是教科书级的过拟合信号。我按下面顺序排查和解决先确认不是数据划分问题——检查训练集和验证集的类别分布确认没有明显差异。看训练集和验证集loss曲线的gap如果gap越来越大基本可以定为过拟合。解决办法依次尝试增加数据增强强度把随机旋转从±10度提高到±15度、加入Dropout在分类头前加了一层nn.Dropout(p0.3)、增大权重衰减从1e-5调到1e-4。最终是数据增强和权重衰减的组合解决了问题验证集准确率从92%左右提升到93%以上。Dropout在这类预训练模型上的收益不如在前两招明显但加上也无妨属于稳定性保险。4.2 问题二模型全部预测为“薄白苔”类别不均衡的陷阱第一次训练完看测试集结果我真有点崩溃——准确率87%看似不低但翻开混淆矩阵一看模型把几乎所有样本都预测成了“薄白苔”而薄白苔在测试集里占比38%。也就是说模型其实是个“懒惰分类器”它学会了用“不管什么输入都输出多数类”来降低整体loss。这是典型的类别不均衡问题。我排查后做了三件事使用加权采样WeightedRandomSampler让每个epoch里各个类别的样本数量基本一致。或者使用Focal Loss降低易分类样本的权重让模型更关注难分类的少数类样本。调整评估指标不再只看准确率改用加权F1和每个类别的召回率。最终我选择了WeightedRandomSampler CrossEntropyLoss的组合因为实现简单、效果可预测。Focal Loss需要调gamma参数在这个四分类任务上提升有限性价比不高。类别不均衡调整后剥苔这个少数类的召回率从35%提升到了78%整体加权F1从0.86提升到了0.92。提示类别不均衡问题必须在训练开始前就检查。训练前打印一下每个类别的样本数量如果最高和最低相差超过3倍建议直接用加权采样或Focal Loss不要等训练完再补救。4.3 问题三Loss突然变成NaN训练直接崩掉训练到第7轮时loss突然变成NaN所有参数归零训练无法继续。检查后定位到两个原因一是某个batch里的数据含有极端像素值二是我把学习率从1e-4调到了5e-4之后没有配合warmup导致梯度爆炸。解决办法是在DataLoader里加drop_lastTrue丢弃不完整的最后一个batch把学习率回调到1e-4在优化器上加了grad_clip梯度裁剪max_norm1.0。之后训练再没出现过NaN。现在回想这就是典型的没有给训练过程加“安全带”导致的翻车。4.4 问题四自采图片验证效果差真实场景的泛化鸿沟用整套流程训练完的模型在公开数据集和医院图片上表现不错但拿自己手机对着人拍几张实测效果就崩——薄白苔被识别成黄苔的情况非常常见。分析后找到原因自采图片的光照环境、拍摄角度、舌头占比和训练数据差异太大模型在训练集分布之外的输入上泛化能力不足。我采取的优化方案是“在线难例挖掘”把手机拍的、模型预测错误的图片全部加入训练集并额外补充了光照变化更强、舌体占比更小的样本。陆续收集了100多张难例后重新训练自采场景的准确率从67%提升到了84%。虽然还达不到完美但至少说明模型开始学到“舌苔”本身的特征而不是数据集的表面统计规律。5. 结果评估与可视化别让“准确率”成为论文里唯一的底气5.1 构建多维度的指标评估体系答辩的时候只报一个“准确率93%”是远远不够的。评委更想看到的是你对自己模型的局限性有清晰认知而这份认知必须建立多维度的评估体系上。我最终的报告里包含以下内容指标数值说明Accuracy93.8%整体正确率Macro-F10.92各类别F1的算术平均Weighted-F10.93各类别F1按样本占比加权Precision/Recall每类见混淆矩阵需要单独列出AUCOvR平均0.98每类比其余类的区分能力特别要注意混淆矩阵它比任何单一数字都能说明问题。我的混淆矩阵里“黄苔”和“厚腻苔”之间存在一定混淆这符合中医诊断直觉——黄厚腻苔本就常常同时出现边界本身模糊。在答辩时主动说出“模型在哪些类别之间容易混淆为什么”会让评委觉得你真正理解了自己的项目而不是只会调包跑代码。5.2 Grad-CAM可视化模型到底在看哪里深度学习模型最大的争议是“黑盒”。为了说明模型的决策依据不是乱七八糟的背景像素而是真正聚焦在舌体上我用Grad-CAM生成了热力图。做法很简单选取模型最后一个卷积层的输出计算类别得分对特征图的梯度做全局平均池化得到权重加权求和后通过ReLU得到热力图再叠加到原图上。核心代码只有十几行def grad_cam(model, image_tensor, target_class): model.eval() features {} def hook_fn(module, input, output): features[feature] output handle model.backbone[-1].register_forward_hook(hook_fn) output model(image_tensor.unsqueeze(0)) model.zero_grad() one_hot torch.zeros_like(output) one_hot[0][target_class] 1 output.backward(gradientone_hot) grads model.backbone[-1].weight.grad pooled_grads torch.mean(grads, dim[2, 3]) activations features[feature] for i in range(activations.shape[1]): activations[:, i, :, :] * pooled_grads[:, i] heatmap torch.mean(activations, dim1).squeeze().detach().cpu().numpy() heatmap np.maximum(heatmap, 0) heatmap (heatmap - heatmap.min()) / (heatmap.max() - heatmap.min()) handle.remove() return heatmap测试发现绝大部分样本的热力区域都集中在舌体中央和舌根部只有极少数样本关注到了嘴唇边缘——这属于标注框偏大时引入的噪声影响。这个结果在答辩时直接作为“模型学到的是舌象特征而非背景伪特征”的证据说服力很强。5.3 错误样本分析用“犯错”来提升项目深度我专门建了一个“错误分析”文件夹把测试集里预测错误的40多张图全部翻出来逐张看。归纳下来出错原因大致有三类光线极暗导致舌色失真占45%、舌体占比过小占30%、舌苔特征本身模糊难以区分占25%。这些结论既帮我找到了数据增强的优化方向也成了论文“局限性”和“未来工作”部分的素材。如果将来要发论文或者做更深度的项目“错误归因”这个思路可以直接迁移。5.4 轻量舌体检测模块为分类加上“前置安检”前面提到我做了一个辅助的舌体检测模块。我用的模型是轻量化的YOLOv5s训练数据就是我之前用LabelImg标注的那800张图加上公开数据集里的200张带标注图共1000张。训练了100轮后mAP0.5达到96%。实际使用时先用检测模型裁剪出舌体区域再做分类。这个串联设计让分类模型输入的背景干扰大幅减少自采图片的准确率又提升了约4个百分点。别小看这种“检测分类”的两级结构。它让项目的技术完整度上了一个台阶也是我论文里“系统设计”章节的核心内容。如果你时间不太够可以先不做检测模块把分类做到极致一样能毕业但如果想冲优秀论文这个辅助模块值得加。6. 毕设留档这份“留档.zip”里到底放了什么6.1 留档目录结构让三个月后的自己也能秒上手毕业设计提交之后这些东西其实是要反复被翻阅的——评阅老师要查、答辩秘书要归档、学弟学妹可能来借。所以留档文件的结构至关重要不能随手一塞。我最终的目录结构是这样留档.zip ├── README.md ├── requirements.txt ├── code/ │ ├── train.py │ ├── train_det.py │ ├── detect.py │ ├── utils/ │ │ ├── dataset.py │ │ ├── transforms.py │ │ └── metrics.py │ ├── configs/ │ │ ├── cls_config.yaml │ │ └── det_config.yaml │ └── weights/ │ ├── classifier_best.pth │ └── detector_best.pt ├── data/ │ ├── train/ │ ├── val/ │ └── test/ ├── logs/ │ ├── train_log_0310.txt │ ├── train_log_0320.txt │ └── eval_results.csv └── docs/ ├── 论文初稿_v1.docx ├── 论文终稿.docx ├── 答辩PPT_final.pptx └── 演示视频.mp4README.md的开头就三句话项目是什么、怎么跑起来、每个文件夹放什么。不要小看这三句话三个月后你自己回来看也会感谢当时写了这个README的自己。requirements.txt里固定了主要依赖的版本torch 1.13.1、torchvision 0.14.1、opencv-python 4.6.0.66、numpy 1.21.6。深度学习框架版本兼容有时候很玄学锁版本是为了保证复现时不会因为版本不一致出错。6.2 权重文件比代码更值钱很多人的毕设留档里只有代码没有权重文件这是个重大失误。如果没有预训练权重别人或者你自己换台电脑想复现你的实验结果就得重新训练几个小时甚至几天。我把最终模型的权重文件和最好的几个中间检查点都留了一份并且命名规范里标注了对应的epoch和验证集指标classifier_best.pth # epoch_58_val_acc_93.8% classifier_epoch_40.pth # epoch_40_val_acc_92.6% classifier_epoch_50.pth # epoch_50_val_acc_93.2%这样即使最终模型在某些测试案例上表现不佳也能回溯中间状态分析模型在不同训练阶段的行为差异。这是很值得养成的习惯——训练时每隔一定轮数保存一次检查点而不是只在最后保存一次。6.3 答辩前的高频问题清单临近答辩时我把老师可能问的问题全部整理成了一个文档逐个准备了回答。这些问题中有几个几乎是必问的提前准备好可以避免现场卡壳为什么用EfficientNet而不是其他模型——答对比了ResNet系列和EfficientNet在同等参数量下EfficientNet对舌苔纹理的特征提取效果更好验证集精度更高。数据量这么少模型可信吗——答使用了迁移学习、数据增强、五折交叉验证等策略缓解小数据问题并且通过Grad-CAM可视化验证了模型关注的是舌体区域。模型在真实环境里能用吗——答目前的实验证明在受控光照下效果尚可但极端光照和舌体占比过小时仍有误判这是后续优化的方向。你的数据和标注是怎么来的——答公开数据集加医院脱敏数据分类标签依据中医舌诊标准制定检测框由我本人标注后由导师抽查确认。这些问题没什么标准答案但提前写一遍回答稿对理清自己项目的整体思路非常有帮助。6.4 给学弟学妹的几条实在建议最后说几点我做这个项目沉淀下来的体会。第一做毕设要给自己留出至少一个半月的缓冲时间。我原计划六周完成实际用了九周多出来的三周全耗在数据清洗和类别不均衡调整上。第二养成每天备份代码和权重的习惯。我训练到第50轮的时候坏过一块硬盘幸好前一天刚把权重同步到网盘不然两个月的训练成果就没了。第三不要一个人闷头做定期跟导师对齐进展非常重要。我发现每次跟导师聊完都能发现自己正在浪费时间的环节及时止损。这个项目做完之后我对深度学习项目的理解不再停留在“跑通一个模型”的层面而是真正明白了数据、模型、评估、部署这个完整链条里每一个环节的坑在哪。这也是为什么我会写这篇长文——做毕设本身只是拿到一张文凭但把做过的项目真正啃透才是这段经历里更长远的收益。如果你正准备开题或者正在训练过程中反复挣扎希望这篇复盘能帮你少走几段弯路。本文还有配套的精品资源点击获取