image-augmentation.md 10.8 KB
Newer Older
A
Aston Zhang 已提交
1
# 图片增广
M
Mu Li 已提交
2

3
[“深度卷积神经网络:AlexNet”](../chapter_convolutional-neural-networks/alexnet.md)小节里我们提到过,大规模数据集是成功使用深度网络的前提。图片增广(image augmentation)技术通过对训练图片做一系列随机变化,来产生相似但又有不同的训练样本,从而扩大训练数据集规模。图片增广的另一种解释是,通过对训练样本做一些随机变形,可以降低模型对某些属性的依赖,从而提高泛化能力。例如我们可以对图片进行不同的裁剪,使得感兴趣的物体出现在不同的位置中,从而使得模型减小对物体出现位置的依赖性。也可以调整亮度色彩等因素来降低模型对色彩的敏感度。在AlexNet的成功中,图片增广技术功不可没。本小节我们将讨论这个在计算机视觉里被广泛使用的技术。
M
Mu Li 已提交
4

A
Aston Zhang 已提交
5
首先,导入本节实验所需的包或模块。
M
Mu Li 已提交
6

A
Aston Zhang 已提交
7
```{.python .input  n=21}
A
Aston Zhang 已提交
8
%matplotlib inline
M
muli 已提交
9
import gluonbook as gb
A
Aston Zhang 已提交
10 11 12 13 14 15 16 17 18
import mxnet as mx
from mxnet import autograd, gluon, image, init, nd 
from mxnet.gluon import data as gdata, loss as gloss, utils as gutils
from time import time
```

## 常用增广方法

我们先读取一张$400\times 500$的图片作为样例。
M
Mu Li 已提交
19

A
Aston Zhang 已提交
20
```{.python .input  n=22}
A
Aston Zhang 已提交
21
gb.set_figsize()
M
muli 已提交
22 23
img = image.imread('../img/cat1.jpg')
gb.plt.imshow(img.asnumpy())
M
Mu Li 已提交
24 25
```

A
Aston Zhang 已提交
26 27
下面定义绘图函数`show_images`。该函数也被定义在`gluonbook`包中供后面章节调用。

A
Aston Zhang 已提交
28
```{.python .input  n=23}
A
Aston Zhang 已提交
29 30 31
def show_images(imgs, num_rows, num_cols, scale=2):                                                                              
    """Plot a list of images."""
    figsize = (num_cols * scale, num_rows * scale)
A
Aston Zhang 已提交
32
    _, axes = gb.plt.subplots(num_rows, num_cols, figsize=figsize)
A
Aston Zhang 已提交
33 34 35 36 37 38 39 40
    for i in range(num_rows):
        for j in range(num_cols):
            axes[i][j].imshow(imgs[i * num_cols + j].asnumpy())
            axes[i][j].axes.get_xaxis().set_visible(False)
            axes[i][j].axes.get_yaxis().set_visible(False)
    return axes
```

41
因为大部分的增广方法都有一定的随机性。接下来我们定义一个辅助函数,它对输入图片`img`运行多次增广方法`aug`并显示所有结果。
M
Mu Li 已提交
42

A
Aston Zhang 已提交
43
```{.python .input  n=24}
M
muli 已提交
44
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
A
Aston Zhang 已提交
45
    Y = [aug(img) for _ in range(num_rows * num_cols)]
A
Aston Zhang 已提交
46
    show_images(Y, num_rows, num_cols, scale)
M
Mu Li 已提交
47 48 49 50
```

### 变形

51
左右翻转图片通常不物体的类别,它是最早也是最广泛使用的一种增广。下面我们使用transform模块里的`RandomFlipLeftRight`类来实现按0.5的概率左右翻转图片:
M
Mu Li 已提交
52

A
Aston Zhang 已提交
53
```{.python .input  n=25}
A
Aston Zhang 已提交
54
apply(img, gdata.vision.transforms.RandomFlipLeftRight())
M
Mu Li 已提交
55 56
```

57
上下翻转不如水平翻转通用,但是至少对于样例图片,上下翻转不会造成识别障碍。
M
Mu Li 已提交
58

A
Aston Zhang 已提交
59
```{.python .input  n=26}
A
Aston Zhang 已提交
60
apply(img, gdata.vision.transforms.RandomFlipTopBottom())
M
Mu Li 已提交
61 62
```

63
我们使用的样例图片里,猫在图片正中间,但一般情况下可能不是这样。[“池化层”](../chapter_convolutional-neural-networks/pooling.md)一节里我们解释了池化层能弱化卷积层对目标位置的敏感度,另一方面我们可以通过对图片随机剪裁来让物体以不同的比例出现在不同位置。
M
muli 已提交
64

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

A
Aston Zhang 已提交
67
```{.python .input  n=27}
A
Aston Zhang 已提交
68
shape_aug = gdata.vision.transforms.RandomResizedCrop(
69
    (200, 200), scale=(0.1, 1), ratio=(0.5, 2))
M
muli 已提交
70
apply(img, shape_aug)
M
Mu Li 已提交
71 72 73 74
```

