Серия PyTorch для тех, кто начинает с глубокого обучения. Следуя подходу, основанному на реализации различных известных архитектур.

Введение

Архитектура Alexnet была прорывом на момент ее публикации, обеспечив минимальные потери в задаче классификации ImageNet. Он использует последовательные сверточные блоки с некоторыми полносвязными слоями для задачи классификации. В этой статье мы разбираемся в архитектуре и кодируем ее в PyTorch.

Архитектура

На блок-схеме показана основная схема процесса.

Входное изображение имеет размер 227, ширину и высоту с 3 цветовыми каналами, то есть RGB. Они проходят через ряд сверточных блоков, состоящих из слоев свертки, ReLU, нормализации и объединения. Затем выходные данные выравниваются до одномерного массива и проходят через несколько плотных слоев. Результатом является одномерный массив, размер вектора которого представляет общее количество классов.

Точные размеры и детали взяты непосредственно из документа Alex Net.

Слои объединения уменьшают размер входных данных, а сверточные фильтры изменяют общее количество каналов во входных данных. Выход сверточных блоков представляет собой тензор (256,6,6), сглаженный до одного измерения, то есть размерный вектор 256x6x6, равный 9216. Вектор проходит через два плотных слоя, состоящих из 4096 нейронов. Последний полносвязный слой уменьшает общее количество выходных нейронов до количества возможных классов.

Сверточные слои

Если мы посмотрим на сверточные слои внутри, то увидим 5 подобных блоков, каждый из которых состоит из одинаковых слоев.

Вход в первый блок представляет собой изображение RGB с 3 цветовыми каналами. Каждый блок последовательно обрабатывает входные данные и передает выходные данные следующему блоку. Выходные размеры каждого блока показаны на изображении.

Сверточный блок

Блок-схема показывает слои, из которых состоит каждый блок.

Каждый блок, кроме 3 и 4, имеет указанную выше структуру. Слои 3 и 4 не имеют слоев нормализации и объединения в конце.

Сверточные размеры

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

Нормализация

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

Обратитесь к документации, чтобы узнать, что представляют собой эти параметры.

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

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

Выполнение

Мы начинаем нашу реализацию со сверточной блочной структуры, которую можно обобщить на все 5 блоков. Это может обеспечить модульность и повторное использование при реализации полной архитектуры Alex Net. Это уменьшает дублирование кода.

class AlexNetBlock(nn.Module):
 def __init__(
     self,
     in_channels,
     out_channels,
     kernel_size,
     stride,
     padding,
     pool_and_norm: bool = True
 ) -> None:

     super(AlexNetBlock, self).__init__()
     self.conv_layer = nn.Conv2d(
         in_channels, out_channels, kernel_size, stride, padding)
     self.relu = nn.ReLU()

     self.pool_and_norm = pool_and_norm
     if pool_and_norm:
         self.norm_layer = nn.LocalResponseNorm(
             size=5, alpha=0.0001, beta=0.75, k=2)
         self.pool_layer = nn.MaxPool2d(stride=2, kernel_size=3)

 def forward(self, x):
     x = self.conv_layer(x)
     x = self.relu(x)

     if self.pool_and_norm:
         x = self.norm_layer(x)
         x = self.pool_layer(x)
     return x

Код говорит сам за себя, где мы получаем параметры для сверточного слоя. Кроме того, мы используем логическое значение pool_and_norm, для которого будет установлено значение False для блоков 3 и 4. Вышеупомянутый блок можно использовать во всей архитектуре Alex Net.

В приведенном ниже коде показана полная модель Alex Net, в которой используется указанный выше блок.

class AlexNet(nn.Module):
 def __init__(self, num_classes, in_channels) -> None:
     super(AlexNet, self).__init__()

     self.block1 = AlexNetBlock(
         in_channels, 96, 11, 4, 0, pool_and_norm=True)
     self.block2 = AlexNetBlock(96, 256, 5, 1, 2, pool_and_norm=True)
     self.block3 = AlexNetBlock(256, 384, 3, 1, 1, pool_and_norm=False)
     self.block4 = AlexNetBlock(384, 384, 3, 1, 1, pool_and_norm=False)
     self.block5 = AlexNetBlock(384, 256, 3, 1, 1, pool_and_norm=True)

     self.flatten = nn.Flatten()
     self.fc1 = nn.Linear(256 * 6 * 6, 4096)
     self.dropout1 = nn.Dropout(0.5)
     self.fc2 = nn.Linear(4096, 4096)
     self.dropout2 = nn.Dropout(0.5)
     self.classification_layer = nn.Linear(4096, num_classes)

 def forward(self, x):
     x = self.block1(x)
     x = self.block2(x)
     x = self.block3(x)
     x = self.block4(x)
     x = self.block5(x)

     x = self.flatten(x)
     x = self.fc1(x)
     x = self.dropout1(x)
     x = self.fc2(x)
     x = self.dropout2(x)
     x = self.classification_layer(x)
     return x

В соответствии с блок-схемой мы создаем 5 блоков Alex Net с описанной конфигурацией. Затем мы последовательно пропускаем входные данные через все слои, пока не получим одномерный массив, представляющий вероятность каждого возможного класса.

Для этого конкретного случая нам нужны два параметра инициализации для AlexNet. Один из них — это входные каналы, которых по умолчанию три для изображений RGB. Второе — это общее количество классов, которое определяет размер нашего вывода. В документе для задачи Image Net использовалось значение 1000. Для своей реализации я использую набор данных CIFAR-10, поэтому я установил его на десять.

На изображении ниже показана архитектура Alex Net.

Заключение

Вышеупомянутая архитектура может быть обучена для задачи классификации с нуля. Я тренировал его для набора данных CIFAR-10 в течение десяти эпох. На приведенном ниже графике показана динамика потерь.

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

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