一、概述
本文是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/
![](https://img.haomeiwen.com/i14755769/38a03fb719c5ae5f.png)
其实上面链接的采访文章中有几点我非常非常感兴趣,但是作者没有解释,加上自己经验匮乏,无法理解。贴上原图,如果哪位大佬有相关的经验,希望不吝赐教!非常感谢!
![](https://img.haomeiwen.com/i14755769/bf9bc3396bc29ca2.png)
- 问题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卷积核则是两者的平衡。
![](https://img.haomeiwen.com/i14755769/7db68013277a55f4.png)
1.4 MiniGoogLeNet
然而,原始Inception是为了GoogLeNet在大型数据集ImageNet上训练而设计的。对于图像空间分辨率更低,规模较小的数据集,我们需要的网络参数可能没有那么多,故可以考虑简化Inception模块。下图就展示了一个非常优美的MiniGoogLeNet。
![](https://img.haomeiwen.com/i14755769/2f8e4c0c8e5e608e.png)
见上图,上方展示了网络的三个基本模块:
- 左上的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
![](https://img.haomeiwen.com/i14755769/23cd545ba9eca4a1.png)
学习率可能设的太大了。SGD相对Adam等优化器,学习率设的应当偏小一点。
实验设置:初始学习率1e-3,每个epoch过后乘以0.95, batchSize 128
![](https://img.haomeiwen.com/i14755769/e8228a3ed1728d83.png)
实验一的结果基本上符合预期,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
![](https://img.haomeiwen.com/i14755769/e2c6204241ff4993.png)
不知道是不是regularization太强了..模型训练的不好。尝试了下更换学习率策略,初始1e-2,在60和100个epoch之后decay 0.1。结果也不是很好。
![](https://img.haomeiwen.com/i14755769/22109598dc2a20fa.png)
就上述几个实验来说用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)
![](https://img.haomeiwen.com/i14755769/bf1428c9ac557b63.png)
参考:
-
https://www.kaggle.com/c/statoil-iceberg-classifier-challenge/discussion/48241#273712
-
Deep Learning for CV with Python
-
Gluon Tutorials
网友评论