Kaggle竞赛:Histopathologic Cancer Detection

1 问题和数据分析

问题:肿瘤判别。判断一个图片中是否含有结构化肿瘤。

以下是比赛中给出的数据介绍

In this dataset, you are provided with a large number of small pathology images to classify. Files are named with an image id. The train_labels.csv file provides the ground truth for the images in the train folder. You are predicting the labels for the images in the test folder. A positive label indicates that the center 32x32px region of a patch contains at least one pixel of tumor tissue. Tumor tissue in the outer region of the patch does not influence the label. This outer region is provided to enable fully-convolutional models that do not use zero-padding, to ensure consistent behavior when applied to a whole-slide image.

The original PCam dataset contains duplicate images due to its probabilistic sampling, however, the version presented on Kaggle does not contain duplicates. We have otherwise maintained the same data and splits as the PCam benchmark.

我们试着翻译上面的数据集介绍看看(主要是强迫自己理解一些概念)

在本数据集中,有许多病理图像被用于分类。每个文件用一个图像id命名。文件train_labels.csv在文件夹train中,其给出了图像及其对应的真实标记。我们需要做的是预测test文件夹中的图像标记(是否是病理图像)。一个正的标记表明图像的一个32×32像素的子区域包含至少一个肿瘤组织的像素。在该子区域以外的肿瘤组织不影响标记。子区域以外的区域主要是方便我们在做全卷积的时候不需要做零填充,是为了以后应用到完整图像中的时候保持图像运算的连贯性。

原始的PCam数据集包含重复的图像,这是因为它的随机采样导致的。但是,Kaggle中用的数据集没有重复图像(已经预处理了吧!)。除此之外,该数据集基本和PCam平台的数据集是一样的。

2 数据预处理

还是老办法,先看看其它的script是如何预处理数据的。

2.1 a-complete-ml-pipeline-fast-ai

推荐给我的我先看看这个
a-complete-ml-pipeline-fast-ai
这个脚本一大段英文,好吧!硬着头皮读下去,也许有帮助!

1) 原始数据

《Kaggle竞赛:Histopathologic Cancer Detection》 clipboard.png

上面这个神奇的图片网址是有道云的图片网址,做了好多的md5运算吧!

  • A good idea is to crop the origin 96×96 image to center 32×32 image.
  • Positive vs negative label rate is 6:4, so the data is imbalance.

使用裁剪后的中央32×32数据作为训练数据,然后在此基础上使用除了裁剪以外的其它的数据增强方法。需要注意这里的训练数据存在不平衡的问题。

2) 数据增强

数据增强方法:

  • 随机旋转
  • 随机裁剪
  • 随机翻转
  • 随机亮度
  • 随机缩放(在上述链接中的脚本没有实现)
  • 高斯噪声(在上述链接中的脚本没有实现)

在数据增强的cell之前,作者给出了为什么使用opencv,而不使用PIL或scikit-image原因。天下武功,唯快不破。

选一些例子看看增强前和增强后的图像

《Kaggle竞赛:Histopathologic Cancer Detection》 clipboard (1).png

《Kaggle竞赛:Histopathologic Cancer Detection》 clipboard (2).png

3) 计算图像统计量

找出特别黑和特别白的图像。

如果是我的话,我一般就不会做这么复杂的事了。

《Kaggle竞赛:Histopathologic Cancer Detection》 clipboard (4).png

还真有1个全黑和6个全白的图像。

2.2 CNN Starter – NasNet Mobile

该脚本的地址为
CNN Starter – NasNet Mobile

该脚本基于keras来进行模型学习和预测。使用nasnet模型!

  • NASNet

先看看如何预处理数据吧!

这个脚本非常暴力!直接用NASNet。

数据处理用了大量的data_gen来处理。

2.3 Histopathologic Cancer Detection (Ensemble)

脚本地址为Histopathologic Cancer Detection (Ensemble)

数据预处理的代码实现是基于pytorch的。更准确的说是基于torchvision的(难怪深度学习在CV领域发展的非常迅速)。

