Диагностика Mock-интервью
Главная · Нейронные сети

Обратное распространение ошибки

Backpropagation — алгоритм, который за один проход по сети вычисляет градиент лосса по каждому из миллионов (или миллиардов) весов. Это сердце обучения любой нейросети: без него не было бы ни CNN, ни GPT.

Интуиция

Обучение сводится к вопросу: «если чуть-чуть подкрутить вот этот вес, насколько изменится ошибка?» Ответ на него для каждого веса — это и есть градиент. Считать его «в лоб», шевеля каждый вес по отдельности и перезапуская сеть, безумно дорого: миллиард весов — миллиард прогонов. Обратное распространение ошибки (backpropagation) делает то же самое за один обратный проход, аккуратно применяя правило цепочки (chain rule).

Chain rule на пальцах: если L зависит от ŷ, а ŷ — от w, то чувствительности перемножаются:

∂L/∂w = (∂L/∂ŷ) × (∂ŷ/∂w)

Сеть — длинная цепочка простых операций, и у каждой операции есть простая локальная производная. Идём от лосса назад к входам и на каждом узле домножаем «входящую» чувствительность на локальную производную. Как эстафета: лосс передаёт назад сигнал «я хочу уменьшиться», и каждый узел переводит этот сигнал на язык своих входов.

Как это работает

Алгоритм состоит из двух проходов:

Например, для узла z = w·x локальные производные — ∂z/∂w = x и ∂z/∂x = w: градиент по весу пропорционален входу, который через него прошёл. Для ReLU локальная производная — 1 или 0: отрицательный вход просто «перекрывает кран» градиенту.

💡 Ключевая мысль Backprop — это не оптимизатор. Он только вычисляет градиенты ∂L/∂w; шаг по ним делает оптимизатор (SGD, Adam). «Backprop обновляет веса» — типичная ошибка на собеседовании.

Затухающие и взрывающиеся градиенты

Обратный проход — это цепочка умножений. Если множители в среднем меньше единицы, градиент к ранним слоям затухает экспоненциально (vanishing gradients): нижние слои перестают учиться. Классическая причина — сигмоидные активации, у которых производная не превышает 0.25. Если множители больше единицы — градиент взрывается (exploding gradients), лосс скачет или уходит в NaN; лечится это обрезкой градиента (gradient clipping).

Именно затухание градиентов десятилетиями мешало учить по-настоящему глубокие сети. Ключевые лекарства: ReLU-подобные активации (производная 1 в рабочей зоне), грамотная инициализация, нормализация слоёв и остаточные связи (residual connections) — прямые «обходные пути» y = x + F(x), по которым градиент течёт к ранним слоям с множителем 1, минуя цепочку. Без residual-связей не было бы ни ResNet на 152 слоя, ни трансформеров.

Backprop в цикле обучения

Полный шаг обучения выглядит так: forward → лосс → backward → шаг оптимизатора → обнуление градиентов. Разделение ролей здесь принципиально: backprop одинаков для любой архитектуры и любого лосса (современные фреймворки строят вычислительный граф и дифференцируют его автоматически — это называется autodiff), а стратегия шага — целиком забота оптимизатора. Полезно помнить и цену: backward стоит примерно как два forward, поэтому обучение всегда в разы дороже инференса.

⚠️ Подводный камень Forward pass обязан хранить промежуточные активации до конца backward — поэтому память при обучении растёт с глубиной и длиной последовательности, и её съедают именно активации, а не только веса. Отсюда трюки вроде gradient checkpointing: не хранить часть активаций, а пересчитывать их на обратном проходе.
🎤 На собеседовании
  • «Объясни backprop одним предложением» — рекурсивное применение chain rule от лосса к весам с переиспользованием промежуточных значений; один backward стоит примерно как два forward.
  • «Чем backprop отличается от SGD?» — backprop считает градиенты, оптимизатор делает шаг. Это два разных этапа цикла обучения.
  • «Почему глубокие сети было трудно учить и что помогло?» — vanishing gradients; помогли ReLU, нормализация, инициализация и residual connections.
  • «Зачем zero_grad() в PyTorch?» — градиенты по умолчанию накапливаются между вызовами backward, их нужно обнулять перед новым шагом.