
简介这是一套面向高校学生与Python初学者的舌象诊断系统完整源码适用于毕业设计、期末大作业与课程设计场景帮助读者快速搭建一个可运行、可演示的深度学习应用项目。资源包共183个文件约42.7MB其中54个py源码文件承载模型训练与界面逻辑61张jpg与1张jpeg为舌象样本及界面素材另有40个pyc编译文件、14个txt说明、7个json配置、2个ui界面文件及ttf字体、docx学习路线文档等结构完整、注释清晰新手也能看懂。目前已有165人学习下载。项目功能完善、界面美观、操作简单下载后简单部署即可使用读者可据此掌握数据预处理、模型构建、界面交互与结果展示的完整流程并参考学习路线文档梳理知识脉络具有较高的实际应用与参考价值。1. 舌象诊断系统源码拆包一份能跑起来的大作业到底长什么样很多同学做「人工智能大作业」时卡在同一个地方模型代码能跑但不知道怎么把它变成一个能演示、能答辩、能交差的完整系统。这份 Python 实现的舌象诊断系统源代码解决的正是这个断层——它不是一段孤立的 CNN 训练脚本而是一套带界面、带推理流程、带注释的完整工程。拿到手之后你面对的是「舌象图片输入 → 预处理 → 深度学习模型推理 → 诊断结果输出」这条链路而不是对着一个 loss 曲线发呆。适合谁期末大作业赶进度的本科生、课程设计需要完整 demo 的研究生、以及想找一个计算机视觉落地案例练手的 Python 入门者。它把「深度学习」从论文里的名词变成了你双击就能看到结果的程序。2. 环境搭建与依赖安装从 python 安装到第一张舌象推理2.1 为什么选 Python 深度学习这套组合舌象诊断本质上是一个图像分类任务输入一张舌头照片输出对应的证型或健康状态标签。这类任务用 Python 做是最省事的因为整个深度学习生态——从数据处理到模型训练再到推理部署——在 Python 里都有成熟库支撑。这份源码选择的技术栈大概率是 PyTorch 或 TensorFlow 二选一配合 OpenCV 做图像预处理再用 Tkinter 或 PyQt 搭一个桌面界面。为什么不用 MATLAB因为 MATLAB 做深度学习大作业在部署环节会卡住你没法把模型和界面打包成一个能独立运行的程序而 Python 可以。选 Python 的另一个现实原因是你搜「python安装教程」「python安装numpy库的方法」能找到的中文资料比任何其他语言都多。遇到报错时你大概率不是第一个踩坑的人。这份源码带注释意味着即使你之前只写过 print(hello)也能顺着注释看懂每一步在干什么。2.2 依赖安装的完整命令与版本注意先确认你的 Python 版本。这份源码通常兼容 Python 3.7 到 3.9太新的版本比如 3.12反而可能因为某些库还没适配而出问题。安装依赖时不要一个一个 pip install直接找项目里的 requirements.txt# 先看 Python 版本确认在 3.7-3.9 之间 python --version # 创建虚拟环境避免污染全局包 python -m venv tongue_env # 激活虚拟环境Windows tongue_env\Scripts\activate # 激活虚拟环境Mac/Linux source tongue_env/bin/activate # 安装依赖-i 指定国内镜像加速 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里有几个参数值得说清楚。python -m venv创建的是独立环境好处是你装崩了直接删掉文件夹重来不会影响系统里的其他项目。-i后面跟的镜像地址是清华源国内下载速度比默认源快很多尤其是 torch 这种几百兆的包。如果 requirements.txt 里没有锁定版本号建议手动加上指定版本比如torch1.13.1避免 pip 自动装最新版导致 API 不兼容。如果项目没有 requirements.txt常见做法是手动装这几个核心包pip install torch torchvision opencv-python pillow numpy matplotlib pip install PyQt5 # 如果界面用的是 PyQt装完之后验证一下import torch import cv2 import numpy as np print(torch.__version__) print(cv2.__version__) print(CUDA available:, torch.cuda.is_available())最后一行是判断你能不能跑 GPU 加速的关键。如果输出 False说明要么你没装 CUDA 版的 torch要么机器上没有 NVIDIA 显卡。对于舌象诊断这种小规模分类任务CPU 推理完全够用不用焦虑。2.3 目录结构与文件职责拿到源码后先别急着跑花五分钟把目录结构看清楚。典型的舌象诊断系统源码目录大概长这样tongue_diagnosis/ ├── data/ # 舌象图片数据集 │ ├── train/ # 训练集 │ └── test/ # 测试集 ├── models/ # 模型定义文件 │ └── cnn_model.py ├── weights/ # 训练好的权重文件 │ └── best_model.pth ├── utils/ # 工具函数 │ ├── preprocess.py # 图像预处理 │ └── dataset.py # 数据加载 ├── ui/ # 界面文件 │ └── main_window.py ├── train.py # 训练入口 ├── predict.py # 单张推理入口 └── requirements.txtweights/目录是重点。如果里面已经有训练好的.pth或.h5文件你直接跑推理就能看到结果不需要自己训练。如果没有你就得先跑train.py这时候data/目录里的图片数量和类别分布就决定了你能不能训出一个可用的模型。3. 模型推理与界面交互把权重文件变成可演示的诊断结果3.1 图像预处理的关键参数舌象图片在送入模型之前必须经过一套标准化处理。这不是可选项而是必须项——训练时用的什么预处理推理时就得用同样的流程否则模型看到的输入分布变了输出就是随机猜。import cv2 import numpy as np from PIL import Image def preprocess_tongue_image(image_path, target_size(224, 224)): 舌象图像预处理 image_path: 图片路径 target_size: 模型输入尺寸通常 224x224 # 读取图片 img cv2.imread(image_path) if img is None: raise ValueError(f无法读取图片: {image_path}) # BGR 转 RGBOpenCV 默认是 BGR img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 缩放到模型输入尺寸 img cv2.resize(img, target_size) # 归一化到 [0, 1] img img.astype(np.float32) / 255.0 # 标准化均值和标准差通常用 ImageNet 的 mean np.array([0.485, 0.456, 0.406]) std np.array([0.229, 0.224, 0.225]) img (img - mean) / std # HWC 转 CHWPyTorch 要求的格式 img np.transpose(img, (2, 0, 1)) # 增加 batch 维度 img np.expand_dims(img, axis0) return img这段代码里有三个参数最容易翻车。第一是target_size必须和训练时一致训练用 224 推理也用 224改成 256 模型就懵了。第二是归一化的 mean 和 std如果训练时用的是 ImageNet 的统计值推理时也得用同一套如果训练时用的是你自己算的舌象数据集统计值那就要换成对应的数字。第三是 BGR 和 RGB 的转换OpenCV 读进来是 BGRPIL 读进来是 RGB搞混了颜色通道模型看到的舌头颜色就是错的。3.2 加载模型并执行推理预处理做完之后加载权重文件执行推理import torch from models.cnn_model import TongueCNN def load_model(weight_path, num_classes5): 加载训练好的模型权重 weight_path: 权重文件路径 num_classes: 分类数量根据实际标签数调整 model TongueCNN(num_classesnum_classes) # 加载权重map_location 确保 CPU 也能加载 GPU 训练的权重 state_dict torch.load(weight_path, map_locationcpu) model.load_state_dict(state_dict) # 切换到推理模式关闭 dropout 和 batchnorm 的训练行为 model.eval() return model def predict(model, image_tensor): 执行推理并返回分类结果 # 关闭梯度计算节省内存 with torch.no_grad(): outputs model(image_tensor) # softmax 转成概率 probabilities torch.nn.functional.softmax(outputs, dim1) # 取最大概率对应的类别 confidence, predicted torch.max(probabilities, 1) return predicted.item(), confidence.item()map_locationcpu这个参数是血泪经验。很多同学在 GPU 机器上训练完模型拿到自己笔记本上跑推理直接报错说找不到 CUDA 设备。加上这个参数PyTorch 会自动把权重映射到 CPU 上兼容性最好。model.eval()也别忘了不加的话 dropout 层会在推理时随机丢弃神经元每次跑出来的结果都不一样你会以为是模型有问题其实是模式没切对。3.3 界面交互与结果展示如果源码带界面通常是用 Tkinter 或 PyQt 做的。核心逻辑是用户点击「选择图片」按钮 → 弹出文件选择框 → 读取图片路径 → 调用预处理和推理函数 → 在界面上显示诊断结果和置信度。from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QFileDialog from PyQt5.QtGui import QPixmap class TongueDiagnosisUI(QMainWindow): def __init__(self, model): super().__init__() self.model model self.init_ui() def init_ui(self): self.setWindowTitle(舌象诊断系统) self.setGeometry(100, 100, 800, 600) # 选择图片按钮 self.btn_select QPushButton(选择舌象图片, self) self.btn_select.move(50, 50) self.btn_select.clicked.connect(self.select_image) # 显示图片的标签 self.label_image QLabel(self) self.label_image.setGeometry(50, 100, 400, 400) # 显示结果的标签 self.label_result QLabel(等待诊断..., self) self.label_result.setGeometry(500, 200, 250, 100) def select_image(self): file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , Images (*.png *.jpg *.jpeg) ) if file_path: # 显示图片 pixmap QPixmap(file_path) self.label_image.setPixmap(pixmap.scaled(400, 400)) # 执行推理 img_tensor preprocess_tongue_image(file_path) img_tensor torch.from_numpy(img_tensor).float() class_id, confidence predict(self.model, img_tensor) # 显示结果 class_names [健康, 气虚, 阴虚, 阳虚, 湿热] result_text f诊断结果: {class_names[class_id]}\n置信度: {confidence:.2%} self.label_result.setText(result_text)这段界面代码的关键在于class_names列表的顺序必须和训练时的标签映射一致。如果训练时 0 对应「健康」推理时你把 0 当成「气虚」那整个系统就是错的。常见做法是在训练脚本里把类别映射存成一个 json 文件推理时读同一个文件避免手动写错。4. 训练自己的舌象模型数据集划分与参数调优4.1 数据集组织与增强策略如果你想用自己的舌象图片重新训练模型第一步是把图片按类别放进对应文件夹data/train/ ├── 健康/ # 放健康舌象图片 ├── 气虚/ # 放气虚舌象图片 ├── 阴虚/ ├── 阳虚/ └── 湿热/每个类别至少准备 50 张图片低于这个数量模型很难学到有效特征。如果手头图片不够用数据增强来凑from torchvision import transforms train_transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), # 随机裁剪 transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.RandomRotation(15), # 随机旋转 ±15 度 transforms.ColorJitter( brightness0.2, contrast0.2, saturation0.2, hue0.1 ), # 颜色抖动 transforms.ToTensor(), transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ]) val_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize( mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ])注意训练集和验证集的 transform 不一样。训练集用了随机裁剪、翻转、旋转、颜色抖动这些叫数据增强目的是让模型看到更多样的样本提高泛化能力。验证集只做 resize 和归一化不做随机变换因为验证集是用来评估模型真实性能的不能引入随机性。颜色抖动里的hue0.1要小心。舌象诊断很依赖颜色特征——舌质偏红还是偏淡、舌苔偏黄还是偏白这些是诊断依据。如果 hue 抖动太大把红色舌头变成绿色舌头模型学到的就是错误特征。所以 hue 参数建议不超过 0.1brightness 和 contrast 也不要设太大。4.2 训练循环与关键超参数训练脚本的核心结构import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader # 超参数 EPOCHS 50 BATCH_SIZE 16 LEARNING_RATE 0.001 NUM_CLASSES 5 # 数据加载 train_dataset TongueDataset(data/train, transformtrain_transform) val_dataset TongueDataset(data/test, transformval_transform) train_loader DataLoader(train_dataset, batch_sizeBATCH_SIZE, shuffleTrue) val_loader DataLoader(val_dataset, batch_sizeBATCH_SIZE, shuffleFalse) # 模型、损失函数、优化器 model TongueCNN(num_classesNUM_CLASSES) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lrLEARNING_RATE) # 学习率调度器每 15 个 epoch 降一半 scheduler optim.lr_scheduler.StepLR(optimizer, step_size15, gamma0.5) # 训练循环 best_acc 0.0 for epoch in range(EPOCHS): model.train() running_loss 0.0 for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() scheduler.step() # 验证 model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in val_loader: outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() val_acc correct / total print(fEpoch [{epoch1}/{EPOCHS}], Loss: {running_loss/len(train_loader):.4f}, Val Acc: {val_acc:.4f}) # 保存最佳模型 if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), weights/best_model.pth) print(f模型已保存最佳准确率: {best_acc:.4f})几个参数需要根据实际情况调整。BATCH_SIZE16是保守值如果你的显卡显存够大8G 以上可以调到 32 甚至 64训练速度更快。LEARNING_RATE0.001是 Adam 优化器的常用初始值如果 loss 震荡厉害就降到 0.0001如果 loss 下降太慢就升到 0.01。StepLR的作用是每隔 15 个 epoch 把学习率乘以 0.5让模型在后期微调时步子迈小一点更容易收敛到好的位置。4.3 训练过程中的监控与早停训练时重点看两个指标训练 loss 和验证准确率。如果训练 loss 持续下降但验证准确率不涨甚至下降说明模型过拟合了。这时候可以增加数据增强强度、加 dropout 层、减小模型参数量、或者提前停止训练。# 早停机制 patience 10 counter 0 if val_acc best_acc: best_acc val_acc counter 0 torch.save(model.state_dict(), weights/best_model.pth) else: counter 1 if counter patience: print(f验证准确率连续 {patience} 个 epoch 未提升提前停止训练) break早停的逻辑很简单如果验证准确率连续 10 个 epoch 都没刷新最佳记录就认为模型已经学不动了继续训下去只会过拟合。这个机制能帮你省下大量等待时间。5. 避坑与排查舌象诊断系统最常见的五个翻车现场5.1 报错「No module named torch」但明明装了现象在终端里pip install torch成功了但运行脚本还是报找不到模块。原因你装 torch 的环境和运行脚本的环境不是同一个。常见于系统里有多个 Python 版本或者虚拟环境没激活就装了包。解决先确认当前用的是哪个 Pythonwhich pythonMac/Linux或where pythonWindows。然后在当前环境里重新装python -m pip install torch。用python -m pip而不是直接pip能保证装到当前 Python 对应的环境里。5.2 推理结果每次都不一样现象同一张舌象图片跑两次得到不同的诊断结果。原因模型没有切换到 eval 模式dropout 层和 batch normalization 层还在按训练模式运行引入了随机性。解决推理前加model.eval()推理时用with torch.no_grad():包住。这两个操作缺一不可前者关闭随机行为后者关闭梯度计算。5.3 界面能打开但点击按钮没反应现象程序启动后界面正常显示但点击「选择图片」按钮没有任何反应也不报错。原因PyQt 的信号槽连接写错了或者按钮的 clicked 信号没有正确绑定到槽函数。解决检查self.btn_select.clicked.connect(self.select_image)这行代码确认函数名拼写一致。另外如果槽函数里有异常但没打印出来界面会静默失败。在槽函数开头加print(按钮被点击了)来确认信号是否触发。5.4 训练准确率很高但实际用起来全是错的现象训练日志显示验证准确率 95% 以上但拿新图片测试时结果离谱。原因数据集划分有问题。如果训练集和验证集里有重复图片或者验证集的图片和训练集来自同一批拍摄模型相当于在「背答案」验证准确率虚高。解决确保训练集和验证集的图片完全不重叠。更严格的做法是按拍摄批次划分——同一批次拍的图片要么全在训练集要么全在验证集避免模型学到拍摄条件而不是舌象特征。5.5 图片读取失败但路径明明是对的现象cv2.imread()返回 None后续操作全部报错。原因路径里有中文或空格OpenCV 在 Windows 下对中文路径支持不好。解决用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)代替cv2.imread(path)这样能正确处理中文路径。或者把图片路径改成全英文。6. 从能跑到能答辩模型评估与演示技巧6.1 用混淆矩阵证明模型不是瞎猜答辩时老师最常问的一句话是「你怎么知道模型真的学到了东西而不是随机猜」这时候你需要混淆矩阵。from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt def evaluate_model(model, val_loader, class_names): model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in val_loader: outputs model(images) _, predicted torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) # 混淆矩阵 cm confusion_matrix(all_labels, all_preds) plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, xticklabelsclass_names, yticklabelsclass_names) plt.xlabel(预测类别) plt.ylabel(真实类别) plt.title(舌象诊断混淆矩阵) plt.savefig(confusion_matrix.png, dpi150) # 分类报告 print(classification_report(all_labels, all_preds, target_namesclass_names))混淆矩阵的对角线越深越好说明每个类别都预测对了。如果某个类别经常被预测成另一个类别比如「气虚」大量被误判为「阳虚」说明这两类舌象在特征上确实相似模型区分不开。这时候可以在答辩时主动说明「这两类在临床上也存在兼证模型混淆有一定合理性」反而显得你理解深入。6.2 用 Grad-CAM 可视化模型关注区域比混淆矩阵更有说服力的是 Grad-CAM 热力图。它能告诉你模型在看图片的哪个区域做判断——如果热力图集中在舌头区域说明模型确实在学舌象特征如果热力图散落在背景上说明模型可能在学背景颜色。from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image def visualize_attention(model, image_tensor, original_image): 生成 Grad-CAM 热力图展示模型关注区域 target_layer model.features[-1] # 最后一个卷积层 cam GradCAM(modelmodel, target_layers[target_layer]) grayscale_cam cam(input_tensorimage_tensor) grayscale_cam grayscale_cam[0, :] # 叠加到原图上 visualization show_cam_on_image( original_image, grayscale_cam, use_rgbTrue ) plt.imshow(visualization) plt.axis(off) plt.savefig(gradcam_result.png, dpi150, bbox_inchestight)Grad-CAM 需要装pytorch-grad-cam库pip install pytorch-grad-cam。生成的热力图里红色区域是模型最关注的地方。如果红色集中在舌体和舌苔上答辩时你就可以理直气壮地说「模型学到的特征和中医舌诊的关注点一致。」6.3 演示时的几个实用习惯答辩演示最怕现场翻车。我一般会提前做三件事第一准备 5 到 10 张测试图片放在桌面文件夹里现场直接选不要临时找图第二把模型权重和代码打包成一个文件夹确保换电脑也能跑第三提前跑一遍完整流程确认从选图到出结果不超过 5 秒太慢的话老师会不耐烦。还有一个细节如果界面上的置信度显示「98.7%」老师可能会问「这个置信度可靠吗」。你可以提前准备一句话「置信度是 softmax 输出的概率值反映的是模型对当前样本的确定程度不是临床诊断的准确率。」这样既展示了技术理解又避免了过度承诺。从那以后我每次交大作业之前都会强制走一遍「换一台电脑从零部署」的流程——因为你自己机器上能跑不代表答辩教室的电脑上能跑。依赖版本、路径大小写、中文字符任何一个细节都可能让你在台上尴尬。希望帮到你。本文还有配套的精品资源点击获取