### 颜色变化

A
Aston Zhang 已提交
75
另一类增广方法是变化颜色。我们可以从四个维度改变图片的颜色:亮度、对比、饱和度和色相。在下面的例子里,我们将随机亮度改为原图的50%到150%。
M
Mu Li 已提交
76

A
Aston Zhang 已提交
77
```{.python .input  n=28}
A
Aston Zhang 已提交
78
apply(img, gdata.vision.transforms.RandomBrightness(0.5))
M
Mu Li 已提交
79 80
```

81
类似的,我们可以修改色相。
M
muli 已提交
82

A
Aston Zhang 已提交
83
```{.python .input  n=29}
A
Aston Zhang 已提交
84
apply(img, gdata.vision.transforms.RandomHue(0.5))
M
Mu Li 已提交
85 86
```

M
muli 已提交
87
或者用使用`RandomColorJitter`来一起使用。
M
Mu Li 已提交
88

A
Aston Zhang 已提交
89
```{.python .input  n=30}
A
Aston Zhang 已提交
90
color_aug = gdata.vision.transforms.RandomColorJitter(
91
    brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
M
muli 已提交
92
apply(img, color_aug)
M
Mu Li 已提交
93 94
```

M
muli 已提交
95 96
### 使用多个增广

A
Aston Zhang 已提交
97
实际应用中我们会将多个增广叠加使用。`Compose`类可以将多个增广串联起来。
M
Mu Li 已提交
98

A
Aston Zhang 已提交
99
```{.python .input  n=31}
A
Aston Zhang 已提交
100 101
augs = gdata.vision.transforms.Compose([
    gdata.vision.transforms.RandomFlipLeftRight(), color_aug, shape_aug])
M
muli 已提交
102
apply(img, augs)
M
Mu Li 已提交
103 104
```

M
muli 已提交
105 106
## 使用图片增广来训练

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

A
Aston Zhang 已提交
109
```{.python .input  n=32}
A
Aston Zhang 已提交
110
show_images(gdata.vision.CIFAR10(train=True)[0:32][0], 4, 8, scale=0.8);
M
Mu Li 已提交
111 112
```

113
我们通常将图片增广用在训练样本上,但是在预测的时候并不使用随机增广。这里我们仅仅使用最简单的随机水平翻转。此外,我们使用`ToTensor`变换来将图片转成MXNet需要的格式,即格式为(批量,通道,高,宽)以及类型为32位浮点数。
M
Mu Li 已提交
114

A
Aston Zhang 已提交
115
```{.python .input  n=33}
A
Aston Zhang 已提交
116 117 118
train_augs = gdata.vision.transforms.Compose([
    gdata.vision.transforms.RandomFlipLeftRight(),
    gdata.vision.transforms.ToTensor(),
M
muli 已提交
119 120
])

A
Aston Zhang 已提交
121 122
test_augs = gdata.vision.transforms.Compose([
    gdata.vision.transforms.ToTensor(),
M
muli 已提交
123
])
M
Mu Li 已提交
124 125
```

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

A
Aston Zhang 已提交
128
```{.python .input  n=34}
M
muli 已提交
129
def load_cifar10(is_train, augs, batch_size):
A
Aston Zhang 已提交
130 131
    return gdata.DataLoader(
        gdata.vision.CIFAR10(train=is_train).transform_first(augs),
M
muli 已提交
132
        batch_size=batch_size, shuffle=is_train, num_workers=2)
M
muli 已提交
133 134
```

A
Aston Zhang 已提交
135 136 137 138 139 140
### 使用多GPU训练模型

我们在CIFAR-10数据集上训练[“残差网络:ResNet”](../chapter_convolutional-neural-networks/resnet.md)一节介绍的ResNet-18模型。我们将应用[“多GPU计算的Gluon实现”](../chapter_computational-performance/multiple-gpus-gluon.md)一节中介绍的方法,使用多GPU训练模型。

首先,我们定义`try_all_gpus`函数,从而能够使用所有可用的GPU。

A
Aston Zhang 已提交
141
```{.python .input  n=35}
A
Aston Zhang 已提交
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
def try_all_gpus():
    ctxes = []
    try:
        for i in range(16):
            ctx = mx.gpu(i)
            _ = nd.array([0], ctx=ctx)
            ctxes.append(ctx)
    except:
        pass
    if not ctxes:
        ctxes = [mx.cpu()]
    return ctxes
```

然后,我们定义`evaluate_accuracy`函数评价模型的分类准确率。与[“Softmax回归的从零开始实现”](../chapter_deep-learning-basics/softmax-regression-scratch.md)[“卷积神经网络(LeNet)”](../chapter_convolutional-neural-networks/lenet.md)两节中描述的`evaluate_accuracy`函数不同,当`ctx`包含多个GPU时,这里定义的函数通过辅助函数`_get_batch`将小批量数据样本划分并复制到各个GPU上。

