В машинном обучении оптимизация нейронных сетей происходит через градиентный спуск — процесс, при котором веса сети обновляются в направлении антиградиента функции потерь. Для слоя с весами (W) и функцией потерь (L) обновление производится по правилу:
[ W W - ]
где () — скорость обучения. Вычисление градиентов осуществляется методом обратного распространения ошибки (backpropagation), который применяет цепное правило для последовательных слоёв:
[ = _{k=l}^{L-1} ]
Здесь (a^{(l)}) — активация на слое (l). Именно этот многоступенчатый продукт производных создаёт условия для затухающего или взрывного градиента.
Затухающий градиент возникает, когда производные функции активации меньше единицы. Например, для сигмоиды:
[ ’(x) = (x)(1-(x)) ]
При многослойной сети произведение множителей меньше единицы экспоненциально уменьшается с количеством слоёв, что приводит к тому, что градиенты для первых слоёв становятся практически нулевыми. Это препятствует эффективному обучению глубоких сетей.
Взрывной градиент наблюдается при больших производных, например при инициализации весов с большой дисперсией или использовании функций активации с большими градиентами (ReLU без нормализации). Произведение больших чисел растёт экспоненциально, вызывая нестабильность весов и переполнение чисел в вычислениях.
1. Инициализация весов
В TensorFlow.js доступны разные схемы инициализации весов через
tf.initializers. Правильный выбор уменьшает вероятность
взрывных или затухающих градиентов:
const model = tf.sequential();
model.add(tf.layers.dense({
units: 128,
inputShape: [784],
activation: 'relu',
kernelInitializer: 'heNormal' // Для ReLU
}));
glorotNormal или glorotUniform подходит
для сигмоид и tanh.heNormal или heUniform рекомендуется для
ReLU и его вариантов.2. Нормализация слоёв
Batch Normalization стабилизирует распределение активаций:
model.add(tf.layers.batchNormalization());
Это позволяет градиентам сохранять умеренные значения и ускоряет обучение.
3. Ограничение нормы градиентов
В TensorFlow.js можно применять gradient clipping, чтобы градиенты не превышали заданный порог:
const optimizer = tf.train.adam(0.001, undefined, undefined, undefined, {
clipNorm: 5.0
});
clipNorm ограничивает L2-норму градиента.clipValue, ограничивающий значения отдельных
компонентов.4. Выбор функций активации
LeakyReLU,
ELU) минимизируют проблему затухающего градиента.5. Использование коротких связей (Residual Connections)
ResNet-подобные архитектуры позволяют градиентам проходить напрямую через слои:
const input = tf.input({shape: [128]});
const dense1 = tf.layers.dense({units: 128, activation: 'relu'}).apply(input);
const dense2 = tf.layers.dense({units: 128}).apply(dense1);
const output = tf.layers.add().apply([dense2, input]); // residual connection
Это предотвращает затухание сигнала в глубоких сетях.
Создание модели с возможностью наблюдать эффект затухающего градиента:
const model = tf.sequential();
model.add(tf.layers.dense({units: 256, inputShape: [784], activation: 'sigmoid', kernelInitializer: 'glorotNormal'}));
for (let i = 0; i < 10; i++) {
model.add(tf.layers.dense({units: 256, activation: 'sigmoid'}));
}
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
model.compile({
optimizer: tf.train.adam(),
loss: 'categoricalCrossentropy',
metrics: ['accuracy']
});
Сигмоида и глубокая архитектура наглядно демонстрируют затухание градиентов: веса первых слоёв обновляются крайне медленно, что приводит к медленной сходимости. Замена активации на ReLU и добавление batch normalization решают проблему.
TensorFlow.js позволяет анализировать значения градиентов через
tf.variableGrads:
const {value, grads} = tf.variableGrads(() => loss(predictions, labels));
console.log(grads['dense/kernel'].dataSync());
Отслеживание градиентов помогает выявить моменты затухания или взрыва и корректировать архитектуру или оптимизатор.