图片增广

“深度卷积神经网络:AlexNet”小节里我们提到过大规模数据集是深度网络能成功的前提条件。在AlexNet当年能取得的成功中,图片增广(image augmentation)功不可没。本小节我们将讨论这个在计算机视觉里被广泛使用的技术。

图片增广是指通过对训练图片做一系列变化来产生相似但又有不同的训练样本,这样来模型训练的时候识别了难以泛化的模式。例如我们可以对图片进行不同的裁剪使得感兴趣的物体出现在不同的位置中,从而使得模型减小对物体出现位置的依赖性。也可以调整亮度色彩等因素来降低模型对色彩的敏感度。

常用增广方法

我们首先读取一张\(400\times 500\)的图片作为样例解释常用的增广方法。

In [1]:
%matplotlib inline
import sys
sys.path.insert(0, '..')
import gluonbook as gb
from mxnet import nd, image, gluon, init
from mxnet.gluon.data.vision import transforms

img = image.imread('../img/cat1.jpg')
gb.plt.imshow(img.asnumpy())
Out[1]:
<matplotlib.image.AxesImage at 0x7ff2b414b5c0>
../_images/chapter_computer-vision_image-augmentation_1_1.png

因为大部分的增广方法都有一定的随机性。接下来我们定义一个辅助函数,它对输入图片img运行多次增广方法aug并画出结果。

In [2]:
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
    Y = [aug(img) for _ in range(num_rows*num_cols)]
    gb.show_images(Y, num_rows, num_cols, scale)

变形

左右翻转图片通常不影响识别图片,它是最早也是最广泛使用的一种增广。下面我们使用transform模块里的RandomFlipLeftRight类来实现按0.5的概率左右翻转图片:

In [3]:
apply(img, transforms.RandomFlipLeftRight())
../_images/chapter_computer-vision_image-augmentation_5_0.png

当然有时候我们也使用上下翻转,至少对于我们使用的图片,上下翻转不会造成人的识别障碍。

In [4]:
apply(img, transforms.RandomFlipTopBottom())
../_images/chapter_computer-vision_image-augmentation_7_0.png

我们使用的样例图片里猫在图片正中间,但一般情况下可能不是这样。“池化层”一节里我们解释了池化层能弱化卷积层对目标位置的敏感度,另一方面我们可以通过对图片随机剪裁来是的物体以不同的比例出现在不同位置。

下面代码里我们每次随机裁剪一片面积为原面积10%到100%的区域,其宽和高的比例在0.5和2之间,然后再将高宽缩放到200像素。

In [5]:
shape_aug = transforms.RandomResizedCrop(
    (200, 200), scale=(.1, 1), ratio=(.5, 2))
apply(img, shape_aug)
../_images/chapter_computer-vision_image-augmentation_9_0.png

颜色变化

形状变化外的一个另一大类是变化颜色。颜色一般有四个可以调的参数:亮度、对比、饱和度和色相。下面例子里我们随机将亮度在当前值上增加或减小一个在0到50%之前的量。

In [6]:
apply(img, transforms.RandomLighting(.5))
../_images/chapter_computer-vision_image-augmentation_11_0.png

同样的修改色相。

In [7]:
apply(img, transforms.RandomHue(.5))
../_images/chapter_computer-vision_image-augmentation_13_0.png

或者用使用RandomColorJitter来一起使用。

In [8]:
color_aug = transforms.RandomColorJitter(
    brightness=.5, contrast=.5, saturation=.5, hue=.5)
apply(img, color_aug)
../_images/chapter_computer-vision_image-augmentation_15_0.png

使用多个增广

实际应用中我们会将多个增广叠加使用。我们可以使用Compose类来将多个增广串联起来。

In [9]:
augs = transforms.Compose([
    transforms.RandomFlipLeftRight(), color_aug, shape_aug])
apply(img, augs)
../_images/chapter_computer-vision_image-augmentation_17_0.png

使用图片增广来训练

接下来我们来看一个将图片增广应用在实际训练的例子,并比较其与不使用时的区别。这里我们使用CIFAR-10数据集,而不是之前我们一直使用的FashionMNIST。原因在于FashionMNIST中物体位置和尺寸都已经统一化了,而CIFAR-10中物体颜色和大小区别更加显著。下面我们展示CIFAR-10中的前32张训练图片。

