Pull to refresh

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

Reading time 13 min
Views 27K

Привет Хабр.



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


Введение



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

Копипастить лекции по нейронным сетям и вики не буду, сразу перейдем у делу. Кто такой этот самый нейрон, как он математически описывается и для чего нужен, думаю каждый, при должной степени заинтересованности, прочтет сам. Далее предлагаю простейшую реализацию на 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.

Tags:
Hubs:
+51
Comments 39
Comments Comments 39

Articles