提交 9b4f42d7 编写于 作者: E Eric Junyuan Xie 提交者: Mu Li

Review chapter_computer-vision/image-augmentation.md (#276)

* fix

* fix
上级 68c00a65
# 微调
之前章节里我们通过大量样例演示了如何在只有6万张图片的FashionMNIST上训练模型。我们也介绍了ImageNet这个当下学术界使用最广的大数据集,它有超过一百万的图片和一千类的物体。但我们平常接触到数据集的规模通常在两者之间。
之前介绍了如何在只有6万张图片的FashionMNIST上训练模型。我们也介绍了ImageNet这个当下学术界使用最广的大数据集,它有超过一百万的图片和一千类的物体。但我们平常接触到数据集的规模通常在两者之间。
想象一下开发一个应用来从图片中识别里面的凳子然后提供购买链接给用户。一个可能的做法是先去找一百把常见的凳子,对每个凳子收集一千张不同的图片,然后在收集到的数据上训练一个分类器。这个数据集虽然可能比FashionMNIST要复杂,但仍然比ImageNet小10倍。这可能导致针对ImageNet提出的模型在这个数据上会过拟合。同时因为数据量有限,最终我们得到的分类器的模型的精度也许达不到实用的要求。
假如你想从图片中识别出各种凳子,然后推荐购买链接给用户。一个可能的做法是先找一百种常见的凳子,为每种凳子拍摄一千张不同角度的图片,然后在收集到的数据上训练一个分类器。这个数据集虽然可能比FashionMNIST要庞大,但仍然比ImageNet小10倍。这可能导致适用于ImageNet的复杂模型在这个数据上会过拟合。同时因为数据量有限,最终我们得到的模型的精度也可能达不到实用的要求。
一个解决办法是收集更多的数据。但注意到收集和标注数据均会花费大量的人力和财力。例如ImageNet这个数据集花费了数百万美元的研究经费。虽然目前的数据采集成本降低了十倍以上,但其成本仍然不可忽略。
一个解决办法是收集更多的数据。但是收集和标注数据会花费大量的时间和资金。例如为了收集ImageNet这个数据集,花费了数百万美元的研究经费。虽然目前的数据采集成本降低了十倍以上,但其成本仍然不可忽略。
另外一种解决办法是迁移学习(transfer learning),它通过将其他数据集来帮助学习当前数据集。例如,虽然ImageNet的图片基本跟椅子无关,但其上训练到的模型可能能做一些通用的图片特征抽取,例如识别边缘、纹理、形状和物体组成。这个对于识别椅子也可能同样有效。
另外一种解决办法是应用迁移学习(transfer learning),将从源数据集学到的知识迁移到目标数据集上。例如,虽然ImageNet的图片基本跟椅子无关,但在其上训练的模型可以抽取通用图片特征,然后用来帮助识别边缘、纹理、形状和物体组成。类似的,它对于识别椅子也可能同样有效。
本小节我们介绍迁移学习里面的一个常用技术:微调(fine tuning)。它由下面四步构成:
本小节我们介绍迁移学习的一个常用技术:微调(fine tuning)。它由下面四步构成:
1. 在源数据(例如ImageNet)上训练一个神经网络$A$。
2. 创建一个新的神经网络$B$,它复制$A$上除了输出层外的所有模型参数。这里的假设是这些模型参数含有源数据上学习到的知识,这些知识同样适用于目标数据集。但最后的输出层跟源数据标注紧密相关,所以不被重用。
2. 创建一个新的神经网络$B$,它复制了$A$上除了输出层外的所有模型参数。我们假设这些模型参数含有源数据上学习到的知识,且这些知识同样适用于目标数据集。但最后的输出层跟源数据标注紧密相关,所以不被重用。
3. 为$B$添加一个输出大小为目标数据集类别数目(例如一百类椅子)的输出层,并将其权重初始化成随机值。
4. 在目标数据集(例如椅子数据集)上训练$B$。我们将从头开始学习输出层,但其余层都是基于源数据上的模型参数进行微调。
......@@ -46,14 +46,14 @@ with zipfile.ZipFile(fname, 'r') as z:
z.extractall(data_dir)
```
我们使用使用`ImageFolderDataset`类来读取数据。它将每个类别文件夹当做一个类,并读取下面所有的图片。
我们使用使用`ImageFolderDataset`类来读取数据。它将每个文件夹当做一个类,并读取下面所有的图片。
```{.python .input n=6}
train_imgs = gdata.vision.ImageFolderDataset(data_dir+'/hotdog/train')
test_imgs = gdata.vision.ImageFolderDataset(data_dir+'/hotdog/test')
```
下面画出前8张正例图片和最后的8张负例图片,可以看到他们性质和高宽各不相同。
下面画出前8张正例图片和最后的8张负例图片,可以看到它们的大小和长宽比各不相同。
```{.python .input}
hotdogs = [train_imgs[i][0] for i in range(8)]
......@@ -61,7 +61,7 @@ not_hotdogs = [train_imgs[-i-1][0] for i in range(8)]
gb.show_images(hotdogs+not_hotdogs, 2, 8, scale=1.4); # 加分号只显示图。
```
我们将训练图片首先扩大到高宽为480,然后随机剪裁出高宽为224的输入。测试图片则是简单的中心剪裁。此外,我们对输入的RGB通道数值进行了归一化。
在训练时,我们先从图片中剪裁出随机大小,随机长宽比的一块,然后将它们统一缩放为长宽都是224的输入。测试时,则使用简单的中心剪裁。此外,我们对输入的RGB通道数值进行了归一化。
```{.python .input n=3}
# 指定 RGB 三个通道的均值和方差来将图片通道归一化。
......@@ -69,7 +69,6 @@ normalize = transforms.Normalize(
[0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
train_augs = transforms.Compose([
transforms.Resize(480),
transforms.RandomResizedCrop(224),
transforms.RandomFlipLeftRight(),
transforms.ToTensor(),
......@@ -77,6 +76,7 @@ train_augs = transforms.Compose([
])
test_augs = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
normalize
......@@ -85,13 +85,13 @@ test_augs = transforms.Compose([
### 微调模型
我们用在ImageNet上训练好了ResNet 18来作为基础模型。这里指定`pretrained=True`来自动下载并加载训练好的权重。
我们用在ImageNet上预先训练的ResNet-18作为基础模型。这里指定`pretrained=True`来自动下载并加载预先训练的权重。
```{.python .input n=6}
pretrained_net = model_zoo.vision.resnet18_v2(pretrained=True)
```
预训练好的模型由两块构成,一是`features`,二是`output`。前者包含从输入开始的大部分卷积和全连接层,后者主要包括最后一层全连接层。这样的划分的主要目的是为了更方便做微调。下面查看`output`的内容:
预训练好的模型由两部分构成:`features``output`。前者包含从输入开始的所有卷积和全连接层,后者主要包括最后一层全连接层。这样划分的主要目的是为了更方便做微调。我们来看一`output`的内容:
```{.python .input n=7}
pretrained_net.output
......@@ -133,7 +133,7 @@ def train(net, learning_rate, batch_size=128, epochs=5):
train(finetune_net, 0.01)
```
为了对比起见,我们训练同样的一个模型,但所有参数都初始成随机值。我们使用较大的学习率来加速收敛。
作为对比,我们训练一个同样的模型,但将所有参数都初始化为随机值。我们使用较大的学习率来加速收敛。
```{.python .input n=14}
scratch_net = model_zoo.vision.resnet18_v2(classes=2)
......@@ -141,18 +141,18 @@ scratch_net.initialize(init=init.Xavier())
train(scratch_net, 0.1)
```
可以看到,微调的模型因为初始值更好,它的收敛比从头开始训练要快很多。在很多情况下,微调的模型最终的收敛到的结果也可能比非微调的模型更好。
可以看到,微调的模型因为初始值更好,收敛速度比从头开始训练要快很多。在很多情况下,微调的模型最终的收敛到的结果也可能比非微调的模型更好。
## 小结
* 微调通过将模型部分权重初始化成在源数据集上预训练好的模型参数,从而将模型在源数据集上学到的知识迁移到目标数据上。
* 微调通过将模型部分权重初始化成在源数据集上预训练的模型权重,从而将模型在源数据集上学到的知识迁移到目标数据上。
## 练习
- `finetune_net`试着增大学习率看看收敛变化。
- 多跑几个`epochs`直到收敛(你可以也需要调调参数),看看`scratch_net``finetune_net`最后的精度是不是有区别
- 试着增大`finetune_net`学习率看看收敛变化。
- 多跑几个`epochs`直到收敛(其他参数可能也需要微调),看看`scratch_net``finetune_net`最后的精度是不是有区别
- 这里`finetune_net`重用了`pretrained_net`除最后全连接外的所有权重,试试少重用些权重,有会有什么区别
- 事实上`ImageNet`里也有`hotdog`这个类,它对应的输出层参数可以如下拿到。试试如何使用它。
- 事实上`ImageNet`里也有`hotdog`这个类,它对应的输出层参数可以用如下代码拿到。试试如何使用它。
```{.python .input n=16}
weight = pretrained_net.output.weight
......
# 图片增广
[“深度卷积神经网络:AlexNet”](../chapter_convolutional-neural-networks/alexnet.md)小节里我们提到过大规模数据集是深度网络能成功的前提条件。在AlexNet当年能取得的成功中,图片增广(image augmentation)功不可没。本小节我们将讨论这个在计算机视觉里被广泛使用的技术。
图片增广是指通过对训练图片做一系列变化来产生相似但又有不同的训练样本,这样来模型训练的时候识别了难以泛化的模式。例如我们可以对图片进行不同的裁剪使得感兴趣的物体出现在不同的位置中,从而使得模型减小对物体出现位置的依赖性。也可以调整亮度色彩等因素来降低模型对色彩的敏感度。
[“深度卷积神经网络:AlexNet”](../chapter_convolutional-neural-networks/alexnet.md)小节里我们提到过,大规模数据集是成功使用深度网络的前提。图片增广(image augmentation)技术通过对训练图片做一系列随机变化,来产生相似但又有不同的训练样本,从而扩大训练数据集规模。图片增广的另一种解释是,通过对训练样本做一些随机变形,可以降低模型对某些属性的依赖,从而提高泛化能力。例如我们可以对图片进行不同的裁剪,使得感兴趣的物体出现在不同的位置中,从而使得模型减小对物体出现位置的依赖性。也可以调整亮度色彩等因素来降低模型对色彩的敏感度。在AlexNet的成功中,图片增广技术功不可没。本小节我们将讨论这个在计算机视觉里被广泛使用的技术。
## 常用增广方法
我们首先读取一张$400\times 500$的图片作为样例解释常用的增广方法
我们首先读取一张$400\times 500$的图片作为样例。
```{.python .input n=1}
%matplotlib inline
......@@ -20,7 +18,7 @@ img = image.imread('../img/cat1.jpg')
gb.plt.imshow(img.asnumpy())
```
因为大部分的增广方法都有一定的随机性。接下来我们定义一个辅助函数,它对输入图片`img`运行多次增广方法`aug`画出结果。
因为大部分的增广方法都有一定的随机性。接下来我们定义一个辅助函数,它对输入图片`img`运行多次增广方法`aug`显示所有结果。
```{.python .input n=2}
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
......@@ -30,21 +28,21 @@ def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
### 变形
左右翻转图片通常不影响识别图片,它是最早也是最广泛使用的一种增广。下面我们使用transform模块里的`RandomFlipLeftRight`类来实现按0.5的概率左右翻转图片:
左右翻转图片通常不物体的类别,它是最早也是最广泛使用的一种增广。下面我们使用transform模块里的`RandomFlipLeftRight`类来实现按0.5的概率左右翻转图片:
```{.python .input n=3}
apply(img, transforms.RandomFlipLeftRight())
```
当然有时候我们也使用上下翻转,至少对于我们使用的图片,上下翻转不会造成人的识别障碍。
上下翻转不如水平翻转通用,但是至少对于样例图片,上下翻转不会造成识别障碍。
```{.python .input n=4}
apply(img, transforms.RandomFlipTopBottom())
```
我们使用的样例图片里猫在图片正中间,但一般情况下可能不是这样。[“池化层”](../chapter_convolutional-neural-networks/pooling.md)一节里我们解释了池化层能弱化卷积层对目标位置的敏感度,另一方面我们可以通过对图片随机剪裁来是的物体以不同的比例出现在不同位置。
我们使用的样例图片里,猫在图片正中间,但一般情况下可能不是这样。[“池化层”](../chapter_convolutional-neural-networks/pooling.md)一节里我们解释了池化层能弱化卷积层对目标位置的敏感度,另一方面我们可以通过对图片随机剪裁来让物体以不同的比例出现在不同位置。
下面代码里我们每次随机裁剪一片面积为原面积10%到100%的区域,其宽和高的比例在0.5和2之间,然后再将高宽缩放到200像素。
下面代码里我们每次随机裁剪一片面积为原面积10%到100%的区域,其宽和高的比例在0.5和2之间,然后再将高宽缩放到200像素大小
```{.python .input n=5}
shape_aug = transforms.RandomResizedCrop(
......@@ -54,13 +52,13 @@ apply(img, shape_aug)
### 颜色变化
形状变化外的一个另一大类是变化颜色。颜色一般有四个可以调的参数:亮度、对比、饱和度和色相。下面例子里我们随机将亮度在当前值上增加或减小一个在0到50%之前的量
另一类增广方法是变化颜色。我们可以从四个维度改变图片的颜色:亮度、对比、饱和度和色相。在下面的例子里,我们随机亮度改为原图的50%到150%
```{.python .input n=6}
apply(img, transforms.RandomBrightness(0.5))
```
同样的修改色相。
类似的,我们可以修改色相。
```{.python .input n=7}
apply(img, transforms.RandomHue(0.5))
......@@ -76,7 +74,7 @@ apply(img, color_aug)
### 使用多个增广
实际应用中我们会将多个增广叠加使用。我们可以使用Compose类来将多个增广串联起来。
实际应用中我们会将多个增广叠加使用。Compose类可以将多个增广串联起来。
```{.python .input n=9}
augs = transforms.Compose([
......@@ -86,14 +84,14 @@ apply(img, augs)
## 使用图片增广来训练
接下来我们来看一个将图片增广应用在实际训练的例子,并比较其与不使用时的区别。这里我们使用CIFAR-10数据集,而不是之前我们一直使用的FashionMNIST。原因在于FashionMNIST中物体位置和尺寸都已经统一化了,而CIFAR-10中物体颜色和大小区别更加显著。下面我们展示CIFAR-10中的前32张训练图片。
接下来我们来看一个将图片增广应用在实际训练中的例子,并比较其与不使用时的区别。这里我们使用CIFAR-10数据集,而不是之前我们一直使用的FashionMNIST。原因在于FashionMNIST中物体位置和尺寸都已经归一化了,而CIFAR-10中物体颜色和大小区别更加显著。下面我们展示CIFAR-10中的前32张训练图片。
```{.python .input n=10}
gb.show_images(gluon.data.vision.CIFAR10(train=True)[0:32][0], 4, 8,
scale=0.8);
```
在训练时,我们通常将图片增广作用在训练图片上,使得模型能识别出各种变化过后的版本。这里我们仅仅使用最简单的随机水平翻转。此外我们使用`ToTensor`变换来图片转成MXNet需要的格式,即格式为(批量,通道,高,宽)以及类型为32位浮点数。
我们通常将图片增广用在训练样本上,但是在预测的时候并不使用随机增广。这里我们仅仅使用最简单的随机水平翻转。此外,我们使用`ToTensor`变换来将图片转成MXNet需要的格式,即格式为(批量,通道,高,宽)以及类型为32位浮点数。
```{.python .input n=11}
train_augs = transforms.Compose([
......@@ -106,7 +104,7 @@ test_augs = transforms.Compose([
])
```
接下来我们定义一个辅助函数来方便读取图片并应用增广。Gluon的数据集提供`transform_first`函数来对数据里面的第一项图片(标签为第二项)来应用增广。另外图片增广将增加计算复杂度,我们使用两个额外CPU进程加来加速计算。
接下来我们定义一个辅助函数来方便读取图片并应用增广。Gluon的数据集提供`transform_first`函数来对数据里面的第一项(数据一般有图片和标签两项)来应用增广。另外图片增广将增加计算复杂度,我们使用两个额外CPU进程加来加速计算。
```{.python .input n=12}
def load_cifar10(is_train, augs, batch_size):
......@@ -117,7 +115,7 @@ def load_cifar10(is_train, augs, batch_size):
### 模型训练
我们使用ResNet 18来训练CIFAR-10。训练的的代码[“残差网络:ResNet”](../chapter_convolutional-neural-networks/resnet.md)一致,除了使用所有可用的GPU和不同的学习率外。
我们使用ResNet 18来训练CIFAR-10。训练的的代码[“残差网络:ResNet”](../chapter_convolutional-neural-networks/resnet.md)一致,除了使用所有可用的GPU和不同的学习率外。
```{.python .input n=13}
def train(train_augs, test_augs, lr=0.1):
......@@ -138,17 +136,17 @@ def train(train_augs, test_augs, lr=0.1):
train(train_augs, test_augs)
```
作为对比,我们只对训练数据做中间剪裁。
作为对比,我们尝试只对训练数据做中间剪裁。
```{.python .input n=15}
train(test_augs, test_augs)
```
可以看到,即使是简单的随机翻转也会有明显效果。使用增广类似于增加了正则项话,它使得训练精度变低,但对提升测试精度有帮助
可以看到,即使是简单的随机翻转也会有明显的效果。图片增广类似于正则项,它使得训练精度变低,但可以提高测试精度
## 小结
* 图片增广现有训练数据生成大量随机图片来有效避免过拟合。
* 图片增广基于现有训练数据生成大量随机图片来有效避免过拟合。
## 练习
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册