Нейронная сеть с нуля для объяснения обратного распространения ошибок

Введение

Нейронные сети (NN), технология, на которой основано глубокое обучение, довольно популярны в машинном обучении. Я помню, как еще в 2015 году после прочтения статьи Нейронная сеть в 11 строках кода Python Эндрю Траск меня сразу же заинтересовала область искусственного интеллекта. Но попробуйте построить сеть с нуля, я считаю, что большинство людей согласятся со мной, что обратное распространение ошибок или просто обратное распространение (BP) будет одним из первых препятствий на пути к выполнению этой задачи, по крайней мере, в зависимости от глубины вы готовы погрузиться в. Для тех, кто не знаком, BP - это алгоритм, используемый вместе с алгоритмом оптимизации, таким как Gradient Descent (GD), для изучения параметров модели NN. BP создает градиенты, которые затем используются при оптимизации. В этой статье я попытаюсь объяснить, как работает этот алгоритм, а затем построю простую нейронную сеть с нуля, чтобы протестировать эту сеть на задаче регрессии, которую я использовал в своем предыдущем сообщении. Для тех, кто борется с этим алгоритмом, я надеюсь, что эта статья станет интуитивно понятным руководством.

Обратите внимание, что хорошее понимание матричной алгебры и многомерного исчисления очень важно для полного понимания этого алгоритма.

Установка

Чтобы получить полное представление о ВР, я начну с представления общей картины сети, которую мы собираемся построить. Мы надеемся, что из этого вы получите интуитивное понимание проектных решений для NN, а также матричных операций, используемых в BP. Обратите внимание, что это всего лишь мой дизайн, который я считаю интуитивно понятным, и у других авторов могут быть другие дизайны. Я собираюсь использовать простую NN с прямой связью, которая представляет собой просто композицию функций / слоев, наложенных друг на друга, как показано на диаграмме ниже.

Алгоритм

Каждая обучающая итерация NN состоит из двух основных этапов.

  1. Прямой проход / распространение
  2. BP

Этап БП состоит из следующих этапов

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

Идея здесь в том, что сеть оценивает целевое значение во время прямого прохода. Затем мы вычисляем, насколько далеко наши оценки от фактических целей на последнем слое (сигнал ошибки δ_k). Наконец, мы рекурсивно вычисляем сигнал ошибки для каждого из предыдущих уровней.

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

Обратите внимание, что h '(a_k) = 1 для линейной активации, и поэтому (∂E_n / ∂y_k) = (∂E_n / ∂a_k). Индекс n был проигнорирован, чтобы уравнение не загромождалось. Величина (y_nk - t_nk) называется сигналом ошибки, δ_k для последнего слоя. Следовательно, градиент для параметра, связывающего конкретный сигнал ошибки и входной сигнал, является произведением входного сигнала и сигнала ошибки. Используя правило цепочки, сигнал ошибки для предыдущего слоя может быть вычислен с использованием сигнала ошибки текущего слоя. На диаграмме выше

Обратите внимание, как сигнал ошибки для узла на предыдущем уровне получается путем взятия взвешенной суммы всех сигналов ошибок от узлов текущего уровня, которым узел предыдущего уровня отправляет свои сигналы, то есть сумма по индексу k . Вот почему это называется обратным распространением ошибок. Также, чтобы увидеть, откуда эта сумма по k происходит математически, обратите внимание, что ∂E_n / ∂a_k - вектор Якоби, а ∂a_k / ∂a_j - матрица Якоби.

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

Аналогично для параметров смещения,

Обратите внимание, что это рекурсивно, сигналы ошибок от текущего уровня используются для оценки сигналов ошибок на предыдущем уровне. Это очень важная часть головоломки, поэтому давайте посмотрим, как мы собираемся векторизовать, а затем реализовать это в коде. Мы собираемся использовать пакетное обучение, но тот же дизайн можно использовать для онлайн-обучения, просто установив размер пакета равным 1.

Для выходного слоя K и входного P веса слоя будут инициализированы как (PxK). Следовательно, для входов N с размером P мы получаем N выводит размер K. Это этап прямого распространения по слою, как показано на диаграмме. Смещение, инициализированное как (K,), здесь не отображается, поскольку оно транслируется в диапазоне N и не будет влияют на размер вывода.

Следовательно, сигнал ошибки δ_k имеет форму (NxK). По причинам, которые мы вскоре увидим, я транспонирую этот сигнал ошибки и передаю в функцию обратного распространения как (KxN). Теперь, умножая параметры слоя на сигнал ошибки уровня, выполняется взвешенное суммирование по k для всех шаблонов N, отсюда и причина, по которой сигнал ошибки сначала транспонируется. Назовем матрицу, полученную на этом этапе, DM.

Теперь вот сложная часть: чтобы завершить вычисление сигнала ошибки, распространенного в обратном направлении на предыдущем уровне, взвешенная сумма каждого узла должна быть умножена на h '(a_j), размер которого равен (NxP). Я назову эту производную матрицу D. В матричной алгебре это делается путем помещения конкретного входного шаблона в виде диагональной матрицы, а затем умножения этой матрицы на соответствующий столбец DM. Я назову эту диагональную матрицу S_n. Я собираюсь обнулить, инициализировать матрицу A размером (PxN) для итеративного накопления сигнала предыдущего уровня.

Как построить матрицу S_n и DM_n - решать вам. Вы увидите один из способов сделать это в разделе кода. Возможно, есть более эффективные способы, не связанные с циклом, дайте мне знать, если вы это знаете :).

Теперь эти сигналы об ошибках передаются на предыдущий уровень L_k-1, для обновления его параметров. Параметры текущего слоя, L_k, обновляются сигналами ошибок, которые были переданы этому слою с помощью его функции «Backprop».

Следует отметить одну тонкость: сигналы ошибки слоя L_k-1 вычисляются перед слоем L_k параметры обновлены.

Как объяснялось ранее, для вычисления градиентов параметров слоя мы умножаем сигнал ошибки на входной сигнал для этого слоя. Я буду называть градиентами веса G_w и градиентами смещения G_b. Для слоя K матрица A равна (KxN), а входной сигнал I равно (NxP)

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

Ладно, хватит разговоров, давайте код!

Код

Сначала импортируйте все, что потребуется

Далее я собираюсь создать класс слоя. Когда этот уровень вызывается, он выполняет прямое распространение с использованием __call__. Несколько слоев можно сложить вместе, передав экземпляр предыдущего слоя в экземпляр текущего слоя. Прямое распространение продолжается от самого раннего слоя к последнему слою. И два слоя могут быть присоединены только в том случае, если выходной размер / размер предыдущего слоя совпадает с входным размером текущего слоя.

Далее класс модели. Этот класс обрабатывает процесс обучения и предсказывает NN.

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

Тестирование

Во-первых, давайте сгенерируем данные

Затем давайте создадим нашу сеть и начнем обучение. В этом примере я создам четырехуровневую сеть.

Наконец, тестирование и построение графиков.

График

Вывод

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

Спасибо, что прочитали мою статью, до встречи в другой! А пока будьте осторожны в эти тяжелые времена!

использованная литература

[1]: Бишоп, К. М. (2006). Распознавание образов и машинное обучение. спрингер.