Предыдущий ‹‹ Введение в компьютерное зрение с помощью PyTorch (3/6)

Многоуровневые CNN

В предыдущем разделе мы узнали о сверточных фильтрах, которые могут извлекать шаблоны из изображений. Для нашего классификатора MNIST мы использовали девять фильтров 5 × 5, в результате чего получился тензор 9 × 24 × 24.

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

!wget https://raw.githubusercontent.com/MicrosoftDocs/pytorchfundamentals/main/computer-vision-pytorch/pytorchcv.py
import torch
import torch.nn as nn
import torchvision
import matplotlib.pyplot as plt
from torchinfo import summary
import numpy as np

from pytorchcv import load_mnist, train, plot_results, plot_convolution, display_dataset
load_mnist(batch_size=128)

Объединение слоев

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

При этом нам также нужно применить одну хитрость: уменьшить пространственный размер изображения. Как только мы обнаружили горизонтальную обводку в скользящем окне, уже не так важно, в каком конкретно пикселе она произошла. Таким образом, мы можем «уменьшить» размер изображения, что делается с помощью одного из слоев объединения:

  • Среднее объединение берет скользящее окно (например, 2 × 2 пикселя) и вычисляет среднее значение значений в этом окне.
  • Максимальное объединение заменяет окно максимальным значением. Идея максимального пула заключается в обнаружении присутствия определенного шаблона в скользящем окне.

В типичной CNN она будет состоять из нескольких сверточных слоев с объединением слоев между ними для уменьшения размеров изображения. Мы бы также увеличили количество фильтров, потому что по мере того, как шаблоны становятся более продвинутыми — появляется больше возможных интересных комбинаций, которые нам нужно искать. Из-за уменьшения пространственных размеров и увеличения размеров функций/фильтров эту архитектуру также называют пирамидальной архитектурой.

В следующем примере мы будем использовать двухслойную CNN:

