Универсальная нейронная сеть на JavaScript.
Метод обратного распространения ошибки
+ 4 простые задачи для демонстрации.

21 июня 2021, автор: Елена Позднякова
Создаем универсальную нейросеть на JavaScript, которая будет способна решать различные задачи на классификацию.
Её универсальность заключается в том, что мы можем произвольно задавать архитектуру нейронной сети (число слоев и число нейронов в каждом слое) и передавать р а з л и ч н ы е учебные наборы. В зависимости от переданного учебного набора, нейросеть обучится решать поставленную задачу. Обучение нейросети мы будем производить методом обратного распространения ошибки.

Демонстрацию работы нейросети произведем на 4-х разных задачах (и, соответственно, будем использовать 4 разных набора учебных данных):
1) логическая операция XOR
2) три поля
3) горизонтальные и вертикальные линии 3х3
4) квадратные цифры 3х5.
Для изучения данной темы требуется знание основ языка JavaScript.

Оглавление:

Видео по теме будет опубликовано позже (если из статьи что-то не понятно, спрашивайте в комментариях).

Описание задачи и наборы данных

Универсальная нейронная сеть может принимать любую структуру (задается с помощью переменной netSize).
В каждом слое, кроме последнего, первый нейрон является нейроном смещения и равен 1. Входные данные также включают нейрон смещения (первый элемент).
Функция активации для каждого нейрона нейронной сети: Сигмоида.
В языке JavaScript формула выглядит так: 1 / (1 + Math.exp( -summator))

Обучение нейросети производится методом обратного распространения ошибки:
Формулы метода
обратного распространения ошибки
для разных слоев
  • Ошибка нейронов ПОСЛЕДНЕГО слоя рассчитывается по формуле:
    "Правильный ответ" минус "Ответ нейрона" и полученный результат умножить на "Производную функции активации для ответа нейрона".
    Ошибка=(Правильный ответ - Ответ нейрона)*Производная функции активации
    В коде формула выглядит так:
    error [ l ][ i ] =(y[ i ]-N[ l ] [ i ])*N[ l ][ i ]*(1-N [ l ][ i ]),
    где N[ l ] [ i ] - значение текущего нейрона,
    y[ i ] - правильный ответ из учебного набора
  • Ошибка нейронов ПРЕДПОСЛЕДНЕГО слоя и ОСТАЛЬНЫХ скрытых слоев рассчитывается по формуле (формула учитывает все ошибки, входящие с последующего слоя):

    error[ l ] [ i ] = summator*N[ l ][ i ]*(1-N[ l ][ i ]),
    где N[ l ] [ i ] - значение текущего нейрона,
    summator - это сумма произведений всех входящих ошибок с последующего слоя на веса связи с текущим нейроном.

    Предпоследний слой отличается от других скрытых слоев тем, что в последнем слое нет нейрона смещения, для которого ошибка не рассчитывается.
А теперь переходим в наборам данных:
Набор №1 "XOR"
Логическая операция (или то, или это, но не оба сразу). 4 примера.
Минимальная структура нейросети:
netSize = [ 3,3,1 ]

let trainingSet =
[
  [  [1,1,1],  [0]  ],
  [  [1,1,0],  [1]  ],
  [  [1,0,1],  [1]  ],
  [  [1,0,0],  [0] ]
]

let fullCheckSet = trainingSet

Набор №2. Три поля.
Определить цвет ячейки по номеру ряда и столбца. 300 примеров.
Минимальная структура нейросети:
netSize = [ 3,6,3 ]

