Всем привет. Начал я активно изучать нейронные сети, и решил похвастаться в этой статье, чему я уже научился.

На мой взгляд самое простое с чего нужно начинать изучать нейросети — это аппроксимация таких простых математических функций, как синус, квадратичная функция, экспонента и т.д. Согласно универсальной теореме аппроксимации — нейронная сеть с одним скрытым слоем может аппроксимировать любую непрерывную функцию многих переменных с любой точностью. Главное чтобы в этой сети было достаточное количество нейронов. И еще важно удачно подобрать начальные значения весов нейронов. Чем удачнее будут подобраны веса, тем быстрее нейронная сеть будет сходиться к исходной функции.

Исходя из этого, я решил проверить эту теорему на практике, и написал вот такую вот программу:

image

Обучение сети в программе, осуществляется методом стохастического градиентного спуска, который реализован в виде алгоритма обратного распространения ошибки. Процесс обучения наглядно отображается на графике в программе. Синим цветом на графике показана исходная функция, красным — функция построенная нейронной сетью. Можно выбирать различные функции активации для нейронов скрытого слоя, подбирать скорость обучения и изменять количество нейронов прямо в процессе обучения. И смотреть в реальном времени как изменение этих параметров влияет на процесс обучения сети.

Сама нейросеть имеет следующую структуру:

image
То есть она имеет два входа и один выход. Первый вход x — это значения функции, которые она может принимать по оси x. На второй вход всегда подается единица. В терминах нейросетей такой вход называется входом смещения. При его наличии сеть намного быстрее обучается и намного быстрее сходится к нужному нам результату. Эта сеть имеет два слоя слой, скрытый и выходной. На данной схеме показана сеть, которая содержит 3 нейрона в скрытом слое. Но в программе число этих нейронов можно менять в реальном времени, т.е. прямо в процессе обучения сети. Во втором же слое, число нейронов всегда неизменно. Т.е. там всегда один нейрон.

В нейронах в скрытого слоя применяется активационная функция — это может быть и выпрямитель «ReLU», сигмоид и гиперболический тангенс. Так же можно вообще убрать оттуда функцию активации «Empty», и там по сути там останется один сумматор. Будет интересно посмотреть как поведет себя сеть, не имея в себе нелинейных функций.

В нейроне второго слоя функции активации нет. Там обычный сумматор. Если бы там тоже была функция активации, то мы бы уже не смогли построить график функции нейронной сети.

Ну давайте теперь посмотрим, как это теперь всё будет работать в программе. Для начала попробуем аппроксимировать самую простою функцию синус. Поставим функцию активации на empty, то есть в нейронов скрытого слоя у нас не будет функции активации, а только сумматор. Поставим скорость обучения 00.1, Проинициализируем веса начальными случайными значениями, нажимаем кнопку старт и видим следующую картину:

image

Красная линия начала дрыгаться, а это значит пошло обучение. Но теперь, как бы мы не меняли настройки программы, кол-во нейронов или скорость обучения, эта дрыгающаяся линия так и остаётся линией. Почему? Да всё очень просто. Потому что в нейронах скрытого слоя нет активационных функций, то есть нет нелинейности в нейронной сети, и на выходе она всегда будет давать прямую линию. Этот пример я показал специально, что в нейросети обязательно должна присутствовать нелинейность, иначе сеть будет выдавать всегда вот такой результат, и в итоге ничему не обучиться.

Теперь давайте попробуем переключить функцию активации на ReLU «выпрямитель», и посмотрим что будет:

image

Ну вот видно, что тут уже поинтереснее начинается поведения сети, и эта линия начала выгибаться, то есть появилась нелинейность в сети. По сути, правую часть синуса, сеть уже аппроксимировала успешно. Давайте пробовать увеличивать количество нейронов, и наблюдать за изменениями:

image

