Нейронные сети на Javascript

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту
Архив новостей

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости



Идея для написания этой статьи возникла прошлым летом, когда я слушал доклад на конференции BigData по нейронным сетям. Лектор «посыпал» слушателей непривычными словечками «нейрон», «обучающая выборка», «тренировать модель»- «Ничего не понял - пора в менеджеры», - подумал я. Но недавно тема нейронных сетей все же коснулась моей работы и я решил на простом примере показать, как использовать этот инструмент на языке JavaScript.

Мы создадим нейронную сеть, с помощью которой будем распознавать ручное написание цифры от 0 до 9. Рабочий пример займет несколько строк. Код будет понятен даже тем программистам, которые не имели дело с нейронными сетями ранее. Как это все работает, можно будет посмотреть прямо в браузере.


Если вы уже знаете что такое Perceptron, следующую главу нужно пропустить.

Совсем немного теории


Нейронные сети возникли из исследований в области искусственного интеллекта, а именно, из попыток воспроизвести способность биологических нервных систем обучаться и исправлять ошибки, моделируя низкоуровневую структуру мозга. В простейшем случае она состоит из нескольких, соединенных между собой, нейронов.

Математический нейрон


Несложный автомат, преобразующий входные сигналы в результирующий выходной сигнал.
Схема математического нейрона
Сигналы x1, x2, x3 - xn, поступая на вход, преобразуются линейным образом, т.е. к телу нейрона поступают силы: w1x1, w2x2, w3x3 - wnxn, где wi - веса соответствующих сигналов. Нейрон суммирует эти сигналы, затем применяет к сумме некоторую функцию f(x) и выдаёт полученный выходной сигнал y.
В качестве функции f(x) чаще всего используется сигмоидная или пороговая функции.
сигмоидная и пороговая функции

Пороговая функция может принимать только два дискретных значения 0 или 1. Смена значения функции происходит при переходе через заданный порог T.+

Сигмоидная - непрерывная функция, может принимать бесконечно много значений в диапазоне от 0 до 1.

UPD: В комментариях также упоминаются функции ReLU и MaxOut как более современные.

Архитектура нейронной сети может быть разной, мы рассмотрим одну из простых реализаций нейронной сети - Perceptron

Архитектура Perceptron


Архитектура Perceptron

Есть слой входных нейронов (где информация поступает из вне), слой выходных нейронов (откуда можно взять результат) и ряд, так-называемых, скрытых слоев между ними. Нейроны могут быть расположены в несколько слоёв. Каждая связь между нейронами имеет свой вес Wij

Входные и выходные сигналы


Перед тем, как подавать сигналы на нейроны входящего слоя сети нам их нужно нормализовать. Нормализация входных данных - это процесс, при котором все входные данные проходят процесс «выравнивания», т.е. приведения к интервалу [0,1] или [-1,1]. Если не провести нормализацию, то входные данные будут оказывать дополнительное влияние на нейрон, что приведет к неверным решениям. Другими словами, как можно сравнивать величины разных порядков?

На нейронах выходного слоя у нас тоже не будет чистой «1» или «0», это нормально. Есть некий порог, при котором мы будем считать, что получили «1» или «0». Про интерпретацию результатов поговорим позже.

«Пример в студию, а то уже засыпаю»


Для удобства я рекомендую себе поставить nodejs и npm.

Мы будем описывать сеть с помощью библиотеки Brain.js. В конце статьи я также дам ссылки на другие библиотеки, которые можно будет сконфигурировать похожим образом. Brain.js мне понравился своей скоростью и возможностью сохранять натренированную модель.

Давайте попробуем пример из документации - эмулятор функции XOR:
var brain = require('brain.js'); var net = new brain.NeuralNetwork(); net.train([{input: [0, 0], output: [0]}, {input: [0, 1], output: [1]}, {input: [1, 0], output: [1]}, {input: [1, 1], output: [0]}]); var output = net.run([1, 0]); // [0.987] console.log(output);

запишем все в файл simple1.js, чтоб пример заработал, поставим модуль brain и запустим
npm install brain.js node simple1.js # [ 0.9331839217737243 ]


У нас 2 входящих нейрона и один нейрон на выходе, библиотека brain.js сама сконфигурирует скрытый слой и установит там столько нейронов, сколько сочтет нужным (в этом примере 3 нейрона).

То, что мы передали в метод .train называется обучающей выборкой, каждый элемент которой состоит из массива объектов со свойством input и output (массив входящих и выходящих параметров). Мы не проводили нормализацию входящих данных, так как сами данные уже приведены в нужную форму.

Обратите внимание: мы на выходе получаем не [0.987] а [0.9331...]. У вас может быть немного другое значение. Это нормально, так как алгоритм обучения использует случайные числа при подборе весовых коэффициентов.

Метод .run применяется для получения ответа нейронной сети на заданный в аргументе массив входящих сигналов.

Другие простые примеры можно посмотреть в документации brain

Распознаем цифры


В начале нам нужно получить изображения с рукописными цифрами, приведенными к одному размеру. В нашем примере мы будем использовать модуль MNIST digits, набор тысяч 28x28px бинарных изображений рукописных цифр от 0 до 9:
nmist пример обучающей выборки