let trainingSet =
[
[[1,4,1],[1,0,0]],
[[1,3,1],[1,0,0]],
[[1,10,2],[1,0,0]],
[[1,1,19],[0,1,0]],[[1,15,12],[0,0,1]],[[1,14,13],[0,0,1]],[[1,13,10],[0,0,1]],[[1,5,12],[0,1,0]],[[1,9,15],[0,1,0]],[[1,2,20],[0,1,0]],[[1,9,3],[1,0,0]],[[1,5,20],[0,1,0]],[[1,11,6],[0,0,1]],[[1,15,7],[0,0,1]],[[1,3,15],[0,1,0]],[[1,8,12],[0,1,0]],[[1,13,6],[0,0,1]],[[1,7,7],[1,0,0]],[[1,1,7],[1,0,0]],[[1,4,14],[0,1,0]],[[1,13,3],[0,0,1]],[[1,5,14],[0,1,0]],[[1,10,4],[1,0,0]],[[1,3,11],[0,1,0]],[[1,10,13],[0,1,0]],[[1,4,13],[0,1,0]],[[1,4,5],[1,0,0]],[[1,12,11],[0,0,1]],[[1,5,16],[0,1,0]],[[1,2,13],[0,1,0]],[[1,13,14],[0,0,1]],[[1,6,17],[0,1,0]],[[1,11,19],[0,0,1]],[[1,11,17],[0,0,1]],[[1,9,10],[1,0,0]],[[1,7,14],[0,1,0]],[[1,12,5],[0,0,1]],[[1,15,1],[0,0,1]],[[1,8,4],[1,0,0]],[[1,3,16],[0,1,0]],[[1,6,5],[1,0,0]],[[1,3,17],[0,1,0]],[[1,3,2],[1,0,0]],[[1,6,11],[0,1,0]],[[1,4,12],[0,1,0]],[[1,13,11],[0,0,1]],[[1,15,8],[0,0,1]],[[1,6,9],[1,0,0]],[[1,7,18],[0,1,0]],[[1,10,9],[1,0,0]],[[1,12,19],[0,0,1]],[[1,2,3],[1,0,0]],[[1,1,2],[1,0,0]],[[1,7,15],[0,1,0]],[[1,11,8],[0,0,1]],[[1,13,13],[0,0,1]],[[1,14,10],[0,0,1]],[[1,5,8],[1,0,0]],[[1,13,9],[0,0,1]],[[1,9,5],[1,0,0]],[[1,4,7],[1,0,0]],[[1,15,4],[0,0,1]],[[1,13,18],[0,0,1]],[[1,11,16],[0,0,1]],[[1,6,3],[1,0,0]],[[1,3,20],[0,1,0]],[[1,12,7],[0,0,1]],[[1,12,16],[0,0,1]],[[1,3,4],[1,0,0]],[[1,15,11],[0,0,1]],[[1,4,16],[0,1,0]],[[1,12,18],[0,0,1]],[[1,11,12],[0,0,1]],[[1,5,11],[0,1,0]],[[1,13,5],[0,0,1]],[[1,2,8],[1,0,0]],[[1,3,9],[1,0,0]],[[1,11,13],[0,0,1]],[[1,4,17],[0,1,0]],[[1,2,19],[0,1,0]],[[1,4,8],[1,0,0]],[[1,14,2],[0,0,1]],[[1,9,8],[1,0,0]],[[1,14,7],[0,0,1]],[[1,2,2],[1,0,0]],[[1,10,7],[1,0,0]],[[1,14,8],[0,0,1]],[[1,15,19],[0,0,1]],[[1,10,19],[0,1,0]],[[1,8,17],[0,1,0]],[[1,8,16],[0,1,0]],[[1,11,11],[0,0,1]],[[1,14,11],[0,0,1]],[[1,5,9],[1,0,0]],[[1,7,8],[1,0,0]],[[1,12,1],[0,0,1]],[[1,5,2],[1,0,0]],[[1,10,12],[0,1,0]],[[1,6,19],[0,1,0]],[[1,9,16],[0,1,0]],[[1,9,6],[1,0,0]],[[1,8,18],[0,1,0]],[[1,15,5],[0,0,1]],[[1,14,15],[0,0,1]],[[1,15,3],[0,0,1]],[[1,15,18],[0,0,1]],[[1,5,15],[0,1,0]],[[1,4,10],[1,0,0]],[[1,2,9],[1,0,0]],[[1,11,20],[0,0,1]],[[1,4,15],[0,1,0]],[[1,14,1],[0,0,1]],[[1,9,4],[1,0,0]],[[1,6,15],[0,1,0]],[[1,12,2],[0,0,1]],[[1,3,5],[1,0,0]],[[1,6,13],[0,1,0]],[[1,8,7],[1,0,0]],[[1,8,1],[1,0,0]],[[1,13,19],[0,0,1]],[[1,1,20],[0,1,0]],[[1,4,18],[0,1,0]],[[1,12,6],[0,0,1]],[[1,14,6],[0,0,1]],[[1,2,4],[1,0,0]],[[1,8,13],[0,1,0]],[[1,1,11],[0,1,0]],[[1,6,18],[0,1,0]],[[1,10,17],[0,1,0]],[[1,3,7],[1,0,0]],[[1,4,11],[0,1,0]],[[1,7,1],[1,0,0]],[[1,5,3],[1,0,0]],[[1,7,16],[0,1,0]],[[1,1,12],[0,1,0]],[[1,9,11],[0,1,0]],[[1,1,13],[0,1,0]],[[1,6,20],[0,1,0]],[[1,11,3],[0,0,1]],[[1,10,14],[0,1,0]],[[1,5,10],[1,0,0]],[[1,11,7],[0,0,1]],[[1,8,14],[0,1,0]],[[1,6,4],[1,0,0]],[[1,4,4],[1,0,0]],[[1,4,19],[0,1,0]],[[1,9,14],[0,1,0]],[[1,15,6],[0,0,1]],[[1,9,18],[0,1,0]],[[1,14,12],[0,0,1]],[[1,11,5],[0,0,1]],[[1,9,7],[1,0,0]],[[1,10,10],[1,0,0]],[[1,15,16],[0,0,1]],[[1,2,10],[1,0,0]],[[1,1,1],[1,0,0]],[[1,15,9],[0,0,1]],[[1,7,17],[0,1,0]],[[1,8,2],[1,0,0]],[[1,10,8],[1,0,0]],[[1,5,6],[1,0,0]],[[1,2,7],[1,0,0]],[[1,10,18],[0,1,0]],[[1,9,9],[1,0,0]],[[1,9,13],[0,1,0]],[[1,8,5],[1,0,0]],[[1,15,2],[0,0,1]],[[1,15,13],[0,0,1]],[[1,5,17],[0,1,0]],[[1,6,12],[0,1,0]],[[1,3,8],[1,0,0]],[[1,9,12],[0,1,0]],[[1,1,6],[1,0,0]],[[1,6,8],[1,0,0]],[[1,5,13],[0,1,0]],[[1,12,15],[0,0,1]],[[1,1,5],[1,0,0]],[[1,7,20],[0,1,0]],[[1,8,15],[0,1,0]],[[1,2,14],[0,1,0]],[[1,7,13],[0,1,0]],[[1,13,2],[0,0,1]],[[1,3,10],[1,0,0]],[[1,14,9],[0,0,1]],[[1,13,7],[0,0,1]],[[1,3,19],[0,1,0]],[[1,8,6],[1,0,0]],[[1,2,6],[1,0,0]],[[1,6,7],[1,0,0]],[[1,2,12],[0,1,0]],[[1,15,14],[0,0,1]],[[1,9,17],[0,1,0]],[[1,14,18],[0,0,1]],[[1,5,19],[0,1,0]],[[1,8,3],[1,0,0]],[[1,7,12],[0,1,0]],[[1,7,9],[1,0,0]],[[1,11,1],[0,0,1]],[[1,15,20],[0,0,1]],[[1,3,14],[0,1,0]],[[1,10,1],[1,0,0]],[[1,4,6],[1,0,0]],[[1,11,15],[0,0,1]],[[1,1,9],[1,0,0]],[[1,1,4],[1,0,0]],[[1,14,20],[0,0,1]],[[1,7,4],[1,0,0]],[[1,14,16],[0,0,1]],[[1,5,4],[1,0,0]],[[1,14,5],[0,0,1]],[[1,3,13],[0,1,0]],[[1,2,16],[0,1,0]],[[1,5,1],[1,0,0]],[[1,1,17],[0,1,0]],[[1,7,11],[0,1,0]],[[1,6,14],[0,1,0]],[[1,9,20],[0,1,0]],[[1,10,11],[0,1,0]],[[1,3,3],[1,0,0]],[[1,13,20],[0,0,1]],[[1,13,1],[0,0,1]],[[1,4,20],[0,1,0]],[[1,8,19],[0,1,0]],[[1,4,3],[1,0,0]],[[1,15,17],[0,0,1]],[[1,12,17],[0,0,1]],[[1,6,6],[1,0,0]],[[1,13,16],[0,0,1]],[[1,10,20],[0,1,0]],[[1,1,8],[1,0,0]],[[1,12,3],[0,0,1]],[[1,11,4],[0,0,1]],[[1,10,5],[1,0,0]],[[1,14,4],[0,0,1]],[[1,2,1],[1,0,0]],[[1,11,18],[0,0,1]],[[1,15,10],[0,0,1]],[[1,1,15],[0,1,0]],[[1,13,8],[0,0,1]],[[1,14,17],[0,0,1]],[[1,3,18],[0,1,0]],[[1,2,5],[1,0,0]],[[1,12,10],[0,0,1]],[[1,13,17],[0,0,1]],[[1,2,17],[0,1,0]],[[1,1,14],[0,1,0]],[[1,7,5],[1,0,0]],[[1,9,19],[0,1,0]],[[1,11,10],[0,0,1]],[[1,9,2],[1,0,0]],[[1,9,1],[1,0,0]],[[1,14,19],[0,0,1]],[[1,10,15],[0,1,0]],[[1,12,9],[0,0,1]],[[1,15,15],[0,0,1]],[[1,2,15],[0,1,0]],[[1,6,2],[1,0,0]],[[1,2,18],[0,1,0]],[[1,11,2],[0,0,1]],[[1,14,14],[0,0,1]],[[1,12,4],[0,0,1]],[[1,7,2],[1,0,0]],[[1,5,18],[0,1,0]],[[1,13,12],[0,0,1]],[[1,8,8],[1,0,0]],[[1,1,10],[1,0,0]],[[1,10,3],[1,0,0]],[[1,4,9],[1,0,0]],[[1,11,9],[0,0,1]],[[1,12,13],[0,0,1]],[[1,1,16],[0,1,0]],[[1,1,3],[1,0,0]],[[1,13,15],[0,0,1]],[[1,7,10],[1,0,0]],[[1,2,11],[0,1,0]],[[1,8,11],[0,1,0]],[[1,8,9],[1,0,0]],[[1,6,1],[1,0,0]],[[1,10,16],[0,1,0]],[[1,10,6],[1,0,0]],[[1,1,18],[0,1,0]],[[1,5,5],[1,0,0]],[[1,3,12],[0,1,0]],[[1,6,16],[0,1,0]],[[1,12,20],[0,0,1]],[[1,12,8],[0,0,1]],[[1,5,7],[1,0,0]],[[1,4,2],[1,0,0]],[[1,8,20],[0,1,0]],[[1,7,19],[0,1,0]],[[1,6,10],[1,0,0]],[[1,12,12],[0,0,1]],[[1,13,4],[0,0,1]],[[1,7,3],[1,0,0]],[[1,7,6],[1,0,0]],[[1,3,6],[1,0,0]],[[1,12,14],[0,0,1]],[[1,14,3],[0,0,1]],[[1,8,10],[1,0,0]],[[1,11,14],[0,0,1]]
]

