Содержание статьи
Пишем нейросеть прямого распространения с нуля | Как создать свою нейросеть
Сеть есть, но её ответы случайны. Как обучать?
Для того, чтобы уменьшить ошибку сети нужно изменить весовые коэффициенты каждого слоя. Как же именно нужно менять весовые коэффициенты матриц на каждом слое? Оказывается, всё довольно просто. Для этого используется метод градиентного спуска, а значит нам необходимо вычислить градиент по весам и сделать шаг в отрицательную сторону от этого градиента. На этапе прямого распространения мы зачем-то запоминали входные сигналы, а при обратном распространении ошибки мы вычисляли дельты в каждом слое. Именно их мы и будем сейчас использовать для нахождения градиента! Градиент по весам равен перемножению входного вектора и вектора дельт (не покомпонентно). Поэтому, чтобы обновить весовые коэффициенты и уменьшить тем самым ошибку сети нужно всего лишь вычесть из матрицы весов результат перемножения дельт и входных векторов, умноженный на скорость обучения. Это можно записать в таком виде: Wt+1 = Wt — η·δ·X , где Wt+1 — новая матрица весов, Wt — текущая матрица весов, X — входное значение слоя, δ — дельта этого слоя. Почему именно так с математической точки зрения хорошо описано в этой статье.
Проверять результаты на тренировочной же выборке довольно скучно, ведь как никак на ней мы сеть обучали, но, увы, для XOR проблемы ничего другого не остаётся. В качестве более серьёзного примера рекомендуем выполнить задачу распознавания картинок с рукописными цифрами MNIST. Это база содержит 60000 картинок написанных от руки цифр размером 28 на 28 пикселей и используется как один из основных датасетов для начала изучения машинного обучения. Не смотря на простоту нашей сети, при грамотном выборе параметров (число нейронов, число слоёв, скорость обучения, число эпох. ) можно получить точность распознавания до 98%! Проверить свою сеть вы можете, поучаствовав в соревновании на сайте Kaggle. Нашей команде удалось достичь точности в 98.171%! А вы сможете больше? 🙂
Таким образом, если маленькое растровое изображение преобразовать в векторы, сделать up-scaling, мы скорее всего уже получим что-то интересное — обратите внимание на программу Potrace. В OpenCV готового для raster->vector ничего нет. Хотя из доступных инструментов и можно слепить что-то, врядли получится лучше чем у Potrace — всё-таки там люди занимались этой проблемой специально. Potrace идёт с исходниками и подробным описанием алгоритмов, так что простор для творчества полный 🙂
Даже для умеренно сложных задач НС обычно не нужна, и не очень даже применима — часто приходится делать обработку видеопотока в реальном времени, а НС не самая быстрая штука далеко. По этой и еще ряду причин большинство разработчиков, уверенных поначалу что им нужна НС, от этой идеи впоследствии отказываются. Благо в OpenCV реализовано огромное количество алгоритмов на все случаи жизни — почти всё, что связано с обработкой видео и изображений там есть.
Что бы понять про что я говорю — попробуй просто отскейлить картинку, а потом попробуй закинуть ее же в waifu2x.udp.jp . А потом уже будешь рассказыать на сколько алгоритм скейла лучше справляется с работой чем НС 🙂 Отдельно попробуй на нескольких фотках и отдельно на нескольких рисованых картинках.
Большинство алгоритмов, реализованных в доступных пакетах, в основном предназначены для down-scaling, спрос на это очевиден — камера снимает в большом разрешении, а в Интернеты лучше постить, предварительно уменьшив фотку — этим пользуются все активные пользователи Интернетов и соцсетей.
Сама нейросеть:
Для обратного распространения ошибки нам потребуется знать значения входов, выходов и значения производных функции активации сети на каждом из слоёв, поэтому создадим структуру LayerT, в которой будет 3 вектора: x — вход слоя, z — выход слоя, df — производная функции активации. Также для каждого слоя потребуются векторы дельт, поэтому добавим в наш класс ещё и их. С учётом вышесказанного наш класс станет выглядеть так:
Перейдём к обратному распространению ошибки. В качестве функции оценки сети E(W) возьмём среднее квадратичное отклонение: E = 0.5 · Σ(y1i — y2i) 2 . Чтобы найти значение ошибки E, нам нужно найти сумму квадратов разности значений вектора, который выдала сеть в качестве ответа, и вектора, который мы ожидаем увидеть при обучении. Также нам потребуется найти дельту для каждого слоя, причём для последнего слоя она будет равна вектору разности полученного и ожидаемого векторов, умноженному (покомпонентно) на вектор значений производных последнего слоя: δlast = (zlast — d)·f’last , где zlast — выход последнего слоя сети, d — ожидаемый вектор сети, f’last — вектор значений производной функции активации последнего слоя.
Up-scaling задача тоже достаточно хорошо проработана, просто спрос на нее несоизмеримо меньше — тем не менее любой дизайнер отлично знает, что нужно делать, чтобы изображение можно было без проблем увеличивать — взять Corel Draw или другой пакет векторной графики, то есть сделать изображение векторным.
Мы написали с вами нейронную сеть прямого распространения и даже обучили её функции XOR. При этом мы позаботились об универсальности, благодаря чему нейросеть может быть обучена на любых данных, главное только подготовить два массива обучающих векторов X и Y, подобрать параметры обучения и запустить само обучение, после чего наблюдать за процессом. Важно помнить, что при использовании сигмоидальной функции активации, выходные значения сети не будут превышать 1, а значит, для обучения данным, которые значительно больше 1 необходимо отнормировать их, то есть привести к отрезку [0, 1].
Почему функция XOR так интересна? Просто потому, что её невозможно получить одним нейроном: 0 ^ 0 = 0, 0 ^ 1 = 1, 1 ^ 0 = 1, 1 ^ 1 = 0. Однако она легко получается увеличением числа нейронов. Мы же попробуем выполнить обучение сети с 3 нейронами в скрытом слое и 1 выходным (так как выход у нас всего один). Для этого нам необходимо создать массив векторов X и Y с обучающими данными и саму нейросеть:
пытаюсь понять что имеется в виду «делает из маленкого большое» 🙂 уже ж есть и большие и маленькие, где кому и за сколько надо еще их делать? Или вы хотите так натренировать систему, чтобы она потом из любой маленькой делала большую хорошего качества — не понимаю
Один нейрон способен входной вектор превратить в одну точку, однако по условию мы хотим получить несколько точек, так как выходной вектор Y может иметь произвольную размерность, определяемую лишь конкретной ситуацией (один выход для XOR, 10 выходов для определения принадлежности к одному из 10 классов и т.д.). Как же нам получить n точек, преобразуя элементы входного вектора X? Оказывается, всё довольно просто: для того, чтобы получить n выходных значений, необходимо использовать не один нейрон, а n. Тогда для каждого из элементов выходного вектора Y будет использовано ровно n различных взвешенных сумм от вектора X. То есть мы получаем, что zi = f(yi) = f(wi0·x0 + wi1·x1 + . + wim — 1·xm — 1)
Теперь нам достаточно знаний, чтобы написать код получения результата нейронной сети. Мы будем писать код на языке C#, однако, уверяем, код будет практически идентичным для других языков программирования. Давайте разберёмся, что нам потребуется для реализации сети прямого распространения: