Затухающие и взрывные градиенты — фундаментальные проблемы при обучении многослойных нейронных сетей с использованием обратного распространения ошибки (backpropagation). В библиотеке Synaptic.js их проявление напрямую связано с архитектурой сети, выбором функций активации и инициализацией весов.
При обучении сети методом обратного распространения ошибка, вычисленная на выходном слое, распространяется обратно через все слои сети. Обновление весов происходит по формуле:
[ w_{ij} = -]
где () — скорость обучения, (E) — функция ошибки. Производная функции активации (’(x)) участвует в расчёте градиента на каждом слое:
[ ^l = ’(z^l) (^{l+1} W^{l+1})]
Если (’(z^l)) принимает значения сильно меньше 1 (например, у сигмоиды при больших (|z|)), произведение градиентов через слои ведёт к затуханию градиента. Напротив, большие значения производных или больших весов могут вызвать взрыв градиента.
В Synaptic.js веса по умолчанию инициализируются случайными значениями в диапазоне [-1,1]. Для сети с большим числом слоев или при использовании сигмоидальной функции активации это часто приводит к:
Классическая сигмоида и тангенс гиперболический особенно чувствительны к этой проблеме:
const { Layer, Network } = require('synaptic');
let inputLayer = new Layer(3);
let hiddenLayer = new Layer(5);
let outputLayer = new Layer(2);
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
let myNetwork = new Network({
input: inputLayer,
hidden: [hiddenLayer],
output: outputLayer
});
Если попытаться обучить эту сеть с большой глубиной и стандартной сигмоидой, градиенты на первых слоях будут стремиться к нулю, а обучение окажется практически невозможным.
1. Инициализация весов Для уменьшения затухания градиента используют специальные схемы инициализации, например Xavier (Glorot) или He initialization, которые подбирают диапазон весов в зависимости от числа входов и выходов слоя:
[ w ]
В Synaptic веса можно переназначить вручную:
hiddenLayer.list.forEach(neuron => {
neuron.connections.inputs.forEach(conn => {
conn.weight = Math.random() * 0.1 - 0.05;
});
});
2. Выбор функции активации ReLU (Rectified Linear Unit) и его модификации уменьшают эффект затухания градиента, так как производная равна 1 для положительных значений. В Synaptic можно реализовать кастомную функцию активации:
Layer.prototype.reLU = function(x) {
return Math.max(0, x);
};
3. Нормализация данных Входные данные должны быть масштабированы, чтобы сигмоида или tanh работали в зоне, где их производная не близка к нулю. Для Synaptic это означает приведение значений к диапазону [0,1] или [-1,1].
4. Регулировка скорости обучения Слишком большой ()
приводит к взрывному градиенту, слишком маленький — к затухающему. В
Synaptic скорость обучения задаётся при обучении через
Trainer:
myTrainer.train(trainingSet, {
rate: 0.01, // оптимальное значение подбирается экспериментально
iterations: 20000,
error: 0.005,
shuffle: true
});
5. Градиентное отсечение (Gradient Clipping) При взрывном градиенте полезно ограничивать максимальное значение изменения веса:
neuron.connections.inputs.forEach(conn => {
conn.weight = Math.max(-1, Math.min(1, conn.weight));
});
С увеличением числа скрытых слоёв проблема затухающего градиента проявляется сильнее. В Synaptic можно построить глубокую сеть через массив скрытых слоёв, но необходимо тщательно подбирать функции активации, инициализацию и нормализацию:
let hiddenLayers = [];
for (let i = 0; i < 10; i++) {
hiddenLayers.push(new Layer(50));
}
inputLayer.project(hiddenLayers[0]);
for (let i = 0; i < hiddenLayers.length - 1; i++) {
hiddenLayers[i].project(hiddenLayers[i+1]);
}
hiddenLayers[hiddenLayers.length-1].project(outputLayer);
Каждое соединение должно учитывать возможность затухания градиента и корректироваться через весовую инициализацию и регуляризацию.
Эти методы позволяют снизить негативное влияние затухающих и взрывных градиентов и добиться стабильного обучения даже для сложных сетевых архитектур в Synaptic.js.