геометрическим глубинным обучением(в качестве короткого введения рекомендуем посмотреть вот этот keynote с ICLR 2021), которое ставит своей целью исследование общих принципов, связывающих устойчивость к различным преобразованием и современные нейросетевые архитектуры (авторы сравнивают свои идеи с эрлангенской программой Феликса Кляйна – отсюда название).
Поговорим о том, как через свёрточный слой протекают градиенты. Нам нужно будет научиться градиент по выходу превращать в градиент по входу и в градиент по весам из ядра.
Начнём с иллюстрации для одномерной свёртки с одним входным каналом, ядром длины $3$ с дополнением по бокам нулями. Заметим, что её можно представить в виде матричного умножения:
$$(x_1,\ldots,x_d) \ast (w_{-1},w_0,w_1) = $$
$$= (0,x_1,\ldots,x_d,0) \cdot\begin{pmatrix} w_{-1} & & & & \\ w_0 & w_{-1} & & & \\ w_1 & w_0 & w_{-1} & & \\ & w_1 & w_0 & \ddots & \\ & & w_1 & \ddots & w_{-1} \\ & & & \ddots & w_0 \\ & & & & w_1 \\ \end{pmatrix} = $$
$$= (x_1,\ldots,x_d) \cdot\begin{pmatrix} w_0 & w_{-1} & & & & & \\ w_1 & w_0 & w_{-1} & & & & \\ & w_1 & w_0 & w_{-1} & && \\ & & w_1 & w_0 & \ddots & & \\ & & & w_1 & \ddots & w_{-1} & \\ & & & & \ddots & w_0 & w_{-1} \\ & & & & & w_1 & w_0 \\ \end{pmatrix} = $$
Обозначим последнюю матрицу через $\widehat{W}$, а ядро свёртки через $W$. Что происходит с градиентом при переходе через матричное умножение, мы уже отлично знаем. Градиент по весам равен
$$\nabla_{X_0}\mathcal{L} = \nabla_{X_0\ast W}\mathcal{L}\cdot\widehat{W}^T$$
Разберёмся, что из себя представляет умножение на $\widehat{W}^T$ справа. Эта матрица имеет вид
$$\begin{pmatrix} w_0 & w_1 & & & & & \\ w_{-1} & w_0 & w_1 & & & & \\ & w_{-1} & w_0 & w_1 & && \\ & & w_{-1} & w_0 & \ddots & & \\ & & & w_{-1} & \ddots & w_1 & \\ & & & & \ddots & w_0 & w_1 \\ & & & & & w_{-1} & w_0 \\ \end{pmatrix}$$
Она тоже соответствует свёртке, только:
Вопрос на подумать. Поменяется ли что-нибудь, если исходный вектор не дополнять нулями?
Общий случай
Рассмотрим теперь двумерную свёртку, для простоты нечётного размера и без свободного члена
$$(X\ast W)_{ijc} = \sum_{p=1}^{c_{\text{in}}}\sum_{k_1 = -k}^k\sum_{k_2=-k}^kW^{c}_{k+1+k_1, k+1+k_2, p}X_{i + k_1, j + k_1, p}$$
$$\frac{\partial\mathcal{L}}{\partial X_{stl}} = \sum_{i, j, c}\frac{\partial (X\ast W)_{ijc}}{\partial X_{stl}}\cdot\frac{\partial\mathcal{L}}{\partial(X\ast W)_{ijc}}$$
Разберёмся с производной $\frac{\partial (X\ast W)_{ijc}}{\partial X_{stl}}$. Во всей большой сумме из определения свёртки для $(X\ast W)_{ijc}$ элемент $X_{stl}$ может встретиться в позициях $X_{i+k_1, j+k_2, l}$ при $i + k_1 = s$, $j + k_2 = t$ и всевозможных $c$, причём это возможно лишь если $k_1 = s - i\in\{-k,\ldots,k\}$, $k_2 = t - j\in\{-k,\ldots,k\}$ (для всех остальных $(X\ast W)_{ijc}$ производная по $X_{stl}$ нулевая). Соответствующий коэффициент при $X_{stl}$ будет равен $W_{k + 1 + k_1, k + 1 + k_2, c}$. Таким образом, производная будет иметь вид:
$$\frac{\partial\mathcal{L}}{\partial X_{stl}} = \sum_{c=1}^{c_{\text{out}}}\sum_{k_1=-k}^k\sum_{k_2=-k}^kW_{k + 1 + k_1, k + 1 + k_2, c}\cdot\frac{\partial\mathcal{L}}{\partial(X\ast W)_{s - k_1, t - k_2, c}}$$
Легко заметить, что это тоже свёртка, но поскольку индексы $k_1, k_2$ в $W$ и в $\frac{\partial\mathcal{L}}{\partial(X\ast W)}$ стоят с разными знаками, получаем, что
$$\color{blue}{\nabla_{X}\mathcal{L} = W\text{[::-1,::-1,:]}\ast\nabla_{X\ast W}\mathcal{L}}$$
$$\frac{\partial\mathcal{L}}{\partial W^q_{ab}} = \sum_{i, j, c}\frac{\partial (X\ast W)_{ijc}}{\partial W^q_{ab}}\cdot\frac{\partial\mathcal{L}}{\partial(X\ast W)_{ijc}}$$
В формуле для $(X\ast W)_{ijc}$ элемент $W^q_{ab}$ может встретиться в позициях $W^q_{k + 1 + k_1, k + 1 + k_2}$, для $k + 1 + k_1 = a$, $k + 1 + k_2 = b$, с коэффициентами $X_{i + k_1, j + k_2, p}$ (для любых $p$). Значит, производная будет иметь вид:
$$\frac{\partial\mathcal{L}}{\partial W^q_{ab}} = \sum_{p=1}^{c_{\text{in}}}\sum_{i=1}^H\sum_{j=1}^WX_{a - k - 1, b - k - 1, p}\cdot\frac{\partial\mathcal{L}}{\partial(X\ast W)_{a - k - 1, b - k - 1, q}}$$
В этой формуле тоже нетрудно узнать свёртку:
$$\color{blue}{\nabla_{W}\mathcal{L} = X\ast\nabla_{X\ast W}\mathcal{L}}$$
Вопрос на подумать. Если всё-таки есть свободные члены, как будет выглядить градиент по $b_c$?
Наигравшись с нашими мысленными экспериментами, давайте обратимся к опыту инженеров и исследователей, который копился с 2012 года (alexnet), чтобы разобраться с тем, как эффективней всего строить картиночные нейронки. Здесь будут перечислены самые важные, по мнению автора, на момент написания параграфа, блоки.
Каждая из $C$ свёрток очередного свёрточного слоя – это новая карта признаков для нашего изображения, и нам, конечно, хотелось бы, чтобы таких карт было побольше: ведь это позволит нам выучивать больше новых закономерностей. Но для картинок в высоком разрешении это может быть затруднительно: слишком уж много будет параметров. Выходом оказалось использование следующей эвристики: сначала сделаем несколько свёрток с $C_1$ каналами, а затем как-нибудь уменьшим нашу карту признаков в 2 раза и одновременно увеличим количество свёрток во столько же. Посчитаем, как в таком случае изменится число параметров: было $H \times W \times K \times K \times C_1 \times C_1$, стало $(H/2) \times (W/2) \times K \times K \times (C_1 \times 2) \times (C_1 \times 2) = H \times W \times K \times K \times C_1 \times C_1$, то есть, ничего не изменилось, а количество фильтров удвоилось, что приводит к выучиванию более сложных зависимостей.
Осталось разобраться, как именно можно понижать разрешение картинки. Тривиальный способ – взять все пиксели с нечетными индексами. Такой подход будет работать, но, как может подсказать здравый смысл, выкидывать пиксели = терять информацию, а этого не хотелось бы делать. Здесь есть много вариантов: например, брать среднее/максимум по обучаемым весам в окне 2x2
, которое идет по карте признаков с шагом 2. Экспериментально выяснилось, что максимум – хороший выбор, и, в большинстве архитектур, используют именно его. Обратите внимание, что максимум берется для каждого канала независимо.
Еще одно преимущество – увеличение receptive field. Получается, что он увеличивается в 2 раза:
Операция понижения разрешения со взятием максимума в окне называется max pooling, а со взятием среднего – average pooling.
Вопрос на подумать. Как будет преобразовываться градиент во время error backpropagation для maxpool с окном и шагом 2x2? А для average pool?
Кстати, ещё одним способом уменьшать размер карт признаков по ходу применения свёрточной сети является использование strided convolution, в которых ядро свёртки сдвигается на каждом шаге на некоторое большее единицы число пикселей (возможно, разное для осей H
и W
; обычная свёртка получается, если установить параметр stride=(1,1)
).
Как свёрточные слои, так и пулинг превращают картинку в «стопку» карт признаков. Но если мы решаем задачу классификации или регрессии, то в итоге нам надо получить число (или вектор логитов, если речь про многоклассовую классификацию). Один из способов добиться этого – воспользоваться тем, что свёртка без дополнения нулями и пулинг уменьшают размер карты признаков, и в итоге при должном терпении и верном расчёте мы можем получить тензор 1x1xC
(финальные, общие признаки изображения), к которому уже можно применить один или несколько полносвязных слоёв. Или же можно, не дождавшись, пока пространственные измерения схлопнутся, «растянуть» всё в один вектор и после этого применить полносвязные слои (именно так, как мы не хотели делать, не правда ли?). Примерно так и происходило в старых архитектурах (alexnet, vgg).
Вопрос на подумать. Попробуйте соорудить конструкцию из свёточных слоёв и слоёв пулинга, превращающую изображение размера 128x128x3
в тензор размера 1x1xC
.
Но у такого подхода есть как минимум один существенный недостаток: для каждого размера входящего изображения нам придётся делать новую сетку.
Позднее было предложено следующее: после скольких-то свёрточных слоёв мы будем брать среднее вдоль пространственных осей нашего последнего тензора и усреднять их активации, а уже после этого строить MLP. Это и есть Global Average Pooling. У такого подхода есть несколько преимуществ:
Оказывается, что, если мы будем бесконтрольно стекать наши свёртки, то, несмотря на использование relu и batch normalization, градиенты все равно будут затухать, и на первых слоях будут почти нулевыми. Интересное решение предлагают авторы архитектуры resnet: давайте будем «прокидывать» признаки на предыдущем слое мимо свёрток на следующем:
Таким образом получается, что градиент доплывет даже до самых первых слоев, что существенно ускоряет сходимость и качество полученной модели. Вопрос: почему именно сумма? Может, лучше конкатенировать? Авторы densenet именно такой подход и предлагают (с оговорками), получая результаты лучше, чем у resnet. Однако, такой подход получается вычислительно сложным и редко используется на практике.
Несмотря на наши ухищрения со свёртками, в современных нейронных сетях параметров все равно оказывается больше, чем количество картинок. Поэтому часто оказывается важным использовать различные комбинации регуляризваторов, которых уже стало слишком много, чтобы все опысывать в этом параграфе, так что мы рассмотрим лишь несколько наиболее важных.
Почти все регуляризаторы, которые использовались в классической машинке и полносвязных сетях, применимы и здесь: l1/l2, dropout и так далее.
Вопрос на подумать. Насколько разумно использовать dropout в свёрточных слоях? Как можно модифицировать метод, чтобы он стал «более подходящим»?
Это один из самых мощных инструментов при работе с картинками. Помогает, даже если картинок несколько тысяч, а нейронная сеть с миллионами параметров. Мы уже выяснили, что смещение\поворот\прочее не меняют (при разумных параметрах) факта наличия на картинке того или иного объекта. На самом деле, есть огромное множество операций, сохраняющих это свойство:
Пример хорошой библиотеки с аугментациями: Albumentations.
Часто оказывается, что нейронная сеть делает «слишком уверенные предсказания»: 0.9999 или 0.00001. Это становится головной болью, если в нашей разметке есть шум – тогда градиенты на таких объектах могут сильно портить сходимость. Исследователи пришли к интересной идее: давайте предсказывать не one-hot метку, а ее сглаженный вариант. Итак, пусть у нас есть $K$ классов:
$$y_{ohot}=(0, 0, \dots, 1, \dots, 0)$$
$$y_{ls}=\left(\frac{\varepsilon}{k-1},\frac{\varepsilon}{k-1}, \dots, 1-\varepsilon,\frac{\varepsilon}{k-1}, \dots, \frac{\varepsilon}{k-1}\right)$$
$$\sum_i y^i_{ohot}=\sum_i y^i_{ls}=1$$
Обычно берут $\varepsilon=0.1$. Тем самым модель штрафуется за слишком уверенные предсказания, а шумные лейблы уже не вносят такого большого вклада в градиент.
Самый интересный вариант. А что будет, если мы сделаем выпуклую комбинацию двух картинок и их лейблов:
где $\alpha$ обычно семплируется из какого-нибудь Бета распределения. Оказывается, что такой подход заставляет модель выучивать в каком-то смысле более устойчивые предсказания, так как мы форсируем некую линейность в отображении из пространства картинок в пространство лейблов. На практике часто оказывается, что это дает значимое улучшение в качестве модели.
Мы разобрались, что для картинок эффективно использовать свёрточные фильтры в качестве основных операторов. Выяснили, какие основные блоки есть почти в каждой картиночной нейронной сети и зачем они там нужны. Разобрались, какие методы регуляризаторы сейчас самые популярные и какая за ними идея.
Дисклеймер: это мнение одного автора. Приведённые в этом разделе вехи связаны преимущественно с архитектурами моделей, а не способом их оптимизации.
Здесь перечислены знаковые архитектуры, заметно повлиявшие на мир свёрточных нейронных сетей в задаче классификации картинок (и не только). К каждой архитектуре указана ссылка на оригинальную статью, а также комментарий автора параграфа с указанием ключевых нововведений. Значение метрики error rate на одном из влиятельных датасетов imagenet указано для финального ансамбля из нейросетей, если не указано иное.
Зачем это полезно изучить (вместе с чтением статей)? Основных причин две:
7 слоев
Первая свёрточная нейронная сеть, показавшая SOTA (State Of The Art) результаты на задаче классификации изображений цифр MNIST. В архитектуре впервые успешно использовались свёрточные слои с ядром 5x5
. В качестве активации использовался tanh, а вместо max pool в тот момент использовался average.
11 слоев
Первая CNN (Convolutional Neural Network), взявшая победу на конкурсе imagenet. Автор предложил использовать ReLU вместо сигмоид (чтобы градиенты не затухали) и популяризовал max-pool вместо average. Что самое важное, обучение модели было перенесено на несколько GPU, что позволило обучать достаточно большую модель за относительное небольшое время (6 дней на двух видеокартах того времени). Также автор обратил внимание, что глубина нейросети важна, так как убирание хотя бы одного слоя стабильно ухудшало качество на несколько процентов.
В статье не привели интересных SOTA результатов, но зато ввели два очень популярных впоследствии модуля. Первый – это GAP (Global Average Pooling), который стоит после последнего свёрточного слоя и усредняет все активации вдоль пространственных осей. Второй – стекинг 1x1
свёрток поверх 3x3
, что эквивалентно тому, что вместо линейной свёртки используется полносвязный слой.
19 слоев
Авторы предложили декомпозировать большие свёртки (5x5
, 7x7
и выше) на последовательное выполнение свёрток 3x3
с нелинейностями между ними. Впоследствии, за нечастым исключением, свёртки 3x3
стали стандартом в индустрии (вместе со свёртками 1x1
).
22 слоя
Ввели inception слой, просуществовавший довольно продолжительное время. Сейчас сам слой уже не используется, но идея лежащая в его основе, эксплуатируется. Идея следующая: будем параллельно применять свёртки с разным пространственными размерами ядер, чтобы можно было одновременно обрабатывать как low, так и high level признаки. Еще полезной для сообщества оказалась идея с dimensionality reduction: перед тяжелой операцией поставим свёртку 1x1, чтобы уменьшить количество каналов и кратно ускорить вычисление.
Авторы внедрили вездесущую batch normalization, которая стабилизирует сходимость, позволяя увеличить шаг оптимизатора и скорость сходимости. Применив идею к архитектуре inception, они превзошли человека на imagenet.
В статье предложили использовать инициализацию весов, берущую во внимание особенность активации ReLU (в предыдущих работах предполагалось, что $Var[x] = \mathbb{E}[x^2]$, что, очевидно, нарушается для $\hat{x} = max(0, x)$). Применение этой и других «свистелок:: на VGG19 позволило существенно уменьшить ошибку на imagenet.
152 слоя
Архитектура, до сих пор (на момент написания – вторая половина 2021 года) являющаяся бейзлайном и отправной точкой во многих задачах. Основная идея – использование skip connections, что позволило градиенту протекать вплоть до первых слоев. Благодаря этому эффекту получилось успешно обучать очень глубокие нейронные сети, например, с 1202 слоями (впрочем, результаты на таких моделях менее впечатляющие, чем на 152 слойной). После этой статьи также стали повсеместно использоваться GAP и уменьшение размерности свёртками 1x1
.
Очень популярная модель для быстрого инференса (на мобильных устройствах или gpu). По качеству хоть и немного проигрывает «монстрам», но в индустрии, оказывается, зачастую этого достаточно (особенно если брать последние варианты модели). Основная деталь – это использование depthwise convolutions: параллельный стекинг свёрток 3x3x1x1
– то есть таких, в которых вычисление для каждого $с_{\text{out}}$ канала просходит только на основе признаков одного $c_{\text{in}}$ канала. Чтобы скомбинировать фичи между каналами, используется классическая 1x1
свёртка.
Одна из первых моделей, полученных при помощи NAS (Neural Architecture Search), которая взяла SOTA на imagenet. После этого, модели, где компоненты подбирались вручную, уже почти не показывали лучших результатов на классических задачах.
Свёрточными нейронными сетями можно решать большой спектр задач, например: