在isic数据集上进行malignant和nevus的分类尝试

本文为一个使用深度学习经典模型对isic中的恶黑malignant和nevus痣进行分类任务的文章


引言

智能医疗领域是现在一个非常火的领域,使用AI去解决医疗领域的一些痛点现在已经是很多企业和组织正在做或者即将做的一件事情,虽然AI在医疗领域中的落地应用上还不能看到明确的道路,但可以预见各个企业个组织必然会在自己的领域上有更多的成果,有更多的可以落地的应用。


皮肤病领域的痛点

皮肤病是发生在皮肤和皮肤附属器官疾病的总称。皮肤是人体最大的器官,皮肤病的种类不但繁多,多种内脏发生的疾病也可以在皮肤上有表现。严重的皮肤病,如恶黑,很可能致人死亡,误诊的后果会非常严重,症状轻的皮肤病,如痣,也许并不能称之为皮肤病,而是皮损,很多人不选择去治疗。更严肃的是很多致死皮肤病和清症状皮肤病形态学及其相似,如上面提到的恶黑和痣,去有效的识别疾病,对挽救患者的生命起着至关重要的作用。

解决问题的论文及思路

有幸拜读了谷歌发出的一篇XXX论文,论文地址
先说说论文的思路。这篇论文是在一个公开数据集isic上面进行的训练和测试,这个数据集包含1万多张皮肤病图片,其中包含恶黑和痣这两种形态学非常相似,但是后果完全不一样的两个皮肤镜下图片数据集,恶黑是一种可致死的疾病,而痣一般不会有重大影响,但医学上经常会有恶黑和痣的误诊存在,给患者造成了很大的损害,因此这篇论文中使用了深度神经网络的方法进行恶黑和痣的识别,也是打开了一个新视野,造成了很大的影响。

论文使用了经典的神经网络inception_v3,使用在imageNet中已经训练好的参数进行了迁移学习,论文结果对恶黑的皮肤病图片识别也达到了92%左右的准确率,在这之后的另外一篇论文XXX中则使用了一个5层的简单网络结构,分别在皮肤病图片的rgb空间、hsv空间和fft空间进行了训练,得到了三个模型,最终对模型进行了模型融合,对恶黑的预测达到了97%的准确率。


尝试重现论文的结果:

首先,需要搭建一个神经网络,使用当下最流行的tensorflow进行了网络结构的搭建和模型的训练,在tensorflow的slim库中提供了inception_v3的封装好的网络结构,我可以直接使用该网络结构进行训练。如下图,网络结构

然后,下载isic的数据集(PS:数据集网站竟然不提供现成的下载链接,但是提供了可以下载数据的API),然后整理出其中的恶黑和痣的图片,人为的将他们分成了训练集和测试集,训练集数据和测试集数据的比例大致为2:1,训练集的恶黑大约1500张,痣约为6500张,测试集恶黑大约为750张,痣3000张左右,这里主要采取随机分配的方式,并没有刻意去筛选质量好的图片作为训练集。

然后,编写数据读取方法(我并没有将图片转化为tensorflow推荐的tfrecords文件再去训练,而是直接读入图片训练),将训练集数据目录全部加载,打乱后,每次读取batch_size个图片,读取数据时,用图片路径读取图片,将256X的图片resize为224224的大小,并做基本的翻转和左右变换,

然后,编写神经网络训练的训练过程,对fearture maps进行softmax,定义损失函数,选择优化器,编写验证方法

然后,使用已经训练好的inception_v3的模型,进行迁移学习,并在训练中对验证集和测试集进行同步输出结果

最后,学出模型

学习率0.1,batchsize为64,参数初始化采用一定范围内的正态分布

可喜可贺可喜可贺可喜可贺,最终的结果非常好,对恶黑识别的准确率达到了惊人的50%左右,意不意外,惊不惊喜,之后当然就是例行的优化程序,所谓一天写网络,一月调参数就是这样的。

这之间我发现再这个数据集上面进行迁移学习和重头开始学习,区别并不大,故之后的训练都是重头开始训练的.


模型优化:

