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

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

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

A
Aston Zhang 已提交
7
## 常用增广方法
M
Mu Li 已提交
8

M
muli 已提交
9
我们首先读取一张$400\times 500$的图片作为样例解释常用的增广方法。
M
Mu Li 已提交
10 11 12

```{.python .input  n=1}
%matplotlib inline
M
muli 已提交
13 14 15 16 17
import sys
sys.path.insert(0, '..')
import gluonbook as gb
from mxnet import nd, image, gluon, init
from mxnet.gluon.data.vision import transforms
M
Mu Li 已提交
18

M
muli 已提交
19 20
img = image.imread('../img/cat1.jpg')
gb.plt.imshow(img.asnumpy())
M
Mu Li 已提交
21 22
```

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

M
muli 已提交
25
```{.python .input  n=2}
M
muli 已提交
26
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
M
muli 已提交
27
    Y = [aug(img) for _ in range(num_rows*num_cols)]
M
muli 已提交
28
    gb.show_images(Y, num_rows, num_cols, scale)
M
Mu Li 已提交
29 30 31 32
```

### 变形

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

```{.python .input  n=3}
M
muli 已提交
36
apply(img, transforms.RandomFlipLeftRight())
M
Mu Li 已提交
37 38
```

M
muli 已提交
39
当然有时候我们也使用上下翻转,至少对于我们使用的图片,上下翻转不会造成人的识别障碍。
M
Mu Li 已提交
40 41

```{.python .input  n=4}
M
muli 已提交
42
apply(img, transforms.RandomFlipTopBottom())
M
Mu Li 已提交
43 44
```

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

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

```{.python .input  n=5}
M
muli 已提交
50 51 52
shape_aug = transforms.RandomResizedCrop(
    (200, 200), scale=(.1, 1), ratio=(.5, 2))
apply(img, shape_aug)
M
Mu Li 已提交
53 54 55 56
```

### 颜色变化

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

```{.python .input  n=6}
M
muli 已提交
60
apply(img, transforms.RandomLighting(.5))
M
Mu Li 已提交
61 62
```

M
muli 已提交
63 64
同样的修改色相。

M
Mu Li 已提交
65
```{.python .input  n=7}
M
muli 已提交
66
apply(img, transforms.RandomHue(.5))
M
Mu Li 已提交
67 68
```

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

M
muli 已提交
71
```{.python .input  n=8}
M
muli 已提交
72 73 74
color_aug = transforms.RandomColorJitter(
    brightness=.5, contrast=.5, saturation=.5, hue=.5)
apply(img, color_aug)
M
Mu Li 已提交
75 76
```

M
muli 已提交
77 78 79
### 使用多个增广

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

M
muli 已提交
81
```{.python .input  n=9}
M
muli 已提交
82 83 84
augs = transforms.Compose([
    transforms.RandomFlipLeftRight(), color_aug, shape_aug])
apply(img, augs)
M
Mu Li 已提交
85 86
```

M
muli 已提交
87 88 89
## 使用图片增广来训练

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

M
muli 已提交
91
```{.python .input  n=10}
M
muli 已提交
92
gb.show_images(gluon.data.vision.CIFAR10(train=True)[0:32][0], 4, 8, scale=0.8)
M
Mu Li 已提交
93 94
```

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

M
muli 已提交
97
```{.python .input  n=11}
M
muli 已提交
98 99 100 101 102 103 104 105
train_augs = transforms.Compose([
    transforms.RandomFlipLeftRight(),
    transforms.ToTensor(),
])

test_augs = transforms.Compose([
    transforms.ToTensor(),
])
M
Mu Li 已提交
106 107
```

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

M
muli 已提交
110
```{.python .input  n=12}
M
muli 已提交
111 112 113 114
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)
M
muli 已提交
115 116
```

M
muli 已提交
117
### 模型训练
M
Mu Li 已提交
118

M
muli 已提交
119
我们使用ResNet 18来训练CIFAR-10。训练的的代码跟[“残差网络:ResNet”](..//chapter_convolutional-neural-networks/resnet.md)一致,除了使用所有可用的GPU和不同的学习率外。
M
Mu Li 已提交
120

M
muli 已提交
121
```{.python .input  n=13}
M
muli 已提交
122 123
def train(train_augs, test_augs, lr=.1):
    batch_size = 256
A
Aston Zhang 已提交
124
    ctx = gb.try_all_gpus()
M
muli 已提交
125
    net = gb.resnet18(10)
M
Mu Li 已提交
126
    net.initialize(ctx=ctx, init=init.Xavier())
M
muli 已提交
127 128 129 130 131
    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)
M
Mu Li 已提交
132 133
```

M
muli 已提交
134
首先我们看使用了图片增广的情况。
M
Mu Li 已提交
135

M
muli 已提交
136
```{.python .input  n=14}
M
Mu Li 已提交
137 138 139
train(train_augs, test_augs)
```

M
muli 已提交
140
作为对比,我们只对训练数据做中间剪裁。
M
Mu Li 已提交
141

M
muli 已提交
142
```{.python .input  n=15}
M
Mu Li 已提交
143 144 145
train(test_augs, test_augs)
```

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

A
Aston Zhang 已提交
148
## 小结
M
Mu Li 已提交
149

M
muli 已提交
150
图片增广对现有训练数据生成大量随机图片来有效避免过拟合。
M
Mu Li 已提交
151 152 153

## 练习

M
muli 已提交
154
尝试在CIFAR-10训练中增加不同的增广方法。
S
Sheng Zha 已提交
155

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

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