Почему считаем с конца
От функции потерь назад по тому же графу, по которому шёл forward
Не путать
Backward вычисляет градиенты. Он не меняет веса: изменение параметров делает отдельный шаг оптимизатора.
Нас интересует итоговая величина \(L\), поэтому обратный проход удобно начинать именно с неё.
Самая первая производная очевидна:
\[ \frac{\partial L}{\partial L}=1. \]
Это означает, что изменение \(L\) по отношению к самому себе передаётся с коэффициентом 1.
Теперь двигаемся на один шаг назад.
Последняя операция была:
\[ L=b^2. \]
При \(b=10\):
\[ \frac{\partial L}{\partial b} = 2b = 20. \]
Теперь мы знаем, насколько функция потерь чувствительна к небольшому изменению \(b\).
Ещё один шаг назад
Предыдущая операция:
\[ b=a+4. \]
Её локальная производная:
\[ \frac{\partial b}{\partial a}=1. \]
К этой операции уже пришёл градиент:
\[ \frac{\partial L}{\partial b}=20. \]
Чтобы передать его дальше к \(a\), используем цепное правило:
\[ \frac{\partial L}{\partial a} = \frac{\partial L}{\partial b} \cdot \frac{\partial b}{\partial a}. \]
Подставляем:
\[ \frac{\partial L}{\partial a} = 20\cdot1 = 20. \]
На каждом шаге backward: пришедший градиент умножаем на локальную производную текущей операции.
Доходим до x
Осталась первая операция:
\[ a=3x. \]
Её локальная производная:
\[ \frac{\partial a}{\partial x}=3. \]
Пришедший градиент:
\[ \frac{\partial L}{\partial a}=20. \]
Поэтому:
\[ \frac{\partial L}{\partial x} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial x} = 20\cdot3 = 60. \]
Что нам даёт этот результат
Мы получили:
\[ \frac{\partial L}{\partial x}=60. \]
Это означает: около текущей точки небольшое увеличение \(x\) увеличивает функцию потерь примерно в 60 раз сильнее.
Но \(x\) здесь — входное значение. В реальной нейронной сети входные данные обычно не меняют во время обучения.
Для обучения нас интересуют такие же производные, но уже по весам:
\[ \frac{\partial L}{\partial w_1}, \qquad \frac{\partial L}{\partial w_2}, \qquad \dots \]
Backward тем же способом проходит назад по графу и доходит до каждого обучаемого параметра.
Зачем нужен backward?
Он доставляет до каждого веса число
\[ \frac{\partial L}{\partial w}, \]
которое показывает, как изменение этого веса повлияет на функцию потерь.
Именно это число затем использует градиентный спуск.
Например, если для некоторого веса получилось:
\[ \frac{\partial L}{\partial w}=60, \]
а скорость обучения равна:
\[ \eta=0.01, \]
то шаг градиентного спуска будет:
\[ w_{\text{new}} = w_{\text{old}} - 0.01\cdot60. \]
То есть вес уменьшится на \(0.6\).
Если бы градиент был отрицательным, вес изменился бы в противоположную сторону.
Forward и backward идут по одному графу
Во время прямого прохода мы двигались слева направо:
\[ x \rightarrow a \rightarrow b \rightarrow L. \]
Считали значения:
\[ 2 \rightarrow 6 \rightarrow 10 \rightarrow 100. \]
Во время backward идём по тому же пути в обратном направлении:
\[ L \rightarrow b \rightarrow a \rightarrow x. \]
И считаем уже не значения, а градиенты:
\[ 1 \rightarrow 20 \rightarrow 20 \rightarrow 60. \]
Обратное распространение, или backpropagation, — способ вычислять градиенты функции потерь по промежуточным значениям и параметрам, проходя по вычислительному графу от результата к его входам.
Backward не меняет веса
Это важно отделять от градиентного спуска.
Backward отвечает на вопрос:
каков градиент каждого параметра?
После backward мы получаем значения вида:
\[ \frac{\partial L}{\partial w}. \]
И только после этого выполняется уже знакомый шаг градиентного спуска:
\[ w_{\text{new}} = w_{\text{old}} - \eta \frac{\partial L}{\partial w}. \]
Forward считает значения.
Backward считает градиенты.
Step изменяет параметры.