Теперь стало еще интересней, получается, что 15 нейронов в принципе хватило, чтобы более-менее аппроксимировать функцию синуса. На этом графике можно видеть, что ReLU — это всё таки грубая функция активации, то есть она ломает нашу прямую линию на множество мелких отрезков, с острыми углами. То есть если мы будем увеличивать количество нейронов, то функция будет становиться более гладкой, за счёт увеличения числа переломов, которые ломают линию на отрезки. Но преимущество ReLU в том, что это очень простая функция. И имеет очень простую производную. Соответственно скорость вычислений существенно возрастает. Поэтому ReLU можно использовать там, где не важна высокая точность, а важно скорость работы нейронной сети.

Но давайте теперь переключим функцию активации на сигмоид, и смотрим что получилось:

image

Вуаля, и сеть с идеальной точность аппроксимировала функцию синуса. Все из-за того, что сигмоид — это более гладкая функция активации. И имеет более высокую точность. В отличии от ReLU, она не ломает прямую, как если бы вы взяли в руки две соломинки, и сломали их пополам. Сигмоид же сгибает эту линию, как сгибается металлическая проволока, в отличии от хрупкой соломинки. Но конечно же недостаток сигмоида в том, что его скорость вычисления ее существенно больше.

Также если теперь переключить функцию активации на гиперболический тангенс. Сеть тоже с идеальной точностью ее аппроксимирует. Но отличие тангенса от сигмоида в том, что тангенс на выходе дает диапазон значений от -1 до 1. А сигмоид от 0 до 1. Поэтому тут уже нужно смотреть, какая функция будет лучше подходить под какую-то конкретную задачу. То есть если в вашей задачи нужно чтобы нейросеть выдавал значения от нуля до единицы, то соответственно подойдет больше сигмоид, если от минус единицы до единицы, то тангенс.

Можно также экспериментировать с аппроксимацией не только синуса, но других, совершенно различных функций. Сеть будет аппроксимировать их без проблем:

image

Выходит, что универсальная теорема аппроксимации действительно верна, и нейронная сеть может аппроксимировать функцию с любой точностью. Тут остается главным только правильный подбор функции активации, скорости обучения и правильная инициализация начальных значений весов. Со всем этим надо экспериментировать, нарабатывать опыт и тренировать свою интуицию. Поскольку нет какого-то четкого правила обучения нейронных сетей. Все зависит от опыта программиста, ну и еще от везения.

Поделиться с друзьями
-->