let fullCheckSet = trainingSet

Набор №3. Вертикальные и горизонтальные линии.
Определить наличие вертикальных и горизонтальных линий в поле 3х3. 24 примера.
Минимальная структура нейросети:
netSize = [ 10,...,2 ]

let trainingSet =
[
[[1,1,1,1,0,0,0,0,0,0],[1,0]],
[[1,0,0,0,1,1,1,0,0,0],[1,0]],
[[1,0,0,0,0,0,0,1,1,1],[1,0]],
[[1,1,0,0,1,0,0,1,0,0],[0,1]],
[[1,0,1,0,0,1,0,0,1,0],[0,1]],[[1,0,0,1,0,0,1,0,0,1],[0,1]],[[1,0,0,1,0,0,1,1,1,1],[1,1]],[[1,1,0,0,1,0,0,1,1,1],[1,1]],[[1,1,1,1,1,0,0,1,0,0],[1,1]],[[1,1,1,1,0,0,1,0,0,1],[1,1]],[[1,0,1,0,1,1,1,0,1,0],[1,1]],[[1,0,0,1,1,1,1,0,0,1],[1,1]],[[1,1,0,0,1,1,1,1,0,0],[1,1]],[[1,1,1,1,0,1,0,0,1,0],[1,1]],[[1,1,1,1,0,1,0,0,1,0],[1,1]],[[1,1,1,1,0,0,0,1,1,1],[1,0]],[[1,1,0,1,1,0,1,1,0,1],[0,1]],[[1,0,0,0,1,1,1,1,1,1],[1,0]],[[1,1,1,1,1,1,1,0,0,0],[1,0]],[[1,1,1,0,1,1,0,1,1,0],[0,1]],[[1,0,1,1,0,1,1,0,1,1],[0,1]],[[1,1,1,1,1,1,1,1,1,1],[1,1]],[[1,1,1,1,0,1,0,1,1,1],[1,1]],[[1,1,0,1,1,1,1,1,0,1],[1,1]],[[1,0,0,0,0,0,0,0,0,0],[0,0]],]
]

