среда, 15 июля 2026 г.

Обратное распространение ошибки и градиентный спуск

 Продолжим изучение темы нейронных сетей. На прошлом уроке мы рассмотрели простейший двухслойный персептрон с двумя входами и двумя выходами состоящий из весовых коэффициентов и не имеющий коэффициентов смещения, с сигмоидой в качестве функции активации для всех нейронов. В том уроке мы рассмотрели прямой проход т.е. алгоритм использования нейронной сети но он также имеется в составе алгоритма её обучения. Обучать нейронную сеть можно разными путями. Например можно случайным образом немного менять весовые коэффициенты, смотреть что получаем а выходе, сохранять наиболее лучшие результаты а также комбирировать между собой и так повторять до тех пор пока результат не станет удовлетворительным. Это называется "генетический" алгоритм. Очень интересный способ, однако он практически не применяется т.к. требует большого объема памяти и очень медленно работает. Помимо "генетического" алгоритма существует также алгоритм обучения на основе градиентного спуска. У этого алгоритма есть множество современных эффективных разновидностей. Но мы, для начала для простоты, рассмотрим классический градиентный спуск. Идея заключается в том чтобы во время обратного прохода, от выходов ко входам, вычислять ошибки создаваемые нейронами и корректировать веса таким образом чтобы немного минимизировать эти ошибки с каким то небольшим шагом за один проход путем изменения весовых коэффициентов в сторону противоположную градиентам ошибок.

Проще всего будет показать на примере как это делается. Возьмем нашу двухслойный персептрон с двумя входами и двумя выходами. Допустим мы уже сделали прямой проход и получили результат на выходе. Теперь у нас есть числа которые мы хотим иметь на выходе.  Чтобы сделать до конца первую итерацию обучения персептрона, нам нужно выполнить обратный проход. На первом этапе мы вычислим ошибки каждого нейрона. Начиная с выходного слоя и заканчивая входным. Чтобы вычислить ошибку нейрона выходного слоя нам нужно из полученного на выходе значения этого нейрона вычесть требуемое значение которое мы хотим чтобы было на выходе этого нейрона. Результат этого вычитания умножить на производную функции активации (в данном случае сигмоида) в точке взвешенной суммы. Т.е. производная сигмоиды просто выражается через саму сигмоиду а выходы нам известны то можно подставить выход в формулу производной сигмоиды и умножить это на разницу т.е. отклонение от выхода.  Далее проделать так для всех остальных нейронов выходного слоя. Теперь нужно рассчитать ошибки нейронов слоев предшествующих выходному. Сделать это уже сложнее но к счастью метод всё таки есть. На данном этапе ошибка как бы распространяется назад. Поэтому метод и называется обратным распространением ошибки. Ошибка распространяется назад и перемножается с весовыми коэффициентами а взвешенная сумма умножается на производную функции активации в точке взвешенной суммы. Таким образом. Можно рассчитать ошибки нейронов скрытых слоев. Возможно принцип работы данного метода, на первый взгляд, не очевиден но главное что он работает если просто примнеять формулы.  После того как ошибки найдены можно приступать к корректировке весов. Чтобы скорректировать вес методом градиентного спуска, из этого веса нужно вычесть произведение этого веса на градиент умноженный на шаг обучения который обычно является небольшим числом например 0.01 или что то вроде того. Выбор шага это большая отдельная тема, пока остановимся на том что это просто небольшое число. Градиент рассчитывается как произведение входа нейрона на ошибку. Можно было бы выделить расчет градиентов в отдельный этап но для реализации на языке программирования лучше объединить этап расчета градиентов с этапом корректировки весов чтобы не занимать лишнюю память т.к. на весовые коэффициенты её и так много приходиться + другие модификации градиентного спуска требуют ещё больше память поэтому очень важно стараться её экономить. Итак мы скорректировали веса. Теперь мы переходим в начало цикла обучения, выполняем прямой поход. Получаем новые значения выходов, можем оценить ошибку на данном этапе. После повторяем обратный проход и так до тех пор пока сеть не обучиться или не застрянет если например её структура такова что сеть не может быть обучена до требуемого уровня теми методами с теми данными и стартовыми условиями которые мы имеем.

Наглядно весь процесс обучения можно увидеть на картинке:

Теперь попробуем это реализовать на языке javascript. Немного изменим код предыдущей программы. Добавим кнопку для запуска обучения а также область вывода результатов. Размеры нейросети и функция активации остаются прежними. Входной вектор обучающей выборки остается прежним + добавляется выходной вектор обучающей выборки. Добавляем шаг обучения. Правильный выбор этого шага очень важен и желательно его делать небольшим но в целях изучения и на такой простой сети с такой просто обучающей выборкой, целесообразно его сделать побольше чтобы результат получился быстрее и нагляднее. Массивы весовых коэффициентов, выходов, ошибок объявляем и инициализируем. Добавим обработчик нажатия кнопки а также обернем итерацию обучения в цикл т.к. одна итерация меняет результат незначительно и для наглядности лучше сделать некоторое количество шагов обучения чтобы заметить изменение на выходе и при этом не пришлось бы слишком много кликать по кнопке. Далее делаем прямой проход и выводим результат работы нейросети. Далее делаем обратный проход по формулам. Сначала рассчитываем ошибки выходного слоя, перемножая разницу выхода требуемого и фактического с производной функции активации в точке взвешенной суммы. Потом ошибки скрытого слоя путем вычисления произведения взвешенной суммы ошибок выходного слоя с производной функции активации. После рассчитываем градиенты и корректируем веса. Полный код приведен в текстовом поле:

Или же можно поиграться с этой нейросетью непосредственно на данной странице

обучающая выборка
x0=,x1=,
out0=,out1=
шаг обучения h =

можно заметить что каждые 50 шагов приближают результат всё ближе и ближе к истине и т.о. нейронная сеть обучается всё точнее и точнее. Чем ближе к результату тем медленнее обучается сеть но это просто особенность класического градиентного спуска. Чтобы ускорить обучение можно использовать другие оптимизаторы например моментум который также помогает преодолевать локальные минимум. Также в данной простой сети нет коэффициентов смешения для того чтобы можно было принимать больше вариантов входных данных, например данные с нулями. И обучающая выборка состоит всего из двух векторов, входного и выходного, но на практике их обычно гораздо больше. Ещё можно пробовать менять функции активации. Например использовать Relu на скрытых слоях что гораздо лучше для глубокого обучения или гиперболический тангенс чтобы использовать область отрицательных значений. В общем сеть ещё можно улучшать но это уже отдельные темы.

На Ютубе можно посмотреть можно на видео данного урока:

Номер биткоин кошелька для поддержки блога - bc1qlhrmmkh77x2lzhqe4lt9qwkglswj64tsqt2l5g