医学影像的识别(recognition)、分割(segmentation)和解析(parsing)是医学影像分析的核心任务。医学影像识别是指识别医学图像中的目标。理论上,目标的识别并不需要对目标进行检测或定位;但是实际上,通常会结合检测和定位去辅助完成目标识别。一旦完成识别,或检测,即得到了目标的最小外包矩形框(bounding box),就可以通过分割的任务寻找目标物体的精确边界。当图像中存在多个目标物体时,对多个目标的分割就变成了语义解析的任务,即对2D图像或3D图像中的像素赋予语义标签(Semantic Labels)。通过将同一目标的像素或体素打上相同的标签,就完成了对该目标的分割。
医学影像识别的目标,是通过解析复杂的语义,和人体解剖基础模型(FMA)进行匹配,将人体结构符号表示为人类可理解的形式,并且机器系统能够导航、解析和解释的。
本文主要是基于CNN的医学影像识别,通过自己搭建的三次网络与经典网络Resnet18进行数据对比与分析,得出结论,并比较二者在四种胃部疾病和正常情况下的医学影像识别结果。
在理解了上述研究背景和概述之后,接下来便要决定如何去探究以及实现医学影像识别的功能了。在实现该功能的过程中,逐步深入了解各个文献资料的内涵,并且掌握一定的数字图像处理和深度学习的能力,同时,也是对自己编程能力、思维能力的训练。
本次课程设计中,通过比较自己构建的网络和经典网络Resnet18,并分别进行横纵向对比,从参数、网络结构以及预测结果来深入理解卷积神经网络与残差神经网络的异同,更加学习这两种网络,也为以后在这方面的科研道路打下坚实基础。
通过此次的学习希望能够熟练掌握如何使用tensorflow与keras库中的一些常用的与神经网络和深度学习有关的函数,并了解一些调整参数的规则和网络搭建的规则,并对深度学习有所掌握与提升,使自己对大数据及人工智能的发展产生了解与想法,并能更好的促进自己的进步。
python3.8、tensorflow+keras、spyder/vscode
stomach(cancer_0、gastric_ulcer_1、gastric_erosion_2、gastric_polyps_3、normal_4、jpg格式)
100-500-1000-2000张(train)
100-200(test/valid)
图1 导入相应的库
(1)其中,对一部分库的解释如下:
PIL(Python Image Library):图像处理库
itertools:迭代器
(2)常用的keras回调函数:
EarlyStopping:当被监测的量不再提升或下降,则停止训练防止过拟合。
ReduceLROnPlateau:当被监测的量不再提升或下降,则降低学习率,当学习停止后,模型总是会受益于降低2-10倍的学习速率。
ModelCheckpoint:训练中保存被监测的量最高或最低的模型。
刚开始我们只选择归一化这一种数据预处理的方式,如下所示,后来在进行图像训练的时候发现效果不是很好,于是进行了改变。
图2 初始的数据预处理
改进如下所示,我们增添了错切变换、随机缩放和随机水平翻转,因为我们要处理的图片,左边有一部分文字会对最后的训练和预测造成一定影响,但是我们并没有选择去剔除文字,而是选择了错切变换这种方式,尽量减少文字的影响。
图3 改进后的数据预处理
在预处理阶段,我学到了如下几个处理数据的知识与方法:
(1)错切变换
图像错切变换在图像几何形变方面非常有用,常见的错切变换分为X方向与Y方向的错切变换。对应的数学矩阵分别如下:
图4 错切变换中X和Y方向的变换矩阵
(2)one-hot编码
One-Hot编码,又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候只有一位有效。
One-Hot编码是分类变量作为二进制向量的表示。这首先要求将分类值映射到整数值。然后,每个整数值被表示为二进制向量,除了整数的索引之外,它都是零值,其它被标记为1。
对标签集进行one-hot编码的原因是我们的训练模型采用categorical_crossentropy作为损失函数,这个函数要求标签集必须采用one-hot编码形式。所以,我们对训练集、验证集和测试集标签均做了编码转换。
(3)均一化
归一化是一种简化计算的方式,即将有量纲的表达式,经过变换,化为无量纲的表达式,成为标量。
数据集先浮点后归一化的目的是提升网络收敛速度,减少训练时间,同时适应值域在(0,1)之间的激活函数,增大区分度。其实归一化有一个特别重要的原因是确保特征值权重一致。举个例子,我们使用mse这样的均方误差函数时,大的特征数值比如(5000-1000)2与小的特征值(3-1)2相加再求平均得到的误差值,显然大值对误差值的影响最大,但大部分情况下,特征值的权重应该是一样的,只是因为单位不同才导致数值相差甚大。因此,我们提前对特征数据做归一化处理,以解决此类问题。
卷积层的功能是对输入数据进行特征提取,其内部包含多个卷积核,组成卷积核的每个元素都对应一个权重系数和一个偏差量,代码如下所示。
图5 卷积层
对于卷积层,除了会调用conv2D这个库函数以外,我们还要思考清楚一下两个问题:
(1)卷积层如何选择卷积核?
应选择小而深的卷积核,单独较小的卷积核也是不好的,只有堆叠很多小的卷积核,模型的性能才会提升。
CNN的卷积核对应一个感受野,这使得每一个神经元不需要对全局图像做感受,每个神经元只感受局部的图像区域,然后在更高层,将这些感受不同局部的神经元综合起来就可以得到全局信息。这样做的一个好处就是可以减少大量训练的参数。
(2)卷积层padding里的same和valid的区别
图6 两种padding——same与valid
激活函数(Activation Function),就是在人工神经网络的神经元上运行的函数,负责将神经元的输入映射到输出端。引入激活函数是为了增加神经网络模型的非线性。
图7 激活函数层
我们采用的是Relu激活函数:在ReLU出现以前,Sigmoid函数和双曲正切函数(hyperbolic tangent)是常用的激励函数 。
Relu函数模型如下:
图8 relu激活函数
对输入的特征图进行压缩,一方面使特征图变小,简化网络计算复杂度;一方面进行特征压缩,提取主要特征。在本次课程设计中,我们采用了极大池化方法。
图9 池化层(最大池化)
极大池化的原理如下图所示:
图10 极大池化原理图
在机器学习的一些模型中,如果模型的参数太多,而训练样本又太少的话,这样训练出来的模型很容易产生过拟合现象。过拟合指的是模型在训练数据上损失函数比较小,预测准确率较高,但是在测试数据上损失函数比较大,预测准确率较低。
Dropout是在标准的bp网络的的结构上,使bp网的隐层激活值,以一定的比例v变为0,即按照一定比例v,随机地让一部分隐层节点失效。使算法使用一个比较大的学习率,来加快学习速度。
Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。
图11 Flatten层
全连接层在整个卷积神经网络中起到”分类器”作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到”将学到的分布式特征表示”映射到样本标记空间的作用。
图12 全连接层
我们通过softmax函数完成最终分类。
softmax可以理解为归一化,如目前图片分类有五种,那经过 softmax 层的输出就是一个五维的向量,向量中的第一个值就是图片属于第一类的概率值,向量中的第二个值就是当前图片属于第二类的概率值…这五维的向量之和为1.
图13 分类层
第一次搭建的网络为8层,其中共有包括2个卷积层、2个激活函数层、1个池化层、1个全连接层、1个Flatten层、1个分类层,网络如下所示:
图14 第一次搭建—8层网络结构
第二次搭建的网络为13层,其中共有包括3个卷积层、3个激活函数层、2个池化层、1个全连接层、2个Dropout层、1个Flatten层、1个分类层,网络如下所示:
图15 第二次搭建—13层网络结构
第三次搭建的网络为17层,其中共有包括4个卷积层、5个激活函数层、1个池化层、2个全连接层、3个Dropout层、1个Flatten层、1个分类层,网络如下所示:
图16 第三次搭建—17层网络结构
图17 三种自己构建的网络参数对比
由上图我们可以看出,13层网络需要训练的参数最少,所以多了一个池化层后参数明显减少,由此可见池化层是参数减少的主要原因。
图18 两种激活函数
图19 两种激活函数的图像
对比之后我们选择了ReLU激活函数,我们发现Relu更容易学习优化,因为其分段线性性质,导致其前传、后传、求导都是分段线性。而传统的sigmoid函数,由于两端饱和,在传播过程中容易丢弃信息,会产生梯度弥散现象,当使用梯度下降法的时候,最初几层的权重变化非常缓慢,以至于它们不能够从样本中进行有效的学习。
为了量化神经网络的拟合效果,我们构造了一个损失函数
图20 自己构造的损失函数
categorical_crossentropy为交叉熵损失函数,交叉熵是用来评估当前训练得到的概率分布与真实分布的差异情况。它刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。
与选择categorical_crossentropy进行对比之后,我们最终选择categorical_crossentropy,原因在于我们自己构造的损失函数在进行绘制loss图,曲线的变化没有categorical_crossentropy迅速,loss的下降区间较小,因此我们选择categorical_crossentropy 。
图21 自己定义的损失函数与categorical_crossentropy的对比
批量梯度下降每次学习都使用整个训练集,因此其优点在于每次更新都会朝着正确的方向进行,最后能够保证收敛于极值点(凸函数收敛于全局极值点,非凸函数可能会收敛于局部极值点),但是其缺点在于每次学习时间过长,并且如果训练集很大以至于需要消耗大量的内存,并且全量梯度下降不能进行在线模型参数更新。
随机梯度下降算法每次只随机选择一个样本来更新模型参数,因此每次的学习是非常快速的,并且可以进行在线更新。最大的缺点在于每次更新可能并不会按照正确的方向进行,因此可以带来优化波动(扰动)。
我们的模型选择的是Adam优化器,自适应学习率梯度下降的一种方法,如下图所示:
图22 编译模型
图23 keras回调函数来训练模型
monitor:监测的值,可以是accuracy,val_loss,val_accuracy。
factor:缩放学习率的值,学习率将以lr = lr*factor的形式被减少。
patience:当patience个epoch过去而模型性能不提升时,学习率减少的动作会被触发。
第一次训练我们选择的数据集是train500+test100+val100,结果如下图所示:
图24 第一次训练结果
从这个结果我们看出,由于第一次构建的8层网络中,没有dropout,所以出现了如图所示的结果,验证集的准确率大大低于训练集,而17层网络相较于13层网络多了两层dropout,也出现了验证集的准确率远远低于训练集的准确率的情况,13层网络中相对于8层网络添加了一层池化层和dropout,在数据集上训练之后训练集的准确率可以达到80%左右,而验证集的准确率可以达到70%左右,因此通过对比之后我们最终选择了13层网络来进行接下来的训练。
第二次训练我们选择的数据集是train1000+test200+val200,这次我们让第一次训练之后选择的13层网络分别迭代30次、50次、100次,分析对比不同迭代次数之下的结果,如下图所示:
图25 第二次训练结果
从这个结果我们可以看出,随着迭代次数的增加,训练集和验证集的准确率都趋于饱和,而这个现象在迭代次数为30次的时候就已经很明显了,所以在接下来的训练过程之中,我们可以把epochs选择为30,去分析训练结果。
第三次训练我们选择的数据集是train2000+test200+val200,这次我们把13层网络和经典网络resnet18同时在epochs为30下进行训练,来分析我们自己构建的网络与经典网络在本次关于医学影像识别的课程设计上的结果,如下图所示:
图26 第三次训练结果
从这个结果我们可以看出,我们自己搭建的网络最后在验证集上的准确率可以达到接近于80%的准确率,而经典网络resnet在验证集上的准确率可以达到85%左右,通过查询相关资料我们发现,深层网络存在着梯度消失或者爆炸的问题,这使得深度学习模型很难训练。
而Resnet的核心思想就是更改了网络结构的学习目的,原本学习的是直接通过卷积得到的图像特征H(X),现在学习的是图像与特征的残差H(X)-X,这样更改的原因是因为残差学习相比原始特征的直接学习更加容易。
评估模型我们选择的是混淆矩阵来进行评估,下面简要叙述一下我们通过查阅相关资料所理解下的混淆矩阵。
混淆矩阵(confusionmatrix)也称误差矩阵,是表示精度评价的一种标准格式,用n行n列的矩阵形式来表示。具体评价指标有总体精度、制图精度、用户精度等,这些精度指标从不同的侧面反映了图像分类的精度。
在人工智能中,混淆矩阵(confusionmatrix)是可视化工具,特别用于监督学习,在无监督学习一般叫做匹配矩阵。在图像精度评价中,主要用于比较分类结果和实际测得值,可以把分类结果的精度显示在一个混淆矩阵里面。混淆矩阵是通过将每个实测像元的位置和分类与分类图像中的相应位置和分类相比较计算的。
本次课程设计最后结果的混淆矩阵如下图所示:
图27 混淆矩阵
由上图中的混淆矩阵我们可以看出,在测试集中所有的癌症图片中,有43%的被预测为癌症,为五类之中最高;在测试集所有的胃溃疡图片中,有57%的被预测为胃息肉,仅有13%的被预测为胃溃疡;在测试集所有的胃息肉图片中,有40%的被预测为正常,仅有13%的被预测为胃息肉;在测试集所有的胃糜烂图片中,有40%的被预测为胃糜烂,为五类之最高;在测试集所有的正常图片中,有48%的被预测为正常,为五类之最高。
以下展示几张预测结果,其中图片上面为预测名称和它对于的概率。
图28 预测结果举例
图29 训练集loss与accuracy
第一次搭建的网络为8层,其中共有包括2个卷积层、2个激活函数层、1个池化层、1个全连接层、1个Flatten层、1个分类层,训练参数为10495909个。
第二次搭建的网络为13层,其中共有包括3个卷积层、3个激活函数层、2个池化层、1个全连接层、、2个Dropout层、1个Flatten层、1个分类层,训练参数为5271525。因为池化层的增加,致使参数数量大量减少,减少了训练所需要的时间。
第三次搭建的网络为17层,其中共有包括4个卷积层、5个激活函数层、1个池化层、2个全连接层、、3个Dropout层、1个Flatten层、1个分类层,训练参数为21000165个。因为池化层的减少加上dropout的增加,致使参数数量大量增加,增加了训练所需要的时间。
图30 自己构建的损失函数
这个损失函数受到吴恩达老师讲解的神经网络与深度学习的启发,所以构造出来了这样一个损失函数,对于这个损失函数来说,假设我们认为预测值和真实值的取值为0或1,当真实值为1时,损失函数化简为,当损失值为0时,刚好预测值也为1;同理,当真实值为0时,损失函数化简为,当损失值为0时,刚好预测值也为0。
通过上述推理我们得出一个结论,我们自己构造的损失函数在理论上可以较好地拟合真实值与预测值之间的关系,使得两个值的变化一致,但是在本次课程设计中,该损失函数的变化区间较分类交叉熵相比较小,因此在实际训练中我们选择的损失函数还是分类交叉熵。
经过此次项目,我们小组基本实现了医学影像识别预测胃部疾病的功能,同时也有自己创新的地方,并且也加深了对数字图像处理、神经网络、深度学习等相关知识的学习。
我们的结论如下:
首先,在搭建神经网络的时候,层数太多或太少都不好,要根据自己的需要和数据集的情况来搭建合适的神经网络,同时也有注意卷积层、激活函数、池化层、dropout等网络层之间的对应关系,尤要注意的是,在神经网络中,上一层输出的维度,一定要和下一层输入的维度对应上,否则搭建出来的神经网络会由于维度不一致而报错,无法进行正常的训练和预测。
其次,原始数据集的左边有部分文字,我们一开始并没有想到把这些文字用裁剪的方式剔除掉,而是采取了错切变换等预处理的方法,尽量减少文字对最终预测结果的影响,可能也正是这部分文字的原因,所以我们最后的准确率并不是很高。
最后,我们的创新点是自己搭建了不同层数的神经网络,并且通过前期的学习,自己构建了一个损失函数,并与分类交叉熵进行对比分析,同时在评估模型的时候我们采用了混淆矩阵来对本次项目最后的结果进行评估,发现基本达到了预期效果,而胃溃疡和胃息肉,可能由于二者的差异比较小,所以在最终识别的结果之中,这两类识别正确的准确率不是很高。
(1)导入包的问题
tensorflow与keras版本不匹配,导致后期代码总是报错,有些库函数也用不了。
(2)数据预处理的方式
从刚开始的均一化,发现处理效果不太好,后来又增加了错切变换和随机水平翻转等预处理的方式。
(3)数据集的划分
大部分图片左边都有一些文字描述,这个是本次课程设计中一个不足的地方,我们在理中是前期把没有文字的少量图片给筛选掉,然后进行训练和预测。
(4)验证集(测试集)准确率高于训练集准确率的原因
①数据集太小,这样会导致数据集切分的时候不均匀,也就是说训练集和测试集的分布不均匀,如果模型能够正确地捕捉到数据内部的分布模式的话,就有可能造成训练集的内部方差大于验证集,会造成训练集的误差更大,这个时候就需要重新划分数据集,使其分布一样。
②模型正则化过多,比如训练时dropout过多,和验证时的模型相差较大,验证时是不会有dropout的。
Dropout能基本上确保测试集的准确性最好,优于训练集的准确性。Dropout迫使神经网络成为一个非常大的弱分类器集合,这就意味着,一个单独的分类器没有太高的分类准确性,只有当把他们串在一起的时候他们才会变得更强大。
而且在训练期间,Dropout将这些分类器的随机集合切掉,因此,训练准确率将受到影响;在测试期间,Dropout将自动关闭,并允许使用神经网络中的所有弱分类器,因此,测试精度提高。
③训练集的准确率是每个batch之后产生的,而验证集的准确率一般是一个epoch后产生的,验证时的模型是训练一个个batch之后的,有一个滞后性,可以说就是用训练得差不多的模型用来验证,当然准确率要高一点。
④训练集的数据做了一系列的预处理,如旋转、仿射、模糊、添加噪点等操作,过多的预处理导致训练集的分布产生了变化,所以使得训练集的准确率低于验证集。
对于这次的医学影像识别的唯一遗憾的是没有把图片左边的文字部分给剔除掉在进行训练和预测,也希望把在本次项目之中学到的知识更加熟练地运用到今后的学习生活之中,而本次项目有创新点的地方在搭的神经网络上和自己构架的损失函数。同时在听了别人的答辩之后也学到了其他的一些经典网络以及更加有效的数据预处理的方法,也是一个一起学习共同进步的过程,在本次课程设计中也增加了自己动手能力以及装软件找错误的能力,希望在接下来的学习里可以让自己做的更好。
最后感谢彭辉老师以及其他同学给予我们小组的帮助。