Замораживание слоёв вручную

В библиотеке Brain.js нейронные сети строятся как последовательность слоёв, каждый из которых содержит веса и смещения, определяющие поведение сети при обработке входных данных. Иногда возникает необходимость частичного обучения сети: обновлять только некоторые слои, оставляя другие неизменными. Для этого применяется техника замораживания слоёв (layer freezing).

Замораживание слоёв позволяет:

  • Сохранять уже обученные представления на начальных слоях.
  • Ускорять обучение, так как уменьшается количество обновляемых параметров.
  • Избегать переобучения на ограниченных данных.

В Brain.js замораживание слоёв реализуется через прямой доступ к весам и смещениям слоёв, а также контроль над их обновлением во время вызова train().


Структура слоёв в Brain.js

Сеть в Brain.js, особенно тип feedforward или recurrent, состоит из массива объектов, каждый из которых представляет слой:

{
  weights: [...], // Матрица весов для текущего слоя
  biases: [...],  // Смещения для нейронов слоя
  activation: 'sigmoid', // Функция активации
}

Каждый слой имеет:

  • weights — матрицу весов размером [число_нейронов_текущего_слоя, число_нейронов_предыдущего_слоя].
  • biases — массив смещений длиной, равной числу нейронов слоя.
  • activation — функция активации, используемая при прямом проходе.

Ручное замораживание слоёв

Для того чтобы слой не обновлялся во время обучения, необходимо блокировать изменение его weights и biases. Brain.js не предоставляет встроенной функции типа freezeLayer(), поэтому решение осуществляется через переопределение градиентов или отключение их обновления вручную.

Метод 1: Прямое копирование весов

Перед тренировкой создаются копии текущих весов и смещений слоёв, которые нужно заморозить:

const frozenLayers = [];
for (let i = 0; i < network.layers.length; i++) {
  if (i < 2) { // Замораживаем первые два слоя
    frozenLayers[i] = {
      weights: JSON.parse(JSON.stringify(network.layers[i].weights)),
      biases: JSON.parse(JSON.stringify(network.layers[i].biases))
    };
  }
}

network.train(trainingData, {
  iterations: 1000,
  learningRate: 0.01,
  log: true,
  logPeriod: 100,
  callback: () => {
    for (let i in frozenLayers) {
      network.layers[i].weights = frozenLayers[i].weights;
      network.layers[i].biases = frozenLayers[i].biases;
    }
  }
});

Ключевые моменты:

  • Слои замораживаются через постоянное восстановление их параметров после каждого шага градиентного обновления.
  • Этот способ не требует модификации внутреннего кода Brain.js.
  • Удобен для сетей с небольшим числом слоёв.

Метод 2: Переопределение шага обучения

Для более точного контроля можно изменить поведение метода обновления весов в слое. При тренировке используется функция обратного распространения ошибки, которая формирует градиенты. Замороженные слои просто пропускаются:

network.train = function(data, options) {
  const frozenIndexes = [0, 1]; // Индексы слоёв для заморозки
  const originalBackward = this._backpropagate; 

  this._backpropagate = function(target) {
    const gradients = originalBackward.call(this, target);
    frozenIndexes.forEach(idx => {
      gradients[idx].weights.fill(0);
      gradients[idx].biases.fill(0);
    });
    return gradients;
  };

  return BrainJSOriginalTrain.call(this, data, options);
};

Пояснения:

  • Функция _backpropagate генерирует градиенты для всех слоёв.
  • Зануление градиентов для замороженных слоёв предотвращает изменение весов и смещений.
  • Метод позволяет масштабировать заморозку для больших сетей и сложных архитектур.

Практические рекомендации

  1. Начальные слои чаще всего замораживают, так как они извлекают общие признаки.
  2. Последние слои обычно остаются обучаемыми для адаптации к конкретной задаче.
  3. Проверка результатов: после каждого шага обучения стоит контролировать ошибки сети, чтобы убедиться, что замороженные слои не изменяются.
  4. Сочетание методов: можно комбинировать восстановление весов после каждого шага и зануление градиентов для надёжности.

Особенности для recurrent сетей

Для рекуррентных сетей (LSTM, GRU) замораживание слоёв усложняется, так как градиенты распространяются во времени. В этом случае:

  • Необходимо замораживать все веса внутри рекуррентного блока (inputWeights, hiddenWeights).
  • Для LSTM стоит контролировать как gate weights, так и biases, иначе часть сети может адаптироваться даже при заморозке основного слоя.

Совместимость с различными функциями активации

Замораживание слоёв работает независимо от функции активации (sigmoid, tanh, relu). Однако при использовании ReLU важно проверять, что замороженные веса не приводят к “мертвым нейронам” после переноса в новую задачу, поскольку они не будут адаптироваться к изменяющемуся диапазону входных данных.