Обратное распространение - это основа машинного обучения, но оно кажется таким устрашающим. Но на самом деле это проще, чем кажется.

Чтобы изучить машинное обучение (ML), не требуется математического гения. По сути, все, что вам нужно, это исчисление первого курса колледжа, линейная алгебра и теория вероятностей, и вы готовы к работе. Но за кажущимся безобидным первым впечатлением от ML стоит множество математических теорий, связанных с ML. Для многих первое реальное препятствие в изучении машинного обучения - это обратное распространение (BP). Это метод, который мы используем для вывода градиента параметров в нейронной сети (NN). Это необходимый шаг в алгоритме градиентного спуска для обучения модели.

BP - очень простой шаг в обучении NN. Он включает в себя цепное правило и матричное умножение. Однако то, как BP вводится во многих курсах или учебных пособиях по машинному обучению, неудовлетворительно. Когда я впервые изучал BP в классе машинного обучения Coursera, я был так сбит с толку процессом его вычислений, что остановился на несколько месяцев. Тем временем я искал дополнительные объяснения БП. Удалось пройти курс. Я закончил задание по кодированию. Но BP по-прежнему остается в моем мозгу очень запутанным и запутанным пятном.

На самом деле не повредит, если вы вообще не понимаете BP и просто рассматриваете его как черный ящик, потому что Tensorflow или Pytorch могут автоматически выполнять BP за вас. Но недавно я просматривал свои заметки по ML, и я начал правильно понимать BP. Мой метод состоит в том, чтобы настроить простой NN и явно записать каждый параметр и переменную матрицу / вектор и записать расчет градиента с помощью цепного правила для каждой матрицы / вектора параметров шаг за шагом. В конце концов, BP оказывается намного проще, чем я думал изначально.

Используйте в качестве примера двухслойную NN и образец с одним входом.

Мои коннотации основаны на Спецификационном курсе 1 по глубокому обучению Coursera. В качестве примера я буду использовать двухуровневую сетевую сеть. Два слоя просто «достаточно глубоки», потому что методы расчета градиента в слое 1 могут быть повторены для любых дополнительных слоев. Также полезно позволить нам увидеть размерность параметров в отношении количества нейронов каждого слоя, например, обратите внимание, что вход - это вектор размерности 2, в слое 1 есть три нейрона, поэтому w [1] является Матрица 3 * 2. В слое 2 или в выходном слое есть два нейрона. Если NN используется для двоичной классификации, выходной слой представляет собой один нейрон; если NN используется для множественной классификации, выходной слой имеет то же количество нейронов, что и классы. Чтобы обобщить наш анализ, мы просто рассмотрим выходной слой с двумя нейронами в качестве примера.

Во входном слое или слое 0 у нас есть входной вектор x 2 * 1. Прямо сейчас мы сосредотачиваемся только на одном образце ввода. Если мы хотим рассматривать входные данные как пакет из m выборок, то размер входных данных на самом деле равен 2 * m. Но давайте рассмотрим здесь только один входной экземпляр.

На первом уровне у нас есть три нейрона, а матрица w [1] представляет собой матрицу 3 * 2. В этой сети есть также вектор смещения b [1] и b [2] в каждом слое. b [1] - вектор 3 * 1, а b [2] - вектор 2 * 1. В прямом проходе у нас есть следующие отношения (как в матричной, так и в векторизованной форме):

Точно так же векторизованная операция слоя 2 может быть записана как:

Пока просто не обращайте внимания на детали функции активации и функции потери.

Для каждого слоя мы будем использовать нелинейную функцию активации g (z), например, сигмовидную функцию или ReLU. Для простоты мы опускаем детали g (z) и просто продолжаем использовать g (z).

Обратите внимание, что производная g (z) по z нам известна, как только мы определим, какой g (z) мы будем использовать.

На выходе NN у нас есть функция потерь J (), которую мы постараемся минимизировать. Обычно это кросс-энтропия. Для простоты мы пока оставим детали J (). Опять же, это функция от a [2] и y, поэтому мы можем вычислить его производную по a [2] напрямую.

Переход от последнего уровня для получения частичной директивы dz [2].

