В данной статье, я хочу представить вам свою программу, сделанную в Unity, которая демонстрирует процесс работы простой нейронной сети.
Unity на данный момент - это очень удобная и современная среда разработки приложений, для которой сейчас появляется достаточно много компонентов и фреймворков, которые позволяют работать с нейронными сетями. Но данная моя программа не использует никаких дополнительных модулей, так как нейронная сеть здесь достаточно простая, состоит всего из одного слоя, и выполнена она в виде обычного небольшого C# скрипта. Я сделал здесь все максимально просто, чтобы по этому скрипту можно было легко изучить алгоритм, по которому обучаются нейронные сети, то есть - это алгоритм обратного распространения ошибки.
Нейросеть, представленная в этой программе, показывает, как можно аппроксимировать различные математические функции, такие как синус, косинус, x2, и другие... На рисунке [2] показана схема данной нейронной сети.
На данной схеме видно, она имеет два входа и один выход. Первый вход x — это значения функции, которые она может принимать по оси x. На второй вход всегда подается единица. В терминах нейросетей такой вход называется входом смещения. При его наличии сеть намного быстрее обучается и намного быстрее сходится к нужному нам результату.
Сеть состоит из двух так называемых "слоев" - скрытого и выходного. В скрытом слое содержится три нейрона. Но в программе число этих нейронов можно менять в реальном времени, т.е. прямо в процессе обучения сети. Что бы можно было в динамике посмотреть, как изменение кол-ва нейронов влияет на точность обучения сети. Причем в скрытом слое у каждого нейрона есть по два входа, на которые подаются значения x и 1 соответственно. Второй "выходной" слой сети, содержит только один нейрон.
В нейронах скрытого слоя применяется функция активации. Она нужна чтобы в сети появилась нелинейность. Если бы в сети отсутствовала эта функция, то такая нейросеть могла бы аппроксимировать только линейные функции, то есть по сути просто прямые линии. Функция активации же придаёт нейросети свойства нелинейности, и она может аппроксимировать уже более сложные функции, у которых очень много перегибов на графике.
В нейроне второго слоя функции активации нет. Там обычный сумматор. Если бы там тоже была функция активации, то мы бы уже не смогли построить график функции нейронной сети.
Рисунок [3] очень хорошо иллюстрирует, почему нелинейность так важна в нейронных сетях. Нелинейные функции активации позволяют нейронной сети строить более сложные границы между классами. Например в задаче классификации точек, без нелинейности нейронная сеть могла бы только провести прямую линию, которая разделяет черные и белые точки. Однако, с нелинейными функциями активации, нейронная сеть может построить более сложную границу между классами, которая может быть кривой и перегибаться, как показано на правом графике. Это позволяет нейронной сети более точно классифицировать данные, и достичь лучшей производительности в задаче классификации.
По сути нейронные сети, могут аппроксимировать функции любой сложности, главное чтобы в этой сети было достаточное количество нейронов.
Существует множество видов активационных функций, но самые распространенные из них - это так называемый "выпрямитель" ReLU (rectified linear unit), а также такие функции как сигмоид и гиперболический тангенс. Отличие всех этих активационных функций друг от друга состоит в том, что например релу ломает прямые и делает из них отрезки с острыми углами, а, например, сигмоид уже сгибает эту линию более плавно. Рисунок [4].
Но в то же время недостаток сигмоида, состоит в том, что скорость его вычисления существенно больше, тогда как скорость вычисления релу намного меньше. Также есть ещё гиперболический тангенс, но в отличие от сигмоида, тангенс на выходе даёт диапазон значений от - 1 до 1, сигмоид же от 0 до 1, поэтому тут уже нужно смотреть, какая функция будет лучше подходить под какую-то конкретную задачу. То есть если в вашей задаче нужно, чтобы нейросеть выдавала значение от 0 до 1, то, соответственно, подойдет больше сигмоид, если от -1 единицы до 1, то тангенс.
Суть обучения нейронной сети заключается в том чтобы правильно подобрать значения весовых коэффициентов. Т.е. значение весов нейронов скрытого и выходного слоя. Процесс обучения происходит следующим образом: Мы просто генерируем случайное вещественное число от - 2 до 2, вычисляем значение исходной функции и функции нейросети в этой точке, а также вычисляем ошибку, то есть разницу значений между исходной функцией и значением которое нам выдала нейронная сеть. И в соответствии с этой ошибкой, просто подстраиваем веса нужным образом, с помощью алгоритма обратного распространения ошибки. Рисунок [5].
И таких итераций обучения происходит очень много, пока мы не получим минимальное значение ошибки, которое нас будет устраивать.
Тут остаётся главным, только правильный подбор функции активации, скорости обучения и правильная инициализация начальных значений весов. Со всем этим надо экспериментировать, нарабатывать опыт, и тренировать, так сказать, свою интуицию. Поскольку нет какого-то чёткого правила обучения таких нейронных сетей, все зависит от опыта программиста, ну и ещё и от везения. Соответственно, данная программа и позволяет наработать какой-то начальный опыт, чтобы вообще научиться как-то понимать, что же такое эти нейронные сети.
Также, вы можете посмотреть видео, в котором более наглядно показано как работает данная программа.
Саму программу демонстрирующую работу нейронной сети можно скачать здесь.