news 2026/9/12 9:52:47

基于Jupyter的糖尿病视网膜病变诊断实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Jupyter的糖尿病视网膜病变诊断实战解析

简介:医学图像分类是深度学习在医疗领域的重要应用方向,其核心原理是利用卷积神经网络自动提取图像特征,从而替代或辅助人工诊断。在实际工程中,迁移学习通过在大型自然图像数据集上预训练模型,再在医学影像上微调,有效解决标注数据不足的问题;结合CLAHE预处理和类别均衡策略,可显著提升模型对少数类别的识别能力。该类技术广泛应用于糖尿病视网膜病变等慢性病的早期筛查,能有效缓解基层医疗资源紧张。本文以基于Jupyter的糖尿病视网膜病变诊断项目为例,完整梳理从眼底图像预处理、模型迁移训练到评估指标(如Quadratic Weighted Kappa)选择及项目组织的方法,为医学影像分类方向的毕业设计和工程实践提供可复现的思路参考。 毕业设计题目一栏写着"基于Jupyter实现的糖尿病视网膜疾病诊断",配套资料还标注了"源码+数据集+模型+文档说明",你大概率跟我当年拿到这个题目时一样:一边觉得这个课题很"人工智能",一边又不知道第一行代码该从哪里落笔。其实这个项目的本质,是把糖尿病视网膜病变(Diabetic Retinopathy,简称DR)这个真实的眼科临床问题,拆解成一个多分类图像识别任务——输入一张眼底彩照,模型输出它属于哪一个严重等级(0到4级)。听起来不复杂,但把数据处理、模型训练、结果评估、文档撰写整个链路在Jupyter环境里完整跑通,里面值得讲的东西比想象中多得多。

这篇文章不是给你一份可以直接抄作业的完整源码,而是把做这个毕设时真正花时间想明白的事情讲清楚:数据集怎么选怎么处理、模型在本科阶段做到什么程度算"够了"、评估指标为什么不能只看准确率、Jupyter项目怎么组织才能在答辩时不慌,以及那些不会出现在教程里的坑。如果你正准备做医学图像分类方向的毕设,或者只是想在一周内把这个方向吃透,这篇文章应该能帮你省下不少时间。

1. 这个毕设到底在做什么:从眼科筛查困境到图像分类任务

1.1 糖尿病视网膜病变,一个值得做的临床问题

糖尿病视网膜病变是糖尿病最常见的微血管并发症之一,也是成年人后天致盲的最主要眼病。患者血糖长期控制不佳,视网膜的毛细血管会逐渐受损,出现微动脉瘤、出血点、渗出甚至新生血管。可怕的地方在于,早期DR几乎没有明显症状,等患者觉得视力下降再就诊,往往已经进入中晚期。

临床上靠眼底照相对高危人群做定期筛查是最有效的手段,但专业的眼底病医生数量有限,大量基层影像没人能及时读。于是"AI读片"成了一个很自然的切入点——让模型先筛一遍,有异常的再转给医生复核,可以极大缓解筛查压力。这就是你这个毕设题目存在的现实意义。

1.2 毕设的技术定位:一套完整的图像分类闭环

放在毕业设计的尺度上,你要做的不是真的去医院部署一套系统,而是把整个AI辅助筛查的技术链路完整走一遍,并且留下可复现的结果。具体到代码层面,这个任务就是一个典型的监督学习图像分类问题:训练集是一批带医生标注的眼底彩照,标签是0到4五个严重等级,模型要做的是从图像特征中学到一套能够区分这些等级的映射关系。你最终交付的,是一个能跑通的训练与推理流程,外加一份解释得清楚的实验报告。

这个定位很重要。很多同学做毕设时容易陷入两个极端:要么把目标定得太高,想做出一个包含前端后端、能上传图片实时诊断的完整产品,结果精力被分流得厉害;要么把目标定得太低,随便找几个模型跑一遍准确率就算完事,答辩时讲不出所以然。正确的做法是抓主线——数据、模型、评估、文档四条线,每条线都做到"能解释清楚为什么这么选、为什么这么做"。

1.3 技术栈选择:为什么要用 PyTorch + Jupyter

这个题目既然点名了Jupyter,开发环境就是它。我的建议是:数据处理和模型训练用PyTorch,Notebook负责可视化、实验记录和推理演示。用PyTorch而不是TensorFlow,理由很现实:近几年的论文和开源实现大多基于PyTorch,你在调试时遇到问题,搜到有效解决方案的概率更高;而且PyTorch的动态图机制在Notebook里调试特别顺手,打印张量shape、画损失曲线、临时改网络结构都很直观。

顺带说一句,现在的Jupyter生态已经不是当年那个"丑但能用"的Notebook了,如果环境允许,建议直接装JupyterLab,界面现代化很多,可以分栏看代码和图表,体验好不少。如果实验室统一用Notebook,那按习惯来就好,功能上对完成这个项目没有本质差别。

2. 数据准备:眼底图像处理里的三个隐藏门槛

2.1 数据集选型:APTOS 2019是本科毕设的最优解

这个方向最常用的公开数据集是EyePACS和APTOS。本科毕设强烈建议用APTOS 2019——Kaggle上一个专门为DR分级举办的比赛数据集,图片质量相对统一,标签由有经验的眼科医生标注,规模对个人电脑也友好。训练集有3662张眼底彩照,测试集大约1900张,五分类规则如下:

等级分类名称眼底表现
0无DR无明显病变
1轻度NPDR仅有微动脉瘤
2中度NPDR病变比1级重但不到3级
3重度NPDR多象限出血、静脉串珠、IRMA
4PDR(增殖期DR)新生血管或玻璃体积血

注意,这里的"严重程度"是眼底照片上的视觉表现,不是视力检查结果。图像分类模型学的是视觉特征到等级的映射。这个表格建议放进毕设文档的"医学背景"章节,答辩时老师很可能会问"你怎么理解每一级之间的区别"——能指着图说出微动脉瘤、渗出、新生血管这些名词,印象分会高很多。

2.2 预处理第一步:去掉黑边,把眼底区域真正露出来

眼科图像和普通图像分类有一个明显区别:照片里除了圆形眼底区域,周围还有一大圈黑色背景。如果直接resize送进模型,黑色区域会带来大量无意义特征,模型的一部分能力就浪费在"学习背景是黑的"这种事上了。所以第一步通常是去除黑边,把圆形眼底区域提取出来。

操作上,用HSV颜色空间就够了。眼底区域的饱和度和亮度跟黑色背景差异很大,通过饱和度通道做阈值可以得到一个二值掩膜,然后按掩膜找到最小外接圆或外接矩形,裁剪出有效区域:

import cv2 import numpy as np def preprocess_fundus(img_path, output_size=224): img = cv2.imread(img_path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV) # 眼底区域饱和度远高于黑色背景,用阈值分离 mask = cv2.inRange(hsv[:, :, 1], 20, 255) mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))) contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) x, y, w, h = cv2.boundingRect(contours[0]) center = (x + w // 2, y + h // 2) radius = max(w, h) // 2 mask_circle = np.zeros_like(mask) cv2.circle(mask_circle, center, radius, 255, -1) masked = cv2.bitwise_and(img, img, mask=mask_circle) cropped = masked[y:y + 2 * radius, x:x + 2 * radius] resized = cv2.resize(cropped, (output_size, output_size)) return resized

2.3 预处理第二步:色彩归一化与数据划分的细节

去完黑边之后,还需要处理色彩差异。不同医院、不同设备拍出来的眼底图色温差异很大,有的偏黄、有的偏白。直接让模型去适应这种差异也可以,但会让训练变慢。更稳妥的做法是做CLAHE(限制对比度自适应直方图均衡化),把亮度分布拉平。这个操作在医学影像预处理里非常常见:

def apply_clahe(img): lab = cv2.cvtColor(img, cv2.COLOR_RGB2LAB) clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) lab[:, :, 0] = clahe.apply(lab[:, :, 0]) return cv2.cvtColor(lab, cv2.COLOR_LAB2RGB)

数据集的划分则要格外小心。因为数据本身类别不均衡——正常和轻度占大头,重度DR样本少——如果简单随机划分,可能某一折里没有3级或4级样本。用train_test_split的stratify参数,按标签比例分层抽样,保证每一折的类别分布和全集一致:

