Реализуем обучение многослойного персептрона на C++ при помощи метода обратного распространения ошибки.  Всем привет!) Перед тем, как мы приступим к основе этой статьи, хочется сказать несколько слов о предыдущих частях. Предложенная мной реализация — это издевательство над оперативной памятью компьютера, т.к. трёхмерный вектор насилует память обилием пустых ячеек, на которые тоже резервирована память. Поэтому способ далеко не самый лучший, но, надеюсь, он поможет понять начинающим программистам в этой области, что находится «под капотом» у самых простых нейросетей. Это раз. Описывая активационную функцию в первой части, я допустил ошибку — активационная функция не обязательно должна иметь ограничения по области значений. Всё это зависит от целей программиста. Единственное условие — функция должна быть определена на всей числовой оси. Это два Так-с, а теперь ближе к теме. Сегодня мы реализуем backpropagation — метод для корректировки весовых коэффициентов сети. Итак, приступим! Ссылка на предыдущие две статьи: habr.com/ru/post/514372 Я наткнулся на классную статью по backpropagation — ссылка. В этой статье всё хорошо расписано, поэтому я, в основном, буду повествовать о использовании тех методов в своём коде. Вкратце объясню принцип метода на вот такой сети:
 Постараюсь правильно передать смысл обучения этим способом, но если что, то поправьте в комментариях. Итак, чтобы обучить сеть, мы должны изменить значения весовых коэффициентов. Причём, изменение веса связи прямо пропорционально ошибке, которую даёт соответствующий нейрон. В той статье ошибка определяется так:  (1), где j — номер элемента, для которого вычисляем ошибку, k — номер слоя, от которого пришла ошибка, т.е. слоя правее на нашей сети. Начнём вычислять ошибку каждого нейрона, идём с конца. Пусть на выходе сети мы ожидаем значение "O". Тогда для нейрона n6 ошибка составляет d6 = (O — y)*( f(in) )*(1 — f(in) ) (2), где in — входное значение в 
элемент n6, f(in) — значение активационной функции от этого входного значения.  Теперь исходя из формулы (1), рассчитываем ошибки для нейронов слоя слева. Для n4 ошибка выглядит так: d4 = d6 * w46 *( f(in4) ) * (1 — f(in4)), где w46 — вес связи между n4 и n6, in4 — входное значение нейрона n4. d5 = d6 * w56 *( f(in5) ) * (1 — f(in5)), для n5. d1 = (d4 * w14 + d5 * w15) *( f(in1) ) * (1 — f(in1)), а вот так выглядит ошибка нейрона n1, у него ведь не одна связь, а две — поэтому мы вносим в формулу ещё ошибку второй связи. Тогда для n2 и n3 ошибки выглядят соответственно: d2 = (d4 * w24 + d5 * w25) *( f(in2) ) * (1 — f(in2)) d3 = (d4 * w34 + d5 * w35) *( f(in3) ) * (1 — f(in3)) А сейчас, наконец, рассчитаем корректировку весов: Δw46 = d6 * A * f(in4), w46 — вес связи n4 и n6 нейрона, а f(in4) — значение активационной функции от входного значения нейрона n4, A — коэффициент скорости обучения обучения, чем он ближе к нулю — тем сеть медленнее, но точнее обучается. Δw56 = d6 * A * f(in5), соответственно корректировка связи элементов n5 и n6. Составим корректировки для остальных связей: Δw14 = d4 * A * f(in1) Δw24 = d4 * A * f(in2) Δw34 = d4 * A * f(in3) Δw15 = d4 * A * f(in1) Δw25 = d4 * A * f(in2) Δw35 = d4 * A * f(in3) Видите закономерность? Именно она и поможет в написании функции обучения. Тогда переходим к этому этапу. Особые моменты функции будем рассматривать подробнее Далее идёт цикл, отсчитывающий кол-во итераций обучения: В следующем цикле вычисляем ошибки нейронов выходного слоя: Следующим шагом начинаем идти от последнего слоя к первому, рассчитывая ошибку каждого нейрона и корректировку связи: Полностью функция выглядит так: Основные моменты для работы сети мы уже прописали. Сейчас давайте накидаем демонстрационный код: Получаем в выводе следующую картину: В первом столбике представлена вероятность (в процентах), нахождения в обучающей выборке конкретной цифры до обучения, от 0 до 9. А распределение вероятности после обучения представлено во втором столбике. Вот мы и видим, что в тестовой выборке наиболее вероятно находится цифра «4»  Дабы быть честным, скажу что мы, по-большому счёту, не обучили сеть. Одной обучающей выборки ничтожно мало для полноценного использования сети. Эта проверка больше нужна для проверки работы алгоритма. Итак, нужно достаточно большое число выборок, чтоб обучить сеть качественно. А на этой ноте изучение персептрона мы завершим. Далее можно заниматься оптимизацией кода, некоторые пути улучшения были описаны в прошлых частях. Спасибо за уделённое статье внимание, и за комментарии к предыдущей публикации. Продублирую ссылку на файлы библиотеки. Оставляйте свои комментарии, предложения. До скорого!