let fullCheckSet = trainingSet

Набор №4. Квадратные цифры.
Определить цифру от 9 до 9 в поле 3х5. 10 примеров.
Минимальная структура нейросети:
netSize = [ 16,26,10 ]

let trainingSet =
[
  [[1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1],[1,0,0,0,0,0,0,0,0,0]],
  [[1,1,1,1,0,0,1,1,1,1,1,0,0,1,1,1],[0,1,0,0,0,0,0,0,0,0]],
  [[1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1],[0,0,1,0,0,0,0,0,0,0]],
  [[1,1,0,1,1,0,1,1,1,1,0,0,1,0,0,1],[0,0,0,1,0,0,0,0,0,0]],
  [[1,1,1,1,1,0,0,1,1,1,0,0,1,1,1,1],[0,0,0,0,1,0,0,0,0,0]],
  [[1,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1],[0,0,0,0,0,1,0,0,0,0]],
  [[1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1],[0,0,0,0,0,0,1,0,0,0]],
  [[1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1],[0,0,0,0,0,0,0,1,0,0]],
  [[1,1,1,1,1,0,1,1,1,1,0,0,1,1,1,1],[0,0,0,0,0,0,0,0,1,0]],
  [[1,1,1,1,1,0,1,1,0,1,1,0,1,1,1,1],[0,0,0,0,0,0,0,0,0,1]]
]

let fullCheckSet = trainingSet

Переменные и функции:

Вектор индексов - это самая первая константа, объявленная в коде нашей сети. Она задается до начала обучения, и, при необходимости, эту константу можно изменять с целью подбора оптимальных параметров сети.
Константа "Вектор индексов" задает размер и структуру сети. Каждый элемент обозначает слой.
Число элементов - это количество слоев.
Значение элемента - количество нейронов в слое.
Данный вектор используется для того, чтобы ссылаясь на него, можно было указать адреса элементов основных массивов сети, таких, как:
N (значения нейронов исходя из последнего рассчитанного примера),
error (значения ошибок для каждого нейрона исходя из последнего рассчитанного примера),
w (значения весовых коэффициентов).
Соответственно, вектор индексов netSize используется и при инициализации всех указанных массивов, для того, чтобы определять число слоев, элементов и т.д.
Узнать число слоев в сети можно с помощью команды netSize.length
Узнать число элементов в конкретном слое можно с помощью команды netSize [ l ], где l - индекс слоя.
Посмотреть структуру сети в консоли можно с помощью вспомогательной функции showStructureOfNet ().

В приведенном примере:
Слой 0 - это входные данные: netSize[0]
Если указать в командной строке netSize[0], то ответом будет число 3 - это число элементов в слое 0.
Слой 1 - скрытый слой: netSize[1] //3
Слой 2 - выходные данные: netSize[2] //1

Обозначения переменных,
которые используются в циклах:

Переменная не объявляется в качестве глобальной и используется в качестве локальной переменной при переборе циклов. Если требуется сослаться на номер слоя или перебрать слои, то в качестве переменной будем выбирать l.

Ссылки:

  • Бесплатный редактор кода Atom
  • КОД (по умолчанию подключен набор №1 XOR). Скачать 2 файла и поместить в одну папку:
    index.html
    script.js

    Первый файл (index.html) - Открыть с помощью - Google Chrome (или другой браузер)
    Второй файл с кодом (script.js) - Открыть с помощью - Atom (или другой редактор, можно блокнот)

    Файл script.js подключен к файлу index.html (для этого они должны быть в одной папке).
    Если вы хотите написать код с нуля, перенесите script.js в другую папку, а в текущей папке создайте пустой текстовый документ и переименуйте его в script.js

