.. _sec_image_augmentation: Aumento de Imagem ================= Mencionamos que conjuntos de dados em grande escala são pré-requisitos para a aplicação bem-sucedida de redes neurais profundas em :numref:`sec_alexnet`. A tecnologia de aumento de imagem expande a escala dos conjuntos de dados de treinamento, fazendo uma série de alterações aleatórias nas imagens de treinamento para produzir exemplos de treinamento semelhantes, mas diferentes. Outra maneira de explicar o aumento de imagem é que exemplos de treinamento que mudam aleatoriamente podem reduzir a dependência de um modelo em certas propriedades, melhorando assim sua capacidade de generalização. Por exemplo, podemos recortar as imagens de diferentes maneiras, para que os objetos de interesse apareçam em diferentes posições, reduzindo a dependência do modelo da posição onde os objetos aparecem. Também podemos ajustar o brilho, a cor e outros fatores para reduzir a sensibilidade do modelo à cor. Pode-se dizer que a tecnologia de aumento de imagem contribuiu muito para o sucesso do AlexNet. Nesta seção, discutiremos essa tecnologia, que é amplamente usada na visão computacional. Primeiro, importe os pacotes ou módulos necessários para o experimento nesta seção. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python %matplotlib inline from mxnet import autograd, gluon, image, init, np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() .. raw:: html
.. raw:: html
.. code:: python %matplotlib inline import torch import torchvision from torch import nn from d2l import torch as d2l .. raw:: html
.. raw:: html
Método Comum de Aumento de Imagem --------------------------------- Neste experimento, usaremos uma imagem com um formato de :math:`400\times 500` como exemplo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python d2l.set_figsize() img = image.imread('../img/cat1.jpg') d2l.plt.imshow(img.asnumpy()); .. figure:: output_image-augmentation_7d0887_12_0.svg .. raw:: html
.. raw:: html
.. code:: python d2l.set_figsize() img = d2l.Image.open('../img/cat1.jpg') d2l.plt.imshow(img); .. figure:: output_image-augmentation_7d0887_15_0.svg .. raw:: html
.. raw:: html
A maioria dos métodos de aumento de imagem tem um certo grau de aleatoriedade. Para facilitar a observação do efeito do aumento da imagem, definimos a seguir a função auxiliar ``aplicar``. Esta função executa o método de aumento de imagem ``aug`` várias vezes na imagem de entrada\ ``img`` e mostra todos os resultados. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def apply(img, aug, num_rows=2, num_cols=4, scale=1.5): Y = [aug(img) for _ in range(num_rows * num_cols)] d2l.show_images(Y, num_rows, num_cols, scale=scale) .. raw:: html
.. raw:: html
.. code:: python def apply(img, aug, num_rows=2, num_cols=4, scale=1.5): Y = [aug(img) for _ in range(num_rows * num_cols)] d2l.show_images(Y, num_rows, num_cols, scale=scale) .. raw:: html
.. raw:: html
Invertendo e Recortando ~~~~~~~~~~~~~~~~~~~~~~~ Virar a imagem para a esquerda e para a direita geralmente não altera a categoria do objeto. Este é um dos métodos mais antigos e mais amplamente usados de aumento de imagem. Em seguida, usamos o módulo ``transforms`` para criar a instância ``RandomFlipLeftRight``, que apresenta uma chance de 50% de que a imagem seja virada para a esquerda e para a direita. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python apply(img, gluon.data.vision.transforms.RandomFlipLeftRight()) .. figure:: output_image-augmentation_7d0887_30_0.svg .. raw:: html
.. raw:: html
.. code:: python apply(img, torchvision.transforms.RandomHorizontalFlip()) .. figure:: output_image-augmentation_7d0887_33_0.svg .. raw:: html
.. raw:: html
Virar para cima e para baixo não é tão comumente usado como girar para a esquerda e para a direita. No entanto, pelo menos para esta imagem de exemplo, virar para cima e para baixo não impede o reconhecimento. Em seguida, criamos uma instância ``RandomFlipTopBottom`` para uma chance de 50% de virar a imagem para cima e para baixo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python apply(img, gluon.data.vision.transforms.RandomFlipTopBottom()) .. figure:: output_image-augmentation_7d0887_39_0.svg .. raw:: html
.. raw:: html
.. code:: python apply(img, torchvision.transforms.RandomVerticalFlip()) .. figure:: output_image-augmentation_7d0887_42_0.svg .. raw:: html
.. raw:: html
Na imagem de exemplo que usamos, o gato está no meio da imagem, mas pode não ser o caso para todas as imagens. Em :numref:`sec_pooling`, explicamos que a camada de pooling pode reduzir a sensibilidade da camada convolucional ao local de destino. Além disso, podemos fazer os objetos aparecerem em diferentes posições na imagem em diferentes proporções aleatoriamente recortando a imagem. Isso também pode reduzir a sensibilidade do modelo à posição de destino. No código a seguir, recortamos aleatoriamente uma região com uma área de 10% a 100% da área original, e a proporção entre largura e altura da região é selecionada aleatoriamente entre 0,5 e 2. Em seguida, a largura e a altura de as regiões são dimensionadas para 200 pixels. Salvo indicação em contrário, o número aleatório entre :math:`a` e :math:`b` nesta seção refere-se a um valor contínuo obtido por amostragem uniforme no intervalo :math:`[a, b]`. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python shape_aug = gluon.data.vision.transforms.RandomResizedCrop( (200, 200), scale=(0.1, 1), ratio=(0.5, 2)) apply(img, shape_aug) .. figure:: output_image-augmentation_7d0887_48_0.svg .. raw:: html
.. raw:: html
.. code:: python shape_aug = torchvision.transforms.RandomResizedCrop( (200, 200), scale=(0.1, 1), ratio=(0.5, 2)) apply(img, shape_aug) .. figure:: output_image-augmentation_7d0887_51_0.svg .. raw:: html
.. raw:: html
Mudando a Cor ~~~~~~~~~~~~~ Outro método de aumento é mudar as cores. Podemos alterar quatro aspectos da cor da imagem: brilho, contraste, saturação e matiz. No exemplo abaixo, alteramos aleatoriamente o brilho da imagem para um valor entre 50% (:math:`1-0.5`) e 150% (:math:`1+0.5`) da imagem original. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python apply(img, gluon.data.vision.transforms.RandomBrightness(0.5)) .. figure:: output_image-augmentation_7d0887_57_0.svg .. raw:: html
.. raw:: html
.. code:: python apply(img, torchvision.transforms.ColorJitter( brightness=0.5, contrast=0, saturation=0, hue=0)) .. figure:: output_image-augmentation_7d0887_60_0.svg .. raw:: html
.. raw:: html
Da mesma forma, podemos alterar aleatoriamente o matiz da imagem. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python apply(img, gluon.data.vision.transforms.RandomHue(0.5)) .. figure:: output_image-augmentation_7d0887_66_0.svg .. raw:: html
.. raw:: html
.. code:: python apply(img, torchvision.transforms.ColorJitter( brightness=0, contrast=0, saturation=0, hue=0.5)) .. figure:: output_image-augmentation_7d0887_69_0.svg .. raw:: html
.. raw:: html
Também podemos criar uma instância ``RandomColorJitter`` e definir como alterar aleatoriamente o ``brightness``, ``contrast``, ``saturation``, e ``hue`` da imagem ao mesmo tempo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python color_aug = gluon.data.vision.transforms.RandomColorJitter( brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5) apply(img, color_aug) .. figure:: output_image-augmentation_7d0887_75_0.svg .. raw:: html
.. raw:: html
.. code:: python color_aug = torchvision.transforms.ColorJitter( brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5) apply(img, color_aug) .. figure:: output_image-augmentation_7d0887_78_0.svg .. raw:: html
.. raw:: html
Métodos de Aumento de Imagem Múltipla Sobreposta ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Na prática, iremos sobrepor vários métodos de aumento de imagem. Podemos sobrepor os diferentes métodos de aumento de imagem definidos acima e aplicá-los a cada imagem usando uma instância ``Compose``. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python augs = gluon.data.vision.transforms.Compose([ gluon.data.vision.transforms.RandomFlipLeftRight(), color_aug, shape_aug]) apply(img, augs) .. figure:: output_image-augmentation_7d0887_84_0.svg .. raw:: html
.. raw:: html
.. code:: python augs = torchvision.transforms.Compose([ torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug]) apply(img, augs) .. figure:: output_image-augmentation_7d0887_87_0.svg .. raw:: html
.. raw:: html
Usando um Modelo de Treinamento de Aumento de Imagem ---------------------------------------------------- A seguir, veremos como aplicar o aumento de imagem no treinamento real. Aqui, usamos o conjunto de dados CIFAR-10, em vez do conjunto de dados Fashion-MNIST que usamos. Isso ocorre porque a posição e o tamanho dos objetos no conjunto de dados Fashion-MNIST foram normalizados, e as diferenças de cor e tamanho dos objetos no conjunto de dados CIFAR-10 são mais significativas. As primeiras 32 imagens de treinamento no conjunto de dados CIFAR-10 são mostradas abaixo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python d2l.show_images(gluon.data.vision.CIFAR10( train=True)[0:32][0], 4, 8, scale=0.8); .. figure:: output_image-augmentation_7d0887_93_0.svg .. raw:: html
.. raw:: html
.. code:: python all_images = torchvision.datasets.CIFAR10(train=True, root="../data", download=True) d2l.show_images([all_images[i][0] for i in range(32)], 4, 8, scale=0.8); .. parsed-literal:: :class: output Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ../data/cifar-10-python.tar.gz 9.6% .. raw:: html
.. raw:: html
Para obter resultados definitivos durante a previsão, geralmente aplicamos apenas o aumento da imagem ao exemplo de treinamento e não usamos o aumento da imagem com operações aleatórias durante a previsão. Aqui, usamos apenas o método de inversão aleatório da esquerda para a direita mais simples. Além disso, usamos uma instância ``ToTensor`` para converter imagens de minibatch no formato exigido pelo MXNet, ou seja, números de ponto flutuante de 32 bits com a forma de (tamanho do lote, número de canais, altura, largura) e intervalo de valores entre 0 e 1. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python train_augs = gluon.data.vision.transforms.Compose([ gluon.data.vision.transforms.RandomFlipLeftRight(), gluon.data.vision.transforms.ToTensor()]) test_augs = gluon.data.vision.transforms.Compose([ gluon.data.vision.transforms.ToTensor()]) .. raw:: html
.. raw:: html
.. code:: python train_augs = torchvision.transforms.Compose([ torchvision.transforms.RandomHorizontalFlip(), torchvision.transforms.ToTensor()]) test_augs = torchvision.transforms.Compose([ torchvision.transforms.ToTensor()]) .. raw:: html
.. raw:: html
A seguir, definimos uma função auxiliar para facilitar a leitura da imagem e aplicar o aumento da imagem. A função ``transform_first`` fornecida pelo conjunto de dados do Gluon aplica o aumento da imagem ao primeiro elemento de cada exemplo de treinamento (imagem e rótulo), ou seja, o elemento na parte superior da imagem. Para descrições detalhadas de ``DataLoader``, consulte :numref:`sec_fashion_mnist`. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python 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=d2l.get_dataloader_workers()) .. raw:: html
.. raw:: html
.. code:: python def load_cifar10(is_train, augs, batch_size): dataset = torchvision.datasets.CIFAR10(root="../data", train=is_train, transform=augs, download=True) dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=is_train, num_workers=d2l.get_dataloader_workers()) return dataloader .. raw:: html
.. raw:: html
Usando um Modelo de Treinamento Multi-GPU ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Treinamos o modelo ResNet-18 descrito em: numref: ``sec_resnet`` no conjunto de dados CIFAR-10. Também aplicaremos os métodos descritos em :numref:`sec_multi_gpu_concise` e usaremos um modelo de treinamento multi-GPU. Em seguida, definimos a função de treinamento para treinar e avaliar o modelo usando várias GPUs. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save def train_batch_ch13(net, features, labels, loss, trainer, devices, split_f=d2l.split_batch): X_shards, y_shards = split_f(features, labels, devices) with autograd.record(): pred_shards = [net(X_shard) for X_shard in X_shards] ls = [loss(pred_shard, y_shard) for pred_shard, y_shard in zip(pred_shards, y_shards)] for l in ls: l.backward() # The True flag allows parameters with stale gradients, which is useful # later (e.g., in fine-tuning BERT) trainer.step(labels.shape[0], ignore_stale_grad=True) train_loss_sum = sum([float(l.sum()) for l in ls]) train_acc_sum = sum(d2l.accuracy(pred_shard, y_shard) for pred_shard, y_shard in zip(pred_shards, y_shards)) return train_loss_sum, train_acc_sum #@save def train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices=d2l.try_all_gpus(), split_f=d2l.split_batch): timer, num_batches = d2l.Timer(), len(train_iter) animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 1], legend=['train loss', 'train acc', 'test acc']) for epoch in range(num_epochs): # Store training_loss, training_accuracy, num_examples, num_features metric = d2l.Accumulator(4) for i, (features, labels) in enumerate(train_iter): timer.start() l, acc = train_batch_ch13( net, features, labels, loss, trainer, devices, split_f) metric.add(l, acc, labels.shape[0], labels.size) 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)) test_acc = d2l.evaluate_accuracy_gpus(net, test_iter, split_f) 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 on ' f'{str(devices)}') .. raw:: html
.. raw:: html
.. code:: python #@save def train_batch_ch13(net, X, y, loss, trainer, devices): if isinstance(X, list): # Required for BERT Fine-tuning (to be covered later) X = [x.to(devices[0]) for x in X] else: X = X.to(devices[0]) y = y.to(devices[0]) net.train() trainer.zero_grad() pred = net(X) l = loss(pred, y) l.sum().backward() trainer.step() train_loss_sum = l.sum() train_acc_sum = d2l.accuracy(pred, y) return train_loss_sum, train_acc_sum #@save def train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices=d2l.try_all_gpus()): timer, num_batches = d2l.Timer(), len(train_iter) animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 1], legend=['train loss', 'train acc', 'test acc']) net = nn.DataParallel(net, device_ids=devices).to(devices[0]) for epoch in range(num_epochs): # Store training_loss, training_accuracy, num_examples, num_features metric = d2l.Accumulator(4) for i, (features, labels) in enumerate(train_iter): timer.start() l, acc = train_batch_ch13( net, features, labels, loss, trainer, devices) metric.add(l, acc, labels.shape[0], labels.numel()) 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)) 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 on ' f'{str(devices)}') .. raw:: html
.. raw:: html
Agora, podemos definir a função ``train_with_data_aug`` para usar o aumento da imagem para treinar o modelo. Esta função obtém todas as GPUs disponíveis e usa Adam como o algoritmo de otimização para o treinamento. Em seguida, ele aplica o aumento da imagem ao conjunto de dados de treinamento e, finalmente, chama a função ``train_ch13`` definida para treinar e avaliar o modelo. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10) net.initialize(init=init.Xavier(), ctx=devices) def train_with_data_aug(train_augs, test_augs, net, lr=0.001): train_iter = load_cifar10(True, train_augs, batch_size) test_iter = load_cifar10(False, test_augs, batch_size) loss = gluon.loss.SoftmaxCrossEntropyLoss() trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr}) train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices) .. raw:: html
.. raw:: html
.. code:: python batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10, 3) def init_weights(m): if type(m) in [nn.Linear, nn.Conv2d]: nn.init.xavier_uniform_(m.weight) net.apply(init_weights) def train_with_data_aug(train_augs, test_augs, net, lr=0.001): train_iter = load_cifar10(True, train_augs, batch_size) test_iter = load_cifar10(False, test_augs, batch_size) loss = nn.CrossEntropyLoss(reduction="none") trainer = torch.optim.Adam(net.parameters(), lr=lr) train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices) .. raw:: html
.. raw:: html
Now we train the model using image augmentation of random flipping left and right. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python train_with_data_aug(train_augs, test_augs, net) .. parsed-literal:: :class: output loss 0.172, train acc 0.941, test acc 0.829 4323.8 examples/sec on [gpu(0), gpu(1)] .. figure:: output_image-augmentation_7d0887_138_1.svg .. raw:: html
.. raw:: html
.. code:: python train_with_data_aug(train_augs, test_augs, net) .. parsed-literal:: :class: output loss 0.173, train acc 0.940, test acc 0.837 5044.9 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)] .. figure:: output_image-augmentation_7d0887_141_1.svg .. raw:: html
.. raw:: html
Resumo ------ - O aumento de imagem gera imagens aleatórias com base nos dados de treinamento existentes para lidar com o sobreajuste. - Para obter resultados definitivos durante a previsão, geralmente aplicamos apenas o aumento da imagem ao exemplo de treinamento e não usamos o aumento da imagem com operações aleatórias durante a previsão. - Podemos obter classes relacionadas ao aumento de imagem do módulo ``transforma`` do Gluon. Exercícios ---------- 1. Treine o modelo sem usar aumento de imagem: ``train_with_data_aug (no_aug, no_aug)``. Compare a precisão do treinamento e do teste ao usar e não usar o aumento de imagem. Este experimento comparativo pode apoiar o argumento de que o aumento da imagem pode mitigar o sobreajuste? Por quê? 2. Adicione diferentes métodos de aumento de imagem no treinamento do modelo com base no *dataset* CIFAR-10. Observe os resultados da implementação. 3. Com referência à documentação do MXNet, que outros métodos de aumento de imagem são fornecidos no módulo ``transforms`` do Gluon? .. raw:: html
mxnetpytorch
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
`Discussões `__ .. raw:: html
.. raw:: html
.. raw:: html