In [10]:
gb.show_images(gluon.data.vision.CIFAR10(train=True)[0:32][0], 4, 8, scale=0.8)
Out[10]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x7ff2866791d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff29007f278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2901f75c0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff292690550>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2866accf8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2926c5390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff290064a20>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2866da0f0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x7ff290031780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff290394e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2902044e0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2901feb70>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2b40f6240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2925618d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2901adf60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff286711d30>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x7ff28670ccc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff292519390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff28676dd30>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2925b70f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff292598780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2926f1e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff29022ef28>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff29022ab70>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x7ff292745240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2902848d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff29026ff60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2900a1630>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2901124a8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff29049b5f8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2904a97f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x7ff2b405f0f0>]], dtype=object)
../_images/chapter_computer-vision_image-augmentation_19_1.png

在训练时,我们通常将图片增广作用在训练图片上,使得模型能识别出各种变化过后的版本。这里我们仅仅使用最简单的随机水平翻转。此外我们使用ToTensor变换来图片转成MXNet需要的格式,即格式为(批量,通道,高,宽)以及类型为32位浮点数。

In [11]:
train_augs = transforms.Compose([
    transforms.RandomFlipLeftRight(),
    transforms.ToTensor(),
])

test_augs = transforms.Compose([
    transforms.ToTensor(),
])

接下来我们定义一个辅助函数来方便读取图片并应用增广。Gluon的数据集提供transform_first函数来对数据里面的第一项图片(标签为第二项)来应用增广。另外图片增广将增加计算复杂度,我们使用两个额外CPU进程加来加速计算。

In [12]:
def load_cifar10(is_train, augs, batch_size):
    return gluon.data.DataLoader(gluon.data.vision.CIFAR10(
        train=is_train).transform_first(augs),
        batch_size=batch_size, shuffle=is_train, num_workers=2)

模型训练

我们使用ResNet 18来训练CIFAR-10。训练的的代码跟“残差网络:ResNet”一致,除了使用所有可用的GPU和不同的学习率外。

In [13]:
def train(train_augs, test_augs, lr=.1):
    batch_size = 256
    ctx = gb.try_all_gpus()
    net = gb.resnet18(10)
    net.initialize(ctx=ctx, init=init.Xavier())
    trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate':lr})
    loss = gluon.loss.SoftmaxCrossEntropyLoss()
    train_data = load_cifar10(True, train_augs, batch_size)
    test_data = load_cifar10(False, test_augs, batch_size)
    gb.train(train_data, test_data, net, loss, trainer, ctx, num_epochs=8)

首先我们看使用了图片增广的情况。

In [14]:
train(train_augs, test_augs)
training on [gpu(0), gpu(1)]
epoch 1, loss 1.4958, train acc 0.466, test acc 0.557, time 12.0 sec
epoch 2, loss 1.0133, train acc 0.640, test acc 0.512, time 11.1 sec
epoch 3, loss 0.7997, train acc 0.718, test acc 0.543, time 11.1 sec
epoch 4, loss 0.6799, train acc 0.761, test acc 0.537, time 11.0 sec
epoch 5, loss 0.5862, train acc 0.795, test acc 0.608, time 10.9 sec
epoch 6, loss 0.5104, train acc 0.822, test acc 0.660, time 10.9 sec
epoch 7, loss 0.4455, train acc 0.846, test acc 0.386, time 11.0 sec
epoch 8, loss 0.3905, train acc 0.865, test acc 0.690, time 10.9 sec

作为对比,我们只对训练数据做中间剪裁。

In [15]:
train(test_augs, test_augs)
training on [gpu(0), gpu(1)]
epoch 1, loss 1.4893, train acc 0.468, test acc 0.492, time 11.0 sec
epoch 2, loss 1.0045, train acc 0.642, test acc 0.616, time 10.9 sec
epoch 3, loss 0.7728, train acc 0.729, test acc 0.569, time 10.9 sec
epoch 4, loss 0.6091, train acc 0.789, test acc 0.518, time 10.9 sec
epoch 5, loss 0.4779, train acc 0.834, test acc 0.622, time 11.0 sec
epoch 6, loss 0.3646, train acc 0.873, test acc 0.584, time 11.0 sec
epoch 7, loss 0.2571, train acc 0.912, test acc 0.564, time 11.0 sec
epoch 8, loss 0.1790, train acc 0.940, test acc 0.591, time 11.0 sec

可以看到,即使是简单的随机翻转也会有明显效果。使用增广类似于增加了正则项话,它使得训练精度变低,但对提升测试精度有帮助。

小结

图片增广对现有训练数据生成大量随机图片来有效避免过拟合。

练习

尝试在CIFAR-10训练中增加不同的增广方法。

扫码直达讨论区