В этом блоге объясняется, что происходит за сверточными сетями и как они работают. Буду использовать библиотеки fastai и PyTorch. Если вы хотите узнать о реализации fastai cnn более широко, обратитесь к статье здесь. Приступим.

❓ Как создать сверточную нейронную сеть

Если вы знакомы с fastai и, в частности, с компьютерным зрением в fastai, то вы знаете, как мы создаем сверточную нейронную сеть. Используем для создания сети. Если вы хотите узнать о семантике create_cnn, обратитесь к ссылке, указанной во введении.

Теперь у нас есть CNN, и мы хотим знать, что происходит в CNN. То, что происходит за кулисами, показано ниже:

Теперь вместо умножения матриц в сверточных нейронных сетях происходит свертка. Свертка - это тоже тип умножения матриц, но у нее есть и другие магнитные свойства. Давайте углубимся в сверточную нейронную сеть.

  • Сверточная нейронная сеть состоит из ядра. Ядро - это другая матрица любого размера, например 2X2, 3X3, 4X4 или 1X1. В этой матрице есть несколько чисел, которые в основном определяют конкретную функцию.
  • Под конкретной функцией я имею в виду, что ядро ​​в первом слое может отфильтровывать верхние края входной матрицы (матрица пикселей, представляющая изображение), ядро ​​во втором слое может фильтровать левые углы, ядро ​​в третьем слой может отфильтровывать диагональные узоры и так далее.
  • Теперь, когда входная матрица умножается на ядро, полученный результат известен как Channel. Теперь слоев может быть сколько угодно. ResNet34 имеет 34 уровня вышеупомянутой операции.
  • Итак, по сути, мы могли бы сказать, что у нас есть много уровней умножения матриц, и в каждом слое умножения матриц мы умножаем ядро ​​на входную матрицу пикселей, и мы получаем канал на выходе.

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

Давайте разберемся в описанной выше операции схематически.

Количество формируемых уравнений приведено ниже:

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

Теперь мы можем понять это традиционным способом нейронной сети, как показано ниже:

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

Теперь рассмотрим еще один случай сверточной нейронной сети. Что, если наша входная матрица и ядро ​​имеют одинаковый размер?
Есть два варианта действий в таких ситуациях:

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

Другими словами, свертка - это просто умножение матриц, при котором происходят две вещи:

  • некоторые записи постоянно обнуляются
  • одинаковые веса ядра умножаются для вычисления разных каналов

Поэтому, когда у вас есть несколько вещей с одинаковым весом, это называется привязка веса.

Это большая часть теоретического понимания сверточных нейронных сетей. Теперь давайте разберемся со сверточными нейронными сетями с практической точки зрения.

  • На самом деле у нас есть входные 3D-изображения, а не 2D-изображения. Каждое изображение имеет разные красные, зеленые и синие пиксели. Итак, вместо тензорного ядра второго ранга у нас есть тензорное ядро ​​третьего ранга, представляющее разные значения для красного, зеленого и синего. Итак, вместо поэлементного умножения 9 вещей (если у нас есть 2D-ядро с девятью значениями), мы собираемся поэлементно умножить 27 вещей (3 на три на 3), и мы все еще собираюсь затем сложить их в одно число.
  • Теперь, когда мы сворачиваем изображение, мы хотим найти не только верхние края, но и другие вещи, такие как обнаружение повторов, градиентов цветов в изображении и т. Д. Чтобы охватить все различные функции, нам нужно все больше и больше ядер, и вот что происходит на самом деле. В каждом слое мы обрабатываем изображение, используя множество ядер. Таким образом, каждый слой состоит из большого количества каналов.
  • Чтобы наша память не вышла из-под контроля из-за большого количества каналов, время от времени мы создаем свертку, в которой мы не переходим через каждый набор 3x3 (учитывая размер ядра), а вместо этого пропускаем по два за раз. Мы начинаем с 3x3 с центром в (2, 2), а затем перескакиваем на (2, 4), (2, 6), (2, 8) и так далее. Это называется сверткой с двумя шагами. Что это значит, это выглядит так же, это все еще просто куча ядер, но мы просто перепрыгиваем через два за раз. Мы пропускаем все остальные входные пиксели. Таким образом, результат будет H / 2 на W / 2. (Мы можем определить свертку stride-n)

Давайте посмотрим на свертку "шаг-4".

Теперь давайте оценим набор данных MNIST и воспользуемся нашей сверточной нейронной сетью. Я использовал Google Colab в практических целях.

from fastai.vision import *

Fastai предоставляет академические наборы данных, и мы можем распаковать их и использовать.

path = untar_data(URLs.MNIST)
path.ls()

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

Первое, что вы говорите, это то, какой у вас список предметов. Итак, в данном случае это список изображений. Тогда откуда вы берете список имён файлов? В этом случае у нас есть папки.

imagelist = ImageList.from_folder(path); imagelist

Итак, внутри списка элементов находится атрибут items, а атрибут items - это то, что вы ему дали. Это то, что он будет использовать для создания ваших предметов. Итак, в этом случае вы дали ему список имен файлов. Вот что досталось из папки.

imagelist.items

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

defaults.cmap='binary'
imagelist[22].show()

Когда у вас есть список элементов изображения, вы затем разбиваете его на обучение и проверку. Вы почти всегда хотите подтверждения. Если вы этого не сделаете, вы можете использовать метод .no_split для создания пустого набора для проверки. Вы не можете полностью это пропустить. Все это определено в fastai data block API.

splitData = imagelist.split_by_folder(train='training', valid='testing'); splitData

