Neural Network from scratch in JS - ru

node v6.17.1
version: 1.0.0
endpointsharetweet
Это пример нейронной сети, решающей XOR, и сделанной с нуля Схема сети: 2 узла на входе(i1, i2), 2 узла на скрытом слое(h1, h2), 1 выходной узел(o1) На каждом уровне используется bias, функция активации - сигмоида.
Ramda - это вспомогательная библиотека а-ля lodash, которая используется исключительно для удобства в вспомогательных функциях. Можно было бы обойтись и без нее
var R = require('ramda'); void 0;
Используем воспроизводимый random, чтобы понять, что мы сделали не так
var random = require('seed-random')(1337);
Данные для обучения - это все возможные комбинации XOR:
var data = [ {input: [0, 0], output: 0}, {input: [1, 0], output: 1}, {input: [0, 1], output: 1}, {input: [1, 1], output: 0}, ];
Эти функции можно посмотреть на WolframAlpha: https://www.wolframalpha.com/input/?i=1+%2F+(1+%2B+e+^+-x) https://www.wolframalpha.com/input/?i=dx+1+%2F+(1+%2B+e+^+-x)
var activation_sigmoid = x => 1 / (1 + Math.exp(-x)); var derivative_sigmoid = x => { const fx = activation_sigmoid(x); return fx * (1 - fx); };
var weights = { i1_h1: random(), i2_h1: random(), bias_h1: random(), i1_h2: random(), i2_h2: random(), bias_h2: random(), h1_o1: random(), h2_o1: random(), bias_o1: random(), };
Сама нейронная сеть (без обучения, только прямой проход)
function nn(i1, i2) { var h1_input = weights.i1_h1 * i1 + weights.i2_h1 * i2 + weights.bias_h1; var h1 = activation_sigmoid(h1_input); var h2_input = weights.i1_h2 * i1 + weights.i2_h2 * i2 + weights.bias_h2; var h2 = activation_sigmoid(h2_input); var o1_input = weights.h1_o1 * h1 + weights.h2_o1 * h2 + weights.bias_o1; var o1 = activation_sigmoid(o1_input); return o1; }
var calculateResults = () => R.mean(data.map(({input: [i1, i2], output: y}) => Math.pow(y - nn(i1, i2), 2))); var outputResults = () => data.forEach(({input: [i1, i2], output: y}) => console.log(`${i1} XOR ${i2} => ${nn(i1, i2)} (expected ${y})`));
И наша ошибка...
calculateResults()
Почему?
outputResults()
Пора научить нашу сеть!
var train = () => { const weight_deltas = { i1_h1: 0, i2_h1: 0, bias_h1: 0, i1_h2: 0, i2_h2: 0, bias_h2: 0, h1_o1: 0, h2_o1: 0, bias_o1: 0, }; for (var {input: [i1, i2], output} of data) { //это код, просто скопированный из функции выше - чтобы научить сеть, нужно сначала делать проход вперед var h1_input = weights.i1_h1 * i1 + weights.i2_h1 * i2 + weights.bias_h1; var h1 = activation_sigmoid(h1_input); var h2_input = weights.i1_h2 * i1 + weights.i2_h2 * i2 + weights.bias_h2; var h2 = activation_sigmoid(h2_input); var o1_input = weights.h1_o1 * h1 + weights.h2_o1 * h2 + weights.bias_o1; var o1 = activation_sigmoid(o1_input); //Обучение начинается: // мы расчитываем разницу var delta = output - o1; // затем берем производную (и выкидываем 2 *, потому что это нам не так важно) var o1_delta = delta * derivative_sigmoid(o1_input); //и для нашей формулы вида w1 * h1 + w2 * h2 мы вначале пытаемся обновить веса w1 и w2 weight_deltas.h1_o1 += h1 * o1_delta; weight_deltas.h2_o1 += h2 * o1_delta; weight_deltas.bias_o1 += o1_delta; //А затем входные значения h1 и h2. //Мы не можем просто взять и изменить их - это выход такой же функции активации //Поэтому мы пропускаем эту ошибку дальше по тому же принципу var h1_delta = o1_delta * derivative_sigmoid(h1_input); var h2_delta = o1_delta * derivative_sigmoid(h2_input); weight_deltas.i1_h1 += i1 * h1_delta; weight_deltas.i2_h1 += i2 * h1_delta; weight_deltas.bias_h1 += h1_delta; weight_deltas.i1_h2 += i1 * h2_delta; weight_deltas.i2_h2 += i2 * h2_delta; weight_deltas.bias_h2 += h2_delta; } return weight_deltas; }
var applyTrainUpdate = (weight_deltas = train()) => Object.keys(weights).forEach(key => weights[key] += weight_deltas[key]);
Попробуем.
applyTrainUpdate(); outputResults(); calculateResults();
Ошибка уменьшилась, но результат все еще не удовлетворяет. Повторим еще раз 100
R.times(() => applyTrainUpdate(), 100) outputResults(); calculateResults();
Ошибка говорит, что все стало лучше, но выглядит все так, словно результат случаен. Продолжим и повторим еще 1000 раз.
R.times(() => applyTrainUpdate(), 1000) outputResults(); calculateResults();
А это уже похоже на правду. Чем больше мы будем повторять дальше, тем ближе мы приблизимся к верному ответу. Итак, мы только что научили наш код воспроизводить модель, которую ему показали на примерах входных данных.
Loading…

3 comments

  • posted 7 years ago by makha
    А должны ли менятся весы от bias-нейронов? Они ведь должны указывать порог активации?
  • posted 6 years ago by solar
    при подобном выборе начальных весов var random = require('seed-random')(1337) почти ни одно обучение не дает сходимости. Ошибка стремится к значению 0.4 и не уменьшается. Нейронка стала обучаться, когда начальные веса стал выбирать с учетом отрицательных чисел math.random(-10,10)*math.random()
  • posted 6 years ago by denix77
    Ошибка в коде обучающего примера, вот и сходимость поэтому редкая птица. При расчете h1_delta и h2_delta нужно еще умножать на веса h1_o1 и h2_o2 соответственно. Угодил день, пока не почитал Хайкина.

sign in to comment