Полный текст кода с комментариями:

index.html

<!DOCTYPE html>
<html lang="en" >
      <head>
          <meta charset="UTF-8">
          <title>Универсальная нейросеть на JavaScript</title>

      </head>
      <body>
        Подробное описание этого кода приведено в статье <br/>
 <a href='https://megabyte.ga/na-puti-k-nejroseti/universalnaya-nejroset-set-na-javascript'
 target='_blank'> Универсальная нейросеть на JavaScript </a> <br/><br/>
 Демонстрация в браузере:<br/>
 1) Открыть в Chrome файл index.html<br/>
 2) Открыть консоль F12<br/>
 3) Для обучения нейросети нужно подключить один из 4-х демонстрационных учебных наборов или СВОЙ<br/>
 Набор для обучения записан в переменную trainingSet, набор для проверки в переменную fullCheckSet<br/>
 Вызовите переменную trainingSet, чтобы проверить, что в учебном наборе.<br/>
 4) Архитектуру нейросети требуется предварительно задать в переменную netSize <br/>
 Функция startCheck() автоматически запускается при загрузке кода и проверяет число входов и выходов сети на соответствие учебным наборам<br/>
 5) Запустить функцию checkAll(), которая проверяет все примеры из набора для проверки fullCheckSet и покажет ошибку сети<br/>
 6) Запустить train(100), 100 эпох обучения нейрона на учебном наборе trainingSet <br/>
 5) Повторно запустить checkAll()

  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
  <script  src="./script.js"></script>
  <div id="curve_chart" style="width: 900px; height: 500px"></div>
    </body>
</html>
script.js

//Задача: Сделать схему универсальной нейронной сети прямого распространения (персептрон, классификация)
//Применить функцию обратного распространения ошибки

//Константа:ВЕКТОР ИНДЕКСОВ
const netSize = [3,3,1];
/*Размер нейронной сети: порядковые номера слоев определяются по индексу: [0], [1], [2]...
слой с индексом [0] - входные данные
netSize[l] - число нейронов в слое l
*/

//Показать структуру сети
function showStructureOfNet () {
  for (let l=0; l<netSize.length; l++) {
    console.log(`Слой [${l}],${(l==0)?`входные данные;`:``} элементы:`);
    for(let i=0; i<netSize[l]; i++ ) {
      console.log(`N [${l}][${i}]${(l!=(netSize.length-1)&&i==0)?`=1, нейрон смещения(bias)`:``}`);
    }
  }
    console.log(`-----------------------------------
Всего слоев - ${netSize.length}.`);
}

/////////////////////////УЧЕБНЫЙ НАБОР/////////////////////////////////////
/*Учебный набор должен включать попарные массивы:
1) ВХОДНЫЕ ДАННЫЕ: число элементов соответствует [0]-му слою, элемент[0]=1
2) ПРАВИЛЬНЫЕ ОТВЕТЫ: число элементов соответствует последнему слою
*/

//Учебный набор XOR x1 ⊕ x2:
let trainingSet =
[
  [  [1,1,1],  [0]  ],
  [  [1,1,0],  [1]  ],
  [  [1,0,1],  [1]  ],
  [  [1,0,0],  [0] ]
]

let fullCheckSet =
[
  [  [1,1,1],  [0]  ],
  [  [1,1,0],  [1]  ],
  [  [1,0,1],  [1]  ],
  [  [1,0,0],  [0] ]
]





//////////////////////////ПРОВЕРКА ПРАВИЛЬНОСТИ ВНЕСЕННЫХ ДАННЫХ//////////////
function startCheck() {
  if
  (netSize[0]!=trainingSet[0][0].length   ||    netSize[netSize.length-1]!=trainingSet[0][1].length)
    {
      alert('Проверьте число элементов в учебной выборке trainingSet ')
    }
      else if
      (netSize[0]!=fullCheckSet[0][0].length   ||   netSize[netSize.length-1]!=fullCheckSet[0][1].length)
      {
          alert('Проверьте число элементов в проверочной выборке fullCheckSet ')
          }
    };

startCheck();



