Проблемы с градиентами

Основные причины и проявления

ConvNetJS, будучи чисто JavaScript-библиотекой для реализации нейронных сетей и свёрточных сетей, использует стохастический градиентный спуск (SGD) и его модификации для оптимизации весов. При этом возникают типичные проблемы с градиентами, которые характерны для глубоких сетей:

  1. Затухание градиентов (Vanishing Gradients) Проявляется в том, что значения градиентов становятся чрезвычайно малыми на ранних слоях сети. Это особенно заметно при использовании активационных функций с ограниченным диапазоном (например, сигмоиды), где производная стремится к нулю при больших по модулю значениях входа. Последствия затухания градиентов:

    • Сеть обучается крайне медленно или вообще перестаёт обновлять веса в ранних слоях.
    • Ошибки распространяются неэффективно назад через слои.
  2. Взрыв градиентов (Exploding Gradients) Обратная ситуация, когда значения градиентов становятся чрезмерно большими. Обычно возникает при глубокой сети и больших начальных весах. Последствия взрыва градиентов:

    • Параметры сети могут принимать огромные значения, приводя к численной нестабильности.
    • Процесс обучения становится хаотичным: функция потерь скачет и может давать NaN значения.

Инструменты ConvNetJS для контроля градиентов

ConvNetJS предоставляет несколько механизмов, позволяющих бороться с вышеописанными проблемами:

  1. Нормализация весов Для свёрточных слоев и полносвязных слоёв важно правильно инициализировать веса. В ConvNetJS используется случайная инициализация из диапазона, зависящего от числа входов и выходов слоя (Math.random() * scale). Для предотвращения взрыва градиентов рекомендуется уменьшать масштаб начальных весов.

  2. Регуляризация градиентов SGD в ConvNetJS поддерживает L2-регуляризацию, которая автоматически снижает амплитуду обновлений. В коде это реализуется через параметр decay, который уменьшает веса после каждой итерации, предотвращая резкий рост градиентов.

  3. Клиппинг градиентов (Gradient Clipping) Для предотвращения взрыва градиентов используется ограничение максимальной нормы градиента. В ConvNetJS это реализуется через проверку if (gradNorm > maxNorm) { scale *= maxNorm / gradNorm }. Такой подход гарантирует, что ни один градиент не превысит заданный порог.

  4. Использование подходящих активаций Для глубоких сетей предпочтительно применять ReLU или его варианты (LeakyReLU), так как их производные не стремятся к нулю для положительных входов. Это снижает вероятность затухания градиентов.

Методы диагностики

ConvNetJS позволяет анализировать градиенты на каждом слое:

  • net.getParamsAndGrads() возвращает массив объектов, где каждый содержит веса слоя, их градиенты и размерность.
  • Мониторинг статистики градиентов (mean, max, min) помогает выявить аномалии: слишком маленькие значения — признак затухания, слишком большие — взрыва.

Пример анализа градиентов на каждой итерации:

var pg = net.getParamsAndGrads();
for(var i=0;i<pg.length;i++) {
    var g = pg[i].grad;
    console.log("Layer " + i + " grad max: " + Math.max.apply(null,g));
}

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

  • Использовать небольшие начальные веса (scale = 0.01) для глубоких сетей.
  • Применять ReLU или LeakyReLU вместо сигмоид и tanh для скрытых слоев.
  • Настраивать learning_rate и decay в SGD для постепенного обучения.
  • Включать clipval или аналогичные параметры для контроля градиентов.
  • Отслеживать распределение градиентов в процессе обучения с помощью getParamsAndGrads.

Особенности ConvNetJS

  • Библиотека выполняет обучение полностью на CPU через JavaScript, что делает её чувствительной к численным ошибкам и накоплению малых значений градиентов.
  • Поскольку библиотека не использует динамическую графовую оптимизацию, все вычисления градиентов проходят по статической схеме, что требует внимательного контроля численной стабильности.
  • Поддержка Dropout и BatchNorm в ConvNetJS может косвенно улучшить стабильность градиентов, снижая их разброс и уменьшая вероятность взрыва или затухания.

Примеры кода для стабилизации обучения

Инициализация с малым масштабом и ReLU активацией:

var layer_defs = [];
layer_defs.push({type:'input', out_sx:32, out_sy:32, out_depth:3});
layer_defs.push({type:'conv', sx:5, filters:16, stride:1, pad:2, activation:'relu', weight_decay:0.001});
layer_defs.push({type:'pool', sx:2, stride:2});
layer_defs.push({type:'fc', num_neurons:64, activation:'relu', weight_decay:0.001});
layer_defs.push({type:'softmax', num_classes:10});

var net = new convnetjs.Net();
net.makeLayers(layer_defs);

var trainer = new convnetjs.SGDTrainer(net, {learning_rate:0.01, momentum:0.9, batch_size:64, l2_decay:0.001, clipval:5.0});

Этот подход обеспечивает устойчивость обучения и минимизирует эффекты затухания и взрыва градиентов.