数据处理中使用了pytorch的Dataset来处理。

  • Dataset
  • 按照train和test两个数据集进行读取处理(没有tfrecord)。

数据预处理做了以下数据增强的工作(与2.1和2.2类似,具体实现细节不一样)。

对训练集做了:

train_transform = Compose([
    RandomAffine(45, translate=(0.15, 0.15), shear=45),
    RandomHorizontalFlip(),
    RandomVerticalFlip(),
    RandomRotation(45),
    RandomApply([ColorJitter(saturation=0.5, hue=0.5)]),
    ToTensor(),
    Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
  • 随机仿射变换
  • 随机水平翻转
  • 随机垂直翻转
  • 随机旋转
  • 随机使用函数预处理数据(对饱和度和色调进行随机添加处理)
  • 转换为pytorch张量
  • 归一化
test_transform = Compose(
    [ToTensor(),
     Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

对测试集做了:

  • 转换为pytorch张量
  • 归一化

(这里不得不说pytorch的代码确实非常的规范和清晰,比tensorflow的代码确实清晰很多)

2.4 You Really Need Attention

脚本地址为You Really Need Attention

这里的图像处理使用了PIL库。我们在2.1节中已经介绍了尽量使用opencv,因此我决定在这里用opencv重写PIL部分的代码,也许对运算速度有提升。

使用albumentations图像增强库来处理。参考地址为albumentations

这里穿插着介绍下albumentations的使用。

3 模型建立和训练

模型相关的工作,第一步就是快速搭建一个baseline。这也是吴恩达建议的。

  • baseline
  • train_test_split

还要考虑训练数据不平衡的问题。

3.1 a-complete-ml-pipeline-fast-ai

作者在处理数据的时候没有做异常数据的清洗。

但是在train_test_split中设置了参数stratify=train_labels来实现数据平衡。

模型是使用fastai和pytorch来实现的,牛逼!(fastai还是第一次听说)

这种方法是非常好的,因为进步靠两点,一要坚持,二要站在巨人肩膀。

作者的模型训练更像是给fastai做广告。真的这么好吗?

这里画图给出了如下比较常见的代码

fig, ax = plt.subplots(row, column)

用fastai寻找超参数

在建立好baseline之后,一直在调整weight_decay和Learning_rate。

  • baseline
  • finetune
  • save result

3.2 CNN Starter – NasNet Mobile

模型结构

def get_model_classif_nasnet():
    inputs = Input((96, 96, 3))
    base_model = NASNetMobile(include_top=False, input_shape=(96, 96, 3))#, weights=None
    x = base_model(inputs)
    out1 = GlobalMaxPooling2D()(x)
    out2 = GlobalAveragePooling2D()(x)
    out3 = Flatten()(x)
    out = Concatenate(axis=-1)([out1, out2, out3])
    out = Dropout(0.5)(out)
    out = Dense(1, activation="sigmoid", name="3_")(out)
    model = Model(inputs, out)
    model.compile(optimizer=Adam(0.0001), loss=binary_crossentropy, metrics=['acc'])
    model.summary()

    return model

这里只使用了NASNetMobile的特征提取部分,因此NASNetMobile中将参数include=False。然后重新设计了分类器部分。

    1. GlobalMaxPooling2D, GlobalAveragePooling2D, Flatten
    1. 合并Concatenate
    1. Dropout:最后分类层的Dropout,用来做集成
    1. Dense,最后输出到一个二元分类器中

3.3 Histopathologic Cancer Detection (Ensemble)

该脚本使用torch的多个内置的模型来进行集成学习。

from torchvision.models import alexnet, densenet121, inception_v3, resnet18, squeezenet1_0, vgg11_bn 

从代码中可以看出用到了Alex的alexnet、以及densenet121, inception_v3, resnet18, squeezenet1.0和vgg11等。这些好像分辨率一般,但是训练和推理的时间会少一点。

改脚本使用模型集成的方法来做分类的。使用了函数train_ensemble来统一实现。

def train_ensemble(nets, optimizers, schedulers, criterion, eval_criterion):
    ensemble_name = 'ensemble'
    train_loss_array = []
    test_loss_array = []
    train_accuracy_array = []
    test_accuracy_array = []

    print('Start training...')
    for epoch in range(MAX_EPOCH):  # loop over the dataset multiple times
        for scheduler in schedulers:
            scheduler.step()
        
        running_loss = 0.0
        for i, data in enumerate(trainloader):
            _, images, labels = data
            #             inputs, labels = Variable(inputs), Variable(labels)
            images, labels = Variable(images).cuda(), Variable(labels).cuda()

            predictions = torch.zeros([images.size(0), NUM_CLASSES]).cuda()
            for net, optimizer in zip(nets, optimizers):
                net.train()
                optimizer.zero_grad()
                outputs = net(images)
                predictions = predictions.add(outputs)

            predictions = predictions / len(nets)
            
            # back prop
            loss = criterion(predictions, labels)
            loss.backward()
            for optimizer in optimizers:
                optimizer.step()
            running_loss += loss.item()
            
            if i % 500 == 499:  # print every 2000 mini-batches
                print('Step: %5d avg_batch_loss: %.5f' % (i + 1, running_loss / 500))
                running_loss = 0.0
                
        print('Finish training this EPOCH, start evaluating...')
        train_loss, train_acc = eval_ensemble(nets, eval_criterion, trainloader)
        test_loss, test_acc = eval_ensemble(nets, eval_criterion, testloader)
        print('EPOCH: %d train_loss: %.5f train_acc: %.5f test_loss: %.5f test_acc %.5f' %
              (epoch + 1, train_loss, train_acc, test_loss, test_acc))

        train_loss_array.append(train_loss)
        test_loss_array.append(test_loss)

        train_accuracy_array.append(train_acc)
        test_accuracy_array.append(test_acc)
#         print('Saving intermitant models...')
#         for net in nets:
#             torch.save(net.state_dict(), './%s/%s-%d.pth' % (folders['models'], net.name, epoch))
    print('Finished Training')

    # plot loss
    plt.clf()
    plt.plot(list(range(1, MAX_EPOCH + 1)), train_loss_array, label='Train')
    plt.plot(list(range(1, MAX_EPOCH + 1)), test_loss_array, label='Test')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.legend()
    plt.title('Loss vs Epochs [%s]' % net.name)
    plt.savefig('./%s/loss-%s.png' % (folders['plots'], ensemble_name))

    # plot accuracy
    plt.clf()
    plt.plot(list(range(1, MAX_EPOCH + 1)), train_accuracy_array, label='Train')
    plt.plot(list(range(1, MAX_EPOCH + 1)), test_accuracy_array, label='Test')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.title('Accuracy vs Epochs [%s]' % net.name)
    plt.savefig('./%s/accuracy-%s.png' % (folders['plots'], ensemble_name))

    print('Saving models...')
    for net in nets:
        torch.save(net.state_dict(), './%s/training-%s.pth' % (folders['models'], net.name))

后面定义了一系列模型,用来做集成。从结果看,做集成的效果还不如在数据分析和处理、以及调参上多下功夫。

简单看下一个模型的定义

# DenseNet121
dense_net = densenet121(pretrained=True)
dense_num_ftrs = dense_net.classifier.in_features
dense_net.classifier = Linear(dense_num_ftrs, NUM_CLASSES)
dense_net.name = "DenseNet121"
dense_net = dense_net.cuda()
dense_optimizer = Adam(dense_net.parameters(), lr=LEARNING_RATE)
dense_exp_lr_scheduler = lr_scheduler.StepLR(dense_optimizer, step_size=STEP_SIZE, gamma=GAMMA)
net_list.append(dense_net)
optimizer_list.append(dense_optimizer)
scheduler_list.append(dense_exp_lr_scheduler)

尤其对于工业应用,使用这种模型stack的方式,确实不是什么好方法!但是对于kaggle比赛而言,这种方法能够达到非常好的结果。

    原文作者:深度学习模型优化
    原文地址: https://www.jianshu.com/p/cbf160d9a92a
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