在调参数之前,首先要知道需要调的参数是什么.可以参考下面链接,学习率、权重初始化方法,加减网络层数,增加正则化都是常见的可以调节的参数。

http://blog.csdn.net/qq_20259...  

1、 首先是学习率

这是可能在调参数过程中第一个应该调的参数,调整学习率可以调大或者调小。

上面网络的初始的学习率为0.1,这对很多数据集的分类来说已经够了,通常来说也不应该有过小的初始化学习率,这通常会导致过拟合,但并不总是这样,何凯明大神一次接受采访时说过,在有些情况下即使学习率达到0.000001,也不会过小。也不应该有过大的学习率,这会导致结果在极值点两边跳动。

分析训练结果,基本可以知道我们的学习率过大了。逐渐的降低学习率并逐步对照,观察训练结果的变化,发现预测的总体精度确实在上升,在学习率达到0.005时测试集整体平均精度达到了最高,为75%左右,再调小会出现测试集精度下降的情况,且训练速度变慢。

这一步的训练结果在测试集上面出现了令人不愉快的表现,召回率非常低,大部分预测都偏向了痣,即恶黑的测试集大部分都预测为了痣,这显然是不对的。

2、 解决数据的问题

  首先,在出现上面的结果后,我查了一些资料,对样本不均衡有了一些新的认识。考虑当前训练出现了样本不均衡,导致训练出现一些问题.参考文章
在分类中如何处理训练集中不平衡问题
深度 | 解决真实世界问题:如何在不平衡类上使用机器学习?

一般推荐从增加数据来源开始,但在此例中,因为数据集来源于公开数据集,并且含有恶黑和痣的分类数据的公开数据集几乎不存在,已不存在增加数据集的可能性。

这时我们考虑使用重采样和降采样

1> 首先降采样,即把痣的训练集减少到和恶黑一样,调整了痣数据的训练集和测试集的比例。继续训练,得到的结果在测试集上中,对恶黑的测试精度得到了大幅提升,但对于负样本痣的精度确下降到合格线以下

2> 然后再采用重采样的方式,将恶黑的图片进行复制,将其扩展到和痣的数量相等。继续训练,得到的结果正好和降采样相反,在测试集上,恶黑精度不高,但总体的精度确实比之前高了,这样的结果同样也是我们不能接受的

上面出现的两个极端情况,再一定程度上来说就是发生了欠拟合和过拟合。减少痣的训练集,导致对痣的训练出现了欠拟合,对恶黑重采样,导致对恶黑的训练出现了过拟合。

那么是否可以通过一定方式生成一些数据呢,考虑原图是256X的尺寸,考虑采用剪裁的方式而不是通过resize的方式去获取图片。对图片进行预处理,对每一幅图片从左上角开始,水平方向每隔1个像素进行一次224高度(数据集中高度不固定,但都小于224)的裁剪,并且对高度垂直方向进行了zero padding,然后对生成的图片进行了水平和垂直方向的随机翻转,生成了新的训练数据集和测试数据集(同样保持训练集中正样本和负样本数量基本相同)。继续训练,情况并没有多少改善,提升也有限,并且依然是痣的精度明显高于恶黑的精度.

这个时候我不对数据集产生了怀疑,是不是因为痣的数据集存在一些过大的噪声干扰呢,于是我将痣的数据集换成脂溢性角化病,即老年斑的数据集进行训练。

isic数据集中也含有1000多例的脂溢性角化病的数据,比恶黑的数据略少,采用上一次的网络进行训练,发现再两个分类上的精度都可以达到98%以上,于是我再返回对痣的数据集进行了审视,发现其中有一半以上的数据中包含不知是用来干什么用的有色圆圈

图片描述图片描述图片描述

我将训练集中把这部分去除掉,用剩下的数据进行训练,把带有有色圆圈的数据先行搁置,对剩余的数据进行训练(恶黑和痣),发现结果有了明显的改善, 在恶黑数据集上的召回率可以达到85%左右,准确率达到89%左右。

