14.1. Аугментация изображений

В Section 8.1 мы упоминали, что большие наборы данных являются обязательным условием успеха глубоких нейронных сетей в различных приложениях. Аугментация изображений создает похожие, но отличные друг от друга примеры обучающей выборки путем внесения случайных изменений в обучающие изображения, тем самым увеличивая размер обучающей выборки. Кроме того, аугментацию изображений можно обосновать тем, что случайные модификации примеров позволяют моделям меньше полагаться на определённые признаки, тем самым улучшая их обобщающую способность. Например, мы можем обрезать изображение различными способами, чтобы интересующий нас объект оказывался в разных позициях, тем самым снижая зависимость модели от положения объекта. Мы также можем регулировать такие параметры, как яркость и цвет, чтобы уменьшить чувствительность модели к цвету. Вероятно, аугментация изображений была необходима для успеха AlexNet в то время. В этом разделе мы обсудим эту широко используемую технику в области компьютерного зрения.

%matplotlib inline
import torch
import torchvision
from torch import nn
from d2l import torch as d2l

14.1.1. Общие методы аугментации изображений

В нашем исследовании распространённых методов аугментации изображений мы будем использовать следующее \(400\times 500\) изображение в качестве примера.

d2l.set_figsize()
img = d2l.Image.open('../img/cat1.jpg')
d2l.plt.imshow(img);

Большинство методов аугментации изображений содержат определённую степень случайности. Чтобы было удобнее наблюдать результат аугментации, далее мы определим вспомогательную функцию apply. Эта функция несколько раз применяет метод аугментации aug к входному изображению img и отображает все полученные результаты.

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)

14.1.1.1. Отражение и кадрирование

Отражение изображения по горизонтали обычно не меняет категорию объекта. Это один из самых ранних и широко используемых методов аугментации изображений. Далее мы используем модуль transforms для создания объекта RandomHorizontalFlip, который с вероятностью 50% отражает изображение по горизонтали.

apply(img, torchvision.transforms.RandomHorizontalFlip())

Отражение по вертикали используется реже, чем отражение по горизонтали. Однако для данного примера изображения отражение по вертикали не мешает распознаванию. Далее мы создадим объект RandomVerticalFlip для отражения изображения по вертикали с вероятностью 50%.

apply(img, torchvision.transforms.RandomVerticalFlip())

На примере изображения, которое мы использовали, кошка находится в центре кадра, однако в общем случае это не всегда так. В Section 7.5 мы объяснили, что слой пулинга снижает чувствительность свёрточного слоя к положению целевой переменной. Кроме того, мы можем случайным образом обрезать изображение, чтобы объекты появлялись в разных позициях и с разными масштабами, что также снижает чувствительность модели к положению целевой переменной.

В приведённом ниже коде мы случайно вырезаем область, площадь которой составляет \(10\% \sim 100\%\) от площади исходного изображения, а соотношение ширины и высоты этой области выбирается случайным образом из \(0.5 \sim 2\). Затем ширина и высота вырезанной области масштабируются до 200 пикселей. Если не указано иное, случайное число между \(a\) и \(b\) в этом разделе означает непрерывное значение, полученное путём равномерной случайной выборки из интервала \([a, b]\).

shape_aug = torchvision.transforms.RandomResizedCrop(
    (200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)

14.1.1.2. Изменение цветов

Другой метод аугментации — изменение цветов. Мы можем изменить четыре аспекта цвета изображения: яркость, контраст, насыщенность и оттенок. В примере ниже мы случайным образом изменяем яркость изображения до значения в диапазоне от 50% (\(1-0.5\)) до 150% (\(1+0.5\)) от исходного изображения.

apply(img, torchvision.transforms.ColorJitter(
    brightness=0.5, contrast=0, saturation=0, hue=0))

Аналогичным образом мы можем случайным образом изменить оттенок изображения.

apply(img, torchvision.transforms.ColorJitter(
    brightness=0, contrast=0, saturation=0, hue=0.5))

Мы также можем создать RandomColorJitter объект и задать, как одновременно случайным образом изменять brightness, contrast, saturation и hue изображения.

color_aug = torchvision.transforms.ColorJitter(
    brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)

14.1.1.3. Комбинирование нескольких методов аугментации изображений

На практике мы будем комбинировать несколько методов аугментации изображений. Например, мы можем объединить различные методы аугментации изображений, описанные выше, и применять их к каждому объекту с помощью Compose.

augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug])
apply(img, augs)