Комментарии (32)


  1. Denxc
    10.07.2017 12:20
    +1

    Было бы интересно посмотреть на аппроксимацию замкнутых кривых!


    1. alex_ey
      10.07.2017 17:17

      У меня уже получилось аппроксимировать окружность, там просто в сеть добавится еще один вход y, плюс еще один скрытый слой. Позже напишу статью об этом.


    1. BelerafonL
      10.07.2017 17:47
      +1

      Есть в интрнетах готовая красивая онлайн-программка для визуализации простых нейросетей http://playground.tensorflow.org .


      1. alex_ey
        10.07.2017 18:40

        Я понимаю, что все это уже есть. Но суть в том, что когда ты напишешь свою программу с нуля, больше будешь понимать некоторые нюансы работы нейросетей. Которые можно упустить, если пользоваться уже готовыми решениями.


  1. kraidiky
    10.07.2017 13:03

    Важное дополнение без которого тебя закалупают всё опытные любители нейронных сетей — скрывать от сети часть входных данных и смотреть как сеть поведёт себя на этих участках. Я бы посоветовал, например, треть входных данных на графике скрывать от обучения и отмечать эти места цветом. Потом посмотреть как качество апроксимации ведёт себя если добавить в скрытый слой больше чем надо нейронов. Бывает довольно поучительное зрелище. А у некоторых других алгоритмов и при правильной регуляризации наоборот получается всё хорошо и красиво.


    1. alex_ey
      10.07.2017 13:33

      Спасибо. Хорошее дополнение.


  1. MaximChistov
    10.07.2017 13:16

    Но преимущество ReLU в том, что это очень простая функция. И имеет очень простую производную. Соответственно скорость вычислений существенно возрастает. Поэтому ReLU можно использовать там, где не важна высокая точность, а важно скорость работы нейронной сети.

    Нифига подобного. Преимущество ReLU в том, что для нее не надо подгонять входные и выходные данные под диапозон [0;1] или [-1;1] с нормированием. Что в реальной жизни на реальных данных далеко не всегда возможно.(Далеко не для всех данных известны максимумы и минимумы, без чего нормализация будет неточной).

    А теперь по функции. Вы решаете проблему, у которой уже есть решения намного более простые и эффективные чем нейронные сети — вывод функции по точкам 100% ей принадлежащим. Для этого нейросети не нужны! А нужны они в реальной жизни, когда к данным примешан случайный шум. И простые алгоритмы аппроксимации показывают себя намного хуже. Хотите, чтобы ваша демка была про нейросети — реализуйте добавление случайного шума хотя бы +-5% ко всем входным f(x).

    Ну и, как правильно указали выше, гораздо интереснее будет выведение нейросетей для замнутых или самопересекающихся кривых, где нейросети пишутся намного быстрее и лучше аналогов.


    1. alex_ey
      10.07.2017 13:39
      +1

      Про relu, такого не знал, спасибо. Я никакую проблему не решал. Этот пример учебный, и сделал для того что бы лучше понять, как работают/обучаются нейросети. Может еще кому-нибудь этот пример и статья пригодится. Начал с самого простого, и до примерами с шумом еще доберусь.


    1. daiver19
      11.07.2017 02:05

      Что-то я не слышал никогда такого аргумента о ReLU. Их основное преимущество — гораздо меньше шансов на затухание и «взрыв» градиента. Собственно, использование ReLU было одним из важных шагов по построению рабочих глубоких нейросетей (ну и скорость тоже, в принципе). А вот масштабировать данные всё равно стоит в подавляющем большинстве случаев.


  1. gudvinr
    10.07.2017 13:20
    +2

    То есть, вы предлагаете скачать левый бинарник? И исходники доступны только после покупки, так что проверить и скомпилировать самостоятельно для проверки не выйдет.
    Запускать исполняемые файлы из недоверенных источников любой здравомыслящий человек не будет.


    В статье никакой описательной части по существу моделей, алгоритмов и принципов работы нет. Просто полотно с красивыми картинками.
    Получается, вся суть в том, чтобы продать кота в мешке?


    1. alex_ey
      10.07.2017 13:46
      -3

      Ну если вы переживаете из-за вирусов в исполняемом файле, то уверяю вас, там их нет.


      Ну насчет исходников, мое право делать с ними все что захочу. Тем более подчеркиваю что эта статья не рекламнаю, тут про их продажу ничего нет. Есть лишь в видео и на моем сайте.


      1. YaakovTooth
        12.07.2017 15:10

        С вирусами или без вирусов (сам с ними разберусь без проблем), давайте, пожалуйста, бинарник побаловаться. :)


  1. myxo
    10.07.2017 17:04
    +1

    Зачем ещё одна начальная статья по нейронным сетям?

    «Выходит, что универсальная теорема аппроксимации действительно верна».
    Нельзя так говорить. Вы лишь проверили пару примеров (и то, это на самом деле не проверка). Нельзя на основе этого утверждать о верности теоремы.


  1. ImSept
    10.07.2017 17:40

    Уважаемый автор, если вы любите сложные задачи, могу подкинуть материал для размышления из геофизической среды. Сам занимаюсь обратными задачами и пытался использовать сети, но, увы, не вышло ничего хорошего.


    1. alex_ey
      10.07.2017 17:42
      -1

      Да я только начал изучение этой темы. Сомневаюсь что у меня сразу получится решить задачу из практики, тем более такую сложную.


    1. erwins22
      10.07.2017 19:28

      У меня знакомый интересуется этой областью.
      Если не жалко....


  1. erwins22
    10.07.2017 18:34

    LeakRelu
    CRelu
    и еще какие нить функции активации было бы интересно посмотреть


  1. aamonster
    10.07.2017 23:30

    троллейбус.jpg
    Взять нейронную сеть и начать с её помощью решать задачу, для которой она заведомо не очень хороша? Ну… Может, как учебная задачка и сойдёт, но я бы не стал.


  1. ilmarin77
    11.07.2017 07:34
    +1

    Функцию Дирихле аппроксимируй, для интереса.


    1. kraidiky
      13.07.2017 14:57

      Ой, а можно я!!! Можно я!!! Моя апроксимация:

      (double x) => { return 1; }
      

      Даёт 100%-ную точность приближения, как показывают тесты.
      Ну что умник, сам догадаешься почему, или подсказка через сутки? :)


      1. ilmarin77
        13.07.2017 16:46

        Свойства не те.


        1. kraidiky
          14.07.2017 14:59
          -1

          Иногда лучше быть добрым, чем не самым умным.

          Сможешь привести пример кода, который продемонстрирует, что свойства не те?
          А знаешь почему не сможешь? Потому что не целые числа в компьютере представляются в виде чисел с плавающей точкой, а они, так уж исторически сложилось, могут представлять только рациональные числа.

          Будь добрее к людям, и менее язвителен.


          1. ilmarin77
            14.07.2017 15:11

            1. kraidiky
              15.07.2017 19:35

              «Because the Dirichlet function cannot be plotted without producing a solid blend of lines» мы вместо неё построим другую функцию. :)))


              1. ilmarin77
                16.07.2017 02:20

                image
                — для необходимого уровня апроксимации — выбираем m и n.


  1. morincer
    12.07.2017 08:16

    Выходит, что универсальная теорема аппроксимации действительно верна

    А разве ее не доказал Цыбенко еще в 1989 году?

    В целом, пока пока статья смотрится бледновато — особенных глубин понимания не продемонстрировано. Настоятельно рекомендую посмотреть на курсере курс Machine Learning стэнфордского университета, хотя бы первые 4 недели курса. Там буквально на пальцах разжевывается суть задачи приближения функции и то, как она связана с нейронной сетью. В частности, там вы узнаете, почему добавление весового нейрона улучшает сходимость, в чем прелесть нормирования входных данных, и как можно строить нелинейные приближения, используя линейную функцию активации


    1. kraidiky
      12.07.2017 12:48

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

      ИМХО — правильный путь от половины до четверти пути, там где ты на основе подсказок и намёков понять что именно нужно делать чтобы это увидеть/провеить делать самому, потом снова читать чтобы нахвататься новых идей подсказок и готовых решений для следующей самостоятельно работы.


  1. mutiev
    12.07.2017 14:41

    Хороший подход к изучению этой темы! Очень правильный!


  1. EndUser
    16.07.2017 14:32

    Я не совсем понял: аппроксимация требует несколько двухмерных точек. А у вас на входе одинокое значение. Где второе для одинокой точки? Как вводятся другие точки?


    1. alex_ey
      17.07.2017 07:14

      Не понял вопроса. И что значит «одинокое значение»?


      1. EndUser
        17.07.2017 11:35

        Для аппроксимации нужна входная информация {x, f(x)}. Например, (-5, 0.96), (-4, 0.76), (-3, -0.14), (-2, -0.91), (-1, -0.84), (0, 0), (1, 0.84), (2, 0.91), (3, 0.14), (4, -0.76), (5, -0.96). Это синус. Для аппроксимации полиномом желательно иметь точек количеством не менее степени полинома, лучше больше. Если меньше, то аппроксимация станет бессмысленной, вообще не аппроксимирующей. А уж ввод функции без аргумента делает аппроксимацию просто невозможной.
        В статье этот вопрос не раскрыт напрочь. То есть для любой выбранной одинокой точки в сеть скармливается только f(x), без x. И не понятно сколько раз вводится.


  1. alex_ey
    17.07.2017 12:16

    Ну если вы невнимательно смотрели на схему, то в сеть скармливается x, а не f(x). На выходе сеть дает значение NetOut. Вычисляется ошибка сети, error = f(x) — NetOut. В соответствии с этой ошибкой корректируется веса. Таким образом делается 10 000 итераций (эпоха обучения). После каждой эпохи, график сети обновляется в программе.