Таков всегда порядок. Сначала создайте свой список предметов, а затем решите, как его разделить. В данном случае мы будем делать это на основе папок. Папка проверки для MNIST называется testing, поэтому мы также упоминаем об этом в методе.

Теперь мы хотим пометить наши данные, и мы хотим пометить данные, используя папку, в которой находятся наши данные.

labelist = splitData.label_from_folder()

Итак, сначала вы создаете список элементов, затем разделяете его, а затем маркируете его.

x,y = labelist.train[0] or labelist.valid[0]
x.show()
print(x.shape, y)

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

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

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

tfms = ([*rand_pad(padding=3, size=28, mode='zeros')], [])
(empty array refers to the validaion set transforms)
transformedlist = labelist.transform(tfms)

Настало время для последнего шага, и он должен создать группу данных. Здесь я не использую статистику изображений для нормализации, так как я не использую предварительно обученные модели, такие как ResNet34, ResNet56 и т. Д. Кроме того, я буду использовать размер пакета 128.

bs = 128
data = transformedlist.databunch(bs=bs).normalize()
x,y = data.train_ds[0]
x.show()
print(y)

Что наиболее интересно, теперь набор обучающих данных дополнен данными, потому что мы добавили преобразования. plot_multi - это функция fast.ai, которая отображает результат вызова некоторой функции для каждого из элементов.

def _plot(i,j,ax): data.train_ds[0][0].show(ax, cmap='gray')
plot_multi(_plot, 3, 3, figsize=(7, 7))

xb,yb = data.one_batch()
xb.shape,yb.shape

data.show_batch(rows=3, figsize=(5,5))

Теперь мы закончили с набором данных. Теперь мы создадим обучаемого и обучим его через нашу собственную CNN.

Базовая CNN с пакетной нормализацией

def conv(ni,nf): return nn.Conv2d(ni, nf, kernel_size=3, stride=2, padding=1)
model = nn.Sequential(
    conv(3, 8), # 14
    nn.BatchNorm2d(8),
    nn.ReLU(),
conv(8, 16), # 7
    nn.BatchNorm2d(16),
    nn.ReLU(),
conv(16, 32), # 4
    nn.BatchNorm2d(32),
    nn.ReLU(),
conv(32, 16), # 2
    nn.BatchNorm2d(16),
    nn.ReLU(),
conv(16, 10), # 1
    nn.BatchNorm2d(10),
    Flatten()     # remove (1,1) grid
)

Давайте разберемся с вышеуказанной функцией.

  • Мы заявляем, что размер ядра составляет 3 * 3.
  • Мы хотим выполнить свертку шага-2.
  • Теперь мы хотим выполнить последовательную операцию, поэтому мы написали nn.Sequential.
  • Первый слой модели - conv (3, 8). 3 означает количество входных каналов. Поскольку у нашего изображения три входных канала, мы объявили это число. См. Изображение ниже.

  • 8 - общее количество каналов на выходе. Это число подразумевает общее количество фильтров, как описано в предыдущем разделе.
  • Количество каналов на выходе одного уровня вводится в следующий уровень. Мы уже упоминали об использовании свертки с шагом 2. Поэтому мы начали с размера изображения 28 * 28. На втором слое он уменьшится до 14 * 14, на следующем слое до 7 * 7, затем до 4 * 4, затем до 2 * 2 и, наконец, до 1 * 1.
  • Вывод будет в форме [128, 10, 1, 1] - каждое изображение в пакете из 128 имеет десять каналов 1 * 1 на выходе в качестве тензоров третьего ранга. Мы сглаживаем его до тензора одного ранга.
  • Между слоями свертки мы добавили пакетную нормализацию и ReLu как нелинейный слой.

Вот и все (͡ᵔ ͜ʖ ͡ᵔ), мы создали нашу сверточную нейронную сеть.

Пришло время создать ученика, как это определено в фастае.

learn = Learner(data, model, loss_func = nn.CrossEntropyLoss(), metrics=accuracy)
learn.summary()

learn.lr_find(end_lr=100)
learn.recorder.plot()

learn.fit_one_cycle(10, max_lr=0.1)

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

❓ Что такое ResNet

Пусть X будет вне ввода. Согласно ResNet, вместо того, чтобы делать как

Y = conv2(conv1(X)),

Это так,

Y = X + conv2(conv1(X)) - Это называется идентификационным соединением или пропуском соединения.

ResNet радикально улучшает поверхность функции потерь. Без ResNets функция потерь имеет множество неровностей, а с ResNet она превращается в сглаженную.

Мы можем создать ResBock, как показано ниже:

class ResBlock(nn.Module):
    def __init__(self, nf):
        super().__init__()
        self.conv1 = conv_layer(nf,nf)
        self.conv2 = conv_layer(nf,nf)
        
    def forward(self, x): return x + self.conv2(self.conv1(x))

Давайте изменим нашу модель, чтобы включить блоки ResNet. Давайте немного порефакторим это. Вместо того, чтобы все время говорить conv, пакетная норма, ReLU, в fast.ai уже есть что-то под названием conv_layer, которое позволяет вам создавать комбинации conv, пакетной нормы и ReLU.

def conv2(ni,nf): return conv_layer(ni,nf,stride=2)
model = nn.Sequential(
    conv2(1, 8),
    res_block(8),
    conv2(8, 16),
    res_block(16),
    conv2(16, 32),
    res_block(32),
    conv2(32, 16),
    res_block(16),
    conv2(16, 10),
    Flatten()
)
learn = Learner(data, model, loss_func = nn.CrossEntropyLoss(), metrics=accuracy)
learn.fit_one_cycle(12, max_lr=0.05)

Это все. Надеюсь, вы поняли логику CNN и ResNets.