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

В этой, первой статье мы рассмотрим архитектуру будущей сети, и все формулы для форварда проходят через сеть. Во второй статье мы подробно остановимся на обратном распространении ошибки, выведем и проанализируем формулы — это основная часть, именно формулы для обучения модели и особенно сверточный слой мне показались наиболее важными. самый сложный. В последней статье будет представлена ​​реализация сети на питоне, а мы попробуем обучить сеть на реальном наборе данных и сравнить результаты с аналогичной реализацией на библиотеке pytorch. На протяжении всего материала я буду показывать код python по частям, чтобы вы сразу видели реализации формул. При написании кода основных слоев сети я сосредоточился на том, чтобы формулы легко «читались» в строках, уделяя меньше времени оптимизации и красоте. В общем, конечная цель — чтобы читатель понял все тонкости обновления параметров сверточных и полносвязных сетей и получил представление о том, как может выглядеть работающий код этой архитектуры.

Чего вы не найдете в этих статьях? Объяснения основ математики и частных производных, подробности интуитивного понимания сути обратного распространения ошибки (для начала можно прочитать эту прекрасную статью) или как вообще работают сети, в том числе сверточные. Для лучшего понимания материала желательно знать эти вещи и особенно основы нейронных сетей.
Итак, приступим к первой статье.

свертка

Рассмотрим формулу свертки. Но сначала, что мы хотим видеть в формуле, что она должна отражать? Обратимся к википедии:

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

Итак, формула свертки должна показывать «движение» ядра по входному изображению или карте признаков yˡ⁻ ¹. Именно это и показывает следующая формула:

Здесь индексы i, j, a, b — это индексы элементов в матрицах, а s — значение шага свертки (шаг). Надстрочные индексы l и l-1 — это индексы сетевых слоев, поэтому:

  • xˡ⁻ ¹ — вывод какой-либо предыдущей функции или входное изображение сети.
  • yˡ⁻ ¹xˡ⁻ ¹ после прохождения функции активации — например, ReLU или Sigmoid (глава о функциях активации будет чуть позже).
  • wˡ —ядро свертки.
  • — смещение (отсутствует на картинке выше).

Таким образом, является результатом операции свертки. Операции выполняются отдельно для каждого элемента i, j матрицы размерностью (N, M).

Ниже приведена отличная иллюстрация работы свертки. yˡ⁻ ¹ отображается синим цветом, — зеленым, а серая движущаяся матрица три на три — это ядро ​​свертки :

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

Центральный элемент ядра

Итак, в зависимости от расположения центрального элемента индексируются элементы ядра. Фактически центральный элемент определяет начало «координатной оси» ядра свертки. Посмотрите на рисунок ниже: слева — ядро ​​с центральным элементом в нулевой строке и нулевом столбце, справа — в первой строке и первом столбце.

Вы видите, как изменилась индексация элементов? И да, я говорю, что во втором ядре центральный элемент «переехал» в первую строку первого столбца, то есть это произошло относительно первого ядра. На самом деле индексы центрального элемента всегда равны (0,0). А для удобства дальнейшего обсуждения я буду говорить «старые» координаты, говоря о дефолтном положении центрального элемента в левом верхнем углу; а «новые» координаты — о положении центра в любом другом месте ядра, используя нумерацию «старой» координатной сетки (например, (1,1) для правого ядра на рисунке выше).

Но, возвращаясь к формуле свертки, что она означает — сумма от минус бесконечности до плюс бесконечности? Ведь само ядро ​​имеет вполне определенные размеры и не имеет бесконечного числа элементов. Я нашел разные способы записи формулы: например, сумму от -a до a и от -b до b (здесь и здесь). Я также нашел варианты с бесконечностью, как в формуле в начале статьи (здесь и здесь). Но последняя мне показалась более общей формулировкой.

Минус в формуле ядра свертки является следствием расположения центрального элемента. Мы должны «перебрать» все возможные существующие элементы, и мы можем начать с минус бесконечности. Или из минус a и b. Если элемент не определяется этими индексами, он умножается на ноль. На самом деле операции начинаются не с минус бесконечности, а с положения центра, умноженного на минус единицу (в нумерации «старых» координат). И операция завершится не при плюс бесконечности, а при значении разницы между размером ядра по оси минус индекс центрального элемента (опять же, в нумерации «старой» координатной оси). При этом последнее значение результирующего диапазона не включается (поскольку индексация идет от нуля).

На словах это может звучать сложно, и, вероятно, лучше всего посмотреть, как это можно реализовать на питоне. Ниже ссылка на блокнот jupyter для расчета индексов новой оси ядра в зависимости от выбранного центрального элемента: recalculating_kernel_indexes.ipynb

На картинке ниже мы объявили центральный элемент ядра в позиции (1,1).

Но «старые» координаты говорят нам о том, что положение центрального элемента должно располагаться по индексам (0,0), а значит, необходимо переопределить оси координат для нового положения центра.

