# Эндрю Ын: «Самый эффективный способ вычисления производных — справа налево»

Источник: https://www.youtube.com/watch?v=nJyUyKN-XBQ
Канал: DeepLearning.AI
Опубликовано: 25.08.2017

---

В данном материале подробно разбирается применение графов вычислений для расчета производных, что составляет математическую основу алгоритма обратного распространения ошибки (backpropagation) в нейронных сетях. Известный специалист в области искусственного интеллекта Эндрю Ын на конкретном примере демонстрирует, как последовательное движение по графу справа налево позволяет эффективно находить градиенты для оптимизации целевых функций. Этот урок является частью образовательной программы от платформы DeepLearning.AI.

## 🧮 Основы графа вычислений и первая производная
[[JUMP:0:00]]

Рассматриваемый граф вычислений служит для пошагового нахождения функции $J$. Основная задача алгоритма обратного распространения — определить, как изменение промежуточных и входных параметров влияет на конечный результат. Если рассмотреть производную функции $J$ по отношению к переменной $v$, то математически это означает оценку изменения значения $J$ при небольшом сдвиге (или «нудже») переменной $v$. В базовом состоянии графа значение $v$ равно $11$, а функция определена как $J = 3v$, что дает начальный результат $33$.

Если искусственно увеличить значение $v$ на минимальную величину — до $11.001$, то итоговое значение функции $J$ вырастет до $33.003$. Этот простой численный эксперимент наглядно показывает, что прирост целевой переменной ровно в три раза превышает прирост аргумента. Таким образом, производная $\frac{dJ}{dv}$ равна $3$. Этот шаг аналогичен классическому дифференцированию функции вида $f(a) = 3a$, где производная $\frac{df}{da} = 3$. В терминах машинного обучения выполнение этой операции означает прохождение первого шага назад по графу вычислений.

## 🔗 Цепное правило и расчет производной для промежуточных переменных
[[JUMP:2:20]]

Следующей задачей становится вычисление производной $\frac{dJ}{da}$, которая показывает влияние изменения входной переменной $a$ на финальную функцию $J$. Исходное значение $a$ в примере равно $5$. Если увеличить его на $0.001$ (до $5.001$), то значение следующего узла графа $v$, определяемого как $v = a + u$, также возрастет на $0.001$ и составит $11.001$. Это изменение распространяется дальше по графу слева направо, приводя к тому, что итоговое значение $J$ увеличивается до $33.003$.

Поскольку увеличение $a$ на $0.001$ вызывает рост $J$ на $0.003$, искомая производная также равна $3$. В математическом анализе этот механизм декомпозиции описывается как цепное правило (chain rule): изменение переменной $a$ напрямую влияет на $v$, а уже изменение $v$ меняет значение $J$. Величина общего изменения является произведением локальных скоростей изменений. Формула цепного правила выглядит следующим образом:

$$\frac{dJ}{da} = \frac{dJ}{dv} \cdot \frac{dv}{da}$$

Из расчетов очевидно, что при увеличении $a$ на единицу значение $v$ увеличивается ровно на столько же, то есть локальная производная $\frac{dv}{da} = 1$. Подставляя ранее найденное значение производной $\frac{dJ}{dv} = 3$, получаем итоговый результат: $3 \cdot 1 = 3$. Данная иллюстрация доказывает, что предварительное вычисление производных на более поздних этапах графа значительно упрощает нахождение градиентов для предыдущих переменных.

## 💻 Нотация в программном коде: упрощение имен переменных
[[JUMP:5:40]]

При практической реализации алгоритма обратного распространения ошибки разработчики сталкиваются с необходимостью эффективно именовать переменные в коде. Так как ключевой задачей оптимизации является расчет производных финальной целевой переменной (которой обычно выступает функция стоимости $J$ или функция потерь $L$) по отношению ко всем остальным параметрам, стандартные математические обозначения могут перегрузить программный код. Использование длинных имен вроде `dJ_over_dv` или `final_output_with_respect_to_v` делает код трудночитаемым.

Для упрощения архитектуры ПО была принята единая конвенция именования: в коде производная финального выхода по конкретной переменной обозначается префиксом `d` перед именем этой переменной. Эта конвенция работает следующим образом:

* Математическое выражение $\frac{dJ}{dv}$ записывается в коде просто как переменная `dv`.
* Математическое выражение $\frac{dJ}{da}$ трансформируется в программную переменную `da`.

В рамках рассматриваемого примера на языке Python программисту достаточно присвоить переменной `dv` значение $3$, а переменной `da` — также значение $3$.

## 🔄 Полный обратный проход: вычисление градиентов для всех узлов
[[JUMP:7:57]]

После очистки графа вычислений и фиксации промежуточных значений `dv = 3` и `da = 3`, алгоритм продолжает движение в обратном направлении для расчета оставшихся производных. Рассмотрим переменную $u$, базовое значение которой равно $6$. При увеличении $u$ на $0.01$ (до $6.01$) значение переменной $v$ увеличивается до $11.01$, а значение $J$ закономерно возрастает до $33.03$. Применяя цепное правило, мы получаем, что производная $\frac{dJ}{du} = \frac{dJ}{dv} \cdot \frac{dv}{du} = 3 \cdot 1 = 3$, что в программном коде соответствует переменной `du = 3`.

Более сложный расчет требуется для определения производной по переменной $b$, которая находится на самом левом (входном) уровне графа. Чтобы понять, как изменение $b$ влияет на $J$, необходимо вычислить произведение локальных производных по цепочке: $\frac{dJ}{db} = \frac{dJ}{du} \cdot \frac{du}{db}$. Если изменить значение $b$ с $3$ до $3.01$, это напрямую повлияет на узел $u$, который рассчитывается как произведение $b \cdot c$. При неизменном значении $c = 2$ величина $u$ возрастет с $6$ до $6.02$.

Это показывает, что локальная производная $\frac{du}{db}$ равна $2$. Перемножая её со значением `du = 3`, получаем финальный градиент $\frac{dJ}{db} = 3 \cdot 2 = 6$, записываемый в коде как `db = 6`. Сквозная проверка математических вычислений подтверждает этот вывод: при $b = 3.01$ значение $u$ становится равным $6.02$, переменная $v = 5 + 6.02 = 11.02$, а итоговая функция $J = 3 \cdot 11.02 = 33.06$, что ровно на $0.06$ больше исходного значения.

Аналогичным образом вычисляется производная для последней переменной $c$. Она определяется через произведение `du` на локальный коэффициент изменения, который равен значению переменной $b$ (то есть $3$). В результате расчетов градиент $\frac{dJ}{dc}$ равен $3 \cdot 3 = 9$, что на уровне программной реализации соответствует переменной `dc = 9`.

## 🎯 Главный вывод и практическое применение алгоритма
[[JUMP:13:19]]

Основной вывод, который формулирует Эндрю Ын, заключается в том, что последовательный расчет производных справа налево (против хода основных вычислений) является наиболее эффективным вычислительным методом. Движение по направлению красных стрелок графа позволяет повторно использовать уже вычисленные значения производных для расчета градиентов последующих (более ранних) переменных. Прямой проход (слева направо) служит для вычисления самой оптимизируемой функции стоимости, тогда как обратный проход (справа налево) предназначен исключительно для быстрого вычисления производных.

Инструктор подчеркивает, что если детали математического вывода или цепного правила показались слишком сложными или промелькнули слишком быстро, студентам не стоит беспокоиться. В следующем видеоуроке этот же алгоритм будет подробно разобран на практическом примере логистической регрессии, где будут показаны точные шаги, необходимые для программирования и расчета производных реальных моделей машинного обучения.