Искусственный интеллект и Web: Часть 0

    Привет Хабр.



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


    Введение



    В этой статье разберем основополагающие понятия, такие как нейрон и ассоциативная память, собственно на этом и основывается большая часть того, что принято называть Искусственным интеллектом.

    Копипастить лекции по нейронным сетям и вики не буду, сразу перейдем у делу. Кто такой этот самый нейрон, как он математически описывается и для чего нужен, думаю каждый, при должной степени заинтересованности, прочтет сам. Далее предлагаю простейшую реализацию на JavaScript нейронной сети, состоящей из одного нейрона и реализующего операцию конъюнкция (операция, впрочем, может быть легко заменена с заменой матрицы обучения сети).

    Итак вот этот класс будет реализовать все функции нейронной сети.
    //Globals weights

    var weights = new Array();
    var defaultWeight = -1;
    //Our small Neuron Class=)
    with (NeuronClass = new Function){
      prototype.tsum = 0;
      prototype.prilly = 0;
      prototype.view = '';
      prototype.vec = new Array();
      //Sum all inputs
      prototype.sum = function(x){
        this.tsum = 0;
        for (var k = 0; k < 4;k++) {
          this.tsum += weights[k] * x[k];
        }
        this.tsum += defaultWeight;
        
        if (this.tsum < 0) {
          return 0;
        }
        else {
          return 1;
        }
        
      }
      //Teach function
      prototype.teach = function(i, d, k){
        this.prilly = 0;
        this.prilly = 0.1 * (1 + Math.sin(0.01 * k * d * i));
        return this.prilly;
      }
      //Check job our neoron
      prototype.check = function(vector){
        this.vec = vector.split(',');
        this.view += this.sum(this.vec);
        $("#out_2").html('Result: ' + this.view);
        this.view = '';
      }
    }


    * This source code was highlighted with Source Code Highlighter.

    Код не претендует на красоту, писалось на коленке, да и не для «совершенного кода». Разберем функционал.
    У нейрона есть четыре входа, внутри реализуется простой сумматор вида
    И реализован методом sum, на выходные значение этого метода «надета» пороговая функция активации (все эти страшные слова всего то
    if (this.tsum < 0) {
          return 0;
        }
        else {
          return 1;
        }
    )


    * This source code was highlighted with Source Code Highlighter.

    Метод teach реализует обучение сети т.е. в зависимости от расхождения значения полученного от нейросети и значения полученного по формуле (заведомо верного) мы изменяем весовые коэффициенты каждого входа нашего нейрона.
    И метод check понадобиться нам уже после обучения сети для проверки как она обучилась.
    Теперь проведем обучение сети на матрице обучения конъюнкции

     var i, j, k, Yt, Yv, d, ms;
        var biasing = new Array();
        var x = new Array();
        var values = new Array();
        var view = '';
        var Neuron = new NeuronClass();
        check = function(vector){
          Neuron.check(vector);
        }
        for (k = 0; k < 4; k++) {
          weights[k] = Math.random();
          biasing[k] = Math.random();
        }
        view += 'Start :  ' + weights[0] + '  ' + weights[1] + '  ' + weights[2] + '  ' + weights[3] + '<br />';
        i = 0;
        while (i <= 200) {
          j = Math.round(Math.random() * 10);
          switch (j) {
            case 1:{
              x[0] = 1;
              x[1] = 1;
              x[2] = 0;
              x[3] = 1;
              Yv = 0;
              break;
            }
            
            case 2:{
              x[0] = 1;
              x[1] = 1;
              x[2] = 1;
              x[3] = 0;
              Yv = 0;
              break;
            }
            
            case 3:{
              x[0] = 1;
              x[1] = 1;
              x[2] = 1;
              x[3] = 1;
              Yv = 1;
              
              break;
            }
            
            case 4:{
              x[0] = 1;
              x[1] = 1;
              x[2] = 0;
              x[3] = 0;
              Yv = 0;
              break;
            }
            
            case 5:{
              x[0] = 1;
              x[1] = 0;
              x[2] = 1;
              x[3] = 1;
              Yv = 0;
              break;
            }
            
            case 6:{
              x[0] = 1;
              x[1] = 0;
              x[2] = 1;
              x[3] = 0;
              Yv = 0;
              break;
            }
            
            case 7:{
              x[0] = 1;
              x[1] = 0;
              x[2] = 0;
              x[3] = 1;
              Yv = 0;
              break;
            }
            
            case 8:{
              x[0] = 1;
              x[1] = 0;
              x[2] = 0;
              x[3] = 0;
              Yv = 0;
              break;
            }
            
            case 9:{
              x[0] = 0;
              x[1] = 1;
              x[2] = 1;
              x[3] = 1;
              Yv = 0;
              break;
            }
            
            case 10:{
              x[0] = 0;
              x[1] = 0;
              x[2] = 0;
              x[3] = 0;
              Yv = 0;
              break;
            }
            
          }
          
          Yt = Neuron.sum(x);
          d = Yv - Yt;
          for (k = 0; k < 4; k++)
            values[k] = Neuron.teach(i, d, biasing[k]);
          for (k = 0; k < 4; k++)
            weights[k] = weights[k] + values[k] * d * x[k];
          i++;
        }
        view += 'Stop :  ' + weights[0] + '  ' + weights[1] + '  ' + weights[2] + '  ' + weights[3] + '<br />';
        $("#out").html(view);


    * This source code was highlighted with Source Code Highlighter.


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

    Yt = Neuron.sum(x);
          d = Yv - Yt;
          for (k = 0; k < 4; k++)
            values[k] = Neuron.teach(i, d, biasing[k]);
          for (k = 0; k < 4; k++)
            weights[k] = weights[k] + values[k] * d * x[k];


    * This source code was highlighted with Source Code Highlighter.


    Тривиальны, но тем не менее, здесь мы проверяем насколько «ушел» результат от ожидаемого и в зависимости от этого вызываем функцию обучения для каждого из входов сети.

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

    Пример http://bnet.su/dvl/nn1/
    Исходники http://bnet.su/dvl/nn1/nn1.zip

    Сеть Хопфилда


    Теперь разберемся с сетью Хопфилда. Данная сеть реализует автоассоциативную память и интересна нам с точки зрения возможности восстанавливать образцы. Тут все просто, получив по вектору (n мерному) матрицу размера n*n мы может подать на «вход» сети искаженный вектор и в итоге получим исходный, это очень полезное свойство сети, думаю не стоить объяснять, где и как это можно использовать. В вики есть множество теоретической информации по этому поводу, так что не будем здесь останавливаться, к тому же, мы преследуем другие цели.

    От слов к коду. Класс MemClass реализует в себе все методы которые нам понадобятся для работы с сетью

    with (MemClass = new Function){
      prototype.global_matrix = new Array();
      prototype.sign = function(value){
        return (parseFloat(value) > 0) ? '1' : '-1';
      }
      prototype.searchW = function(vector){
        var vec = new Array();
        var returned = new Array();
        var tmp = new Array();
        vec = vector.split(',');
        this.ViewerW(this.getW(this.getTmp(vec))[1]);
      }
      prototype.getTmp = function(vec){
        var tmp = new Array();
        var count = 0;
        count = vec.length;
        for (var i = 0; i < count; i++) {
          tmp[i] = parseFloat(2 * vec[i] - 1);
        }
        return tmp;
      }
      prototype.getW = function(tmp){
        var view = '';
        var returned = new Array();
        var count = 0;
        count = tmp.length;
        returned[0] = new Array();
        for (var i = 0; i < count; i++) {
          for (var j = 0; j < count; j++) {
            //alert(returned[i]);
            if (j == 0)
              returned[i] = new Array();
            returned[i][j] = parseFloat(tmp[i] * tmp[j]);
            if (i == j)
              returned[i][j]--;
            if (returned[i][j] >= 0)
              view += ' ';
            view += returned[i][j];
          }
          view += '<br />';
        }
        this.global_matrix = returned;
        //tmp
        return Array(returned, view);
      }
      prototype.check = function(vector, j){
        var sum = 0;
        for (var i = 0; i < vector.length; i++) {
          sum = sum + parseFloat(vector[i]) * parseFloat(this.global_matrix[j][i]);
        }
        return sum;
      }
      prototype.checkMatrix = function(vector){
        var view = '';
        var vec = new Array();
        vector = vector.split(',');
        for (var i = 0; i < vector.length; i++) {
          vec[i] = this.sign(this.check(vector, i));
          view += vec[i];
        }
        this.ViewerCheck(view);
        prototype.ViewerW = function(matrix){
          $("#matrix").html(matrix);
          $("#form_second").css({
            display: "block"
          });
        }
      }
        prototype.ViewerCheck = function(vector){
          $("#check_vector").html(vector);
          
        }
      prototype.ViewerW = function(view) {
        $("#matrix").html(view);
        $("#matrix").show("drop", {
          direction: "right"
        }, 500);
        $("#form_second").css({display: 'block'});
        
      }


    * This source code was highlighted with Source Code Highlighter.


    Разберемся что к чему. При вводе вектора для запоминания, в конечном итоге вызывается метод getW, в котором реализована следующая функция , где I – единичная матрица, размера n*n. На этом математика не кончается, теперь при восстановлении вектора используется след операции (реализованные методом checkMatrix) . Ну вот и практически все, теперь мы можем запомнить какой ни будь бинарный вектор, и всячески его изменяя, понять и найти когда сеть скажет «Фу!».

    Ну и немного колдовства с jQuery и все готово

    $(function(){
      $('#form_first label').tooltip({
        track: true,
        delay: 100,
        showBody: '::',
        opacity: 0.85,
        bodyHandler: function(){
          return 'Введите образец для запоминания, это должен быть бинарный вектор состоящий из "1" и "-1" (через запятую) любой размерности (во вменяемый пределах =))';
        }
      });
      $('#form_second label').tooltip({
        track: true,
        delay: 100,
        showBody: '::',
        opacity: 0.85,
        bodyHandler: function(){
          return 'Введите вектор, той же размерности, но с некоторыми отличиями (отличие во всех символах не будет восстановлено )';
        }
      });
      $('#matrix').tooltip({
        track: true,
        delay: 100,
        showBody: '::',
        opacity: 0.85,
        bodyHandler: function(){
          return 'Матрица для запоминая введеного вектора';
        }
      });
      $('#check_vector').tooltip({
        track: true,
        delay: 100,
        showBody: '::',
        opacity: 0.85,
        bodyHandler: function(){
          return 'Восстановленный вектор';
        }
      });
    });


    * This source code was highlighted with Source Code Highlighter.


    Для большей понятности вешаем подсказки, используем для этого tooltip плагин для jQuery.

    Пример http://bnet.su/dvl/nn2/.
    Исходники http://bnet.su/dvl/nn2/nn2.zip.

    Заключение.


    В этой статье нами были рассмотрены основы основ нейронных сетей, надеюсь что для математиков рассказал не слишком голословно и необоснованно, а для программистов не слишком сухо и скучно. В следующей статье речь пойдет о так называемых «генетических» алгоритмах, ну и собственно о том, зачем вебу нейросети.
    Зеркало статьи у меня в блоге http://bnet.su/blog/?p=30.

    Метки:
    Поделиться публикацией
    Комментарии 39
    • +5
      Не лохо. Понравилось :)
      • +3
        Не лохо :-)
      • +2
        А я ничего не понял, но читать было жутко интересно =)))
        Скажите, что покурить чтобы создать подобие точечной жизни?
    • –1
      А на практике это как то использовать можно? Или это памятка о 7 классе «Истина/Ложь»?
      • +1
        На практике использовать однослойную нейронную сеть нельзя (вернее можно, но толку от неё никакого) по поводу памятки, перечитайте статью, вы наверно не все поняли. А на практике будут использоваться многослойные сети, состоящие и большого количества нейронов, но для того что бы понять как они работают мной и был начат цикл статей о нейронный сетях.
        • 0
          Было интересно почитьать и посмотреть на примере, как это все работает.
          Вы упомянули многослойные сети… Не планируете ли вы случайно про XOR рассказывать в следующих статьях этого цикла?
          (ивините, наглею) =)
          • +1
            В следующей статье речь пойдет о генетических алгоритмах, в дальнейших будем подробно рассматривать многослойки. XOR НЕ решается один нейроном как не крути, по поводу того сможет ли его решить сеть со скрытым слоем, поссмотрим далее :)
            • 0
              Так и знал, что одним нейроном тут не обойтись )
              Спасибо.
            • 0
              не надо про генетический отбор.
              лучше если есть примеры кода на тормозящих связях.
          • НЛО прилетело и опубликовало эту надпись здесь
      • +1
        У тому обучать сеть можно на любых таблицах.
        • 0
          Ну что ж, хочется пожить, хочется увидеть.
    • +1
      Очень интересно, вот вчера только подумал, чем же себя увлечь ))), а тут такая тема началась, Жду продолжения…
    • –2
      очень похоже на построение системы фильтров (как в электронике), не более
    • 0
      Не совсем понятно что с функциями активации? Их нет или я их не заметил?
      • 0
        Сигмоид, вот же они
        return (parseFloat(value) > 0) ? '1' : '-1';
        • 0
          Не заметил для второго примера (
          (parseFloat(value) > 0)? '1': '-1' — не совсем сигмоид конечно ;-)
          больше похоже на пороговую функцию).

          А в первом примере функии активации нет?

          • 0
            Тоже есть, правда причем тажа функция, только написана «коряво» ;(
            Вот
            if (this.tsum < 0) {
            return 0;
            }
            else {
            return 1;
            }
            )
    • 0
      Неужели нашлись люди, которые это всё прочитали?

      P.S. Я НЕ ТУПОЙ!!! Я НЕ ТУПОЙ!!!
      • +1
        Вроде как преследовались цели написать «попроще» =) А примеры то вообще тривиальные.
    • 0
      На премию Лёбнера не собираетесь?

      Вот бот A.L.I.C.E который получил эту премию в 2008м году.

      там можно с ней початиться, только к сожалению моего знания английского языка не хватает чтобы заставить её сказать что то такое что отличило бы её от человека.
      • 0
        Отичный бот, кстати если верить алисе, то она голосовала за Обаму =).
      • +2
        Ну это же просто :)
        Human: hello
        ALICE: Hi there!
        Human: hello
        ALICE: Hi there!
        Human: hello
        ALICE: Hi there!
        Human: are you bot?
        ALICE: I don't know whether or not I am bot. I am a chatterbot developed by Dr. Richard S. Wallace.
        Human: you are stupid bot
        ALICE: Hey, I'll tell my other robot friends you called me stupid.
    • –1
      И это Вы называете простым и интересным языком?
      • 0
        Да, это простой язык, во всяком случае более понятного объяснение принципов работы нейро сети я не находил.
        • +1
          Я немного знаком с НС, но если честно не сильно быстро сориентировался в вашей статье.
          Думаю человеку никогда не сталкивовшимся с НС нелегко будет понять что и как (некоторым — «Stepler», даже показалось похоже на систему фильтров ).

          Возможно, читающим было бы интересно все же узнать о некоторых базовых принципах НС перед тем как углубляться в код:
          a) о том, что существуют неверояное количество архитектур и модификаций НС, а также алгоримов обучения
          b) что такое алгорим обучения и для чего он нужен
          с) какие преимущества дают НС при решении слабоформализоавнных задач
          d) какие плюсы и минусы нейросетевого подхода в сравнении с другими подходами

          Вообще на мой взгляд рассмативать принципы работы НС на javascript-е, на данный момент еще рано.
          Одно дело переносить уже функционирующую (я имею ввиду обученную) НС на javascript.
          Другое дело, когда задача не решена и предстоит ее решить.

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

          В любом случае атору спасибо за статью, возможно кто-то из прочитавших заинтересуется НС и напишет систему которая все-таки пройдет Тест Тьюринга ;-).
          • 0
            Спасибо за критику, про то почему выбран JS написал ниже.
    • –1
      Лучше бы тогда на Ruby реализовал ;)
      P.S. не плохо* :DD
    • –1
      Статья написана на простом и интересном языке — имелся ввиду JavaScript, видимо. Кошмар.
      • 0
        Имелся ввиду русский язык. Что кошмар?
        • 0
          Для статьи, заявленной как «на простом и интересном русском языке», слишком много кода. Он больше половины занимает!

          Заявляете теорию — пишете в основном о практике, на отдельном примере, не поясняя сути.

          Я не говорю о том, что статья никому не полезна. Я говорю о том, что она не соответствует заявляемым в её начале целям и формирует неверное ожидание, которое начинает обламываться на отсылках типа «теорию каждый может почитать сам». Мой Вам совет — перечитайте свою статью, и перепишите вступление.
    • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        мы не нейроны, мы, по крайней мере, довольно хорошо обученные многослойные персептроны ;-)
      • 0
        На JS написано так как
        1) Сеть элементарная и язык реализации не существенен
        2) Для того что бы можно было «не отрываясь» от браузера поссмотреть код и проверить функционал
        3) Более сложные сети будут реализованы на чем нибудь более «тяжелом»
    • 0
      статью хорошо дополнила бы картинка иллюстрирующая динамику обучения.

      и не совсем понятно, зачем weights сделаны глобальными.
    • +1
      Спасибо за материал, быть может тоже приму участие в написании статей по нейронным сетям, т.к. тема очень интересна!
    • 0
      а мне приятно было пример расковырять и посмотреть. Понятно, некоторые принципы сразу стали очевидны
    • 0
      Код не претендует на красоту, писалось на коленке, да и не для «совершенного кода».

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

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.