Основные определения
Искусственная нейронная сеть (далее — нейронная сеть) — это сложная дифференцируемая функция, задающая отображение из исходного признакового пространства в пространство ответов, все параметры которой могут настраиваться одновременно и взаимосвязанно (то есть сеть может обучаться end-to-end).
В частном (и наиболее частом) случае представляет собой последовательность дифференцируемых параметрических преобразований.
Внимательный читатель может заметить, что под указанное выше определение нейронной сети подходят и логистическая, и линейная регрессия. Это верное замечание: и линейная, и логистическая регрессии могут рассматриваться как нейронные сети, задающие отображения в пространство ответов и логитов соответственно.
Сложную функцию удобно представлять в виде суперпозиции простых, и нейронные сети обычно предстают перед программистом в виде конструктора, состоящего из более-менее простых блоков (слоёв, layers). Вот две простейшие их разновидности:
-
Линейный слой (linear layer, dense layer) — линейное преобразование над входящими данными. Его обучаемые параметры — это матрица и вектор : (). Такой слой преобразует -мерные векторы в -мерные.
-
Функция активации (activation function) — нелинейное преобразование, поэлементно применяющееся к пришедшим на вход данным. Благодаря функциям активации нейронные сети способны порождать более информативные признаковые описания, преобразуя данные нелинейным образом. Может использоваться, например, ReLU (rectified linear unit) или уже знакомая вам из логистической регрессии сигмоида . К более глубокому рассмотрению разновидностей и свойств различных функций активации вернёмся позднее.
Даже самые сложные нейронные сети обычно собираются из относительно простых блоков, подобных этим. Таким образом, их можно представить в виде вычислительного графа (computational graph), где промежуточным вершинам соответствуют преобразования. На иллюстрации ниже приведён вычислительный граф для логистической регрессии.
Не правда ли, похоже на слоёный пирог из преобразований? Отсюда и слои.
Графы могут быть и более сложными, в том числе нелинейными:
Давайте разберёмся, что тут происходит.
Input — это вход нейросети, который получает исходные данные. Обычно требуется, чтобы они имели вид матрицы («объекты-признаки») или тензора (многомерной матрицы). Вообще говоря, входов может быть несколько: например, мы можем подавать в нейросеть картинку и какие-нибудь ещё сведения о ней — преобразовывать их мы будем по-разному, поэтому логично предусмотреть два входа в графе.
Дальше к исходным данным применяются два линейных слоя, которые превращают их в промежуточные (внутренние, скрытые) представления и . В литературе они также называются активациями (не путайте с функциями активации).
Каждое из представлений и подвергается нелинейному преобразованию, превращаясь в новые промежуточные представления и соответственно. Переход от к двум новым матрицам (или тензорам) и можно рассматривать как построение двух новых (возможно, более информативных) признаковых описаний исходных данных.
Затем представления и конкатенируются (то есть признаковые описания всех объектов объединяются).
Дальше следует ещё один линейный слой и ещё одна активация, и полученный результат попадает на выход сети, то есть отдаётся обратно пользователю.
Нейросеть, в которой есть только линейные слои и различные функции активации, называют полносвязной (fully connected) нейронной сетью или многослойным перцептроном (multilayer perceptron, MLP).
Посмотрим, что происходит с размерностями, если на вход подаётся матрица :
Примечание о терминологии
В литературе, увы, нет единства терминологии.
Так, например, никто не мешает нам объявить «единым и неделимым слоем» композицию линейного слоя и активации (в ознаменование того, что мы почти никогда не используем просто линейный слой без нелинейности). Например, в фреймворке keras активацию можно указать в линейном слое в качестве параметра.
Также в ряде источников слоями называется то, что мы называем промежуточными представлениями. Нам, впрочем, кажется, что промежуточные результаты правильнее называть именно представлениями: ведь это новые признаковые описания, представляющие исходные объекты. Кроме того, во всех нейросетевых фреймворках слои — это именно преобразования, поэтому и нам кажется правильным объявлять слоями именно преобразования, связывающие промежуточные представления.
А вот и настоящий пример из реальной жизни. GoogLeNet (она же Inception-v1), показавшая SotA-результат на ILSVRC 2014 (ImageNet challenge), выглядит так:
Здесь каждый кирпичик — это некоторое относительно простое преобразование, а белым помечены входы и выходы вычислительного графа.
Современные же сети часто выглядят и ещё сложней, но всё равно они собираются из достаточно простых кирпичиков-слоёв.
Примечание. Впрочем, в общем случае нейронная сеть — это просто некоторая сложная функция (или, что эквивалентно, граф вычислений). Поэтому в некоторых (очень нетривиальных) случаях нет смысла разбивать её на слои.
В качестве иллюстрации ниже приведены структуры агностических нейронных сетей WANN, представленных в работе Weight Agnostic Neural Networks, NeurIPS 2019.
Forward & backward propagation
Информация может течь по графу в двух направлениях.
Применение нейронной сети к данным (вычисление выхода по заданному входу) часто называют прямым проходом, или же forward propagation (forward pass). На этом этапе происходит преобразование исходного представления данных в целевое и последовательно строятся промежуточные (внутренние) представления данных — результаты применения слоёв к предыдущим представлениям. Именно поэтому проход называют прямым.
При обратном проходе, или же backward propagation (backward pass), информация (обычно об ошибке предсказания целевого представления) движется от финального представления (а чаще даже от функции потерь) к исходному через все преобразования.
Механизм обратного распространения ошибки, играющий важнейшую роль в обучении нейронных сетей, как раз предполагает обратное движение по вычислительному графу сети. С ним вы познакомитесь в следующем параграфе.
Архитектуры для простейших задач
Как мы уже упоминали выше, нейросети — это универсальный конструктор, который из простых блоков позволяет собрать орудия для решения самых разных задач. Давайте посмотрим на конкретные примеры. Безусловно, мир намного разнообразнее того, что мы покажем вам в этом параграфе, но с чего-то ведь надо начинать, не так ли?
В тех несложных ситуациях, которые мы сейчас рассмотрим, архитектура будет отличаться лишь самыми последними этапами вычисления (у сетей будут разные «головы»). Для иллюстрации приведём примеры нескольких игрушечных архитектур для решения игрушечных задач классификации и регрессии на двумерных данных:
Бинарная классификация
Для решения задачи бинарной классификации подойдёт любая архитектура, на выходе у которой одно число от до , интерпретируемое как «вероятность класса 1». Обычно этого добиваются, взяв
где — некоторая функция, превращающая представление в число (если — матрица, то подойдёт , где — вектор-столбец), а — наша любимая сигмоида. При этом может получаться как угодно, лишь бы хватало оперативной памяти и не было переобучения.
В качестве функции потерь удобно брать уже знакомый нам log loss.
Многоклассовая классификация
Работая с другими моделями, мы порой вынуждены были выдумывать сложные стратегии многоклассовой классификации; нейросети позволяют это делать легко и элегантно.
Достаточно построить сеть, которая будет выдавать неотрицательных чисел, суммирующихся в 1 (где — число классов); тогда им можно придать смысл вероятностей классов и предсказывать тот класс, «вероятность» которого максимальна.
Превратить произвольный набор из чисел в набор из неотрицательных чисел, суммирующихся в 1, позволяет, к примеру, функция
Наиболее популярные архитектуры для многоклассовой классификации имеют вид
где — функция, превращающая в матрицу (где — размер батча), а может быть получен любым приятным вам образом.
Но какой будет функция потерь для такой сети? Мы должны научиться сравнивать «распределение вероятностей классов» с истинным (в котором на месте истинного класса стоит 1, а в остальных местах 0). Сделать это позволяет кросс-энтропия, она же negative log-likelihood — некоторый аналог расстояния между распределениями:
где снова — размер батча, а — число классов. Легко видеть, что при получается та самая функция потерь, которую мы использовали для обучения бинарной классификации.
(Множественная) регрессия
С помощью нейросетей легко создать модель, которая предсказывает не одно число, а сразу несколько. Например, координаты ключевых точек лица — кончика носа, уголков рта и так далее.
Достаточно сделать, чтобы последнее представление было матрицей , где — размер батча, а — количество предсказываемых чисел. Особенностью большинства моделей регрессии является то, что после последнего слоя (часто линейного) не ставят функций активации. Вы тоже этого не делайте, если только чётко не понимаете, зачем вам это. В качестве функции потерь можно брать, например, по всей матрице .
Всё вместе
Если вы используете нейросети, то ваши таргеты могут иметь и различную природу. Например, можно соорудить одну-единственную сеть, которая по фотографии нескольких котиков определяет их количество (регрессия) и породу каждого из них (многоклассовая классификация).
Лосс для такой модели может быть равен (взвешенной) сумме лоссов для каждой из задач (правда, не факт, что это хорошая идея). Так что, по крайней мере в теории, сетям подвластны любые задачи. На практике, конечно, всё гораздо хитрей: для обучения слишком сложной сети у вас может не хватить данных или вычислительных мощностей.
Популярные функции активации
Для начала поговорим о том, зачем они нужны.
Казалось бы, можно последовательно выстраивать лишь линейные слои, но так не делают: после каждого линейного слоя обязательно вставляют функцию активации. Но зачем? Попробуем разобраться.
Рассмотрим нейронную сеть из двух линейных слоёв. Что произойдёт, если между ними будет отсутствовать нелинейная функция активации?
Линейная комбинация линейных отображений есть линейное отображение, то есть два последовательных линейных слоя эквивалентны одному линейному слою.
Добавление функций активации после линейного слоя позволяет получить нелинейное преобразование, и подобной проблемы уже не возникает. Вдобавок правильный выбор функции активации позволяет получить преобразование, обладающее подходящими свойствами.
В качестве функции активации может использоваться, например, уже знакомая вам из логистической регрессии сигмоида или ReLU (Rectified linear unit) . К более глубокому рассмотрению разновидностей и свойств различных функций активации вернёмся позднее.
Примечание. На самом деле бывают ситуации, когда два линейных слоя подряд — это полезно. Например, если вы понимаете, что у вас очень много параметров, а информации в данных не так много, вы можете заменить линейный слой, превращающий -мерные векторы в -мерные, на два, вставив посередине -мерное представление, где :
С точки зрения линейной алгебры это примерно то же самое, что потребовать, чтобы матрица исходного линейного слоя имела ранг не выше . И с точки зрения сужения «информационного канала» это иногда может сработать. Но в любом случае вы должны понимать, что два линейных слоя подряд стоит ставить, только если вы хорошо понимаете, чего хотите добиться.
Вернёмся к функциям активации. Вот наиболее популярные:
Рассмотрим их подробнее.
ReLU, Rectified linear unit
Формула:
ReLU это простая кусочно-линейная функция. Одна из наиболее популярных функций активации. В нуле производная доопределяется нулевым значением.
Плюсы:
- простота вычисления активации и производной.
Минусы:
- область значений является смещённой относительно нуля;
- для отрицательных значений производная равна нулю, что может привести к затуханию градиента.
ReLU и её производная очень просты для вычисления: достаточно лишь сравнить значение с нулём. Благодаря этому использование ReLU позволяет достигать прироста в скорости до четырёх-шести раз относительно сигмоиды.
Более подробно о затухании градиента
Рассмотрим нейронную сеть из нескольких линейных слоёв с сигмоидой в качестве функции активации. Пусть
где в качестве обозначен результат применения линейного слоя. Свободный член, как и ранее, опущен для упрощения выкладок.
Рассмотрим график сигмоиды и её производной:
На «хвостах» её производная практически равна нулю (ведь сигмоида представляет собой почти константу). То есть если какое-то значение было достаточно велико по абсолютной величине (например, ), то градиент функции потерь для этой компоненты будет домножен на очень малое число и фактически станет равным нулю:
Получается, что -ая строка матрицы не будет обновлена: ведь градиент равен нулю. Это может привести к «отмиранию» части весов: неудачное значение параметров приведёт к невозможности их обновления.
Примечание. Это одна из причин необходимости нормировки данных и выбора правильной инициализации для начальных значений параметров нейронной сети.
Но помимо явного обнуления градиента есть и вторая проблема: максимальное значение производной сигмоиды составляет . То есть она всегда уменьшает значение градиента не менее чем в четыре раза. Если же представить себе глубокую сеть с 20+ слоями, то для каждого следующего (если считать с конца) слоя градиент будет домножаться на число, не превосходящее . Так, для переменных из первого слоя коэффициент составит .
Leaky ReLU
Формула:
Гиперпараметр обеспечивает небольшой уклон слева от нуля, что позволяет получить более симметричную относительно нуля область значений. Также меньше провоцирует затухание градиента благодаря наличию ненулевого градиента и слева, и справа от нуля.
Плюсы:
Минусы:
PReLU, Parametric ReLU
Формула:
Аналогична Leaky ReLU, но параметр настраивается градиентными методами.
ELU
ELU – это гладкая аппроксимация ReLU. Обладает более высокой вычислительной сложностью, достаточно редко используется на практике.
Sigmoid, сигмоида
Формула:
Исторически одна из первых функций активации. Рассматривалась в том числе и как гладкая аппроксимация порогового правила, эмулирующая активацию естественного нейрона.
Плюсы:
Минусы:
- область значений смещена относительно нуля;
- сигмоида (как и её производная) требует вычисления экспоненты, что является достаточно сложной вычислительной операцией. Её приближённое значение вычисляется на основе ряда Тейлора или с помощью полиномов, Stack Overflow question 1, question 2;
- на «хвостах» обладает практически нулевой производной, что может привести к затуханию градиента;
- максимальное значение производной составляет , что также приводит к затуханию градиента.
На практике сигмоида редко используется внутри сетей, чаще всего в случаях, когда внутри модели решается задача бинарной классификации (например, вероятность забывания информации в LSTM).
Tanh, гиперболический тангенс
Формула:
Плюсы:
- как и сигмоида, имеет ограниченную область значений;
- в отличие от сигмоиды, область значений симметрична.
Минусы:
- требует вычисления экспоненты, что является достаточно сложной вычислительной операцией;
- на «хвостах» обладает практически нулевой производной, что может привести к затуханию градиента.
Вопрос на подумать. А почему симметричность области значений может быть ценным свойством?
Ответ (не открывайте сразу; сначала подумайте сами!)
Разберём на примере сигмоиды. Пусть оказалось так, что все веса линейных слоёв инициализированы положительными числами.
Сигмоида от положительного числа даёт нечто, большее , и с каждым дальнейшим слоем ситуация может усугубляться, приводя к тому, что сигмоиды будут выдавать всё более близкие к единице значения. Это покарает нас, когда мы начнём считать градиенты: они начнут «затухать».
Немного о мощи нейросетей
Рассмотрим для начала задачу регрессии. Ясно, что линейная модель (то есть однослойная нейросеть) может приблизить только линейную функцию, но уже двухслойная нейросеть может приблизить почти что угодно. Есть ряд теорем на эту тему, мы упомянем одну из них. Обратите внимание на год: как мы уже упоминали, нейросети начали серьёзно изучать задолго до того, как они начали превращаться в state of the art.
Теорема Цыбенко (1989). Для любой непрерывной функции и для любого найдётся число , а также числа , , для которых
для любых из единичного куба в .
В сумме из теоремы Цыбенко легко опознать двуслойную нейросеть с сигмоидной функцией активации. В самом деле, сперва мы превращаем в — это можно представить в виде одной матричной операции (линейный слой!):
где — вектор-столбцы, а каждое из прибавляется к -му столбцу, после чего поэлементно берём от сигмоиду (активация) , после чего вычисляем
и это второй линейный слой (без свободного члена).
Правда, теорема не очень помогает находить такие функции, но это уже другое дело. В любом случае — если дать нейросети достаточно данных, она действительно может выучить почти что угодно.
Упражнение. Мы не будем приводить результатов, касающихся классификации, но рекомендуем воспользоваться замечательной песочницей. Убедитесь сами, что при использовании одного скрытого слоя из двух нейронов и сигмоиды в качестве функции активации, можно неплохо классифицировать данные со сложной, совсем даже не линейной границей между классами. Вы также можете поиграть с разными функциями активации.
А для получения решения нам необходим метод автоматической настройки всех параметров нейронной сети — метод обратного распространения ошибки, или же error backpropagation. Рассмотрим его в деталях в следующем параграфе.