from sklearn.model_selection import train_test_split train_df, val_df = train_test_split( df, test_size=0.2, stratify=df['level'], # 按标签分层 random_state=42 )

这一层如果你忽略了,后面模型在验证集上的表现会忽高忽低,而且你很难判断到底是模型问题还是数据划分问题。真到答辩前才发现这层问题,重跑实验的时间成本就太高了。

3. 模型选型与训练:本科毕设的"够用"标准

3.1 迁移学习:本科毕设最靠谱的起点

很多同学一上来就想去搞最新的ViT、Swin Transformer,或者什么自研的高性能架构。如果你的算力和调参经验都很充足,尝试这些当然没问题,但对绝大多数本科毕设来说性价比不高:训练时间更长、显存要求更高、调参更复杂,而在APTOS这种中等规模数据集上,经典CNN配合迁移学习完全能拿到足够好的结果。

为什么迁移学习在医学图像领域几乎是标配解法?因为医学图像数据集通常只有几千张,而ImageNet有上百万张。用预训练权重初始化网络,等于让模型站在"已经知道怎么识别纹理、边缘、形状"的肩膀上,只需要在眼底图像上做微调。训练速度更快、收敛更稳定、精度更高,三赢。

实际操作上,两个模型最合适:

  • ResNet50:最经典、显存友好、训练稳定,本科阶段首选,作为baseline非常合适。
  • EfficientNet-B4:同样搭配迁移学习,精度通常比ResNet50高一点,但训练速度慢、显存占用大。如果机器配置不错,可以做对比实验。答辩时一句"我用ResNet50作baseline,再引入EfficientNet做对比"比只跑一个模型有说服力得多。

3.2 模型搭建:只需要改最后几行

PyTorch中构建模型的核心代码其实很简单,关键是把最后的全连接层换成自己的分类头:

import torch.nn as nn from torchvision import models def build_model(num_classes=5, model_name='resnet50'): if model_name == 'resnet50': weights = models.ResNet50_Weights.IMAGENET1K_V1 model = models.resnet50(weights=weights) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(0.5), nn.Linear(in_features, num_classes) ) elif model_name == 'efficientnet_b4': weights = models.EfficientNet_B4_Weights.IMAGENET1K_V1 model = models.efficientnet_b4(weights=weights) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(in_features, num_classes) ) return model

这里为什么要加一个Dropout层?因为医学图像分类场景里类别间特征差异比较细微,全连接层的参数量又很大,很容易出现过拟合。Dropout在训练时随机丢弃一部分神经元连接,相当于每次前向传播都在训练一个不同的小网络,最后在推理时取平均,对防止模型死记训练集有明显帮助。

3.3 训练配置与类别不均衡处理

一个我自己实测下来比较稳的训练配置:

  • 输入尺寸:224×224(用EfficientNet-B4可以尝试更大尺寸,但不强求)
  • 损失函数:CrossEntropyLoss,加类别权重
  • 优化器:Adam,lr=1e-4,weight_decay=1e-4
  • Batch size:ResNet用32或64;EfficientNet-B4在12G显存下建议32以内
  • 轮数:15~25轮,配合早停
  • 学习率调度:ReduceLROnPlateau,验证集loss连续3轮不降就衰减为原来的0.1

类别不均衡是医疗影像分类的常见问题。如果直接用原始分布训练,模型会倾向把样本预测为占比高的0级和1级,导致少数类别的召回率很低。一种标准做法是给Loss加一个权重系数,让少数类别被错分的代价更高:

from sklearn.utils.class_weight import compute_class_weight import torch class_weights = compute_class_weight( class_weight='balanced', classes=np.array([0, 1, 2, 3, 4]), y=train_df['level'].values ) class_weights = torch.tensor(class_weights, dtype=torch.float32).to(device) criterion = nn.CrossEntropyLoss(weight=class_weights)

3.4 数据增强:宁小勿大,别把病变特征增强没了

医学图像的数据增强跟通用分类任务比要保守得多,因为病变本身的形态是临床诊断的核心依据。水平翻转、垂直翻转在眼底图上完全合理,因为眼底左右眼结构本身就存在镜像关系;但如果旋转角度过大,正常血管的位置关系就会失真;颜色抖动如果调得猛,可能把一个真实的出血点给调没了。

我的经验是:水平翻转、垂直翻转、小角度旋转(±10度)、轻微缩放(0.9~1.1)就够用,颜色类增强幅度宁小勿大。用albumentations库实现很方便:

