О задаче
Часто, для того чтобы продемонстрировать ограниченные возможности однослойных персептронов при решении задач прибегают к рассмотрению так называемой проблемы XOR – исключающего ИЛИ. Данная логическая функция принимает два аргумента, которые могут быть 1 или 0. Функция принимает значение 1 когда один из двух аргументов 1, а другой 0. Если же оба равняются 0 или 1, то в результате мы получаем 0. На такое однослойная НС не способна.
Модель
Посмотрите внимательно на данную картинку.
В прошлый раз у нас было два входных нейрона, один выходной и от двух входных нейронов связи шли к выходному. Теперь мы имеем нейронную сеть, входные нейроны которые отправляют сигнал на скрытый слой, где у нас два обрабатывающих нейрона, и те передают сигнал на выходной нейрон, который тоже обрабатывает сигнал. Для такой задачи мы используем двумерные матрицы для скрытых нейронов, так как каждый нейрон имеет две связи. Это будет гораздо удобней, чем использовать обычные матрицы. Если вы не поняли что такое матрицы — примите это за массив.
Программируем
Программировать будем на JavaScript. Для начала нам необходимо написать все необходимые массивы и переменные: синапсы, входы, выход и нейроны.
{
var enters = new Array(2); // 2 входа
var hidden_layer = new Array(2); // два скрытых нейрона
var synapses_hidden = [[0.3, 1.3], [0.5,0.1]]; // от входов к скрытым нейронам
var synapses_output = [0.5, 0.1]; // от скрытых нейронов к выходу
var output = 0; // выход
Теперь создадим матрицу обучения сети.
var learn = [[0,0],[0,1],[1,0],[1,1]];
var learn_answers = [0,1,1,0];
}
Далее мы описываем сумматор, функции активации, передачу сигнала. В этот раз это выглядит чуть сложнее, чем в прошлый раз.
function sum(){
for ( var i = 0; i < hidden_layer.length; i++ ){
hidden_layer[i] = 0;
for ( var j = 0; j < enters.length; j++ )
hidden_layer[i] += synapses_hidden[j][i] * enters[j];
if ( hidden_layer[i] > 0.5 ) hidden_layer[i] = 1; else hidden_layer[i] = 0;
}
output = 0;
for ( var i = 0; i < hidden_layer.length; i++ )
output += synapses_output[i] * hidden_layer[i];
if ( output > 0.5 ) output = 1; else output = 0;
}
Обратное распространение ошибки
Уйдём ненадолго от программирования. Метод обратного распространения ошибки похож на delta правило. То есть сначала мы вычисляем ошибку(правильный ответ минус ответ сети), и затем меняем веса по формуле: w = w_last + err * n * x
Где w_last — прошлый вес, err — ошибка, n — скорость обучения и x — входной сигнал
В данном случае мы передаём ошибку по таким же связям, откуда пришёл сигнал. То есть от выхода к входу. Ещё необходимо завести счётчик ошибок обрабатывающего слоя и глобальную ошибку(gError = Math.abs(lErr))
var gError = 0; // глобальная ошибка
var errors = new Array(hidden_layer.length); // слой ошибок
do {
gError = 0; // обнуляем
for ( var p = 0; p < learn.length; p++ ){
for ( var i = 0; i < enters.length; i++ )
enters[i] = learn[p][i]; // подаём об.входы на входы сети
sum(); // запускаем распространение сигнала
var error = learn_answers[p] - output; // получаем ошибку
gError += Math.abs(error); // записываем в глобальную
for ( var i = 0; i < errors.length; i++ )
errors[i] = error * synapses_output[i]; // передаём ошибку на слой ошибок
// по связям к выходу
for ( var i = 0; i < enters.length; i++ ){
for ( var j = 0; j < hidden_layer.length; j++ )
synapses_hidden[i][j] += 0.1 * errors[i] * enters[j]; // меняем веса
}
for ( var i = 0; i < synapses_output.length; i++ )
synapses_output[i] += 0.1 * error * hidden_layer[i]; // меняем веса
}
} while(gError != 0);
Теперь пишем скрипт на запуск сети:
for ( var p = 0; p < learn.length; p++ ){
for ( var i = 0; i < enters.length; i++ )
enters[i] = learn[p][i]; // записываем входы
sum(); // распространяем сигнал
document.write(output + "<br/>"); // выводим ответы
}
Мы получим такой результат:
0 1 1 0
Если вам интересно — демонстрация обучения и написание сети на видео:
Финал
Данные метод не демонстрирует полные возможности многослойных сетей, лишь вводит вас в классификацию. Всем спасибо!
Комментарии (28)
AlexSerbul
11.02.2017 21:20-13Важное направление, нужно двигать и разъяснять, спасибо. Только бы не на JavaScript, а на более современном и правильном языке типа Scala — чтобы прививать любовь к правильному
EmeraldSoft
11.02.2017 21:22+1Языки программирования будут каждый раз меняться, а может буду давать примеры сразу на нескольких языках.
AlexSerbul
11.02.2017 21:28-5Фреймворки бы разные еще людям объяснить полезно: Keras, Torch7, TF, Deeplearning4j с примерчиками простенькими, те же AND и XOR, как фундаментальные. Но что делать с уровнем математики у населения, сам не знаю :-)
worldmind
11.02.2017 23:40+2Python думаю был бы идеален в данном случае
AlexSerbul
12.02.2017 00:28-1да, с питона проще начать будет. Хотя torch7 на lua и есть подозрение, что Javascript-ом нас поддалкивают к torch ;-)
Jabher
12.02.2017 11:18+3гхм. Я 2 месяца назад писал гораздо более объемную и полную статью про нейронные сети на JS. С объяснением того, зачем нам нужен сумматор и функция активации, как работает градиентный спуск, и с хорошим таким ноутбуком кода на RunKit-е.
https://habrahabr.ru/company/epam_systems/blog/317050/
И даже тогда я волновался, не пишу ли я вторичный контент.
Перед публикацией стоит проверять, не делал ли кто-то то же самое до вас.
К тому же — в примерах стоит использовать максимально читаемый код, в вашем я даже зная, что пытаются делать путаюсь.
EmeraldSoft
12.02.2017 11:34-1Да, я читал вашу статью. Дело в том что это небольшой курс статей для новичков, и каждая статья — новая часть. Прервать какие-то важный моменты из-за того, что кто-то опубликовывал подобное до меня было бы неправильно. Спасибо за отзыв, учту по поводу кода.
Jabher
12.02.2017 12:49+2в качестве общего курса для новичков стоит пользоваться известным, проверенным курсом. Например, курс Andrew Ng на coursera. На русском тоже есть большое количество известных и популярных курсов.
В случае с моей публикацией — она затачивалась конкретно под JS-программистов, которые пытаются встать на дорожку DS, в вашем же случае идет какое-то очень странное распыление.
MaximChistov
12.02.2017 13:05ой да вы все «огроменные циклы статей» обещаете, оправдывая этим унылость начальных, а потом на них все и заканчивается… :(
bitver
12.02.2017 12:21+2Статью изначально неинтересно читать, так как непонятно зачем это вообще нужно.
//Мой мозг мне кричит: "Здесь всё ясно, а в том коде много лишнего даже и не вздумай запоминать". function XOR(a,b) { return ( a || b ) && !( a && b ); }
Да и что за магические константы у синапсов?MaximChistov
12.02.2017 13:06+1function XOR(a,b) { return ( a || b ) && !( a && b ); }
Тот самый момент, когда еще не дошел до оператора ^bitver
12.02.2017 14:07+1Тот самый момент, когда читаешь очередной коммент с «Тот самый момент...» на протяжении 5 лет разработки на JS.
Это логическое исключающее ИЛИ(которое описано в статье!), а не побитовое — оно даёт ясно понять с каким типом данных идёт работа (true | false).MaximChistov
12.02.2017 14:18Что мешает преобразовать к
a !=0 ^ b != 0
?MaximChistov
12.02.2017 14:29Или более JS-style вариант:
!!a ^ !!b
bitver
12.02.2017 14:40var a = true, b = false; (a !=0 ^ b != 0) !== true; //Как ни крути (!!a ^ !!b) !== true; // ^ - операция не логическая
bitver
12.02.2017 14:34Вы придираетесь не по теме, как меня научили в универе на курсах дискретки делать, я так и делаю. Это выражение понятно всем и легко читается, а ваши варианты — пляски со свойствами конкретного языка, которые ещё вспомнить надо.
Akon32
Жаль, что десятки разных "Введений в нейросети" заканчиваются на таком же неглубоком уровне.
EmeraldSoft
Если наберутся хоть какие-то положительные мнения, то выпущу статью о LSTM сетях. Далее о сетях Хопфилда, методе градиентного спуска, сложной классификации, распознавании цветных изображений и т.д
Akon32
Проблема в том, что уже существуют куча объяснений "для новичков" сетей Хопфилда, Хэмминга, Кохонена, перцептронов (особенно перцептронов!), градиентного спуска, обратного распространения ошибки, но нет (или очень мало) туториалов по практическому применению нейросетей и по типам сетей, которые имеют практическое применение. В результате обучение этой теме обычно останавливается на примитивном устаревшем и теоретическом уровне.
Если есть возможность, пожалуйста, распишите пару архитектур сетей для распознавания изображений или звуков, с какими-нибудь рабочими примерами. Или принципы построения нейросетей для управления роботом (скажем, для формирования управляющих сигналов дрона по датчикам для стабилизации его ориентации). Да хотя бы для управления кондиционером.
Это будет намного интереснее, чем сухие теоретические примеры (тем более, что и так тысячи их).
EmeraldSoft
Ну суть моих статей и заключается как раз в том, что я даю людям теорию и практиктическое применение.
Akon32
Вы действительно применяли нейросеть для функции xor? Неужели это эффективно?
EmeraldSoft
С однослойной НС — нет. Для того, чтобы показать самый простой пример классификации на логической функции XOR — сойдёт многослойная НС.