/////////////////////////////////////ИНИЦИАЛИЗАЦИЯ ВЕСОВ////////////////////////
let w = [];

/*Инициализация трехмерной матрицы весов
Адрес каждого веса между нейроном1 и нейроном2 [l][i][b],
где l - слой нейрона2, i - индекс нейрона2, b - индекс нейрона1*/
function createArray_w (){
  w[0]=NaN;
for (let l=1; l<netSize.length; l++) { //перебираем слои
w[l] = []
for (let i=0; i<netSize[l]; i++) { //перебираем нейроны каждого слоя
  w[l][i] = [];
  for (let b=0; b<netSize[(l-1)]; b++) { //перебираем связи каждого нейрона

if ((l!=netSize.length-1)&&i==0) {
  w[l][i][b] = NaN}
else {w[l][i][b] = Math.random()} //Math.random()-0.5
}}}}


createArray_w();




/////////////////////////////////////////////////////////////////////////
/*Создаем массив данных N, который сохраняет значения всех рассчитанных нейронов
Сначала он пустой, но после каждого рассчитанного примера,
 будут сохранены последние значения, включая входные данные*/
 let N=[];

      function createArray_N () {
      for (let l=0; l<netSize.length; l++) { //перебираем слои
        N[l] = [];
        }
      }

createArray_N ()

//Так создавать массив нельзя, потом глючит и заполняет элементы не по одному а сразу все
// let N = new Array(netSize.length);
// N.fill([]);


/////////////////////////////////////////////////////////////////////////////////
//Создаем массив данных Error, который сохраняет значения ошибок для всех рассчитанных нейронов
//Сначала он пустой, но после каждого рассчитанного примера будут оставаться все значения, первый слой - NaN
let error=[];

function createArray_Error (){
  error[0]=NaN;
for (let l=1; l<netSize.length; l++) { //перебираем слои
error[l] = []
 }
}

createArray_Error();


////////////////////////////ОТЧЕТ СЕТИ//////////////////////////////////////////

let report = [['число эпох','ошибка сети']];//report.push([1000,0.5])

let numberOfEpochs = 0;//накопительная переменная
let netError;

// function showReport() {
//   report.push([1000,0.5])
//   google.charts.setOnLoadCallback(drawChart);
// }

google.charts.load('current', {'packages':['corechart']});
     //google.charts.setOnLoadCallback(drawChart);

      function drawChart() {
        var data = google.visualization.arrayToDataTable(report);

        var options = {
          title: 'Отчет сети',
          curveType: 'function',
          legend: { position: 'bottom' }
        };

        var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));

        chart.draw(data, options);
      }


/////////////////////////////////ОТВЕТ СЕТИ/////////////////////////////////////

      function answer(x=trainingSet[0][0]) {
        //в качестве параметра по умолчанию передаем первую часть первого примера из учебного набора
        //заполняем нулевой слой значений массива N входными данными
        N[0]=x;

        for (let l = 1; l < netSize.length; l++) {// перебираем слои, начиная с 1
          for (let i = 0; i < netSize[l]; i++) {//перебираем нейроны

              let summator = 0;

                for (let b = 0; b < netSize[l-1]; b++) {

                    summator+=N[l-1][b]*w[l][i][b]
                }

              N[l][i]= 1 / (1 + Math.exp( -summator));

              //заменяем значения нейронов смещения на 1
                if (l!= netSize.length-1)

                { N[l][0] = 1 }

                //Раскомментировать строчку ниже, если требуется отчет: "Значение каждого нейрона"
                //console.log(`N[${l}][${i}]=${N[l][i]}`);
              }
            }
          }


