Backpropagation
Обратное распространение ошибки применяет правило цепочки в обратном направлении по нейронной сети, чтобы за один проход получить градиент для каждого веса. Математика, вычислительные затраты и код.
Обратное распространение ошибки — алгоритм, который вычисляет вклад каждого веса в нейронной сети в ошибку предсказания. Начиная с функции потерь, он применяет правило цепочки слой за слоем в направлении входных данных и повторно использует промежуточные результаты. Поэтому одного обратного прохода достаточно, чтобы получить градиент для каждого веса независимо от того, содержит модель тысячи параметров или миллиарды. Затем оптимизатор использует эти градиенты для обновления весов, поэтому обратное распространение ошибки служит этапом обучения почти для всех моделей глубокого обучения. Румельхарт, Хинтон и Уильямс популяризировали этот метод для нейронных сетей в статье 1986 года в Nature Learning representations by back-propagating errors.
Как работает обратное распространение ошибки#
Обучение на одном батче включает два прохода. Во время прямого прохода входные данные последовательно проходят через каждый слой, промежуточные активации сохраняются, а в конце вычисляется функция потерь. Обратный проход начинается с функции потерь и проходит по слоям в обратном порядке. Каждый слой получает градиент функции потерь по своим выходным данным, называемый входящим градиентом, и умножает его на собственные локальные производные, получая два результата: градиент весов слоя, который сохраняется для оптимизатора, и градиент по входным данным слоя, который становится входящим градиентом для нижележащего слоя.
Поскольку каждому слою нужны только входящий градиент и сохранённые активации, производную никогда не приходится вычислять дважды. В терминах автоматического дифференцирования обратное распространение ошибки — это дифференцирование в обратном режиме по вычислительному графу сети. PyTorch строит граф во время прямого прохода и обходит его в обратном направлении при вызове loss.backward(). Для сети с двумя весами результат autograd в PyTorch можно проверить, записав правило цепочки вручную:
import torch
# A two-weight network: prediction = w2 * tanh(w1 * x)
x, target = torch.tensor(1.5), torch.tensor(0.5)
w1 = torch.tensor(0.8, requires_grad=True)
w2 = torch.tensor(-1.2, requires_grad=True)
# Forward pass, keeping the hidden activation h for the backward pass
h = torch.tanh(w1 * x)
loss = (w2 * h - target) ** 2
# Backward pass: autograd applies the chain rule from the loss back to each weight
loss.backward()
# The same chain rule by hand, reusing the upstream gradient dL/dy for both weights
dL_dy = 2 * (w2 * h - target)
dL_dw2 = dL_dy * h
dL_dw1 = dL_dy * w2 * (1 - h**2) * x
print(f"autograd: dL/dw1 = {w1.grad:.4f}, dL/dw2 = {w2.grad:.4f}")
print(f"by hand: dL/dw1 = {dL_dw1:.4f}, dL/dw2 = {dL_dw2:.4f}")Обе строки выводят dL/dw1 = 1.6475, dL/dw2 = -2.5016. В версии с ручным вычислением dL_dy вычисляется один раз и повторно используется для обоих весов — так же autograd работает с сетями, содержащими миллиарды весов. Обратное распространение во времени (BPTT) — это тот же алгоритм, применённый к рекуррентной нейронной сети, развёрнутой по временным шагам.
Обратное распространение ошибки и оптимизатор#
Обратное распространение ошибки только вычисляет градиенты и не меняет веса. Алгоритм оптимизации, например стохастический градиентный спуск (SGD) или Adam, преобразует эти градиенты в обновления весов, масштабированные с учётом скорости обучения. PyTorch добавляет результат каждого обратного прохода к градиентам, уже сохранённым для весов, поэтому один шаг оптимизатора может объединять несколько батчей:
Обучение Ultralytics YOLO использует накопление градиентов, чтобы получить номинальный размер батча в 64 изображения (nbs=64): после этапа разогрева при запуске batch=16 для каждого батча выполняется обратное распространение ошибки, но оптимизатор делает шаг только после каждого четвёртого батча, предварительно ограничив норму объединённого градиента значением 10.
Исчезающие и взрывающиеся градиенты#
На обратном проходе локальные производные перемножаются слой за слоем, поэтому величина градиента может геометрически уменьшаться или увеличиваться с глубиной. Если большинство множителей меньше 1, как у насыщенных блоков sigmoid, ранние слои почти не получают сигнал — это проблема исчезающего градиента. Если большинство множителей больше 1, проблема взрывающегося градиента приводит к огромным обновлениям и потерям NaN. Активации семейства ReLU, масштабированная инициализация весов, пакетная нормализация и пропускающие связи в остаточных сетях поддерживают стабильную величину градиента даже в сетях из сотен слоёв.
Затраты вычислительных ресурсов и памяти#
Каждая активация, необходимая для локальной производной, должна оставаться в памяти с прямого прохода до тех пор, пока обратный проход не дойдёт до неё. Поэтому потребление памяти при обучении растёт вместе с размером пакета, разрешением входных данных и глубиной сети, а для обучения нужно гораздо больше памяти, чем для инференса. Контрольные точки градиента сохраняют только часть активаций, а остальные пересчитывают во время обратного прохода: это снижает потребление памяти ценой дополнительных вычислений. При смешанной точности большинство активаций хранится в 16-битном формате, который занимает вдвое меньше места, чем 32-битные числа с плавающей точкой. По вычислениям обратный проход обходится примерно вдвое дороже прямого, поскольку на каждом слое градиенты рассчитываются и для весов, и для входных данных. Поэтому в оценках по законам масштабирования учитывают около 6 операций с плавающей точкой на параметр на обучающий токен: 2 в прямом проходе и 4 в обратном.