import albumentations as A from albumentations.pytorch import ToTensorV2 train_transform = A.Compose([ A.RandomResizedCrop(224, 224, scale=(0.9, 1.0)), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.Rotate(limit=10), A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2() ])

3.5 训练监控与模型保存

训练过程建议在Notebook里记录每个epoch的loss和验证集指标。可以在Notebook里手动画损失曲线,也可以用wandb,但毕设场景下用matplotlib就够了,因为你要在文档里贴图,自画的图更可控。

这里有一个特别重要的习惯:模型不要只保存最后一个epoch。正确做法是每个epoch结束后在验证集上评估一次,如果当前指标超过历史最好,就保存这一份权重。这样即使后几个epoch过拟合了,你手里也有一份最优的模型文件。训练代码里建议加类似这样的逻辑:

if val_score > best_score: best_score = val_score torch.save(model.state_dict(), 'weights/best_model.pt')

4. 评估与结果分析:答辩时站得住脚的三个指标

4.1 指标选择:准确率只是起点,Kappa才是关键

这个任务的评估指标很有讲究。如果只看Accuracy,你会看到一个假象:因为0级、1级样本加起来占了数据的一半以上,一个只会猜"0或1"的模型都能刷到六七十的准确率。所以毕设里一定要用以下三个指标一起说:

  • Accuracy:总体分类正确的比例,直观但脆弱,受类别分布影响很大。
  • Macro F1-Score:对每个类别分别计算F1再取平均,类别不均衡时比Accuracy可靠。
  • Quadratic Weighted Kappa(QWK):医学诊断领域最重要的指标。它把"预测错一级"和"预测错三级"分开对待,错得越多,惩罚越重。

QWK值得多说几句。DR分级本身是有序的,0级到4级的严重程度是递增的。一个把2级错判成3级的模型,比把0级错判成4级的模型要"靠谱"不少。但Accuracy对这些错误一视同仁,只有QWK能体现出这种有序关系。这也是当年Kaggle比赛官方排名用的指标。你在答辩里写一句"本实验采用与官方评测一致的Quadratic Weighted Kappa作为主要指标",评委一眼就知道你做过功课。

计算起来也简单:

from sklearn.metrics import cohen_kappa_score kappa = cohen_kappa_score(y_true, y_pred, weights='quadratic')

4.2 混淆矩阵与错误案例复盘

混淆矩阵一定要画。它比任何指标都能直观说明模型错在哪里。画法很简单:

import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix cm = confusion_matrix(y_true, y_pred, labels=[0, 1, 2, 3, 4]) plt.figure(figsize=(6, 5)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=[0, 1, 2, 3, 4], yticklabels=[0, 1, 2, 3, 4]) plt.xlabel('Predicted') plt.ylabel('True') plt.title('Confusion Matrix') plt.show()

典型的结果是:0级和1级容易互相混淆,2级容易被分到1级或3级,3级和4级之间也有一定交叉。原因很直观:0级和1级的关键区分点是微动脉瘤等小病灶,在224×224分辨率下细节容易丢失;2级本身是个"中间地带",视觉特征不够鲜明。画出矩阵之后,你会对"模型为什么出错"有一个直观认识,而不是对着准确率数字瞎猜。把这些观察写进毕设文档的"结果分析"章节,整个项目立刻就有了真实的临床感。

4.3 做一个演示级的推理 Demo

建议在Notebook里写一个推理demo cell,随机挑几张测试集图片,把原图、真实等级、预测等级、各类别预测概率打印出来。这个步骤有两个作用:一是你自己能直观判断模型的预测是否符合常理,二是答辩时直接运行给老师看,效果远好于放几张静态截图。

import torch from PIL import Image model.eval() with torch.no_grad(): img = preprocess_fundus('sample.jpg') # 返回 224x224x3 的 ndarray tensor = val_transform(image=img)['image'].unsqueeze(0).to(device) probs = torch.softmax(model(tensor), dim=1).squeeze().cpu().numpy() pred_class = int(probs.argmax()) print(f"预测等级: {pred_class}, 置信度: {probs[pred_class]:.3f}") print("各类别概率:", [round(p, 3) for p in probs])