A
Aston Zhang 已提交
158
```{.python .input  n=36}
A
Aston Zhang 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
def _get_batch(batch, ctx):
    features, labels = batch
    if labels.dtype != features.dtype:
        labels = labels.astype(features.dtype)
    # 当 ctx 包含多个GPU时,划分小批量数据样本并复制到各个 GPU 上。
    return (gutils.split_and_load(features, ctx),
            gutils.split_and_load(labels, ctx),
            features.shape[0])

def evaluate_accuracy(data_iter, net, ctx=[mx.cpu()]):
    if isinstance(ctx, mx.Context):
        ctx = [ctx]
    acc = nd.array([0])
    n = 0
    for batch in data_iter:
        features, labels, _ = _get_batch(batch, ctx)
        for X, y in zip(features, labels):
            y = y.astype('float32')
            acc += (net(X).argmax(axis=1)==y).sum().copyto(mx.cpu())
            n += y.size
        acc.wait_to_read()
    return acc.asscalar() / n
```
M
Mu Li 已提交
182

A
Aston Zhang 已提交
183 184
接下来,我们定义`train`函数使用多GPU训练并评价模型。

A
Aston Zhang 已提交
185
```{.python .input  n=37}
A
Aston Zhang 已提交
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
def train(train_iter, test_iter, net, loss, trainer, ctx, num_epochs):
    print('training on', ctx)
    if isinstance(ctx, mx.Context):
        ctx = [ctx]
    for epoch in range(1, num_epochs + 1):
        train_l_sum, train_acc_sum, n, m = 0.0, 0.0, 0.0, 0.0
        start = time()
        for i, batch in enumerate(train_iter):
            Xs, ys, batch_size = _get_batch(batch, ctx)
            ls = []
            with autograd.record():
                y_hats = [net(X) for X in Xs]
                ls = [loss(y_hat, y) for y_hat, y in zip(y_hats, ys)]
            for l in ls:
                l.backward()
            train_acc_sum += sum([(y_hat.argmax(axis=1) == y).sum().asscalar()
                                 for y_hat, y in zip(y_hats, ys)])
            train_l_sum += sum([l.sum().asscalar() for l in ls])
            trainer.step(batch_size)
            n += batch_size
            m += sum([y.size for y in ys])
        test_acc = evaluate_accuracy(test_iter, net, ctx)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, '
              'time %.1f sec'
              % (epoch, train_l_sum / n, train_acc_sum / m, test_acc,
                 time() - start))
```

现在,我们可以定义函数使用图片增广来训练模型了。
M
Mu Li 已提交
215

A
Aston Zhang 已提交
216
```{.python .input  n=38}
A
Aston Zhang 已提交
217
def train_with_data_aug(train_augs, test_augs, lr=0.01):
M
muli 已提交
218
    batch_size = 256
A
Aston Zhang 已提交
219
    ctx = try_all_gpus()
M
muli 已提交
220
    net = gb.resnet18(10)
M
Mu Li 已提交
221
    net.initialize(ctx=ctx, init=init.Xavier())
A
Aston Zhang 已提交
222 223
    trainer = gluon.Trainer(net.collect_params(), 'sgd',
                            {'learning_rate': lr})
A
Aston Zhang 已提交
224 225 226
    loss = gloss.SoftmaxCrossEntropyLoss()
    train_iter = load_cifar10(True, train_augs, batch_size)
    test_iter = load_cifar10(False, test_augs, batch_size)
A
Aston Zhang 已提交
227
    train(train_iter, test_iter, net, loss, trainer, ctx, num_epochs=8)
M
Mu Li 已提交
228 229
```

A
Aston Zhang 已提交
230
我们先观察使用了图片增广的结果。
M
Mu Li 已提交
231

A
Aston Zhang 已提交
232
```{.python .input  n=39}
A
img aug  
Aston Zhang 已提交
233
train_with_data_aug(train_augs, test_augs)
M
Mu Li 已提交
234 235
```

236
作为对比,我们尝试只对训练数据做中间剪裁。
M
Mu Li 已提交
237

A
Aston Zhang 已提交
238
```{.python .input  n=40}
A
img aug  
Aston Zhang 已提交
239
train_with_data_aug(test_augs, test_augs)
M
Mu Li 已提交
240 241
```

A
Aston Zhang 已提交
242 243 244
可以看到,即使是简单的随机翻转也会有明显的效果。图片增广类似于正则化,它使得训练精度变低,但可以提高测试精度。

本节中描述的`try_all_gpus``evaluate_accuracy``train`函数被定义在`gluonbook`包中供后面章节调用。
M
Mu Li 已提交
245

A
Aston Zhang 已提交
246
## 小结
M
Mu Li 已提交
247

248
* 图片增广基于现有训练数据生成大量随机图片来有效避免过拟合。
M
Mu Li 已提交
249 250 251

## 练习

A
Aston Zhang 已提交
252
* 尝试在CIFAR-10训练中增加不同的增广方法。
S
Sheng Zha 已提交
253

A
Aston Zhang 已提交
254
## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/1666)
S
Sheng Zha 已提交
255

A
Aston Zhang 已提交
256
![](../img/qr_image-augmentation.svg)