Обратное распространение ошибки
Backpropagation — алгоритм, который за один проход по сети вычисляет градиент лосса по каждому из миллионов (или миллиардов) весов. Это сердце обучения любой нейросети: без него не было бы ни CNN, ни GPT.
Интуиция
Обучение сводится к вопросу: «если чуть-чуть подкрутить вот этот вес, насколько изменится ошибка?» Ответ на него для каждого веса — это и есть градиент. Считать его «в лоб», шевеля каждый вес по отдельности и перезапуская сеть, безумно дорого: миллиард весов — миллиард прогонов. Обратное распространение ошибки (backpropagation) делает то же самое за один обратный проход, аккуратно применяя правило цепочки (chain rule).
Chain rule на пальцах: если L зависит от ŷ, а ŷ — от w, то чувствительности перемножаются:
Сеть — длинная цепочка простых операций, и у каждой операции есть простая локальная производная. Идём от лосса назад к входам и на каждом узле домножаем «входящую» чувствительность на локальную производную. Как эстафета: лосс передаёт назад сигнал «я хочу уменьшиться», и каждый узел переводит этот сигнал на язык своих входов.
Как это работает
Алгоритм состоит из двух проходов:
- Прямой проход (forward pass): вычисляем выход сети слева направо и сохраняем все промежуточные значения (активации). Они понадобятся: локальные производные считаются именно в этих точках. Отсюда, кстати, и расход памяти при обучении.
- Обратный проход (backward pass): начинаем с ∂L/∂L = 1 и идём справа налево. Для каждого узла градиент по входу = градиент по выходу × локальная производная. Когда узел — умножение на вес, попутно получаем градиент и по весу.
Например, для узла z = w·x локальные производные — ∂z/∂w = x и ∂z/∂x = w: градиент по весу пропорционален входу, который через него прошёл. Для ReLU локальная производная — 1 или 0: отрицательный вход просто «перекрывает кран» градиенту.
Затухающие и взрывающиеся градиенты
Обратный проход — это цепочка умножений. Если множители в среднем меньше единицы, градиент к ранним слоям затухает экспоненциально (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, поэтому обучение всегда в разы дороже инференса.
- «Объясни backprop одним предложением» — рекурсивное применение chain rule от лосса к весам с переиспользованием промежуточных значений; один backward стоит примерно как два forward.
- «Чем backprop отличается от SGD?» — backprop считает градиенты, оптимизатор делает шаг. Это два разных этапа цикла обучения.
- «Почему глубокие сети было трудно учить и что помогло?» — vanishing gradients; помогли ReLU, нормализация, инициализация и residual connections.
- «Зачем zero_grad() в PyTorch?» — градиенты по умолчанию накапливаются между вызовами backward, их нужно обнулять перед новым шагом.