如果模型把一张1级图预测成了2级,你可以分析:是不是图像拍摄角度偏了?是不是白内障影响了成像清晰度?这些观察虽然不能直接改进模型,但会让你在答辩时面对"这个错误怎么解释"这类问题时不慌。

5. 把 Jupyter 项目组织成能答辩的样子:文件结构与文档说明

5.1 Notebook 与 Python 脚本的分工

如果从头到尾所有代码都在一个巨大的Notebook里跑,实验过程中当然没问题,但答辩前翻阅和复现会非常痛苦。我建议把项目拆成两层:Notebook负责"看得见的演示",Python脚本负责"可复用的逻辑"。

一个比较合理的项目结构长这样:

DR_Project/ ├── data/ │ ├── train_images/ # 原始眼底图 │ └── train.csv # 图片名与label ├── src/ │ ├── __init__.py │ ├── dataset.py # 数据集类与数据增强 │ ├── model.py # 模型构建 │ ├── train.py # 训练主流程 │ └── evaluate.py # 评估与可视化 ├── notebooks/ │ ├── 01_EDA.ipynb # 数据探索与可视化 │ ├── 02_DataPrep.ipynb # 预处理流程演示 │ ├── 03_Training.ipynb # 训练记录与曲线 │ └── 04_Inference_Demo.ipynb # 推理演示与结果分析 ├── weights/ │ └── best_model.pt ├── figures/ # 论文/答辩用图 ├── README.md └── requirements.txt

这样组织有几个好处。一是Notebook里可以import src模块,让Notebook看起来不像"写代码",而像"用代码做研究";二是提交压缩包时,老师

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 11:16:18

从数据复制到地址地理增强,读懂 SAP HANA Cloud 的数据集成能力

很多 SAP 项目做到数据平台这一层时,都会碰到一个很现实的问题。业务数据并不只存在于一个数据库里。订单可能来自 SAP S/4HANA,客户主数据可能来自另一套业务系统,历史数据可能留在本地 SAP HANA,分析团队又可能把部分数据放在 Databricks。真正进入分析阶段以后,我们面对…

作者头像 李华
网站建设 2026/9/2 1:23:45

Python微服务实战:从单体拆分、通信到注册发现

这次我们来看一条从单体应用到微服务的 Python 实战路径。微服务这个概念已经被讨论了很多年,但你真正动手去改一个项目时就会发现,难点不在“把代码拆成几个仓库”,而在三个问题:按什么边界拆、拆完之后服务之间怎么通信、服务变…

作者头像 李华
网站建设 2026/8/31 3:54:57

DFT矩阵整数分解逼近:从理论到硬件实现的优化策略

1. 从一道赛题看工程与理论的交汇点 去年带学生备赛,看到这道关于DFT类矩阵整数分解逼近的题目时,我第一反应是:这题出得真“刁钻”,也真“实在”。它不像很多纯理论推导题那样飘在天上,而是把一个信号处理、通信工程里…

作者头像 李华
网站建设 2026/9/2 18:06:28

AI搜索体验与成本如何平衡?细粒度努力程度选择器技术解析

这次的话题不是又一个本地推理模型,而是 Perplexity 在 AI 搜索里做的一个细节功能:开发新的粒度“努力程度选择器”。所谓“努力程度”,通俗讲就是让系统在回答一个问题之前愿意花多少计算量——是先给一个快速答案,还是先拆解问…

作者头像 李华
网站建设 2026/9/2 0:12:58

数据中心的自动化运维之路

十几年间, 自动化运维一直被反复提及, 然而始终未见质的显著提升, 数据中心的运维工作不但未因自动化运维得到缓解,反而变得极其繁重且异常复杂, 这与数据中心近些年来发生的巨大改变密切连在一起, 因为数据中心所承载各类应用日益增多, 所以相应的运维工作难以处理…

作者头像 李华
网站建设 2026/9/11 2:40:03

人工智能为什么用python

对于人工智能在实现进程里, 需运用数量庞大的算法来加以进行数据分析活动以及模型构建工作, 而语言具备简洁易学、易于阅读、易于书写、具有很强可扩展性等优点, 从而成为人工智能开发当中极为流行的编程语言种类中的一种。那么, 人工智能为何要使用呢? 本文将要从人工智能开发…

作者头像 李华