12.1. 图像增广¶
8.1节提到过大型数据集是成功应用深度神经网络的先决条件。 图像增广在对训练图像进行一系列的随机变化之后,生成相似但不同的训练样本,从而扩大了训练集的规模。 此外,应用图像增广的原因是,随机改变训练样本可以减少模型对某些属性的依赖,从而提高模型的泛化能力。 例如,我们可以以不同的方式裁剪图像,使感兴趣的对象出现在不同的位置,减少模型对于对象出现位置的依赖。 我们还可以调整亮度、颜色等因素来降低模型对颜色的敏感度。 可以说,图像增广技术对于AlexNet的成功是必不可少的。本节将讨论这项广泛应用于计算机视觉的技术。
%matplotlib inline
import albumentations as A
import mlx.core as mx
import mlx.data as dx
import mlx.optimizers as optim
import numpy as np
from mlx import nn
from d2l import mlx as d2l
12.1.1. 常用的图像增广方法¶
在对常用图像增广方法的探索时,我们将使用下面这个尺寸为\(400\times 500\)的图像作为示例。
d2l.set_figsize()
img = d2l.Image.open('../img/cat1.jpg')
d2l.plt.imshow(img);
大多数图像增广方法都具有一定的随机性。为了便于观察图像增广的效果,我们下面定义辅助函数apply。
此函数在输入图像img上多次运行图像增广方法aug并显示所有结果。
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
Y = [aug(image=np.array(img))["image"] for _ in range(num_rows * num_cols)]
d2l.show_images(Y, num_rows, num_cols, scale=scale)
12.1.1.1. 翻转和裁剪¶
左右翻转图像通常不会改变对象的类别。这是最早且最广泛使用的图像增广方法之一。
接下来,我们使用transforms模块来创建RandomFlipLeftRight实例,这样就各有50%的几率使图像向左或向右翻转。
apply(img, A.HorizontalFlip())
上下翻转图像不如左右图像翻转那样常用。但是,至少对于这个示例图像,上下翻转不会妨碍识别。接下来,我们创建一个RandomFlipTopBottom实例,使图像各有50%的几率向上或向下翻转。
apply(img, A.VerticalFlip())
在我们使用的示例图像中,猫位于图像的中间,但并非所有图像都是这样。 在 7.5节中,我们解释了汇聚层可以降低卷积层对目标位置的敏感性。 另外,我们可以通过对图像进行随机裁剪,使物体以不同的比例出现在图像的不同位置。 这也可以降低模型对目标位置的敏感性。
下面的代码将随机裁剪一个面积为原始面积10%到100%的区域,该区域的宽高比从0.5~2之间随机取值。 然后,区域的宽度和高度都被缩放到200像素。 在本节中(除非另有说明),\(a\)和\(b\)之间的随机数指的是在区间\([a, b]\)中通过均匀采样获得的连续值。
shape_aug = A.RandomResizedCrop(
(200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
12.1.1.2. 改变颜色¶
另一种增广方法是改变颜色。 我们可以改变图像颜色的四个方面:亮度、对比度、饱和度和色调。 在下面的示例中,我们随机更改图像的亮度,随机值为原始图像的50%(\(1-0.5\))到150%(\(1+0.5\))之间。
apply(img, A.ColorJitter(
brightness=0.5, contrast=0, saturation=0, hue=0))
同样,我们可以随机更改图像的色调。
apply(img, A.ColorJitter(
brightness=0, contrast=0, saturation=0, hue=0.5))
我们还可以创建一个RandomColorJitter实例,并设置如何同时随机更改图像的亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)。
color_aug = A.ColorJitter(
brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)
12.1.1.3. 结合多种图像增广方法¶
在实践中,我们将结合多种图像增广方法。比如,我们可以通过使用一个Compose实例来综合上面定义的不同的图像增广方法,并将它们应用到每个图像。
augs = A.Compose([
A.HorizontalFlip(), color_aug, shape_aug])
apply(img, augs)
12.1.2. 使用图像增广进行训练¶
让我们使用图像增广来训练模型。 这里,我们使用CIFAR-10数据集,而不是我们之前使用的Fashion-MNIST数据集。 这是因为Fashion-MNIST数据集中对象的位置和大小已被规范化,而CIFAR-10数据集中对象的颜色和大小差异更明显。 CIFAR-10数据集中的前32个训练图像如下所示。
all_images = dx.datasets.load_cifar10(train=True, root="../data")
d2l.show_images([all_images[i]["image"] for i in range(32)], 4, 8, scale=0.8);
为了在预测过程中得到确切的结果,我们通常对训练样本只进行图像增广,且在预测过程中不使用随机操作的图像增广。
在这里,我们只使用最简单的随机左右翻转。
此外,我们使用ToTensor实例将一批图像转换为深度学习框架所要求的格式,即形状为(批量大小,通道数,高度,宽度)的32位浮点数,取值范围为0~1。
train_augs = A.Compose([
A.HorizontalFlip()
])
test_augs = A.Compose([])
def load_cifar10(is_train, augs, batch_size):
dataset = dx.datasets.load_cifar10(root="../data", train=is_train)
def process_sample(samples: dict) -> dict:
image = samples["image"]
label = samples["label"]
image = augs(image=image)["image"]
image = image.astype("float32") / 255.0
label = label.astype("int32")
return {"X": image, "y": label}
dataset = dataset.sample_transform(process_sample)
num_batch = len(dataset) // batch_size
data_iter = (
dataset.shuffle_if(is_train)
.to_stream()
.batch(batch_size)
)
return data_iter, num_batch
12.1.2.1. 多GPU训练¶
我们在CIFAR-10数据集上训练 8.3节中的ResNet-18模型。
回想一下 sec_multi_gpu_concise中对多GPU训练的介绍。
接下来,我们定义一个函数,使用多GPU对模型进行训练和评估。
#@save
def train_batch_ch13(net, X, y, train_step, trainer):
"""用多GPU进行小批量训练"""
net.train()
loss_and_grad_fn = nn.value_and_grad(net, train_step)
(loss, acc), grads = loss_and_grad_fn(net, X, y)
trainer.update(net, grads)
train_loss_sum = loss.item()
train_acc_sum = acc.item()
return train_loss_sum, train_acc_sum
#@save
def train_ch13(net, train_iter, num_train_batches, test_iter, loss, trainer, num_epochs):
"""用多GPU进行模型训练"""
timer = d2l.Timer()
num_batches = num_train_batches
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 1],
legend=['train loss', 'train acc', 'test acc'])
def train_step(net, X, y):
y_hat = net(X)
los = loss(y_hat, y, reduction='sum')
acc = mx.sum(mx.argmax(y_hat, axis=-1) == y)
return los, acc
for epoch in range(num_epochs):
# 4个维度:储存训练损失,训练准确度,实例数,特点数
metric = d2l.Accumulator(4)
for i, samples in enumerate(train_iter):
features, labels = mx.array(samples["X"]), mx.array(samples["y"])
timer.start()
l, acc = train_batch_ch13(
net, features, labels, train_step, trainer)
metric.add(l, acc, labels.shape[0], labels.shape[0])
timer.stop()
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(metric[0] / metric[2], metric[1] / metric[3],
None))
train_iter.reset()
test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {metric[0] / metric[2]:.3f}, train acc '
f'{metric[1] / metric[3]:.3f}, test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec ')
现在,我们可以定义train_with_data_aug函数,使用图像增广来训练模型。该函数获取所有的GPU,并使用Adam作为训练的优化算法,将图像增广应用于训练集,最后调用刚刚定义的用于训练和评估模型的train_ch13函数。
batch_size, net = 256, d2l.resnet18(10, 3)
def init_model_weights(model):
"""
一个用于 net.apply 的函数,它会根据layer的类型来决定初始化策略。
"""
for name, layer in model.named_modules():
if isinstance(layer, nn.Linear):
print(f"Initializing Linear: {name}")
for param in layer.parameters():
print("Init before", name, "\n", layer, "\n", layer[param])
if hasattr(layer, 'weight') and layer.weight is not None:
new_weight = nn.init.glorot_uniform()(layer.weight)
layer.update({"weight": new_weight})
if hasattr(layer, 'bias') and layer.bias is not None:
new_bias = nn.init.constant(0)(layer.bias)
layer.update({"bias": new_bias})
for param in layer.parameters():
print("Init after", name, "\n", layer, "\n", layer[param])
elif isinstance(layer, nn.Conv2d):
print(f"Initializing Conv2D: {name}")
for param in layer.parameters():
print("Init before", name, "\n", layer, "\n", layer[param])
if hasattr(layer, 'weight') and layer.weight is not None:
new_weight = nn.init.glorot_uniform()(layer.weight)
layer.update({"weight": new_weight})
if hasattr(layer, 'bias') and layer.bias is not None:
new_bias = nn.init.constant(0)(layer.bias)
layer.update({"bias": new_bias})
for param in layer.parameters():
print("Init after", name, "\n", layer, "\n", layer[param])
mx.eval(model.parameters()) # Ensure changes are applied
def train_with_data_aug(train_augs, test_augs, net, lr=0.001):
train_iter, num_batches = load_cifar10(True, train_augs, batch_size)
test_iter, _ = load_cifar10(False, test_augs, batch_size)
loss = nn.losses.cross_entropy
trainer = optim.Adam(learning_rate = lr)
train_ch13(net, train_iter, num_batches, test_iter, loss, trainer, 10)
让我们使用基于随机左右翻转的图像增广来训练模型。
train_with_data_aug(train_augs, test_augs, net)
loss 0.466, train acc 0.838, test acc 0.664
5468.4 examples/sec
12.1.3. 小结¶
图像增广基于现有的训练数据生成随机图像,来提高模型的泛化能力。
为了在预测过程中得到确切的结果,我们通常对训练样本只进行图像增广,而在预测过程中不使用带随机操作的图像增广。
深度学习框架提供了许多不同的图像增广方法,这些方法可以被同时应用。
12.1.4. 练习¶
在不使用图像增广的情况下训练模型:
train_with_data_aug(no_aug, no_aug)。比较使用和不使用图像增广的训练结果和测试精度。这个对比实验能支持图像增广可以减轻过拟合的论点吗?为什么?在基于CIFAR-10数据集的模型训练中结合多种不同的图像增广方法。它能提高测试准确性吗?
参阅深度学习框架的在线文档。它还提供了哪些其他的图像增广方法?