Classfication基础实验系列二——MiniGoogLeNet & CIFAR10

一、概述

  本文是Classification基础实验系列的第二篇。上一篇我们学习了一个非常简单的网络——MiniVGG并通过几组对比实验观察了BN, Dropout, 以及GAP等组件对模型泛化和时间的影响。本文将简要复习下GoogLeNet的基本原理,然后搭建一个较小型的版本——MiniGoogLeNet,并在CIFAR-10上进行训练。

  一个有趣的地方,本文的MiniGoogLeNet是可以用于实际任务的,而不仅仅是一个toy model。比如Kaggle Iceberg 冰川挑战的冠军解决方案就用到了该模型。
见:https://www.pyimagesearch.com/2018/03/26/interview-david-austin-1st-place-25000-kaggles-popular-competition/

14755769-38a03fb719c5ae5f.png
Fig. 1. 冰山挑战赛冠军选手的获奖感言

其实上面链接的采访文章中有几点我非常非常感兴趣,但是作者没有解释,加上自己经验匮乏,无法理解。贴上原图,如果哪位大佬有相关的经验,希望不吝赐教!非常感谢!


14755769-bf9bc3396bc29ca2.png
Fig. 2. 该方案中涉及的一些techniques
  • 问题1:"use unsupervised methods to identify natural patterns in the data, and use that learning to determine what DL apporoaches to take donw-stream."如何理解?
  • 问题2:KNN在这里具体是如何提供帮助的?
  • 问题4: 使用100多个小CNN(如MiniGoogLeNet)作为弱分类器然后集成的方法,是否可以有效避免过拟合?有没有相关例子?
  • 问题4:最后一句,使用第一步中非监督学习识别出的数据(原始数据集的子集)retrain有什么意义?

  虽然上面的是题外话,但是我觉得这几个问题非常有意思,并且以目前的水平并不能理解,所以先记录在这里。

1.1 复习下Inception:

  Inception的思路其实也比较直接,就是在面对多个尺寸的卷积核难以做出选择时,尝试直接将这些卷积核融合进同一个模型。这样做的好处是,将这个混合模块变成一个多尺度的特征提取器——5x5卷积核拥有较大的感受野,进而可以学习更加抽象的特征;1x1卷积核学习更加局部的特征,而3x3卷积核则是两者的平衡。

14755769-7db68013277a55f4.png
Fig. 3. Inception模块。注意第一个1x1Conv学习局部特征,后三个用于降维

1.4 MiniGoogLeNet

  然而,原始Inception是为了GoogLeNet在大型数据集ImageNet上训练而设计的。对于图像空间分辨率更低,规模较小的数据集,我们需要的网络参数可能没有那么多,故可以考虑简化Inception模块。下图就展示了一个非常优美的MiniGoogLeNet。


14755769-2f8e4c0c8e5e608e.png
Fig. 4. MiniGoogLeNet

  见上图,上方展示了网络的三个基本模块:

  • 左上的Conv Module,实现Conv+BN+ReLU,注意这里作者将BN放在ReLU之前。实验时可以考虑交换顺序比较下结果~
  • 上方为Miniception Module,同时使用两组卷积,1x1和3x3,注意这里3x3卷积之后没有降维的1x1卷积,因为输入的feature map体量不大。
  • 右上为Downsample Module,同时使用3x3大小,2x2步长的卷积和Max Pool进行降维。

二、代码实现——MiniGoogLeNet

  看着Fig. 4搭建模型并不复杂。我练习的时候写了Keras和Gluon两个版本的,这里给出Gluon版本的模型:

from mxnet.gluon import nn, data as gdata
from mxnet import gluon, nd, autograd, init
from mxnet.gluon.data.vision.datasets import CIFAR10
import mxnet as mx
import time
from mxnet.gluon.utils import split_and_load

def ConvSame(channel, k, s, activation=None):
    """'same' mode convolution for stride=1"""
    return nn.Conv2D(channel, k, (1,1), int((k-1)/2), activation=activation)

class ConvModule(nn.HybridBlock):
    def __init__(self, channel, k, s, padding='same', **kwags):
        super(ConvModule, self).__init__(**kwags)
        if padding=='same':
            self.conv = ConvSame(channel, k, s)
        else:
            self.conv = nn.Conv2D(channel, k, s, padding)
        self.batchnorm = nn.BatchNorm()
        self.relu = nn.Activation('relu')
        
    def hybrid_forward(self, F, x):
        x = self.conv(x)
        x = self.batchnorm(x)
        return self.relu(x)