//////////////////////ПОЛНАЯ ПРОВЕРКА/////////////////

      function checkAll(set=fullCheckSet) {
      //по умолчанию в качестве параметра передается проверочная выборка, но можно вручную подставить и trainingSet
      netError = 0;

      for
        (let sample = 0; sample < fullCheckSet.length; sample++) {//перебираем примеры проверочной выборки

          answer(set[sample][0]);

            console.log(`
----------------------индекс примера: ${sample}
            вход:${set[sample][0]}, целевой ответ:${set[sample][1]},
            ответ сети:${N[netSize.length-1]}`);

            //определяем, правильный ли ответ
            //для этого перебираем правильные ответы и ответы сети. Оба показателя - это массивы

            for (let i = 0; i < set[sample][1].length; i++) {//

              if ((set[sample][1][i]-Math.round(N[netSize.length-1][i]))!=0)
                {
                  netError+=Math.abs(set[sample][1][i]-N[netSize.length-1][i]);

                        console.log(`[${i}] ошибка: ${set[sample][1][i]-N[netSize.length-1][i]}`);
                }

                  else {console.log(`[${i}] правильно`)};
              }
            }
            console.log(`------------------------------------------------------------
              ошибка сети: ${netError}`);

              // показать отчет по ошибке сети (если значение ошибки до обучения в таблице уже заполнено, то ничего не делать )

              // if (report[1]) {
              //
              // }
              //
              // else {
                report.push([numberOfEpochs,netError]) //добавить значение в таблицу

                google.charts.setOnLoadCallback(drawChart); //показать график ошибки
            //  }
            }





//////////////Вспомогательная функции для обучения ////////////////////////////
/////////////////////////Ошибка сети/////////////////////////////////////////


      function calculateError (y=trainingSet[0][1]) { //запускать после answer (x)

        //считаем ошибки последнего слоя (от скрытых слоев отличается формулой)
        for (let l = netSize.length-1, i=0; i < netSize[l]; i++) {
          error [l][i] =(y[i]-N[l][i])*N[l][i]*(1-N[l][i]);
          //Раскомментировать строчку ниже, если требуется отчет: "Значение ошибки каждого нейрона последнего слоя"
          //console.log(`error[${l}][${i}]=${error[l][i]}`);
        }


      /*считаем ошибки предпоследнего скрытого слоя
      (от других скрытых слоев данный слой отличается тем, что учитывает все ошибки,
      входящие с последнего слоя, в последнем слое нет нейрона смещения, для которого ошибка не рассчитывается
      */

        for (let l = netSize.length-2, i = 1; i < netSize[l]; i++) {
          let summator = 0;
            for (let b = 0; b < netSize[l+1]; b++) {
            summator+=error[l+1][b]*w[l+1][b][i];
          }
          error[l][i] = summator*N[l][i]*(1-N[l][i]);
          //Раскомментировать строчку ниже, если требуется отчет: "Значение ошибки каждого нейрона скрытого слоя"
          //console.log(`error[${l}][${i}]=${error[l][i]}`);
        }


        //считаем ошибку остальных скрытых слоев
        for (let l = netSize.length-3; l > 0; l--) {
          for (let i = 1; i < netSize[l]; i++) {
            let summator = 0;
              for (let b = 1; b < netSize[l+1]; b++) {
              summator+=error[l+1][b]*w[l+1][b][i];
            }
            error[l][i] = summator*N[l][i]*(1-N[l][i]);
            //Раскомментировать строчку ниже, если требуется отчет: "Значение ошибки каждого нейрона скрытого слоя"
            //console.log(`error[${l}][${i}]=${error[l][i]}`);
          }
          }

      }



///////////////////////ОБУЧЕНИЕ СЕТИ ////////////////////////////////////////



      function train( epoch = 1, t = 0.3 ) {
        for (let e = 0; e < epoch; e++) { //перебираем эпохи
          for (let sample = 0; sample < trainingSet.length; sample++) {//перебираем примеры обучающей выборки
            answer (trainingSet[sample][0]);
            calculateError(trainingSet[sample][1]);
            ////////////////////////КОРРЕКТИРОВКА ВЕСОВ//////////////////////
            for (let l = 1; l < netSize.length; l++) { //перебираем слои
              for (let i = 0; i < netSize[l]; i++) { //перебираем нейроны в слое
                for (let b = 0; b < netSize[l-1]; b++) { //перебираем связи с нейронами предыдущего слоя
                  //NaN игнорируем и тоже перебираем, потому что NaN останется NaN
                  w[l][i][b]+=t*error[l][i]*N[l-1][b]
                  }

                  }
                }
              }

            }
            numberOfEpochs+=epoch
            checkAll();
          }


Дополнительные материалы:

JavaScript на одном листе

JavaScript

Учебник по JavaScript

Полный курс по JavaScript за 6 часов
от Владилена Минина