Оригинальная база данных MNIST содержит 60 000 примеров для обучения и 10 000 примеров для тестирования, ее можно можно загрузить с сайта LeCun. Автор MNIST digits сделал доступной часть этих примеров для языка JavaScript, в библиотеке уже проведена нормализация входящих сигналов. С помощью этого модуля мы можем получать обучающую и тестовую выборку автоматически.

Мне пришлось клонировать библиотеку MNIST digits, так как там есть небольшая путаница с данными. Я повторно загрузил 10 000 примеров из оригинальной базы данных, так что использовать надо MNIST digits из моего репозитория.

Конфигурация сети


Во входном слое нам необходимо 28x28=784 нейрона, на выходе 10 нейронов. Скрытый слой brain.js сконфигурирует сам. Забегая наперед, уточню: там будет 392 нейрона. Обучающая выборка будет сформирована модулем mnist

Тренируем модель


Установим mnist
npm install https://github.com/ApelSYN/mnist


Все готово, обучаем сеть
const brain = require('brain.js'); var net = new brain.NeuralNetwork(); const fs = require('fs'); const mnist = require('mnist'); const set = mnist.set(1000, 0); const trainingSet = set.training; net.train(trainingSet, { errorThresh: 0.005, // error threshold to reach iterations: 20000, // maximum training iterations log: true, // console.log() progress periodically logPeriod: 1, // number of iterations between logging learningRate: 0.3 // learning rate } ); let wstream = fs.createWriteStream('./data/mnistTrain.json'); wstream.write(JSON.stringify(net.toJSON(),null,2)); wstream.end(); console.log('MNIST dataset with Brain.js train done.')

Создаем сеть, получаем 1000 элементов обучающей выборки, вызываем метод .train, который производит обучение сети - сохраняем все в файл './data/mnistTrain.json' (не забудьте создать папку "./data").

Если все сделали правильно, получите приблизительно такой результат:
[root@HomeWebServer nn]# node train.js iterations: 0 training error: 0.060402555338691676 iterations: 1 training error: 0.02802436102035996 iterations: 2 training error: 0.020358600820106914 iterations: 3 training error: 0.0159533285799183 iterations: 4 training error: 0.012557029942873513 iterations: 5 training error: 0.010245175822114688 iterations: 6 training error: 0.008218147206099617 iterations: 7 training error: 0.006798613211310184 iterations: 8 training error: 0.005629051609641436 iterations: 9 training error: 0.004910207736789503 MNIST dataset with Brain.js train done.


Все можно распознавать


Осталось написать совсем немного кода - и система распознавания готова!
const brain = require('brain.js'), mnist = require('mnist'); var net = new brain.NeuralNetwork(); const set = mnist.set(0, 1); const testSet = set.test; net.fromJSON(require('./data/mnistTrain')); var output = net.run(testSet[0].input); console.log(testSet[0].output); console.log(output);


Получаем 1 случайный тестовый пример из выборки 10 000 записей, загружаем натренированную ранее модель, передаем на вход сети тестовую запись и смотрим правильно ли она распозналась.

Вот пример выполнения
[ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 ] [ 0.0002863506627761867, 0.00002389940760904011, 0.00039954062883041345, 0.9910109896013567, 7.562879202664903e-7, 0.0038756598319246837, 0.000016752919557362786, 0.0007205981595354964, 0.13699517762991756, 0.0011053963693377692 ]

В примере в сеть на входящие нейроны поступила оцифрованная тройка (первый масив это идеальный ответ), на выходе сети мы получили массив елементов, один из которых близок к единице (0.9910109896013567) это тоже третий бит. Обратите внимание на четвертый бит там 7.56- в -7 степени, это такая форма записи чисел с плавающей точкой в JavaScript.

Ну что же, распознавание прошло правильно. Поздравляю, наша сеть заработала!

Немного «причешем» наши результаты функцией softmax, которую я взял из одного примера по машинному обучению:
function softmax(output) { var maximum = output.reduce(function(p,c) { return p>c ? p : c; }); var nominators = output.map(function(e) { return Math.exp(e - maximum); }); var denominator = nominators.reduce(function (p, c) { return p + c; }); var softmax = nominators.map(function(e) { return e / denominator; }); var maxIndex = 0; softmax.reduce(function(p,c,i){if(p<c) {maxIndex=i; return c;} else return p;}); var result = []; for (var i=0; i<output.length; i++) { if (i==maxIndex) result.push(1); else result.push(0); } return result; }


Функцию можно поместить в начало нашего кода и последнюю строку заменить на
console.log(softmax(output));


Все друзья - теперь все работает красиво:
[root@HomeWebServer nn]# node simpleRecognize.js [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ] [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ] [root@HomeWebServer nn]# node simpleRecognize.js [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ] [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ] [root@HomeWebServer nn]# node simpleRecognize.js [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ] [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ]


Иногда сеть может давать неправильный результат (мы взяли небольшую выборку и поставили не достаточно строгую погрешность).

А как распознать цифру, которую напишете вы?


Конечно, тут нет никакой подтасовки, но все же хочется самому проверить «на прочность» то, что получилось.

С помощью HTML5 Canvas и все тем же brain.js-ом с сохраненной моделью мне удалось сделать реализацию распознавания в браузере, часть кода для отрисовки и дизайн интерфейса я позаимствовал в интернете. Можете попробовать вживую. В мобильном устройстве рисовать можно пальцем.

Ссылки по теме




UPD: Альтернативные реализации живого примера 1, 2 на JavaScript из комментариев и личной переписки.

Источник: habrahabr.ru

Комментарии: