ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Jupyter的糖尿病视网膜病变诊断:从环境搭建到EfficientNet模型微调实践

基于Jupyter的糖尿病视网膜病变诊断:从环境搭建到EfficientNet模型微调实践 简介这是一套基于Jupyter完成的糖尿病视网膜疾病诊断毕业设计项目面向计算机、人工智能、自动化及相关专业学生与从业者适用于课程设计、毕业设计或深度学习进阶练习。资源包共30个文件以16个Jupyter notebook为主完整覆盖数据预处理、EDA探索、EfficientNet系列模型训练与交叉验证、基于logits的模型集成等关键环节另含2个Python脚本、9张结果标注图、分类结果CSV及说明文档压缩包大小30.44MB目录结构清晰便于按流程查阅。目前已有58人学习浏览代码全部调试通过可运行。整套资料从数据划分、模型搭建到预测提交均有完整记录同时附带模型权重和文档说明既能帮助初学者快速复现糖尿病视网膜病变诊断流程也能作为毕业设计的高分基线方案——项目答辩评审曾获98分具备较高的学习与二次开发价值。1. 基于Jupyter的糖尿病视网膜疾病诊断源码、数据、模型拿到手怎么落地我之前帮人看毕业设计十个里有八个卡在Jupyter跑糖尿病视网膜疾病诊断时环境装不上。其实这个方向早就是公开数据集上的成熟任务了难点不在算法在于你拿到源码数据集模型文档后能不能一次性复现出来。这个项目是典型的“毕设全家桶”Jupyter Notebook做的完整实验记录、划分好的眼底照片数据集、预训练权重微调脚本、还有一册能直接改页眉的说明文档。如果你正准备做医学图像识别方向的毕设或者想把手上的眼底照片快速跑出一个像样的分类模型这个包值得你花一个下午从头到尾复现一遍。这一章先讲它是什么、适合谁后面几章全部按实战步骤走。2. 选型逻辑为什么是Jupyter Notebook而不是PyCharm跑全套流程2.1 毕业论文和代码不想分离Markdown写推导代码块做实验医疗影像方向的毕业设计有个特别普遍的现象论文里的实验分析比如Loss曲线、混淆矩阵、数据增强可视化图和代码是割裂在Word和IDE里的。你用PyCharm写好训练脚本跑完存个日志再去Excel里画曲线再贴回论文里面来回切换至少多花一周时间。这个项目干脆用Jupyter Notebook把整个流程串起来。每个章节前用Markdown写清楚“这一节在干什么、为什么调这个参数”下面紧跟可执行的代码块。比如数据清洗那节前面会有一段说明“眼底照片边缘经常有黑色背景直接缩放会导致特征被干扰因此我们按圆形遮罩裁剪”后面就是对应的処理代码。你复现的时候等于顺着作者的思路走了一遍交毕业设计时的“工作量证明”直接截代码块和运行结果就有说服力。Jupyter另一个不可替代的优势是增量运行。你不需要像跑普通Python脚本那样每次都从头执行全套训练只要数据集加载那节跑过一次后续调参时可以只重新运行某个单元格。训练出问题也不必从头再来直接改最后一个cell重新跑就行。这对反复刷参数做对比实验的毕设场景是刚需。2.2 环境搭建Anaconda建虚拟环境安装依赖包拿到项目先把隔离环境建好社区里翻车大多是因为把项目依赖直接装到base环境导致包版本冲突。进入项目根目录后我一般习惯先看一眼requirements.txt里有没有锁版本没有的话再手动去装。# 创建python 3.8虚拟环境不要用3.10以上的版本 # 很多老毕设源码在3.10以上跑会出SciPy和matplotlib的兼容性错误 conda create -n retina python3.8 -y conda activate retina # 安装核心依赖这里指定版本是为了兼容Jupyter内核 pip install torch1.10.0 torchvision0.11.0 --extra-index-url https://download.pytorch.org/whl/cu113 pip install jupyter notebook6.4.12 pip install opencv-python4.5.5.64 pillow8.4.0 scikit-learn1.0.2 pandas1.3.5 matplotlib3.5.1代码里的逻辑分三步先创建独立的Python3.8内核避免项目之间环境污染再安装PyTorch时特意指定了cu113版本对应你的NVIDIA驱动CUDA版本如果你用CPU跑可以去掉--extra-index-url那行最后装的OpenCV和scikit-learn版本都是和Torch1.10验证过兼容的组合直接用最新版容易碰到API改名的报错。参数说明里有个细节值得注意notebook6.4.12不要用JupyterLab替代。这个项目的.ipynb文件是旧版格式在JupyterLab上虽然能打开但有时会出现markdown渲染偏移和代码块输入框重叠的情况。用回经典Notebook界面最稳妥。装完环境后终端输入jupyter notebook接着会自动打开浏览器页面进入后先找到项目解压目录再打开主文件main.ipynb。# 确认环境没问题然后启动notebook jupyter notebook --notebook-dir你解压的物理路径/retina_detection这里有个常见误区不要在Anaconda Prompt里直接双击.ipynb文件那样它会被系统默认的文本编辑器打开根本跑不了。先进Jupyter再找文件是对的打开姿势。3. 数据集准备与划分拿到的图片不能直接灌给模型3.1 源码包里数据集长什么样解压后数据目录结构一般是这样的路径内容数量级dataset/train/0/无DR标签的彩色眼底图训练集约800~2000张dataset/train/1/轻度DR眼底图按原数据集比例dataset/train/2/中度DR眼底图按原数据集比例dataset/train/3/重度DR眼底图按原数据集比例dataset/train/4/增值性DR眼底图按原数据集比例dataset/val/按同样5个类分好的验证集占总样本10~15%dataset/test/未知标签用于最终性能测试若干张文件夹命名直接以0-4作为类别编号对应国际通用的糖尿病视网膜病变严重程度分级0为无病变4为最严重的增值性病变。这个编号顺序和原论文指标里的表格是对应上的做混淆矩阵时不用改代码。一个很容易忽略的坑是图片尺寸不统一。源数据里有些图片能到2000×3000像素直接resize会损失大量细节。这个项目里已经把原始图片统一裁到224×224了但仍需确认每张图的通道顺序是BGR还是RGB。用OpenCV读图默认是BGR如果你拿PIL读同一张图再混着用模型输入颜色就会错乱最后验证集AUC很难看。3.2 手写数据划分脚本防止验证集泄漏和类别顺序偏置源码包里自带一个划分好的版本但假如你要加自己的图片进去或者想换数据集重跑就得知道它内部划分的机制。项目里实现是按“病人级别”划分这比按“图片级别”划分要严谨得多——同一个人两只眼的多张照片会被分到同一个集合不然特征重复会导致验证集得分虚高。import os import random import shutil from collections import defaultdict random.seed(42) source_root your_raw_dataset # 原始图片路径按类别0-4归类 output_root dataset train_ratio 0.85 # 思路按图片名前缀把同一病人的图片分到同一个桶里 patient_buckets defaultdict(list) for class_id in range(5): cls_path os.path.join(source_root, str(class_id)) for img_name in os.listdir(cls_path): # 假设文件名格式为 “病人ID_编号.jpg”比如 P001_01.jpg patient_id img_name.split(_)[0] patient_buckets[patient_id].append((class_id, img_name)) for patient_id, img_list in patient_buckets.items(): random.shuffle(img_list) split_idx int(len(img_list) * train_ratio) for img_info in img_list[:split_idx]: cls_id, img_name img_info shutil.copy( os.path.join(source_root, str(cls_id), img_name), os.path.join(output_root, train, str(cls_id), img_name) ) for img_info in img_list[split_idx:]: cls_id, img_name img_info shutil.copy( os.path.join(source_root, str(cls_id), img_name), os.path.join(output_root, val, str(cls_id), img_name) )这段代码的核心是patient_buckets这个字典它以文件名里截取出来的病人ID为主键把同一病人的所有照片放进一个桶里再统一做切分。这样训练集和验证集之间不会出现同一个病人的不同张照片交叉污染。train_ratio0.85表示每个病人约15%的照片进验证集比随机乱切更能反映真实泛化性能。跑完后建议检查一下dataset/val/目录下是否有空文件夹。部分类别样本极少时小概率会出现整个桶都被分进训练集的情况导致验证集缺那一类。判断标准是验证集五个类别目录下都要有文件否则后续计算混淆矩阵那节直接报错。3.3 制作分类权重类别不平衡是医疗图像最大的坑糖尿病数据集里0类无病变图片通常占大头如果按原始比例训练模型会学成“全猜0”。项目里在数据加载阶段实现了权重均衡采样这是整个毕设能过盲审的关键点之一。在main.ipynb的第二个cells能看到类似的实现from torch.utils.data import WeightedRandomSampler class_counts [1200, 400, 300, 180, 120] # 实际数据集的类别分布 total_count sum(class_counts) weights [total_count / c for c in class_counts] sample_weights [] for label_idx, count in enumerate(class_counts): sample_weights.extend([weights[label_idx]] * count) sampler WeightedRandomSampler(sample_weights, num_samplestotal_count, replacementTrue)WeightedRandomSampler的原理是让少样本的类比多样本的类更容易被抽中。这里weights计算方式就是总样本数除以该类数量样本少的类权重自然大。replacementTrue允许同一张图在一个epoch里出现多次虽然增加了训练时间但对5分类中类别极不均衡的场景帮助很大。每训练完一个epoch建议打印一下每个类别的准确率不是只看总acc你会发现如果没加这个采样器3类和4类的召回率会低到不能看。4. 模型架构与训练流程用EfficientNet做预训练微调不再从零搭CNN4.1 选型理由为什么是EfficientNet而不是ResNet眼科图像识别在Kaggle这类比赛里近年基本是EfficientNet系列和ResNet系列两分天下。源码包选择EfficientNet的主要原因是在同样精度水平下它的参数量和计算量比ResNet小一个量级。以EfficientNet-b3为例推理速度在GTX 1660上大约是ResNet50的1.5倍而AUC还略微领先。模型Top-1 Acc (ImageNet)参数量适合的毕设场景ResNet5075.3%25.6M设备一般追求稳定EfficientNet-b076.8%5.3M小而快适合快速迭代EfficientNet-b381.1%12.0M性价比高本文项目默认毕设答辩时老师问你“为什么选这个模型”答案核心是因为眼底照片分辨率高、病灶区域小EfficientNet在相同FLOPS下能看得更细。同时它的复合缩放策略让宽度、深度、分辨率三个维度同时调整很容易适配224×224输入。如果你的显卡显存超过6G可以把源码包里的模型名字改成efficientnet-b4或efficientnet-b5输入尺寸在配置文件里同步改到300或456能再涨两个点。4.2 训练代码核心参数解读冻结层数、学习率、EMA模型加载部分和训练循环结合在一个cell里结构不算长但参数值得一个个看import torch import torch.nn as nn import torch.optim as optim from efficientnet_pytorch import EfficientNet def create_model(num_classes5, freeze_level0): # load pretrained weights model EfficientNet.from_pretrained(efficientnet-b3, weights_pathmodel/efficientnet-b3.pth) # freeze前几层保留低级特征微调高级特征 if freeze_level 0: for param in model.parameters(): param.requires_grad True elif freeze_level 1: for i, param in enumerate(model.parameters()): if i 250: param.requires_grad False num_features model._fc.in_features model._fc nn.Linear(num_features, num_classes) return model model create_model(freeze_level1) criterion nn.CrossEntropyLoss() optimizer optim.AdamW(model.parameters(), lr3e-4, weight_decay0.01) scheduler optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max30, eta_min1e-6)freeze_level1意味着前250层参数被冻结只训练后面接近分类层的权重。这样做的意义是眼底照片的纹理特征血管走向、亮光区域和ImageNet上学习到的低级特征比较接近不需要重新学只微调高级语义特征即可可大幅减少过拟合尤其适合几百张的小数据集。AdamW比Adam好在weight_decay实现更规范配合weight_decay0.01能有效限制权重范数防止模型在噪声样本上记死。CosineAnnealing调度器让学习率从3e-4余弦下降到1e-6前10个epoch做粗调后20个epoch慢慢收敛到一个平坦极小值。训练时如果发现loss下不去或者accuracy停在90%0类拉高不动可以考虑把冻结层数调到0这会增加训练时间但给模型更强的拟合能力。另一个实操技巧是开启混合精度训练代码里已经预留了torch.cuda.amp的相关逻辑记得检查你的显卡是否支持不支持的话不要开否则训练过程会出现NaN。5. 避坑指南跑Jupyter毕设最容易翻车的5个场景5.1 跑了一下午准确率停在71%验证集表现也垃圾现象模型总loss在下降但验证集混淆矩阵里几乎全成了0类其它类别完全分不清。原因数据加载时忘了加归一化处理。眼底照片原图的像素范围是0-255模型预训练时接受的是0-1范围的tensor分布不一致导致特征提取器一塌糊涂。解决找到项目预处理cell确认加了transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])。用ImageNet的均值和方差是通用做法不要自己另算。改完这个准确率基本能直接跳到85%以上。5.2 Jupyter内核重启后报错ModuleNotFoundError现象刚装完环境跑通训练的Cell但关浏览器第二天重启内核第一行import torch直接报红在终端里pip list又能看到torch存在。原因Jupyter内核和conda虚拟环境没绑定。你在conda activate之后启动的jupyter理论上内核对应的是当前虚拟环境但如果你是从Anaconda Navigator的界面直接点击启动它用的是base环境。解决在虚拟环境里安装ipykernel执行python -m ipykernel install --user --name retina --display-name Python (retina)然后重启Jupyter在Kernel菜单里切换内核选择retina。从那以后每次新开项目我都会先确认右上角kernel名而不是傻傻地重装一遍包。5.3 Loss一路跑偏直接变成NaN现象训练到约第15个epochloss突然变成nan之后一直没有恢复。原因余弦退火学习率降到后期AdamW的自适应梯度更新出现分母极小的数值不稳或者输入的图片像素出现0分割失败导致图片全黑在归一化后变为负值经过网络后导致梯度爆炸。解决先在数据检查阶段右键点开几张图片看是否全黑再把torch.autograd.set_detect_anomaly(True)临时打开定位是哪个层输出的值变成inf。一般处理方式是降低初始化学习率到1e-4或者在BatchNorm层前加一个小常数eps1e-5双保险。5.4 加载官方预训练权重时state_dictKey名对不上现象model.load_state_dict(torch.load(model/efficientnet-b3.pth))报错提示尺寸不匹配或页缺少多个key。原因官方EfficientNet的权重是原模型导出没有带_fc.分类层的参数。而代码里已经修改了model._fc为新的5分类线性层所以新旧权重有一部分对不上。解决加载时设置strictFalse告诉PyTorch允许缺失项。再加关键一步——单独保存修改后的模型权重后续推理直接加载微调后的文件。在项目里一般是model/retina_best.pth则不存在这个问题前提是你别误删这个文件。5.5 数据集读着读着突然内存溢出现象训练第一个epoch正常第二个跑到一半OSError报“无法分配内存”。原因没用DataLoader的num_workers限制并行读写Jupyter里默认是0还好有些同学看了别的博客调到8加上图片解码缓存8G内存直接顶不住。解决把num_workers调回2并加上pin_memoryTrue。这个是给GPU训练加速用的。如果你的机器内存只有8G建议直接在config里改掉。6. 进阶玩法用训练好的模型对新眼底照片做推理并固化推理结果6.1 单张图片推理代码输出5类置信度而不是只贴标签做毕业设计答辩时老师不只看训练报告还想看模型对真实世界新照片的预测效果。这个源码包里有个inference_single.py核心逻辑是把图片读进来走一遍预处理然后输出每个类别的概率。很多新手只取argmax返回的类别连概率都不展示写论文没有说服力。import torch import numpy as np from PIL import Image from torchvision import transforms # 与训练阶段完全一致的预处理流程 preprocess transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def predict_single(model, image_path): model.eval() img Image.open(image_path).convert(RGB) # 统一转3通道避免灰度图意外 img preprocess(img).unsqueeze(0) # 增加batch维度 with torch.no_grad(): logits model(img) probs torch.softmax(logits, dim1).cpu().numpy()[0] class_names [No DR, Mild, Moderate, Severe, Proliferative DR] confidences {class_names[i]: round(float(probs[i]), 4) for i in range(5)} predicted_class class_names[int(np.argmax(probs))] return predicted_class, confidences if __name__ __main__: model create_model(load_weightsmodel/retina_best.pth) category, confs predict_single(model, test_imgs/case1.jpg) print(诊断结果, category) print(置信度分布, confs)这段输出的置信度分布很有价值如果预测结果为“No DR”但“Mild”的概率有35%论文里可以写“模型倾向判断为早期病变需要进一步随访”这让结果落地成医学可解释的方式比单纯0/1分类有说服力。注意model.eval()切到评估模式因为在训练模式开启Dropout后推理结果每次都不一样。6.2 批量跑完文件夹里所有图片并自动生成CSV报告更进阶的用法是把predict_single函数写进一个循环遍历整个test目录把结果和概率全部汇总到一张CSV里。这个CSV可以直接作为论文附录的实验证据。import os import csv import torch from tqdm import tqdm model create_model(load_weightsmodel/retina_best.pth) image_dir test_imgs/ save_path report/predict_results.csv with open(save_path, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([image_name, predicted_class, conf_no_dr, conf_mild, conf_moderate, conf_severe, conf_proliferative]) for img_name in tqdm(os.listdir(image_dir)): if not img_name.endswith((.jpg, .png, .jpeg)): continue pred_class, confs predict_single(model, os.path.join(image_dir, img_name)) writer.writerow([img_name, pred_class, confs[No DR], confs[Mild], confs[Moderate], confs[Severe], confs[Proliferative DR]])用encodingutf-8-sig生成CSV可以在Excel里直接打开而不出现中文乱码——这是一个很隐蔽的坑默认的utf-8写入Excel里反而是乱码很多。tqdm负责显示进度条几十张图跑完会有日志方便你有“模型在干活”的感觉。跑完之后逐个验证CSV里的置信度分布。如果发现大量图片预测概率比较平均最高概率低于0.6说明模型对这个样本的把握度不够要回到训练阶段检查数据增强强度是否过大。偶尔会有图片因为扫描噪声被判断成重度DR这属于模态差异不是代码bug。遇到这类情况直接排除那几张图再出最终结果表即可。从那以后我每次在Jupyter里加载这套毕设模型做推理都会强制先跑一遍数据预处理检查再确认模型路径存在最后才执行批量预测确保整个流程一次出结果。这已经成了我的条件反射希望帮到你。本文还有配套的精品资源点击获取
返回列表