14.1.2. Обучение с аугментацией изображений

Давайте обучим модель с аугментацией изображений. В этом разделе мы используем набор данных CIFAR-10 вместо Fashion-MNIST, который применялся ранее. Это связано с тем, что положение и размер объектов в наборе данных Fashion-MNIST нормализованы, тогда как в наборе данных CIFAR-10 наблюдаются более существенные различия в цвете и размере объектов. Первые 32 обучающих изображения из набора данных CIFAR-10 приведены ниже.

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);

Чтобы получить однозначные результаты при предсказании, мы обычно применяем аугментацию изображений только к обучающим примерам и не используем случайные операции аугментации во время предсказания. Здесь мы используем лишь самый простой метод случайного горизонтального отражения. Кроме того, мы используем объект ToTensor для преобразования мини-батча изображений в формат, требуемый фреймворком глубокого обучения, то есть в 32-битные числа с плавающей запятой в диапазоне от 0 до 1 с формой (размер батча, количество каналов, высота, ширина).

train_augs = torchvision.transforms.Compose([
     torchvision.transforms.RandomHorizontalFlip(),
     torchvision.transforms.ToTensor()])

test_augs = torchvision.transforms.Compose([
     torchvision.transforms.ToTensor()])

Далее мы определяем вспомогательную функцию для упрощения чтения изображений и применения аугментации. Аргумент transform, предоставляемый набором данных PyTorch, применяет аугментацию для преобразования изображений. Подробное введение в DataLoader см. в Section 4.2.

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

14.1.2.1. Обучение на нескольких GPU

Мы обучаем модель ResNet-18 из Section 8.6 на наборе данных CIFAR-10. Вспомним введение в многопроцессорное обучение на нескольких GPU из Section 13.6. В дальнейшем мы определим функцию для обучения и оценки модели с использованием нескольких GPU.

#@save
def train_batch_ch13(net, X, y, loss, trainer, devices):
    """Train for a minibatch with multiple GPUs (defined in Chapter 13)."""
    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()):
    """Train a model with multiple GPUs (defined in Chapter 13)."""
    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):
        # Sum of training loss, sum of training accuracy, no. of examples,
        # no. of predictions
        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)}')

Теперь мы можем определить функцию train_with_data_aug для обучения модели с применением аугментации изображений. Эта функция получает все доступные GPU, использует Adam в качестве алгоритма оптимизации, применяет аугментацию изображений к обучающему набору данных и, наконец, вызывает только что определённую функцию train_ch13 для обучения и оценки модели.

batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10, 3)
net.apply(d2l.init_cnn)

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)
    net(next(iter(train_iter))[0])
    train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices)

Давайте обучим модель с использованием аугментации изображений на основе случайного зеркального отражения по горизонтали.

train_with_data_aug(train_augs, test_augs, net)

14.1.3. Резюме

  • Аугментация изображений генерирует случайные изображения на основе имеющихся обучающих данных, чтобы повысить обобщающую способность моделей.

  • Чтобы получить достоверные результаты при прогнозировании, мы обычно применяем аугментацию изображений только к обучающим примерам и не используем аугментацию со случайными операциями во время прогнозирования.

  • Фреймворки глубокого обучения предоставляют множество различных методов аугментации изображений, которые можно применять одновременно.

14.1.4. Упражнения

  1. Обучите модель, не используя аугментацию изображений: train_with_data_aug(test_augs, test_augs). Сравните точность на обучающей и тестовой выборках при использовании и без использования аугментации изображений. Может ли этот сравнительный эксперимент подтвердить утверждение о том, что аугментация изображений помогает снизить переобучение? Почему?

  2. Объедините несколько различных методов аугментации изображений при обучении модели на наборе данных CIFAR-10. Улучшает ли это точность на тестовой выборке?

  3. Обратитесь к онлайн-документации фреймворка глубокого обучения. Какие ещё методы аугментации изображений он предоставляет?

Обсуждения