class Miniception(nn.HybridBlock):
    def __init__(self, ch1, ch3, **kwags):
        super(Miniception, self).__init__(**kwags)
        self.conv1x1 = ConvModule(ch1, 1, 1)
        self.conv3x3 = ConvModule(ch3, 3, 1)
    
    def hybrid_forward(self, F, x):
        #这样也可以,但是相当于在模型中使用了固定的NDArray接口,故无法使用net.hybridize()
        #return nd.concat(self.conv1x1(x), self.conv3x3(x))  
        return F.concat(self.conv1x1(x), self.conv3x3(x))

class DownsampleModule(nn.HybridBlock):
    def __init__(self, ch3, **kwags):
        super(DownsampleModule, self).__init__(**kwags)
        self.conv3x3 = ConvModule(ch3, k=3, s=2, padding=(0,0))  # 'valid' conv
        self.maxpool = nn.MaxPool2D((3,3),2)
    def hybrid_forward(self, F, x):
        #return nd.concat(self.conv3x3(x), self.maxpool(x))
        return F.concat(self.conv3x3(x), self.maxpool(x))

def MiniGoogLeNet(num_classes):
    net = nn.HybridSequential()
    net.add(
        ConvModule(96, 3, 1),
        Miniception(32, 32),
        Miniception(32, 48),
        DownsampleModule(80),
        
        Miniception(112, 48),
        Miniception(96, 64),
        Miniception(80, 80),
        Miniception(48, 96),
        DownsampleModule(96),
        
        Miniception(176, 160),
        Miniception(176, 160),
        
        nn.GlobalAvgPool2D(),
        nn.Dropout(0.5),
        nn.Dense(num_classes)   
    )
    return net

三、实验

3.1 不使用regularization

  实验设置:初始学习率1e-1,batchSize 128

14755769-23cd545ba9eca4a1.png
Fig. 5. 实验一

   学习率可能设的太大了。SGD相对Adam等优化器,学习率设的应当偏小一点。

  实验设置:初始学习率1e-3,每个epoch过后乘以0.95, batchSize 128

14755769-e8228a3ed1728d83.png
Fig. 6. 实验二

  实验一的结果基本上符合预期,MiniGoogLeNet在CIFAR10上可以取得91%左右的准确率。不过由于未使用除了Dropout之外的任何正则化,训练过程存在过拟合的现象。

3.2 加入数据增强

dataset_train = CIFAR10()
dataset_test = CIFAR10(train=False)
trans_train = transforms.Compose([
    transforms.RandomResizedCrop(size=(32,32)),
    transforms.RandomFlipLeftRight(),
    transforms.RandomColorJitter(brightness=0.1, contrast=0.1, 
                                 saturation=0.1, hue=0.1),
    transforms.ToTensor(),
    transforms.Normalize([0.4914, 0.4822, 0.4465],
                         [0.2023, 0.1994, 0.2010]),
])
trans_test = transforms.Compose([
    transforms.ToTensor()
])
train_iter = gdata.DataLoader(dataset_train.transform_first(trans_train),
                        batch_size=128, shuffle=True, last_batch='rollover', num_workers=-1)
test_iter = gdata.DataLoader(dataset_test.transform_first(trans_test),
                        batch_size=128, shuffle=True, last_batch='rollover', num_workers=-1)

  实验设置:初始学习率1e-2,每个epoch过后乘以0.95, batchSize 128

14755769-e2c6204241ff4993.png
Fig. 7. 实验三

  不知道是不是regularization太强了..模型训练的不好。尝试了下更换学习率策略,初始1e-2,在60和100个epoch之后decay 0.1。结果也不是很好。


14755769-22109598dc2a20fa.png
Fig. 8. 实验四

  就上述几个实验来说用Gluon的代码训练的结果没有Keras训练的好。下面的是之前用Keras训练的结果:

  实验设置:BatchSize 64,Epochs 70;学习率:初始0.01,使用线性衰减直到最后一个epoch减到0。数据增强:

aug = ImageDataGenerator(width_shift_range=0.1, height_shift_range=0.1,
               horizontal_flip=True, fill_mode="nearest", zoom_range=0.1)
14755769-bf1428c9ac557b63.png
image.png

参考:

猜你喜欢

转载自blog.csdn.net/weixin_34342578/article/details/87228287
今日推荐