首页 / 资讯中心 / 文章详情

基于CNN的糖尿病视网膜病变检测:Matlab深度学习实战

基于CNN的糖尿病视网膜病变检测:Matlab深度学习实战 ★ FEATURED ARTICLE
先聊聊这个项目标题本身。它涉及的“糖尿病视网膜病变检测”不是我第一次接触的方向。在医院场景里眼科医生每天要面对大量糖尿病患者的眼底照片一张一张盯着微动脉瘤、出血点、渗出物看时间长了眼睛酸涩漏诊风险就上来了。而标题里的CNN——卷积神经网络恰恰擅长做“看图说话”这件事。这篇文章我按照“任务拆解 → 数据准备 → 网络设计 → Matlab落地 → 避坑经验”的顺序来写适合正在做医学图像方向课题、或者想用Matlab快速搭一个深度学习原型验证效果的读者。我会把代码骨架和关键参数的选取逻辑都摆出来尽量让新手能直接照着改。1. 我先把这个“检测”拆开看任务边界与CNN为什么能顶上来1.1 DR分级到底在分什么视网膜病变检测不是一个简单的“有病/没病”二分类问题。临床上更常用的是按严重程度分五级分级名称眼底主要表征0无可见病变无明显异常1轻度非增殖期少量微动脉瘤2中度非增殖期出血点、硬性渗出物增多3重度非增殖期广泛出血、静脉串珠、棉绒斑4增殖期新生血管形成有失明风险建模的时候你可以把这个问题做成五分类也可以降维成“是否需要转诊”的二分类——比如0/1级不需要紧急处理2级以上要尽快找医生。你所用的损失函数、评价指标、网络输出层设计都会因为任务定义不同而完全不一样。但也正是这种粒度差异给CNN提供了发挥空间微动脉瘤可能只有几个像素大小对比度又低靠人眼逐张看很容易漏而CNN的特征提取天然是从局部小区域开始累积小病灶特征在浅层卷积极容易被捕捉再通过深层网络组合成“这片区域像增殖期病变”的高层语义。这个“局部细节到全局判断”的抽象过程恰好对应了DR筛查的核心需求。1.2 为什么传统图像方法做不到、CNN行早年间做视网膜病变检测主流方案是手工设计特征比如提取血管分叉点、计算渗出物面积、做形态学开闭运算然后把特征喂给SVM或随机森林。这套路线不是不能用但泛化能力很有限。不同眼底相机的光照条件不同拍摄角度不同患者的视网膜色素背景也千差万别手工特征在某些数据集上表现不错换一个来源的数据就直接掉点。CNN不需要你手动定义“什么是出血点”。你给它成千上万张标注好的图像它自己会从数据中学习出一组滤波器——有些滤波器可能响应微动脉瘤边缘有些响应血管走向有些响应渗出物的纹理。训练的本质就是让这些滤波器组合成一种最优的映射关系。具体到视网膜影像上CNN还有一个隐含优势它的卷积核具备平移等变性。这意味着同一个出血点出现在眼底图的左上角还是右下角网络都能用同一组参数识别出来不需要你手动截取ROI区域。对尺寸差异明显、病灶位置随机的视网膜图来说这个特性非常省心。2. 数据和预处理这一关决定你后面所有训练值不值2.1 公开数据集要怎么选、怎么用做DR检测目前比较常用的公开资源有Kaggle上的APTOS 2019竞赛数据、EyePACS的筛查数据、还有IDRiD等带病灶位置标注的数据集。如果你只需要图像级分类标签APTOS那类数据用起来最直接每张图对应一个0到4的等级标签。我习惯的做法是先把数据按“患者”维度划分而不是按“图像”维度划分。就是说同一个人的左右眼照片不能一张在训练集、一张在测试集否则模型可能记住的是这个人眼睛的纹理而不是病变特征测试指标虚高。这一点很多人忽略但在医学影像任务里属于基本纪律。数据划分比例参考训练集70%验证集15%测试集15%。如果数据总量比较少我建议至少确保测试集里每个类别都有一定数量样本。有些等级比如4级增殖期本身数量就少硬分很容易让测试集某一个类只有三张图评估时稍微抖动一下准确率都比方差还大。2.2 预处理做哪几步、每步为了什么视网膜影像的预处理一般按下面几步走每一步都有明确目的尺寸统一输入网络前把所有图像缩放成固定尺寸常见的有224×224、256×256。如果你用的是预训练网络必须对齐它要求的输入尺寸否则第一层全连接维数就对不上。自己从零设计网络时尺寸也不用太大DR病灶虽然细节重要但224×224经过数据增强后已经足够学到可分辨特征硬拉高分辨率只会增大显存压力。通道选择眼底图里绿色通道对出血点、微动脉瘤的对比度最好。红色通道过曝蓝色通道噪声大。有些预处理会把G通道单独拿出来作为灰度图用可以减少计算量但这样做会丢失颜色信息——硬性渗出物和黄斑区在RGB结合下更分明。我的习惯是保留三通道但会做一个加权调整这个可以根据实验数据决定没有统一答案。CLAHE增强对比度受限的自适应直方图均衡化是眼底图预处理里非常实用的一步。普通直方图均衡化会把背景噪声一起放大而CLAHE限制了对比度拉伸幅度可以针对性增强血管和病灶细节。Matlab里一条adapthisteq函数就能搞定操作上建议对LAB颜色空间的L通道做或者只对绿色通道做别对整个RGB三通道无脑操作否则颜色容易失真。归一化像素值转成单精度后除以255缩放到[0,1]用预训练网络时还要做均值方差归一化尽量让输入分布和该网络预训练时的分布一致。下面是几张图的预处理简单示意代码级操作后面实操部分会给全。理解层面你只需要记住预处理的本质是把“人类看起来清楚”的图像变成“网络容易学”的张量。2.3 数据增强和类别不均衡我踩过的两件事数据增强在医学影像里要谨慎做但必须做。旋转、水平翻转、垂直翻转、小范围缩放和随机裁剪都是常规操作。要注意这些增强只能用在训练集绝不是验证集和测试集否则你评估出来的“好结果”只是证明了模型对增强手段的适应性强而不是诊断能力强。我就见过一个半成品项目把增强加到了所有数据上验证时模型表现波动剧烈排查了半天才发现错误出在数据管线。类别不均衡是DR数据里绕不开的坎。0级样本可能占了一半还多4级样本只有寥寥几十张。第一种思路是过采样对少数类样本重复抽取让每个batch里各类别比例大致均衡。第二种思路是损失加权让少数类的分类错误在损失函数中占更高权重。Matlab自带的classificationLayer没有直接暴露类别权重参数所以我更推荐简单的过采样方案——先把图像路径列表按类别列出来然后对数量少的类别重复复制再统一打乱。实用不花哨而且解决90%的问题。3. 网络结构怎么搭从零训练还是迁移学习3.1 轻量自定义网络的一个可参考骨架如果项目时间充裕、或者你想把CNN的每个组件讲透那自己搭建一个轻量网络完全可行。数据量不大时比如几千张图从零训练一个深度很深的网络很容易过拟合。我的建议是先搭一个相对浅的结构验证从预处理到训练评估的闭环跑通了再逐层加深。这里给出一个简单、可跑的Matlab网络骨架layers [ imageInputLayer([224 224 3], Name, input) convolution2dLayer(3, 16, Padding, same, Name, conv1) batchNormalizationLayer(Name, bn1) reluLayer(Name, relu1) maxPooling2dLayer(2, Stride, 2, Name, pool1) convolution2dLayer(3, 32, Padding, same, Name, conv2) batchNormalizationLayer(Name, bn2) reluLayer(Name, relu2) maxPooling2dLayer(2, Stride, 2, Name, pool2) convolution2dLayer(3, 64, Padding, same, Name, conv3) batchNormalizationLayer(Name, bn3) reluLayer(Name, relu3) maxPooling2dLayer(2, Stride, 2, Name, pool3) fullyConnectedLayer(512, Name, fc1) reluLayer(Name, relu4) dropoutLayer(0.5, Name, dropout1) fullyConnectedLayer(5, Name, fc2) softmaxLayer(Name, softmax) classificationLayer(Name, output) ];逐层说下为什么这样设计convolution2dLayer用3×3小卷积核堆叠多层。小核参数量少但感受野可以通过堆叠扩大而且非线性更强。每个卷积后面接batchNormalizationLayer很有必要。医学图像的亮度分布不均匀batchnorm会约束中间层激活值的分布训练更容易收敛也变相起到正则作用。三层下采样后特征图为28×28到14×14再到7×7信息逐步抽象。到全连接前特征数量已经压缩到64通道接512维全连接足够表达。dropoutLayer放在全连接前是防止最后一层全连接记住训练集。0.5是惯例但对于小数据集可以适当调低到0.4避免信息过少学不动。输出层对应5个类别。如果按二分类转诊与否来设计把最后fullyConnectedLayer(5)改成2标签改一下就行。3.2 迁移学习方案及选型逻辑数据量超过一两万张且有GPU支持时用预训练模型收益通常更高。眼底图像虽然和自然图像长得不像但预训练网络底层的边缘、纹理、角点滤波器是通用的尤其是视网膜图像里的血管边缘本质上也是边缘特征。所以冻结前几层卷积权重只重新训练后面几层加分类头是一种经济且稳定的方案。具体来说在Matlab里可以用resnet50或googlenet加载预训练权重把最后一层全连接替换成适合DR任务的输出层——5类或2类然后把全网络的学习率设为原来的一部分只让新增层用正常学习率训练或者干脆把所有卷积层冻结只训全连接头。前一种叫“微调”适应性强后一种叫“特征提取”速度快但表达能力受限于原网络特征。病灶比较细微时微调效果更明显。选型建议情况推荐方案数据量少5000张预训练模型冻结卷积层只训分类头数据量中等5000~20000张预训练模型全体微调降低初始学习率数据量大且想发论文从零设计网络加合适的正则与增强3.3 训练参数和验证策略的设定训练参数没有绝对标准但有几组常用的起手值。优化器我推荐adam它自带自适应学习率对医学图像的复杂损失面更稳。初始学习率从1e-3开始如果损失在训练过程中震荡剧烈改成5e-4或1e-4。MiniBatchSize要综合考虑显存输入224×224、batch size 32在6G显存显卡上基本能跑再大就多出显存溢出错误。训练轮次不要死磕一个数值。我一般把MaxEpochs设为30然后配合验证集损失做早停——如果连续若干个迭代验证集损失不降反升就停止训练。你可以在trainingOptions里设置输出函数保存验证集最优的模型权重或者手动每轮记录指标。Matlab里设置验证数据的写法是options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MiniBatchSize, 32, ... MaxEpochs, 30, ... ValidationData, augValidImds, ... ValidationFrequency, 30, ... Shuffle, every-epoch, ... Plots, training-progress, ... Verbose, true);注意验证集不需要扩展但也要经过尺寸调整和归一化否则输入尺寸不一致会直接报错。4. Matlab实操完整工作流与关键代码4.1 从imageDatastore到训练完成主流程代码Matlab里做深度学习核心是三步用imageDatastore加载图像和标签、用augmentedImageDatastore做增强、用trainNetwork训练。先看清楚数据目录结构。假设你有如下文件组织data/ 0/ 1/ 2/ 3/ 4/每个子文件夹名称就是对应类别标签。可以用下面代码直接加载imds imageDatastore(fullfile(data), ... IncludeSubfolders, true, ... LabelSource, foldernames); % 划分训练集和测试集 [idxTrain, idxTest] splitEachLabel(imds, 0.8, randomized); trainImds subset(imds, idxTrain); testImds subset(imds, idxTest);splitEachLabel是按每个类别分别取80%尽量保住各个类别的比例这比不分类别直接随机划分稳妥得多。接着做增强和输入封装。augmentedImageDatastore可以同时完成尺寸调整和随机增强比手动一个图一个图处理高效得多inputSize [224 224 3]; augTrain augmentedImageDatastore(inputSize, trainImds, ... ColorPreprocessing, gray2rgb, ... % 若图像是灰度转成RGB三通道 DataAugmentation, augmentation);这里augmentation是imageDataAugmenter对象定义常用的旋转平移翻转augmentation imageDataAugmenter( ... RandRotation, [-30 30], ... RandXTranslation, [-10 10], ... RandYTranslation, [-10 10], ... RandXScale, [0.9 1.1], ... RandYScale, [0.9 1.1], ... RandXReflection, true);训练完成后用classify得到预测标签再用confusionchart看结果predLabels classify(net, augTest); testLabels testImds.Labels; cm confusionchart(testLabels, predLabels); cm.Title DR 5-class Confusion Matrix;4.2 自定义网络层定义——如果你想从头训这个部分如果你直接用了迁移学习方案可以跳过去。但自己搭网络对理解误差反向传播、特征图变化非常有帮助。代码骨架我已经在3.1节给出了完整放进脚本时还需要做一步——检查标签顺序和类别数。如果你的数据文件夹只有0到3四类别忘了把最后一层的5改成4否则训练会报错。我建议第一次跑通时在全连接层前加一个dropoutLayer并把验证集监控打开。训练过程中观察损失曲线如果出现断崖式上升先查是不是图像里有非图像文件——比如文件夹里混入了一张TIF或PNG的小图标imageDatastore分不清会把它们也当成有效数据读进来。4.3 评估环节混淆矩阵与ROC一个不能少混淆矩阵能直接告诉你哪些类别容易被混淆。对DR任务最常见的混淆存在于1级和2级之间——轻度非增殖期和中度非增殖期从图像上看也就是微动脉瘤数量的多与少边界本身模糊混淆太正常了。我见过不少同学只看整体准确率混淆矩阵一拉出来发现4级增殖期几乎全被分成了3级这就是没认真看矩阵的代价。对于二分类版本还需要画ROC曲线并计算AUC。Matlab里简单实现% 二分类时输出层前可以用sigmoid或者直接用softmax两分类 [~, scores] predict(net, augTest); [rocObj, tpr, fpr, auc] perfcurve(testLabels, scores(:, 2), positiveClass); plot(fpr, tpr, LineWidth, 2); xlabel(False Positive Rate); ylabel(True Positive Rate); title(sprintf(ROC, AUC %.3f, auc));对筛查工具来说AUC一般要超过0.9才有实际辅助诊断价值。如果你的模型AUC在0.85徘徊排查重点是类别不平衡和数据质量而不是无脑加深网络。5. 训练中反复遇到的坑我帮你提前排掉5.1 “准确率挺高但敏感度很低”指标解读误区有次一个朋友跑实验模型准确率达到了91%兴奋得不行。我让他把混淆矩阵发过来结果4级增殖期召回率只有35%。问题出在哪数据里0级和1级占了绝大多数模型把所有图像都往多数类预测准确率照样很好看。这个现象在医学任务里极其致命——漏诊一个增殖期患者比错判十个正常人严重得多。所以在DR检测里我一直建议重点关注这几个指标召回率尤其对2级以上、特异度、还有F1值。Matlab里可以通过混淆矩阵直接计算这些值。如果你想用单个指标作为模型选择标准我宁可看加权F1也不看准确率。5.2 数据量小、过拟合后怎么办GT硬跑小数据集的典型症状训练损失掉到接近0验证损失不降反升。对策不外乎以下几点加强数据增强强度比如增加随机弹性形变模拟眼底图像的弯曲失真。把预训练模型作为初始化权重然后用小学习率微调。这比从零训练稳健得多。早停不要把训练轮次拉太长。再加一层dropout或者在全连接层之间加权重衰减。我最推荐的操作是一旦发现过拟合首先把MaxEpochs砍半然后调低学习率。很多情况下你训练到第40轮开始过拟合不代表第15轮的权重不可用。与其堆更多正则不如及时保存最优验证模型。5.3 显存不够、训练太慢的现实问题Matlab跑深度学习确实比Python生态更吃显存这是框架特性决定的。如果你只有一块4G显存的卡224×224输入、batch size 64大概率会直接OOM。我的经验是把batch size降到16同时把网络里第一个卷积层的通道数从32改成16训练时间能明显降下来精度损失可控。还有一个冷门但高频的问题——训练时CPU的imageDatastore读取变成瓶颈。特别是图像放在机械硬盘上、文件数量多时每次epoch都在不停地读图速度被拖得很慢。解决思路把整个数据集放到固态硬盘上或者在Matlab里用tall数组做后台数据预处理让增强在GPU训练的同时并行完成。如果条件允许先把所有图像一次性读入内存缓存再交给augmentedImageDatastore去处理能省掉大量IO等待时间。结合我自己的经验这类医学图像任务真正卡人的地方不是模型结构而是数据管线和评估视角。CNN是核心但只是整个系统的一个环节。你花时间把数据清洗做扎实、把评估指标分析清楚比换一个更深的网络对结果的影响大得多。如果你正打算用Matlab做DR检测建议先把imageDatastore和trainingOptions这两个函数吃透后面的一切都顺了。
阅读完成 · 觉得有帮助?
咨询建站