Чтобы выполнить БП, нам нужно перейти от последнего слоя назад к первому. На последнем уровне мы можем напрямую получить частную производную da [2] из функции потерь J, поскольку J является функцией a [2]. Также легко получить частную производную dz [2] из da [2] с помощью следующих функций, если нам известна производная g ’(z). Обратите внимание, что оператор в последнем уравнении (точка в круге) указывает поэлементное умножение.

Получив dz [2], мы попытаемся найти dw [2] и db [2].

Возьмем для примера w7 в w [2], этот параметр умножается на a1, а затем прибавляется к z4. По цепному правилу dw7 равно:

Следовательно, мы имеем следующую связь между w [2] и dz [2]:

Вы можете видеть, что dw [2] - это не что иное, как векторное умножение dz [2] и транспонирование a [1]. db [2] - это сам dz [2].

Затем мы можем перейти к уровню 1 и вычислить da [1] и dz [1].

В качестве примера возьмем a1 в [1]. Обратите внимание, что a1 умножается на w7 и w8 и прибавляется к z4 и z5 соответственно. По правилу цепочки da1 также будет состоять из этих двух частей. Подробности da [1] и dz [1] приведены ниже:

Обратите внимание, что da [1] вычисляется с параметрами слоя 2 w [2] и dz [2].

С помощью dz [1] мы можем получить dw [1] и db [1], как в слое 2.

Мы видим, что вычисление dw [1] является умножением вектора dz [1] и транспонированием x. Вы также можете представить x как [0], выходной вектор слоя «0». Следовательно, мы получаем согласованное выражение dw [l] = dz [l] * a [l-1] .T (.T обозначает транспонирование, l - случайный номер слоя).

Выполнив описанные выше шаги, мы получили dw [2], db [2], dw [1] и db [1]. Для NN с более чем двумя слоями шаги могут быть повторены для каждого слоя: для любого слоя нам просто нужно получить dz [l] (либо непосредственно из J, либо из предыдущего слоя), используйте это для вычисления dw [l] и db [l]; получить da [l-1] и dz [l-1] и перейти к слою l-1.

Для NN с L слоями BP сводится к четырем основным шагам ниже:

  1. Из последнего слоя вычислите da [L] по функции потерь J; затем получите dz [L] из da [L]; положим l = L,
  2. Расчет градиента параметров текущего слоя с помощью dz [l]: рассчитать dw [l] = dz [l] a [l-1] .T; db [l] = dz [l];
  3. Вычислить градиент вывода предыдущего слоя с помощью dz [l]: вычислить da [l-1] с помощью da [l-1] = w [l] .T * dz [l] и получить dz [ l-1] из da [l-1].
  4. Установите l = l-1 и повторите, начиная с шага 2, до первого слоя.

Одним словом, на каждом слое мы хотим найти dz [l], и все идет оттуда.

Затем мы подробно рассмотрим J () и g (). Рассмотрим, что g (z) - сигмоидальная функция, а J () - кросс-энтропия. Их формат и градиент приведены ниже.

В случае нашей NN, J является функцией a [2] и y, и мы можем получить dz [2] из следующих функций. Как только у нас есть явное выражение для dz [2], мы можем выполнить BP с вышеуказанными шагами.

Наконец, давайте посмотрим на ситуацию с несколькими экземплярами ввода. Если вход содержит m экземпляров, то выходная и промежуточная матрица A [1], A [2], Z [1] и Z [2] имеют m столбцов.

Если функция потерь J представляет собой ожидаемую кросс-энтропию, тогда расчет dw и db должен включать член 1 / m, но все остальное в основном остается таким же:

Вот и все. Мы используем простой и понятный двухуровневый NN в качестве примера и выполняем BP для одного входа и для нескольких входов. Надеюсь, это простое руководство поможет вам лучше понять АД.

Ссылка:

[1] Спецификация Coursera Deep Learning, курс 1: Нейронные сети и глубокое обучение

[2] https://www.youtube.com/watch?v=ibJpTrp5mcE&feature=youtu.be

[3] https://speech.ee.ntu.edu.tw/~tlkagk/courses/ML_2016/Lecture/BP.pdf