Привет Хабр.
Почитав то, что на хабре пишут по нейронным сетям захотелось более простым и интересным языком рассказать о искусственном интеллекте. Идея такова, во-первых написать цикл статей об основах нейронных сетей, ну а во-вторых есть несколько идей для интересных проектов, совмещающих интерактивность присущую всему вебдванольному и обучаемость нейросетей, но это позже.
Введение
В этой статье разберем основополагающие понятия, такие как нейрон и ассоциативная память, собственно на этом и основывается большая часть того, что принято называть Искусственным интеллектом.
Копипастить лекции по нейронным сетям и вики не буду, сразу перейдем у делу. Кто такой этот самый нейрон, как он математически описывается и для чего нужен, думаю каждый, при должной степени заинтересованности, прочтет сам. Далее предлагаю простейшую реализацию на 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.
Код не претендует на красоту, писалось на коленке, да и не для «совершенного кода». Разберем функционал.
У нейрона есть четыре входа, внутри реализуется простой сумматор вида
![](http://bnet.su/dvl/nn1/f1.png)
И реализован методом 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, в котором реализована следующая функция
![](http://bnet.su/dvl/nn1/f2.png)
![](http://bnet.su/dvl/nn1/f3.png)
Ну и немного колдовства с 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.