Если мы подставим наши значения в код выше, мы получим список python, заполненный значениями из диапазона (-1, 2) — то есть список будет содержать [-1,0,1]. Еще раз, почему диапазон (-1, 2)? «Минус один» потому, что операция начинается с минус-индекса нашего центрального элемента, а «два» получается как длина оси (равная трем) минус индекс центрального элемента в «старых» координатах (т.е. , один). Последний элемент диапазона не включается.

Взаимная корреляция

Приведу еще раз формулу свертки:

А ниже приведена формула взаимной корреляции:

Да, разница только в том, что минусы при расчете индексов yˡ⁻ ¹ заменяются плюсами. На практике, применяя формулу свертки, мы видим, что ядро ​​«переворачивается» при свертке (причем переворачивается относительно центрального элемента!), в то время как при кросс-корреляции элементы ядра сохраняют свое положение во время свертки. Посмотрите на иллюстрацию, чтобы лучше понять, что здесь имеется в виду:

Здесь видно положение ядра, его расположение при свертке относительно матрицы yˡ⁻ ¹. Ниже jupyter с примерами, аналогичными тем, что на картинке выше, только уже для всех i и j:demo_of_conv_feed.ipynb

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

В зависимости от метода свертки (свертка или кросс-корреляция), разных размеров шага и выбора центрального элемента ядра — размерность выходной матрицы xˡ может различаться. В простейшем случае при шаге, равном единице, размерность матрицы xˡ будет равна yˡ⁻ ¹. Общую формулу для расчета размерности матрицы xˡ можно взять из документации pytorch к Conv2d:

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

Функции активации

Функция активации позволяет сделать сеть нелинейной. И если бы мы не использовали функции активации (тогда получилось бы, что yˡ=xˡ) или использовали линейную функцию, то не имело бы значения, сколько слоев было бы в сети: они могли бы все заменить одним слоем с линейной функцией активации. Ниже приведены формулы функций активации, которые можно использовать в будущей модели. На самом деле это просто превращение в следующим образом: yˡ=f(xˡ).

Сигмоид используется только в том случае, если классов не более двух (для задачи классификации): на выходе модели будет число от нуля (первый класс) до единицы (второй класс). Для большего количества классов используется softmax: чтобы выход модели отражал вероятность этих классов (а сумма вероятностей для выходов сети была равна единице).

Здесь n — количество классов. Функция выглядит простой, но при расчете формул обратного распространения возникнут некоторые трудности.

Малксоулирующий слой

Malxooling извлекает из карт признаков важные признаки, придает инвариантность положению объекта на изображении, а также уменьшает размерность карт, ускоряя время работы сети.

Код макспулинга очень похож на свертку, и даже сохранены те же параметры: выбор шага, флаг операции свертки или кросс-корреляции (поскольку по логике этой функции окно макспулинга совпадает с ядром свертки) и выбором центрального элемента. Но, разумеется, здесь нет поэлементного перемножения матриц, а только, по сути, выбор максимального значения из заданного окна. Классическими значениями параметров maxpooling являются взаимная корреляция и положение центрального элемента в левом верхнем углу. Демонстрация макспулинга в питоне: demo_of_maxpooling.ipynb.

Функция из демонстрационного кода возвращает две матрицы — выходную матрицу меньшей размерности и еще одну матрицу с координатами элементов, которые были выбраны как максимальные из исходной матрицы при операции maxpooling. Вторая матрица будет использоваться во время обратного распространения ошибки.

Полностью связанный слой

После слоев свертки мы получим набор карт объектов. Мы переформируем их в один вектор и скормим этот вектор полносвязному слою.

Формула для полносвязного слоя выглядит так:

А в матричном виде (ниже размеры матриц):

Те же матрицы в «открытом» виде:

Функции потерь

Завершающим этапом сети является функция, оценивающая качество всей модели. Функция потерь находится в самом конце, после всех слоев сети. Это может выглядеть так (среднеквадратичная ошибка):

¹⁄₂ здесь нужен только для сокращения формулы при обратном распространении ошибки. Можно убрать ¹⁄₂ и ничего принципиально не изменится.

Прочитав эту статью, я решил использовать кросс-энтропию:

Кросс-энтропия сильнее «наказывает» за неправильный ответ (когда модель слабо уверена в выборе действительно правильного класса). Но MSE, в свою очередь, хорошо подходит для регрессии.

Структура будущей модели

Проанализировав основные слои сети, мы можем представить примерный вид будущей модели:

  1. Функция, извлекающая изображение/партию из набора данных.
  2. Первый слой сверточной сети, принимающий на вход изображение/партию, на выходе выдает карты признаков.
  3. Слой максимального объединения, который уменьшает размерность карт объектов.
  4. Второй слой сверточной сети принимает карты объектов и дает на выходе другие карты объектов.
  5. Преобразуйте карты объектов в один вектор.
  6. Первый слой полносвязной сети принимает вектор и выполняет вычисления, которые на выходе дают значения для следующего скрытого полносвязного слоя.
  7. Далее следует второй слой полносвязной сети, количество выходных нейронов которого равно количеству классов в используемом наборе данных.
  8. Выходные данные всей модели подаются в функцию потерь, которая сравнивает прогнозируемое значение с истинным значением и вычисляет разницу между ними.

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

Следующие статьи цикла: