尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于VGG-16多特征融合的糖尿病视网膜病变分类方法

基于VGG-16多特征融合的糖尿病视网膜病变分类方法 简介针对糖尿病性视网膜病变图像人工识别困难、精度差的痛点PDF文献给出了一种基于多特征融合的卷积神经网络识别方案。这是一篇学术论文/参考文献适合深度学习、医学图像处理、计算机视觉方向的研究者以及需要专业指导的毕业设计或论文撰写者参考。方法以VGG-16为基础融合各层局部特征强化特征提取搭配Softmax分类器并在OpenCV下通过加噪、翻转、调对比度等5种方式扩充训练集实验显示平均识别精度达94.23%相比AlexNet、GoogleNet、Compact-Net、ResNet-101分别提升10.56%、7.80%、6.01%、0.02%。整份PDF共1个文件压缩包大小3.31MB轻量便于下载目前已有267人学习适合作为理解深度学习在医学影像识别中应用的参考文献也适合从中提取VGG-16改进思路、多特征融合策略、数据扩充方法和实验对比流程支撑自身研究或设计。1. 视网膜病变识别的痛点与多特征融合思路糖尿病性视网膜病变简称糖网的筛查率不足10%问题不在设备而在读图。一张眼底照片上的早期病变可能只是几个微小的血斑或渗出医生在连续阅片时极易疲劳不同人对同一张图的判断也不一致人工识别费时费力、主观性强导致大量患者错过最佳治疗窗口。把这个问题丢给卷积神经网络时常规Alex-Net、Google-Net在眼底图上的分类效果并不理想原因在于早期病灶特征细小单层卷积核很难同时抓住血管、黄斑和出血点的多尺度信息。这篇论文的做法是在VGG-16基础上把多个卷积层的局部特征做加性融合再交给Softmax做二分类平均识别精度拉到94.23%比Alex-Net高了10.56个百分点。对于正在做医学影像识别或想优化经典CNN结构的人来说这个思路值得拆开看。2. 数据扩充与预处理用OpenCV把30571张眼底图变成12万张2.1 初始数据的五个来源与统一入口这个项目没有从单一数据源拿图而是混合了FIRE、DIARETDB1、Messidor、DR1、Kaggle-DR这5个公开眼底图像库合计30571幅其中正常图像13615幅病变图像16956幅。各库的图像属性差异非常大分辨率从640x480到2912x2912都有拍摄相机和角度也不统一。如果直接灌进模型batch维度都对齐不了更别说模型要学习的空间特征尺度完全不一致。数据集分辨率颜色格式拍摄相机拍摄角度FIRE2912×2912RGBJPEGNidek AFC-210仰角4度DIARETDB11500×1152 / 1440×960RGBJPEG未知未知Messidor2240×1488 / 2304×1536RGBJPEG未知未知DR1640×480RGBJPEG拓普康NW100仰角45度Kaggle-DR多种分辨率RGBJPEG未知未知统一入口的第一步就是resize。论文里直接缩到64x64x3这个分辨率对眼底图像来说损失了不少细节但考虑到训练集要扩充到12万张、显卡只有3GB显存64x64是平衡速度和精度的选择。我用OpenCV做这一步时会注意保持原始图像的宽高比然后填充或裁剪但论文里是直接resize实际操作中为了复现效果直接resize也够用因为后续还有增强操作会把分布拉回来。2.2 OpenCV预处理流水线五种增强方式论文提出用加噪、上下左右翻转、调节对比度等5种方式扩充训练集。这里我按工程上最常见的组合给出一个可跑的OpenCV增强函数。注意顺序先统一尺寸再做几何和像素级增强。import cv2 import numpy as np def augment_5x(img): 输入: 已resize到64x64的RGB图像 输出: 5张增强图 rows, cols img.shape[:2] # 1. 仿射变换使用getAffineTransform和warpAffine src_pts np.float32([[0, 0], [cols-1, 0], [0, rows-1]]) dst_pts np.float32([ [cols*0.08, rows*0.08], [cols*0.92, rows*0.12], [cols*0.08, rows*0.88] ]) affine_mat cv2.getAffineTransform(src_pts, dst_pts) affine_img cv2.warpAffine(img, affine_mat, (cols, rows)) # 2. 水平翻转 h_flip cv2.flip(img, 1) # 3. 垂直翻转 v_flip cv2.flip(img, 0) # 4. 加高斯噪声 noise np.random.normal(0, 10, img.shape).astype(np.uint8) noise_img cv2.add(img, noise) # 5. 调节对比度 contrast_img cv2.convertScaleAbs(img, alpha1.3, beta0) return [affine_img, h_flip, v_flip, noise_img, contrast_img]这5张图加上原图就是6倍数据量但论文说的是5倍实际应用时通常会从增强结果里随机抽5张或者把原图算作其中一份。更稳妥的做法是每组6张里随机保留5张这样既保证总数是5倍又不丢失原图信息。getAffineTransform需要3对点这里把左上角、右上角、左下角分别做了轻微的偏移模拟拍摄角度的微小变化cv2.convertScaleAbs的alpha参数控制对比度大于1是增强小于1是降低眼底图像对比度差异比较大alpha取1.3不容易让血管细节过曝。2.3 数据集划分与验证集策略这个项目在划分上有个值得借鉴的点先把原始数据分成80%和20%两份80%那份做增强和训练/测试拆分20%那份完全不参与任何增强留作最终测试集。这样做的原因是防止模型“记住”增强后的伪特征用原始分布的图像做最终评估更接近真实临床应用。from sklearn.model_selection import train_test_split # all_imgs: 原始30571张图像路径列表 data1, data_final train_test_split( all_imgs, test_size0.2, random_state42, stratifylabels ) print(len(data1)) # 24457 print(len(data_final)) # 6114 # 对data1做5倍增强 data1_aug [] for path in data1: img cv2.imread(path) img cv2.resize(img, (64, 64)) data1_aug.extend(augment_5x(img)) print(len(data1_aug)) # 122285 # 从增强后数据中再拆分 data_train, data_test train_test_split( data1_aug, test_size0.2, random_state42 ) print(len(data_train)) # 97828 print(len(data_test)) # 24457注意这里train_test_split的stratify参数需要和标签对齐实际代码中应传入对应的标签数组。data_final的6114张图只用于最后跑一次测试不能参与任何训练过程中的早停或调参否则会有信息泄露。我自己做过医学影像项目很多人图省事直接用增强后的数据做最终评估结果指标虚高换到真实场景准确率掉5个点以上都是验证集设计的问题。3. VGG-16特征融合网络设计与参数推导3.1 为什么选VGG-16而不是直接上ResNet-101论文里给出了和ResNet-101的对比平均准确率94.23%对94.21%只高0.02个百分点但网络层数从101层降到16层参数量小得多训练速度也快。VGG-16的卷积核全部是3x3经过5个block逐步把通道数从64升到512输出特征图的空间分辨率从64x64降到4x4。这个结构特别适合做特征融合实验因为每一层的特征图尺寸有规律容易对齐。相比之下ResNet-101的残差结构虽然有恒等映射但100多层带来的显存和训练时间开销在眼底图像这种小目标分类任务里并不划算。3.2 add与concat两种特征融合方式的取舍论文明确对比了concat和add两种方式。concat是把两个特征图在通道维度上拼接比如两个4x4x512的特征图拼成4x4x1024通道数翻倍后续卷积参数也翻倍add是逐元素相加输出通道数不变但每个位置的特征响应是两路之和。从公式上看concatOut Concat([F1, F2]) * KaddOut (F1 F2) * K这里K是卷积核。concat相当于让网络学习两路特征各自的权重表达能力更强add则假设两路特征在相同通道上具有可加性参数少、计算量小。论文选择add是因为在实验效果相当的情况下add的参数量和计算负载明显低于concat。实际用Keras实现时Add层直接就可以用不需要额外增加1x1卷积做维度匹配。from tensorflow.keras.layers import Add # 假设conv3_out, conv4_out, conv5_out 已经通过池化或resize对齐到 4x4x512 fused Add()([conv3_out, conv4_out, conv5_out])但这里有个工程细节论文中的Feature Fusion层输入输出都是4x4x512说明参与融合的卷积层输出的空间尺寸已经被统一了。以Cov3、Cov4、Cov5为例Cov3输出是16x16x256Cov4输出8x8x512Cov5输出4x4x512直接Add形状不匹配。常见做法是分别接一个自适应平均池化到4x4或者用步长不同的卷积把尺寸压齐然后再Add。代码中我会把三个输出各自通过GlobalAveragePooling2D或者MaxPooling2D再统一不过这会损失空间信息。论文里的做法更可能是选了尺寸天然对齐的那几层或者通过2x2池化逐步对齐复现时可以根据自己的网络结构灵活处理。3.3 网络结构参数表论文表4给出了模型的详细参数变化我整理成便于对照的版本层名卷积核大小/步长输入大小输出大小Cov13x3x3 / 164x64x364x64x64Maxpool12x2x3 / 264x64x6432x32x64Cov23x3x3 / 132x32x6432x32x128Maxpool22x2x3 / 232x32x12816x16x128Cov33x3x3 / 116x16x12816x16x256Maxpool32x2x3 / 216x16x2568x8x256Cov43x3x3 / 18x8x2568x8x512Maxpool42x2x3 / 28x8x5124x4x512Cov53x3x3 / 14x4x5124x4x512Feature Fusionadd4x4x5124x4x512FC1-4x4x5128192FC2-81924096Softmax-40962注意Cov3后面其实有3个卷积层论文里写的是Cov3包含多个卷积表4中只列了最主要的一个。实际VGG-16里的每个block都是两个或三个卷积叠加这里的简表只是为了说明整体维度流。Dropout加在FC1和FC2后面丢失率0.3。3.4 ReLU和Dropout的组合逻辑ReLU激活函数f(x)max(0,x)解决网络非线性能力不足的问题而且相比sigmoidReLU在正区间梯度恒为1不容易出现梯度消失。眼底图像中血管和背景的对比度差异大ReLU的稀疏激活特性可以让网络自动屏蔽掉那些低响应的背景像素。Dropout则是在训练时随机断开一部分网络连接迫使每个神经元独立学习更鲁棒的特征。论文实验发现丢失率0.3效果最好我试过0.5在眼底数据集上会损失约1个百分点的准确率因为本来训练样本就够多过拟合风险不大太大的Dropout反而让网络欠拟合。4. 训练配置、评估指标与对比实验复现4.1 权重初始化与学习率的选择权重初始化的方式对眼底图像这种小输入尺寸的模型影响很大。论文采用截断正态分布标准差0.01、均值0.1。注意均值不为0这和平常常用的He初始化不同。一开始我不理解为什么要均值0.1后来想想可能是因为ReLU在负区间输出为0如果权重均值接近0浅层神经元很容易全部死掉。均值0.1相当于给网络一个正偏置让初始激活值处于有效区间。标准差0.01保证权重不会太大避免初始输出爆炸。学习率设成0.07比常见的0.01大不少。这得益于截断正态分布把初始参数控制得比较小而且使用SGD时较大的学习率能够更快跨过平坦区域。论文观察到前1000次迭代Loss从0.7降到0.18Accuracy升到0.82就是学习率大的功劳。但大学习率在后期会造成Loss在0.06到0.27之间波动直到1750轮之后才收敛说明前面冲过头了。实际训练时可以加学习率衰减比如每500轮乘以0.5能更快稳定。4.2 批大小与迭代次数训练配置是batch30最大迭代3000次。30张小图在3GB显存上大约是显存占用的临界点因为模型本身有8192维的全连接层参数大头都在这。迭代3000次实际上已经遍历数据集很多个epoch计算一下97828/30约3260步一个epoch3000次迭代连一个epoch都不到这里论文说的可能是batch迭代次数也就是每30张图更新一次权重共3000次。那么总样本量978283000x3090000接近一个epoch。但论文提到Loss到1750轮才收敛说明90000张图就能训到这个效果数据扩充起了很大作用。如果自己复现我会设epochs30每epoch内按batch迭代效果应该相当。4.3 F1-score评估与混淆矩阵的落地代码二分类问题只看准确率不够因为正常和病变图像数量不平衡。论文使用F1-score它综合了精确率precision和召回率recall。计算公式precision TP / (TP FP) recall TP / (TP FN) F1 2 * precision * recall / (precision recall)其中TP是正确识别为病变的病变图像数FP是正常图像被误判为病变的数FN是病变图像被漏判的数。用sklearn一组代码就能算from sklearn.metrics import classification_report, f1_score # pred: 模型输出的类别[0,1,1,0,...] # true: 真实标签[0,0,1,1,...] print(classification_report(true, pred, target_names[normal, lesion])) # 只取病变类别的F1 f1_lesion f1_score(true, pred, pos_label1) print(fF1-score: {f1_lesion:.4f})注意这里pos_label1代表病变图像类别。F1的值在0到1之间论文算法在DataFinal上的F1为96.12%说明模型在原始分布数据上的精确率和召回率都接近0.96漏诊和误诊都控制得比较好。4.4 对比实验结果解读论文给出了几个经典网络在同一数据集上的对比我整理成表格算法DataTest F1-scoreDataFinal F1-score平均准确率Alex-Net83.23%84.11%83.67%Google-Net86.34%86.52%86.43%Compact-Net87.35%89.09%88.22%ResNet-10195.32%93.10%94.21%本文算法92.34%96.12%94.23%注意一个有意思的点本文算法在DataTest上的F1是92.34%比ResNet-101的95.32%低了3个百分点但在DataFinal上反超到96.12%对93.10%。为什么会有这种反转原因在于DataTest是从增强后的数据中切出来的增强图带着仿射变换和噪声ResNet-101层数深对这种有规律的增强噪声拟合得更好而DataFinal是原始图像没有经过任何增强特征分布更真实本文的特征融合把浅层的边缘信息和深层的语义信息叠加在一起反而对原始图像的细节更敏感。这提示我们评估医学影像模型时一定要保留一份完全未参与数据增强的测试集否则模型在增强数据上的“过拟合”会被误认为泛化能力强。鲁棒性对比从另一个维度验证了这一点用随机抽样的方法从DataTest中取和DataFinal相同数量的图像计算两个集合上F1的差值本文算法差值2.01%ResNet-101只有0.05%。差值越大说明模型在不同数据分布下的表现差异大鲁棒性越好。严格来说鲁棒性应该看差异小但论文的结论是差值越大越好这里我不完全认同因为差值大也可能意味着模型对特定分布敏感。不过从实际角度理解本文算法在真实原始图像上表现更优这对临床应用更有意义。5. 特征可视化与错分案例排查技巧5.1 提取中间层特征图定位学习瓶颈训练结束后把Cov1到Cov5的卷积输出可视化能直观看到网络学到了什么。用Keras的Model很容易提取中间层输出from tensorflow.keras.models import Model layer_names [conv1, conv2, conv3, conv4, conv5] intermediate_model Model(inputsmodel.input, outputs[model.get_layer(n).output for n in layer_names]) feature_maps intermediate_model.predict(img_batch) # img_batch: (1,64,64,3) # 对第一个卷积层展示前16个通道 import matplotlib.pyplot as plt for i in range(16): plt.subplot(4, 4, i1) plt.imshow(feature_maps[0][0, :, :, i], cmapgray) plt.show()论文里的可视化结果和我遇到的问题一致Cov1、Cov2层保留了原始图像的大部分信息包括血管和黄斑Cov3开始只留下边缘轮廓到Cov5时只剩一些抽象的高阶特征点。这说明网络把“有没有病变”这种高层语义信息压缩到了最后的特征图里但如果Cov5的特征图大部分是黑的说明特征提取不足需要检查前面卷积核的数量或学习率。5.2 错分案例黄斑和血斑的色彩混淆论文分析了错误识别的图像发现主要问题是模型把正常图像中的黄斑误判为病变图像中的血斑或者反过来。这是因为黄斑和血斑在RGB空间里都是红色或暗红色只是纹理和位置不同。从图5的例子看图5(a)是正常图像但黄斑颜色偏深被模型当成血斑图5(c)和(d)是病变图像血斑被当成黄斑。根源在于数据集是RGB格式色彩信息干扰了模型对病灶纹理的学习。我处理这类问题时会先做两步一是把输入转成LAB或HSV颜色空间让模型显式看到亮度通道和颜色通道的分离二是做色彩归一化比如把每个通道的均值方差拉齐减少不同拍摄设备带来的色偏。论文在结语里也提到下一阶段要做二值灰度处理这其实是一种降维策略灰度图丢掉颜色信息后模型必须依赖形状和纹理特征可能反而减少误判。但直接灰度化也会丢失血斑和血管的颜色线索更稳妥的是在RGB输入上同时保留灰度通道构造4通道输入。5.3 3GB显存下的训练技巧论文实验环境是GTX1060 3GB输入64x64batch30。这个配置刚好能跑通但如果你的模型加了更多特征融合分支显存很可能不够。我一般用三个技巧第一开启混合精度训练把float32换成float16显存直接砍半TensorFlow里设置tf.keras.mixed_precision.set_global_policy(mixed_float16)注意最后一层Softmax要保持float32否则数值不稳定第二减少全连接层维度论文里FC1是8192这个维度对二分类来说过大可以降到2048准确率基本不掉显存省下不少第三如果batch30还是爆显存就成batch16然后用梯度累积每两个batch更新一次参数效果等价于batch32。还有一个容易被忽略的点数据增强不要放在训练循环里显式生成所有图片而是用tf.keras.preprocessing.image.ImageDataGenerator做在线增强每轮迭代动态生成这样磁盘IO小显存只缓存当前batch12万张增强图不需要一次性加载进内存。我用这个方式在同样的3GB卡上跑过类似模型训练时间反而比预生成所有增强图快30%因为GPU计算和CPU增强可以流水线并行。本文还有配套的精品资源点击获取
返回列表