但实际上我们并没有达到原论文中的对rgb空间中预测准确率93%的结果,但这个时候训练集的精度已经达到了99%,已经到了无法再继续训练的状况,继续训练就必须考虑提升模型的泛化能力,调整drop率从0.8到0.7、0.6、0.5,没有明显提高,就考虑继续从训练集下手。

测试集精度低于训练集精度,这种现象就是过拟合的现象,增强泛化能力就必须减轻过拟合的影响。考虑到上一步中对原图像每隔1像素进行裁剪获取训练集和测试机的方法是不是和之前的重采样一样,出现了很大程度的过拟合呢?虽然从一张图片中新生成的训练集都不相同,但他们都是从一张原图中裁剪出来的,对生成的训练图像进行训练是不是相当于对原图进行了多次训练呢?

为了验证这个情况,我调整了生成图片的间隔,即把每隔1像素裁剪,变成每隔5个像素再去裁剪一次,通过在这种方式生成的训练数据和测试数据中的实践,在恶黑的表现召回率可以达到88%左右,准确率达到94.5%左右。同时,考虑到我们的测试集是滑动生成的,预测时是根据原测试集生成的数据预测,但实际要进行的预测则是,对原图进行滑动裁剪,计算所有裁剪图片的分类概率分布,统计平均值得出预测结果的方法,那么可以想想最终的预测效果必然会更好(没有验证过,但至少结果不会比当前结果差,因为准确率和召回率都超过了50%)

在以上工作的基础上,继续采用一些常见的优化模型的方法,如修改常规的梯度下降优化器尝试带有动量的梯度下降优化器学习率下降的优化器尝试采用不同的激活函数尝试使用不同的drop率等常用的优化训练过程,再训练和收敛速度上,最终训练结果再测试集上的表现都有少量的提升


踩到的坑

batch norm的坑

在使用inception_v3进行训练时,因为inception_v3网络结构中存在batch_norm,因为之前对batch_norm的不充分了解,导致踩到了一个坑里面。

batch norm在网络训练过程中会起到关键性的作用,他可以加速训练过程,并且一定程度上提升模型的效果。原理是在训练过程中,计算mini-batch的平均值和标准差,对数据进行正则化,将数据约束到一个范围内,保持他们的平均值和标准差不变,对正则化之后的数据进行训练,
在学习卷积核和偏置项参数时,同时会学习batch norm的β和γ,但在测试时就完全不同了,在测试时不需要对测试的mini-batch范围进行约束,只需要使用训练得到的固化的参数进行预测就行,而我就犯了相同的错误。

我起初在训练和测试的时候没有要求is_training这个参数,这个参数指定了是要取当前mini-batch的平均值和标准差,还是使用固化的参数,tensorflow中提供的inception_v3网络的is_training默认为True,所以我在训练好模型之后做预测时,不同的加载数据方式,总会得到不同的结果,如,顺序加载所有类的数据,打乱加载所有类数据,每个mini-batch中各类数量相同等,重新调整了batch_norm后解决(slim.batch_norm有坑,虽然不知道什么原因,但is_training如何设置都会有问题),编写了一个batch_norm方法解决了这个问题

def batch_norm(x, is_training, decay=0.9, eps=1e-5):
    shape = x.get_shape().as_list()
    assert len(shape) in [2, 4]

    n_out = shape[-1]
    beta = tf.Variable(tf.zeros([n_out]))
    gamma = tf.Variable(tf.ones([n_out]))

    if len(shape) == 2:
        batch_mean, batch_var = tf.nn.moments(x, [0])
    else:
        batch_mean, batch_var = tf.nn.moments(x, [0, 1, 2])

    ema = tf.train.ExponentialMovingAverage(decay=decay)

    def mean_var_with_update():
        ema_apply_op = ema.apply([batch_mean, batch_var])
        with tf.control_dependencies([ema_apply_op]):
            return tf.identity(batch_mean), tf.identity(batch_var)

    mean, var = tf.cond(is_training, mean_var_with_update,
                        lambda: (ema.average(batch_mean), ema.average(batch_var)))

    return tf.nn.batch_normalization(x, mean, var, beta, gamma, eps)
阅读 3.6k

推荐阅读