class MultiLayerCNN(nn.Module):
    def __init__(self):
        super(MultiLayerCNN, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(10, 20, 5)
        self.fc = nn.Linear(320,10)

    def forward(self, x):
        x = self.pool(nn.functional.relu(self.conv1(x)))
        x = self.pool(nn.functional.relu(self.conv2(x)))
        x = x.view(-1, 320)
        x = nn.functional.log_softmax(self.fc(x),dim=1)
        return x

net = MultiLayerCNN()
summary(net,input_size=(1,1,28,28))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
├─Conv2d: 1-1                            [1, 10, 24, 24]           260
├─MaxPool2d: 1-2                         [1, 10, 12, 12]           --
├─Conv2d: 1-3                            [1, 20, 8, 8]             5,020
├─MaxPool2d: 1-4                         [1, 20, 4, 4]             --
├─Linear: 1-5                            [1, 10]                   3,210
==========================================================================================
Total params: 8,490
Trainable params: 8,490
Non-trainable params: 0
Total mult-adds (M): 0.47
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.06
Params size (MB): 0.03
Estimated Total Size (MB): 0.09
==========================================================================================

Обратите внимание на несколько моментов в определении:

  • Вместо использования слоя Flatten мы сглаживаем тензор внутри функции forward с помощью функции view, которая аналогична reshape. > функция в numpy. Поскольку у выравнивающего слоя нет обучаемых весов, нам не требуется создавать отдельный экземпляр слоя внутри нашего класса — мы можем просто использовать функцию из пространства имен torch.nn.functional.
  • В нашей модели мы используем только один экземпляр слоя Pooling, в том числе потому, что он не содержит никаких обучаемых параметров, и, следовательно, один экземпляр можно эффективно использовать повторно.
  • Количество обучаемых параметров (~8,5 тыс.) значительно меньше, чем в предыдущих случаях (80 тыс. в персептроне, 50 тыс. в однослойной CNN).
    Это происходит потому, что сверточные слои обычно имеют мало параметров, независимо от размера входного изображения. Кроме того, из-за объединения размерность изображения значительно уменьшается перед нанесением окончательного плотного слоя. Небольшое количество параметров положительно влияет на наши модели, поскольку помогает предотвратить переобучение даже при меньших размерах наборов данных.
hist = train(net,train_loader,test_loader,epochs=5)
Epoch  0, Train acc=0.949, Val acc=0.978, Train loss=0.001, Val loss=0.001

Вероятно, вам следует заметить, что мы можем достичь более высокой точности и гораздо быстрее — всего за 1 или 2 эпохи. Это означает, что сложной сетевой архитектуре требуется гораздо меньше данных, чтобы понять, что происходит, и извлечь общие закономерности из наших изображений.

Изучите набор данных CIFAR-10

Давайте загрузим набор реальных изображений различных объектов, который называется
CIFAR-10. Он содержит 60 тыс. цветных изображений размером 32×32, разделенных на 10 классов.

transform = torchvision.transforms.Compose(
    [torchvision.transforms.ToTensor(),
     torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=14, shuffle=True)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=14, shuffle=False)
classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
display_dataset(trainset,classes=classes)

Известная архитектура CIFAR-10 называется LeNet и была предложена Яном ЛеКуном. Он следует тем же принципам, которые мы изложили выше. Однако, поскольку все изображения являются цветными, размер входного тензора составляет
3 × 32 × 32, а сверточный фильтр 5 × 5 применяется и к цветовому измерению — это означает, что размер матрицы ядра свертки составляет
3×5×5.

Мы также делаем еще одно упрощение этой модели — мы не используем log_softmax в качестве функции активации вывода, а просто возвращаем выходные данные последнего полностью подключенного слоя. В этом случае мы можем просто использовать функцию потерь CrossEntropyLoss для оптимизации модели.

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.conv3 = nn.Conv2d(16,120,5)
        self.flat = nn.Flatten()
        self.fc1 = nn.Linear(120,64)
        self.fc2 = nn.Linear(64,10)

    def forward(self, x):
        x = self.pool(nn.functional.relu(self.conv1(x)))
        x = self.pool(nn.functional.relu(self.conv2(x)))
        x = nn.functional.relu(self.conv3(x))
        x = self.flat(x)
        x = nn.functional.relu(self.fc1(x))
        x = self.fc2(x)
        return x

net = LeNet()

summary(net,input_size=(1,3,32,32))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
├─Conv2d: 1-1                            [1, 6, 28, 28]            456
├─MaxPool2d: 1-2                         [1, 6, 14, 14]            --
├─Conv2d: 1-3                            [1, 16, 10, 10]           2,416
├─MaxPool2d: 1-4                         [1, 16, 5, 5]             --
├─Conv2d: 1-5                            [1, 120, 1, 1]            48,120
├─Flatten: 1-6                           [1, 120]                  --
├─Linear: 1-7                            [1, 64]                   7,744
├─Linear: 1-8                            [1, 10]                   650
==========================================================================================
Total params: 59,386
Trainable params: 59,386
Non-trainable params: 0
Total mult-adds (M): 0.65
==========================================================================================
Input size (MB): 0.01
Forward/backward pass size (MB): 0.05
Params size (MB): 0.24
Estimated Total Size (MB): 0.30
==========================================================================================

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

Чтобы добиться лучших результатов обучения, нам может потребоваться поэкспериментировать с некоторыми параметрами обучения, такими как скорость обучения. Таким образом, мы явно определяем здесь оптимизатор Sстохастического градиентного спуска (SGD) и передаем параметры обучения. Вы можете настроить эти параметры и наблюдать, как они влияют на тренировку.

opt = torch.optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
hist = train(net, trainloader, testloader, epochs=3, optimizer=opt, loss_fn=nn.CrossEntropyLoss())
Epoch  0, Train acc=0.261, Val acc=0.388, Train loss=0.143, Val loss=0.121
Epoch  1, Train acc=0.437, Val acc=0.491, Train loss=0.110, Val loss=0.101
Epoch  2, Train acc=0.508, Val acc=0.522, Train loss=0.097, Val loss=0.094

Точность, которой нам удалось достичь за 3 эпохи обучения, не кажется большой. Однако помните, что слепое угадывание даст нам только 10% точности, и что наша задача на самом деле значительно сложнее, чем классификация цифр MNIST. Достижение точности выше 50% за такое короткое время обучения кажется хорошим достижением.

Вынос

В этом модуле мы изучили основную концепцию нейронных сетей компьютерного зрения — сверточные сети. Реальные архитектуры, которые обеспечивают классификацию изображений, обнаружение объектов и даже сети генерации изображений, основаны на CNN, только с большим количеством слоев и некоторыми дополнительными приемами обучения.

Приятного обучения!
Далее ›› Введение в компьютерное зрение с помощью PyTorch (5/6)