Отладка промежуточных значений

При работе с нейронными сетями на базе библиотеки Brain.js часто возникает необходимость отслеживания промежуточных значений, таких как активации отдельных слоев и выходы нейронов. Это особенно важно для анализа работы сети, выявления ошибок обучения и корректировки структуры модели. Brain.js предоставляет ряд инструментов и подходов для этого.


Структура сети и доступ к слоям

Brain.js поддерживает несколько типов сетей: Feedforward, Recurrent, LSTM. Каждая из них имеет внутреннюю структуру слоев, каждый слой содержит набор нейронов, у каждого нейрона есть весовые коэффициенты и значение активации. Основной объект сети обычно создаётся через:

const brain = require('brain.js');
const net = new brain.NeuralNetwork({
  hiddenLayers: [4, 4] // два скрытых слоя по 4 нейрона
});

Для Feedforward сетей Brain.js не предоставляет прямого API для извлечения значений отдельных нейронов, однако есть обходные пути через JSON-представление сети.


Сериализация и анализ структуры

Метод toJSON() возвращает объект с полной структурой сети:

const json = net.toJSON();
console.log(json);

Ключевые поля JSON:

  • layers — объект, где ключи соответствуют слоям (input, hidden0, hidden1, output), значения — массивы нейронов.
  • weights — веса нейронов относительно предыдущего слоя.
  • biases — смещения нейронов.
  • outputs — выходные значения нейронов после последнего запуска run().

Используя это представление, можно вручную вычислять промежуточные активации:

function forwardPass(input, networkJSON) {
  let activations = { input: input };

  let prevLayer = input;
  Object.keys(networkJSON.layers).forEach(layerKey => {
    if (layerKey === 'input') return;

    const layer = networkJSON.layers[layerKey];
    const layerActivations = [];

    layer.forEach((neuron, i) => {
      const sum = neuron.bias + neuron.weights.reduce((acc, w, idx) => acc + w * prevLayer[idx], 0);
      const activation = 1 / (1 + Math.exp(-sum)); // сигмоида
      layerActivations.push(activation);
    });

    activations[layerKey] = layerActivations;
    prevLayer = layerActivations;
  });

  return activations;
}

Эта функция позволяет получить значение каждого нейрона на каждом слое, что является основой для глубокой отладки сети.


Использование callback-функций при обучении

Метод train() принимает объект конфигурации, где есть поля log и callback. Параметр callback можно использовать для мониторинга обучения на каждом шаге:

net.train(trainingData, {
  iterations: 2000,
  log: true,
  logPeriod: 100,
  callback: (stats) => {
    console.log(`Эпоха: ${stats.iterations}, Ошибка: ${stats.error}`);
  },
  callbackPeriod: 50
});

Это позволяет видеть динамику ошибки и промежуточные значения функции потерь, что помогает корректировать гиперпараметры.


Отладка рекуррентных и LSTM-сетей

Для LSTM и recurrent сетей важно отслеживать состояние скрытых нейронов между шагами последовательности. Brain.js не предоставляет прямого API для извлечения внутренних состояний c и h LSTM, но можно использовать net.toJSON() для анализа:

  • Каждое состояние слоя хранится в виде массива весов и смещений.
  • Для проверки влияния на выходы можно вручную прогонять последовательность через функцию forwardPass, реализованную по JSON.

Для визуализации временных изменений рекомендуется строить графики активаций скрытых слоёв по шагам последовательности. Это помогает выявить затухание или взрыв градиентов, которые часто встречаются при обучении LSTM.


Логирование и визуализация

Для комплексной отладки полезно сохранять промежуточные значения в массивы и визуализировать:

const activationsLog = [];

trainingData.forEach(sample => {
  const activations = forwardPass(sample.input, net.toJSON());
  activationsLog.push(activations);
});

console.table(activationsLog[0]); // значения нейронов первого примера

Использование таких таблиц или графиков помогает анализировать поведение нейронов, выявлять сильно активные или “мертвые” нейроны, а также проверять, как изменения весов влияют на выходы сети.


Советы по точной отладке

  • Проверять масштаб входных данных: нормализация входов между 0 и 1 критична для корректной работы сигмоидных функций.
  • Сравнивать выходы сети с ожидаемыми промежуточными результатами, используя ручной расчёт на небольших примерах.
  • Использовать минимальные сети с несколькими нейронами и слоями для экспериментов перед обучением больших моделей.
  • Логировать градиенты и веса на нескольких шагах, если используется ручная модификация обучающих алгоритмов.

Brain.js позволяет глубоко анализировать работу нейронной сети через JSON-представление и кастомные функции forwardPass, что делает возможной детальную отладку промежуточных значений и корректировку обучения. Такой подход особенно полезен при работе с LSTM и рекуррентными сетями, где прозрачность внутренних состояний критична для устойчивого обучения.