От производных к обратному распространению: теория, на которой основан Micrograd

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

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

Математически это задача оптимизации, а именно минимизация: мы ищем значения параметров модели, которые делают потери как можно меньшими. Хотя методы нулевого порядка, такие как MeZO, могут направлять обновления, случайным образом изменяя множество параметров одновременно и оценивая потери, стандартный подход заключается в использовании производной информации для управления поиском. Сегодня нейронные сети преимущественно обучаются по алгоритму с использованием градиентов, вычисляемых с помощью алгоритма обратного распространения ошибки — подхода, который мы здесь разработаем.

Чтобы понять обратное распространение ошибки, мы будем опираться на несколько идей: локальные производные, цепное правило и графы вычислений. Попутно мы рассмотрим три метода вычисления производных: числовое дифференцирование, символьное дифференцирование и автоматическое дифференцирование. Мы начнем с функций, достаточно маленьких, чтобы их можно было дифференцировать вручную, и сравнить их точные производные с оценками в конечных разностях, а затем перейдем к функциям с несколькими входами. Мы будем использовать частные производные, чтобы изолировать эффект каждого входа, сохраняя при этом остальные фиксированными, и собрать эти показатели в градиент.

Далее мы проследим изменения через связанные операции: цепное правило перемножает производные вдоль пути и складывает вклады, когда один вход влияет на выход несколькими путями. Главная идея состоит в том, что знание производных небольшого набора базовых операций позволяет дифференцировать построенные из них функции. Даже производную квадрата можно получить из одного лишь умножения. Мы свяжем эту идею с символьным дифференцированием, а затем проследим прямой и обратный проходы, чтобы увидеть, как автоматическое дифференцирование в обратном режиме повторно использует промежуточные результаты для вычисления градиентов по многим входам. На этом основано обратное распространение.

В этой статье закладываются теоретические основы для понимания Micrograd Андрея Карпати, небольшого движка Autograd с API, подобным PyTorch. В своем видеоразборе он создает движок и использует его для обучения нейронной сети. Те же принципы лежат в основе torch.autograd.backward PyTorch, который вычисляет градиенты с использованием цепного правила.

От скорости изменения к производным

Начнем с простой линейной зависимости z=3xz=3x и найдем ее производную, исследуя наклон ее графика. Производная говорит нам, как быстро изменяется выходной сигнал функции при изменении ее входных данных при определенном входном значении. Здесь любое увеличение xx приводит к увеличению zz в три раза. Используя Δ\Delta для обозначения изменения, мы можем написать:

Δz=3 Δx.\Delta z=3\,\Delta x.

Когда мы рисуем xx горизонтально, а zz вертикально, эта постоянная скорость изменения дает прямую линию. Для прямой линии производная равна ее наклону, поэтому мы можем найти ее, рассчитав отношение изменения выпуска к изменению входных данных, которое часто называют «превышением превышения»:

slope=ΔzΔx=riserun\text{slope}=\frac{\Delta z}{\Delta x}=\frac{\text{rise}}{\text{run}}

На графике ниже мы можем видеть, как синяя линия z=3xz=3x поднимается на 33 на участке 11, давая наклон 3/1=33/1=3.

Compare slopes of 3, 1, and 0.5 on the same axesThe solid blue line z = 3x passes through (0, 0), (1, 3), and (2, 6). A green horizontal segment shows a run of 1, and an orange vertical segment shows a rise of 3. The dashed purple line z = x and dotted gray line z = 0.5x rise by 1 and 0.5 per unit of x. All three use the same axes: the larger slope gives the steeper line.1326390xzz = xz = 0.5xrun = 1rise = 3z = 3x

Пунктирная серая линия z=0.5xz=0.5x и пунктирная фиолетовая линия z=xz=x поднимаются только на 0.50.5 и 11 для того же прогона 11. При тех же масштабах осей больший абсолютный наклон означает более крутую линию. Все три наклона положительны, поэтому при движении вправо линии поднимаются; отрицательный наклон приведет к падению линии.

Мы обнаружили, что синяя линия имеет наклон 33, поэтому производная от z=3xz=3x равна 33. Мы пишем его как dzdx\frac{dz}{dx} и читаем как «производное от zz относительно xx». Фраза «по отношению к xx» определяет входные данные, которые мы изменяем: производная измеряет, насколько быстро изменяется выходной сигнал zz при изменении xx. Наша функция имеет только один вход, xx, поэтому других входов, которые можно было бы удерживать фиксированными, нет. Для нашей синей линии мы получаем:

dzdx=3\frac{dz}{dx}=3

Символы dd обозначают производную, тогда как Δz/Δx\Delta z/\Delta x описывает среднюю скорость изменения за конечный интервал. Для z=3xz=3x скорость изменения постоянна и не зависит от xx, поэтому производная равна 33, где бы мы ее ни вычисляли.

Мы использовали наклон линии, чтобы найти производную. Мы также можем вычислить его, используя правила дифференцирования. Поскольку z=3xz=3x, мы можем заменить zz на его выражение и написать:

dzdx=ddx(3x)\frac{dz}{dx}=\frac{d}{dx}(3x)

Здесь дифференциальный оператор ddx\frac{d}{dx} означает «дифференцировать следующее выражение относительно xx». В ddx(3x)\frac{d}{dx}(3x) это выражение 3x3x рассматривается как функция xx. Это обозначение особенно полезно, когда мы рассматриваем отдельные термины в более широком выражении как функции и дифференцируем их отдельно.

Используя правило постоянного множителя, мы сохраняем множитель 33 и дифференцируем xx. Производная от xx по отношению к xx равна 11, поскольку увеличение входа на любую величину увеличивает выход xx точно на такую же величину:

ddx(3x)=3ddx(x)=3×1=3.\frac{d}{dx}(3x)=3\frac{d}{dx}(x)=3\times1=3.

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

Производная, зависящая от входного значения

Давайте теперь посмотрим на функцию, скорость изменения которой зависит от ее входных данных. Это проясняет локальную природу производной: она описывает, как выходные данные изменяются вблизи определенного входного значения, где мы их оцениваем. Чтобы продемонстрировать это, мы будем использовать функцию с одним входом xx с именем LL:

L(x)=x2+3.L(x)=x^2+3.

Мы можем записать эту производную как dL/dxdL/dx, используя то же обозначение, которое мы ввели выше, называемое , обозначение Лейбница. Другой способ записи — L′(x)L'(x), который читается как «L простое число x», который называется Lagrange notation или простое обозначение. Оба означают производную от LL по отношению к xx.

Чтобы найти производную от L(x)=x2+3L(x)=x^2+3, мы продифференцируем два его члена отдельно, а затем сложим результаты. При этом используются три правила:

ПравилоКак мы это используем
Правило властиДифференцировать x2x^2
Постоянное правилоДифференцируем постоянный член 33
Правило суммСложите производные двух членов

Давайте сначала применим правило производной степенной функции к x2x^2: умножьте на показатель степени, затем уменьшите показатель степени на единицу. В общем,

ddx(xn)=nxn−1\frac{d}{dx}(x^n)=nx^{n-1}

Для x2x^2 это дает 2x2x. Затем мы применяем правило производной константы к 33. Его производная по xx равна нулю, поскольку она не меняется при изменении xx. И, наконец, мы используем правило суммы для сложения этих производных. Итак, для нашей функции производная равна:

L′(x)=2x+0=2xL'(x)=2x+0=2x

Чтобы увидеть, что говорит нам эта формула, давайте рассчитаем выход функции и ее производную для трех значений xx:

Ввод xxВыход L(x)=x2+3L(x)=x^2+3Производная L′(x)=2xL'(x)=2x
−5-5(−5)2+3=28(-5)^2+3=282×(−5)=−102\times(-5)=-10
0002+3=30^2+3=32×0=02\times0=0
5552+3=285^2+3=282×5=102\times5=10

В отличие от постоянной скорости изменения 33 в z=3xz=3x, эта скорость зависит от текущего значения xx. Выходные данные функции одинаковы для x=−5x=-5 и x=5x=5, но производные имеют противоположные знаки. Положительная производная x=5x=5 означает, что выходное значение увеличивается, поскольку xx немного увеличивается. Отрицательная производная x=−5x=-5 означает, что выход уменьшается, а xx немного увеличивается.

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

L(x) = x² + 3 against Parameter xThe function curve is U-shaped with a minimum at x = 0, where the output is 3. At x = −5 and x = 5, the output is 28. Arrows follow the curve downward toward zero: increase x from −5, but decrease x from 5.-60-310020330640L(x) = x² + 3Parameter x(−5, 28)(5, 28)Increase x →← Decrease xMinimum (0, 3)

Этот график показывает нам, что если наша цель состоит в том, чтобы уменьшить выходной результат функции, знак производной подскажет нам, в какую сторону двигаться от каждой начальной точки. Зеленые стрелки показывают эти направления, которые мы можем определить, не пробуя увеличивать и уменьшать xx и не пересчитывая вывод функции. Обе стрелки ведут к x=0x=0, где эта функция достигает минимума, а производная равна нулю, хотя нулевая производная не всегда указывает на минимум.

Распространенный способ визуализировать производную — это наклон касательной к графику в выбранной точке. В точке x=5x=5 производная равна 1010, поэтому касательная имеет наклон 1010. Измените Параметр x ниже, чтобы увидеть, как изменяется наклон фиолетовой касательной вдоль синей кривой.

Output L(x) against Parameter xAt x=5, the output is 28 and the tangent slope is 10. -60-310.75021.5332.25643Output L(x)Parameter x
Exact derivative10L′(x) = 2x

Касательная также помогает нам оценить, насколько сильно изменяются выходные данные функции поблизости. В x=5x=5, где выходной сигнал равен 2828, а производная 1010, небольшое изменение в xx приводит к изменению выходного сигнала примерно в 1010 раз больше:

ΔL≈10 Δx.\Delta L\approx10\,\Delta x.

Мы используем ≈\approx в приведенном выше выражении, поскольку оцениваем изменение выходных данных за небольшой шаг. В точке x=5x=5 производная равна точно 1010, но наклон меняется по мере продвижения по кривой. Установка для Параметр x значения 4.94.9 в виджете дает наклон 9.89.8; меньшее движение до 4.994.99 даст 9.989.98. Чем меньше шаг, тем меньше изменяется наклон, поэтому рассмотрение его как постоянного значения 1010 дает более точную оценку изменения выходного сигнала.

Оценка производной с помощью конечных разностей

Оценка производных от значений функции на соседних входах называется численным дифференцированием. Конечные разности — один из способов сделать это. Нам нужно только вычислить функцию, не зная ее внутренних операций и не выводя формулу для ее производной.

Оценка прямой разницы делит изменение выхода на небольшое ненулевое изменение входа hh:

L(x+h)−L(x)h\frac{L(x+h)-L(x)}{h}

Давайте рассчитаем оценку конечной разности, используя L(x)=x2+3L(x)=x^2+3. Начиная с x=5x=5, шаг h=0.001h=0.001 приведет нас к 5.0015.001:

L(5.001)−L(5)0.001=28.010001−280.001=10.001\frac{L(5.001)-L(5)}{0.001} =\frac{28.010001-28}{0.001} =10.001

Это средняя скорость изменения между двумя входными параметрами. Геометрически это наклон прямой, соединяющей их точки на кривой, называемый секущей. Производная дает наклон касательной в начальной точке.

В виджете ниже установите для Параметр x значение 55, где фиолетовая касательная имеет наклон 1010. При Малое изменение h на 0.0010.001 оранжевая секущая имеет наклон 10.00110.001. Это конечно-разностная оценка. Когда мы уменьшаем hh, оранжевый наклон приближается к 1010, точной производной.

L(x) = x² + 3 against Parameter xAt x=2, the output is 7 and the tangent slope is 4. With h=0.8, the secant slope is 4.8.-60-310.75021.5332.25643L(x) = x² + 3Parameter x
Tangent: exact derivativeSecant: finite difference
Exact derivative4L′(x) = 2x
Finite difference4.8(L(x + h) − L(x)) / h

Мы называем это средним значением, поскольку делим общее изменение выхода на общее изменение затрат за интервал. Приближение конечных разностей использует только два значения конечных точек, поэтому оно не говорит нам, как функция ведет себя между ними. Его выпуск может меняться медленнее в одной части интервала и быстрее в другой.

Поскольку мы знаем формулу, мы можем построить кривую между конечными точками и увидеть это различие. На графике ниже используется более широкий интервал от x=1x=1 до x=5x=5, чтобы разница была видна. Пунктирная линия, соединяющая конечные точки, имеет средний наклон; зеленая кривая показывает, как на самом деле изменяется выходной сигнал. Заштрихованный треугольник показывает общее изменение входных данных Δx=4\Delta x=4 и изменение выходных данных ΔL=24\Delta L=24, поэтому средний наклон равен 24/4=624/4=6.

The average slope uses only the endpoints
Average slope as change in output divided by change in parameterA dashed blue secant passes through (1, 4) and (5, 28). A shaded right triangle shows the parameter change Δx = 4 and output change ΔL = 24, giving an average slope of 6. The midpoint is (3, 12). An orange secant joins (1, 4) to (3, 12), with average slope 4. A purple secant joins (3, 12) to (5, 28), with average slope 8. The green curve is L(x) = x² + 3.Outputx(3, 12)3Δx = 4ΔL = 24(1, 4)1(5, 28)5
Whole intervalFirst halfSecond halfFunction curve

Мы можем сравнить среднюю ставку на всем интервале со ставками на двух его половинах. Разделение по x=3x=3, где выходной сигнал равен 1212, дает:

ИнтервалИзменение вводаИзменение выводаСредняя ставка
Весь интервал1→51 \to 54→284 \to 28(28−4)/(5−1)=6(28-4)/(5-1)=6
Первая половина1→31 \to 34→124 \to 12(12−4)/(3−1)=4(12-4)/(3-1)=4
Вторая половина3→53 \to 512→2812 \to 28(28−12)/(5−3)=8(28-12)/(5-3)=8

Определение предела уточняет идею скорости изменения в одной точке: это значение, к которому приближается средняя скорость изменения по мере того, как интервал сжимается к нулю:

L′(x)=lim⁡h→0L(x+h)−L(x)hL'(x)=\lim_{h\to0}\frac{L(x+h)-L(x)}{h}

Мы позволяем hh приближаться к нулю, а не заменять h=0h=0, что даст 0/00/0. Учет этого предела позволяет нам вывести правило L′(x)=2xL'(x)=2x.. Для нашей функции сначала упростите соотношение, сохраняя при этом hh ненулевым:

L(x+h)−L(x)h=(x+h)2+3−(x2+3)h=2xh+h2h=2x+h\begin{aligned} \frac{L(x+h)-L(x)}{h} &=\frac{(x+h)^2+3-(x^2+3)}{h}\\ &=\frac{2xh+h^2}{h}\\ &=2x+h \end{aligned}

Это делает различие явным: расчет конечной разности на выбранном шаге дает 2x+h2x+h. При достижении предела, когда hh приближается к нулю, лишний hh удаляется, что дает нам правило производной:

L′(x)=2x,L′(5)=10L'(x)=2x,\qquad L'(5)=10

Это объясняет правило, которое мы символически применили ранее. Получив формулу 2x2x, мы можем вычислить ее непосредственно на любом входе, не вычисляя функцию на соседнем входе.

Разница вперед сравнивает xx с x+hx+h. Выборка после xx дает оценку обратной разности, а равномерная выборка с обеих сторон дает оценку центральной разности. Это другие типы конечно-разностной аппроксимации. Для той же функции L(x)=x2+3L(x)=x^2+3 мы теперь будем использовать больший шаг h=0.1h=0.1:

МетодФормулаОценить на x=5x=5, h=0.1h=0.1
ВпередL(x+h)−L(x)h\dfrac{L(x+h)-L(x)}{h}10.110.1
НазадL(x)−L(x−h)h\dfrac{L(x)-L(x-h)}{h}9.99.9
ЦентральныйL(x+h)−L(x−h)2h\dfrac{L(x+h)-L(x-h)}{2h}10.010.0

В точной арифметике, когда мы уменьшаем шаг hh, оценки прямой, обратной и центральной разностей сходятся к точной производной — для нашей функции в x=5x=5 это значение равно 1010. Центральная разность точна для этого квадрата в точной арифметике, но обычно остается приближением.

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

Эти методы полезны для проверки производных, но дополнительные вычисления функций делают их дорогостоящими, если применять их отдельно к миллионам параметров.

Несколько входов и частные производные

До сих пор мы использовали функции z=3xz=3x и L(x)=x2+3L(x)=x^2+3 только с одним параметром xx. Давайте теперь посмотрим на z=x+yz=x+y и z=xyz=xy с двумя параметрами xx и yy, которые мы можем изменять отдельно. Чтобы понять влияние каждого параметра на zz, мы изменяем один, оставляя другой фиксированным. Изменение обоих одновременно приведет к смешиванию их эффектов.

Ранее мы написали dz/dxdz/dx, чтобы описать, как выходной сигнал zz изменяется при использовании одного входного xx. Для наших функций с двумя входами нам нужно указать, какой вход мы изменяем. Чтобы измерить, как изменяется zz, когда мы изменяем xx, сохраняя фиксированным yy, мы используем частную производную, записанную ∂z/∂x\partial z/\partial x. Мы читаем это как ' — частная производная от zz относительно xx'. Порядок обозначений означает то же самое, что и раньше; символ ∂\partial указывает, что мы изменяем один вход, оставляя остальные фиксированными.

Геометрически два входа определяют точку на поверхности z=f(x,y)z=f(x,y). Частная производная измеряет наклон в этой точке в направлении одного входного сигнала, в то время как другой входной сигнал остается фиксированным. Поэтому нам нужны оба входных значения, чтобы указать, где мы измеряем наклон, даже если мы рассматриваем изменения только в одном направлении.

При вычислении частной производной мы применяем правила дифференцирования, рассматривая остальные входные данные как константы.

Начнем с дополнения z=x+yz=x+y. Удерживая yy фиксированным, увеличение xx на 11 увеличивает zz на 11, то есть ∂z/∂x=1\partial z/\partial x=1. Изменение ролей дает ∂z/∂y=1\partial z/\partial y=1.

Для умножения z=xyz=xy скорость зависит от входных данных, которые мы фиксируем. В следующей таблице показано, как изменяется zz при увеличении xx при сохранении постоянного значения yy 55:

x\boldsymbol{x}y\boldsymbol{y}z=xy\boldsymbol{z=xy}
33551515
44552020
55552525
66553030

Мы видим, что каждый раз, когда xx увеличивается на 11, zz увеличивается на 55. Если yy удерживается в точке 55, скорость изменения относительно xx равна 55:

∂z∂x=y=5\frac{\partial z}{\partial x}=y=5

Как видите, скорость изменения равна постоянному значению yy. Здесь используется то же правило постоянного множителя, которое мы применили к z=3xz=3x. Множитель в z=3xz=3x был 33. В z=xyz=xy множителем является yy, который мы сохраняем постоянным при дифференцировании xx. Поэтому, когда мы сохраняем одну переменную постоянной, она все равно участвует в расчетах и ​​влияет на то, как выходные данные реагируют на изменения другой переменной.

Дифференцирование по yy работает таким же образом: мы сохраняем константу xx и применяем правило множителя константы, получая ∂z/∂y=x\partial z/\partial y=x. Два входа играют симметричные роли, но их эффекты зависят от текущих значений xx и yy. В x=3x=3 и y=5y=5 выходные данные — 1515, а частные производные — 55 и 33 соответственно.

Теперь мы можем суммировать правила локальной производной для обеих операций:

Операция∂z/∂x\partial z/\partial x∂z/∂y\partial z/\partial y
z=x+yz=x+y1111
z=xyz=xyyyxx

Кроме того, изменение любого входа меняет выход на ту же величину. При умножении каждая частная производная равна другому входному значению. Эти правила называются локальными, поскольку они описывают, как выходные данные отдельной операции реагируют на ее входные данные. Позже мы подключим их, чтобы следить за изменениями посредством нескольких операций.

Частные производные 55 и 33 описывают чувствительность, а не части выходного сигнала 1515. Для одинаковых небольших изменений изменение только xx имеет 5/35/3 раз больший эффект, чем изменение только yy. Чтобы оценить эффект от изменения обоих входных данных, нам нужны как производные, так и предлагаемые изменения входных данных.

Для этого мы собираем частные производные в вектор, который называется градиентом. Обозначаем его символом ∇\nabla, поэтому для функции z=xyz=xy при x=3x=3 и y=5y=5 пишем ∇z=(5,3)\nabla z=(5,3). Этот вектор имеет и геометрический смысл в плоскости входов: среди перемещений одинаковой малой евклидовой длины он указывает направление наибольшего роста выхода в приближении первого порядка. Противоположный вектор указывает направление наиболее быстрого локального убывания.

На графике ниже показаны эти направления для z=xyz=xy. Вы можете видеть, как фиолетовая стрелка ведет вверх от (3,5)(3,5), где выходной сигнал равен 1515, а зеленая стрелка ведет вниз. Они отслеживают траектории поверхности, когда мы перемещаем входные данные в направлении градиента или противоположном ему. График масштабирует высоту отдельно; градиент определяет направление во входной плоскости.

The surface z = xy and gradient directions at (3, 5)The horizontal axes are inputs x and y; height is output z. The saddle surface has height 15 at (3, 5). The purple surface path follows input direction (5, 3), and the green surface path follows (-5, -3), the directions of steepest local increase and decrease in output per unit input distance.xyz-36036(3, 5, 15)
∇z = (5, 3): local increase−∇z = (−5, −3): local decrease
Drag to rotate, or focus the graph and use the arrow keys. Height is scaled separately; the gradient describes direction in the input plane.

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

Δz≈∂z∂xΔx+∂z∂yΔy\Delta z\approx\frac{\partial z}{\partial x}\Delta x+\frac{\partial z}{\partial y}\Delta y

Это скалярное произведение градиента и вектора изменения входных данных. Для нашей функции z=xyz=xy в точке (3,5)(3,5) это дает:

Δz≈5 Δx+3 Δy\Delta z\approx5\,\Delta x+3\,\Delta y

Например, увеличение обоих входов на 0.10.1 дает:

Δz≈5(0.1)+3(0.1)=0.8\Delta z\approx5(0.1)+3(0.1)=0.8

Увеличение xx на 0.10.1 вносит вклад 0.50.5 в прогнозируемое изменение, а увеличение yy на 0.10.1 способствует 0.30.3. Каждый вклад представляет собой частную производную, умноженную на соответствующее изменение входных данных. Производные описывают чувствительность; выбранные изменения определяют, какая часть этой чувствительности входит в прогноз.

Оценка функции на новых входах дает фактическое изменение 3.1×5.1−15=0.813.1\times5.1-15=0.81. Оставшийся 0.010.01 представляет собой член взаимодействия Δx Δy\Delta x\,\Delta y, который не учитывается линейным приближением. Таким образом, термины 0.50.5 и 0.30.3 описывают вклад в прогнозируемое изменение, а не доли исходного результата 1515. То же различие применимо и к обратному распространению ошибки: оно вычисляет чувствительность потерь к каждому весу. Умножение этих производных на предлагаемые изменения веса позволяет оценить их вклад в изменение потерь.

Сочетание правил дифференцирования для более сложной функции

В предыдущем разделе мы вычислили частные производные для сложения и умножения. Теперь объединим правила дифференцирования в выражении с несколькими членами и степенями:

f(x,y)=xy2+x3f(x,y)=xy^2+x^3

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

Сначала дифференцируем, затем подставляем значения

Мы сохраняем xx и yy в качестве символов, находя частную производную для каждого входа, рассматривая другой как константу. Затем мы оценим обе производные по (x,y)=(3,2)(x,y)=(3,2).

Сначала мы дифференцируем по xx, оставляя yy фиксированным и дифференцируя каждый член. Коэффициент y2y^2 действует как постоянный множитель xx:

∂f∂x=y2∂∂x(x)+∂∂x(x3)=y2⋅1+3x2=y2+3x2\begin{aligned} \frac{\partial f}{\partial x} &=y^2\frac{\partial}{\partial x}(x) +\frac{\partial}{\partial x}(x^3)\\ &=y^2\cdot1+3x^2\\ &=y^2+3x^2 \end{aligned}

Фиксация yy не удаляет его из расчета: его значение определяет множитель y2y^2. Дифференцируем x3x^3 с помощью степенного правила.

Затем мы дифференцируем по yy, меняя роли и оставляя xx фиксированным. Теперь xx является постоянным множителем y2y^2, и весь член x3x^3 является постоянным:

∂f∂y=x∂∂y(y2)+∂∂y(x3)=x⋅2y+0=2xy\begin{aligned} \frac{\partial f}{\partial y} &=x\frac{\partial}{\partial y}(y^2) +\frac{\partial}{\partial y}(x^3)\\ &=x\cdot2y+0\\ &=2xy \end{aligned}

Член x3x^3 вносит нулевой вклад, поскольку изменение yy при фиксированном xx не может изменить его значение.

Это дает нам следующие частные производные на выбранных нами входных данных:

Ввод меняемВвод зафиксированЧастная производная в точке (3,2)(3,2)Прогнозируемое изменение объема производства
xxy=2y=222+3(32)=312^2+3(3^2)=31Δf≈31 Δx\Delta f\approx31\,\Delta x
yyx=3x=32(3)(2)=122(3)(2)=12Δf≈12 Δy\Delta f\approx12\,\Delta y

Сначала фиксируем один вход, затем дифференцируем сечение

Теперь рассмотрим второй подход: сначала фиксируем один вход, затем дифференцируем функцию сечения. Подставляем значение одного входа в f(x,y)=xy2+x3f(x,y)=xy^2+x^3 и получаем обычную функцию оставшегося входа.

Сначала мы устанавливаем y=2y=2 и оставляем xx свободным для изменения:

f(x,2)=x(22)+x3=4x+x3f(x,2)=x(2^2)+x^3=4x+x^3

Это фрагмент исходной функции, а не производная: мы заменили значение только на один вход.

Для среза с y=2y=2 у нас больше нет yy в качестве переменной в выражении, поскольку замена 22 превратила y2y^2 в постоянный множитель 44. Однако эффект yy по-прежнему передается через множитель 44. Теперь, когда в качестве переменной осталась только xx, возьмем обычную производную по xx:

ddx(4x+x3)=4+3x2⟹4+3(32)=31at x=3\frac{d}{dx}(4x+x^3)=4+3x^2 \quad\Longrightarrow\quad 4+3(3^2)=31\quad\text{at }x=3

Затем, возвращаясь к исходной функции, мы устанавливаем x=3x=3 и оставляем yy свободным для изменения:

f(3,y)=3y2+33=3y2+27f(3,y)=3y^2+3^3=3y^2+27

А для среза с x=3x=3 дифференцируем по yy:

ddy(3y2+27)=6y⟹6(2)=12at y=2\frac{d}{dy}(3y^2+27)=6y \quad\Longrightarrow\quad 6(2)=12\quad\text{at }y=2

Оба среза проходят через одну и ту же выбранную точку, где находится f(3,2)=3(22)+33=39f(3,2)=3(2^2)+3^3=39. Дифференцирование каждого среза дает те же частные производные, которые мы рассчитали выше:

∂f∂x(3,2)=31,∂f∂y(3,2)=12\frac{\partial f}{\partial x}(3,2)=31,\qquad \frac{\partial f}{\partial y}(3,2)=12

Оба подхода оставляют другой вход постоянным, поэтому его значение можно подставить до дифференцирования либо сохранить его как символ и подставить значение позже. Первый подход даёт общие формулы производных, y2+3x2y^2+3x^2 и 2xy2xy, которые можно вычислить для любой пары входов. Второй даёт формулы для конкретных сечений: 4+3x24+3x^2, когда y=2y=2, и 6y6y, когда x=3x=3. Например, подстановка x=3x=3 в 2xy2xy даёт 6y6y, как и дифференцирование функции сечения 3y2+273y^2+27. Каждое сечение получаем отдельной подстановкой в исходную функцию.

Эти производные измеряют чувствительность, а не доли текущего выхода. Как и раньше, мы собираем частные производные в вектор градиента. В точке (3,2)(3,2) это дает ∇f=(31,12)\nabla f=(31,12). Предлагаемые входные изменения образуют вектор (Δx,Δy)(\Delta x,\Delta y). Их скалярное произведение оценивает совокупное изменение выхода:

Δf≈∇f(3,2)⋅(Δx,Δy)=31Δx+12Δy\Delta f\approx\nabla f(3,2)\cdot(\Delta x,\Delta y) =31\Delta x+12\Delta y

В (x,y)=(3,2)(x,y)=(3,2) обе координаты указывают точку, в которой мы измеряем уклоны. Перемещение в направлении xx с удерживанием y=2y=2 дает наклон касательной 3131; перемещение в направлении yy, удерживая x=3x=3, дает наклон 1212. Это два направления, проходящие через одну и ту же точку поверхности. Каждая скорость является частной производной исходной функции с двумя входами и обычной производной соответствующего среза с одним входом. Градиент объединяет эти скорости в направлении наибольшего локального увеличения во входной плоскости.

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

Surface and both fixed-input slicesBoth slices pass through (3, 2, 39). One varies x with y fixed at 2; the other varies y with x fixed at 3. Their tangent slopes are 31 and 12. Drag or use arrow keys to rotate.x varies; y stays fixedy varies; x stays fixedSurface path along the gradient direction at the selected point
f(x, 2) = 4x + x³-505-1450145xf

∂f/∂x = 31

f(3, y) = 3y² + 27-5052764.5102yf

∂f/∂y = 12

Начиная с (3,2)(3,2), перемещение ползунка xx влияет на два графика по-разному. Слева xx — это изменяющийся вход, поэтому выбранная точка перемещается по той же кривой f(x,2)=4x+x3f(x,2)=4x+x^3, а y=2y=2 остается фиксированной. Справа xx — это фиксированный ввод, определяющий срез, поэтому изменение его с 33 на 44 меняет формулу с f(3,y)=3y2+27f(3,y)=3y^2+27 на f(4,y)=4y2+64f(4,y)=4y^2+64. Другими словами, перемещение xx перемещает точку вдоль левого среза и изменяет срез, отображаемый справа. Перемещение yy делает обратное.

Производные через последовательность связанных операций

Ранее мы разделили два термина по отдельности и добавили их производные, поскольку эти термины были добавлены в исходное выражение. В более общем смысле мы можем рассматривать каждое промежуточное вычисление как функцию исходных входных данных. Связь их результатов определяет, как мы объединяем их производные.

В следующей таблице показаны правила сложения, умножения и вложения. Мы будем использовать g(x)g(x) и h(x)h(x) в качестве общих имен для двух дифференцируемых функций xx:

ПравилоКак сочетаются функцииПроизводная по xx
Правило суммg(x)+h(x)g(x)+h(x)g′(x)+h′(x)g'(x)+h'(x)
Правило продуктаg(x)h(x)g(x)h(x)g′(x)h(x)+g(x)h′(x)g'(x)h(x)+g(x)h'(x)
Правило цепочкиg(h(x))g(h(x))g′(h(x))h′(x)g'(h(x))h'(x)

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

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

Давайте сначала разделим нашу функцию f(x,y)=xy2+x3f(x,y)=xy^2+x^3 на отдельные операции с именованными промежуточными результатами:

u=y2,v=xu,w=x3,f=v+wu=y^2,\qquad v=xu,\qquad w=x^3,\qquad f=v+w

Например, возведение в квадрат отображает yy в u=y2u=y^2, а отображение умножения xx и uu в v=xuv=xu. Выход квадрата становится входом для умножения. Когда выходные данные одной функции становятся входными данными для другой, функции компонуются — связь, называемая композицией функций, показанная как g(h(x))g(h(x)) в таблице выше. Операция имеет свои собственные непосредственные входные данные; Операции соединения делают ее результат также зависимым от предыдущих входных данных.

Разделение соответствует зависимостям выражения: u=y2u=y^2 должен быть доступен до v=xuv=xu, а vv и ww должны быть доступны до f=v+wf=v+w. Поскольку w=x3w=x^3 не зависит от uu или vv, мы можем вычислить его до или после этой ветви. Это разделение не уникально: мы могли бы выразить возведение в квадрат и куб как умножение. Здесь мы рассматриваем их как отдельные операции и используем степенное правило для их локальных производных.

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

Цепное правило: один путь

Начнем с дифференцирования по yy, оставив xx фиксированным. Разделение показывает, почему yy является полезной отправной точкой: его изменение влияет на u=y2u=y^2, затем на v=xuv=xu, а w=x3w=x^3 остается постоянным. После этого мы рассмотрим xx, который влияет как на vv, так и на ww. На данный момент есть только один путь от yy до конечного результата:

y ⟶ u=y2 ⟶ v=xu ⟶ f=v+wy\ \longrightarrow\ u=y^2\ \longrightarrow\ v=xu\ \longrightarrow\ f=v+w

Чтобы увидеть разницу между локальной производной и цепным правилом, сосредоточьтесь всего на двух узлах операции: квадрате u=y2u=y^2 и умножении v=xuv=xu, соединенных u→vu\to v.

Если рассматривать само по себе, то умножение принимает в качестве входных данных xx и uu. Его локальные частные производные:

∂v∂x=u,∂v∂u=x\frac{\partial v}{\partial x}=u, \qquad \frac{\partial v}{\partial u}=x

Чтобы получить каждую локальную производную, нам нужно только выражение операции и входные данные, по которым мы дифференцируем. Для v=xuv=xu обработка xx как константы дает ∂v/∂u=x\partial v/\partial u=x, независимо от того, как рассчитывался uu или где используется vv. Остальная часть графика нам не нужна для этого шага. Квадрат отдельно поставляется du/dy=2ydu/dy=2y.

Обратите внимание, что цепное правило включает производную узла u=y2u=y^2, du/dy=2ydu/dy=2y отдельно, хотя узел v=xuv=xu уже использует значение uu. Локальная производная ∂v/∂u=x\partial v/\partial u=x сообщает нам только, как vv реагирует на изменение uu. Он не включает в себя изменения uu с yy. Эта скорость равна du/dy=2ydu/dy=2y, поэтому нам нужны оба фактора:

∂v∂y=∂v∂u⏟how v responds to u⋅dudy⏟how u responds to y=x⋅2y\frac{\partial v}{\partial y} =\underbrace{\frac{\partial v}{\partial u}}_{\text{how v responds to u}} \cdot\underbrace{\frac{du}{dy}}_{\text{how u responds to y}} =x\cdot2y

При фиксированном xx умножение является внешней функцией, а возведение в квадрат — внутренней функцией. Сама по себе внешняя производная не может сказать нам, как изменяется внутренняя функция: умножение принимает значение uu, не зная, получено ли оно из y2y^2, 2y2y или другого вычисления. В y=2y=2 и y2y^2, и 2y2y производят u=4u=4, но их производными являются 44 и 22 соответственно. Таким образом, одно и то же значение не подразумевает одинаковую реакцию на изменение yy. Это различие применимо всякий раз, когда мы дифференцируем композицию.

Если мы заменим u=y2u=y^2 на v=xuv=xu, полное выражение v=xy2v=xy^2 будет содержать эту внутреннюю зависимость. Дифференцирование его напрямую дает 2xy2xy, учитывающее обе скорости. График наглядно демонстрирует эти два фактора.

В таблице ниже показан только эффект изменения yy с фиксированным xx. Он фокусируется на двух операциях u=y2u=y^2 и v=xuv=xu, останавливаясь на vv для вычисления ∂v/∂y\partial v/\partial y:

ШагСимволическая форма
Определить операцииu=y2u=y^2, затем v=xuv=xu
Вывод формул локальной производнойdu/dy=2ydu/dy=2y, ∂v/∂u=x\partial v/\partial u=x
Объедините формулы, используя цепное правило∂v/∂y=(∂v/∂u)(du/dy)=x⋅2y\partial v/\partial y=(\partial v/\partial u)(du/dy)=x\cdot2y

Связи имеют значение, когда мы объединяем локальные производные: путь y→u→vy\to u\to v говорит нам умножить ∂v/∂u\partial v/\partial u на du/dydu/dy, чтобы найти ∂v/∂y\partial v/\partial y. Операция обеспечивает местное правило; график подсказывает нам, как связать его с другими правилами.

Теперь приступим к последнему добавлению. Исходная функция принимает xx и yy, но каждая операция имеет свои входные данные. Для окончательного добавления f=v+wf=v+w эти входы — vv и ww. Его локальная производная ∂f/∂v\partial f/\partial v спрашивает, как изменяется вывод дополнения, когда vv изменяется, в то время как ww остается фиксированным. Это равно 11, поскольку добавление небольшой суммы к vv добавляет такую ​​же сумму к ff. Затем цепное правило соединяет эти локальные ответы обратно с исходным входом yy.

В следующей таблице перечислены локальные производные по полному пути:

ОперацияЛокальная производная
Квадрат yy для получения uudu/dy=2ydu/dy=2y
Умножьте uu на фиксированное значение xx, чтобы получить vv∂v/∂u=x\partial v/\partial u=x
Добавьте фиксированное значение ww, чтобы получить ff∂f/∂v=1\partial f/\partial v=1

Чтобы получить производную конечного результата по yy, , мы теперь можем применить цепное правило, умножая локальные производные вдоль y→u→v→fy\rightarrow u\rightarrow v\rightarrow f:

∂f∂y=∂f∂v⋅∂v∂u⋅dudy=1⋅x⋅2y=2xy\frac{\partial f}{\partial y} =\frac{\partial f}{\partial v}\cdot\frac{\partial v}{\partial u}\cdot\frac{du}{dy} =1\cdot x\cdot2y =2xy

Это соответствует формуле, которую мы получили путем прямого дифференцирования xy2+x3xy^2+x^3. Здесь ∂f/∂y\partial f/\partial y равно ∂v/∂y\partial v/\partial y, поскольку последняя операция умножает скорость на 11.

Виджет ниже отображает график этих связанных вычислений. Он начинается с xx и yy — входов, от которых зависит функция. Выражения внутри узлов, такие как u=y2u=y^2 и v=xuv=xu, определяют операции. Сплошные стрелки указывают от результата операции к операции, которая его использует. Таким образом, u→vu\to v означает, что v=xuv=xu использует результат u=y2u=y^2, делая их состав видимым. Умножение использует uu, а не его производное du/dydu/dy; метки производных и пунктирные стрелки в поле цепочки правил показывают, как мы дифференцируем вычисления. Следуйте оранжевому пути и умножьте локальные производные, чтобы получить 2y⋅x⋅1=2xy2y\cdot x\cdot1=2xy.

One path from y through u and v to fHolding x constant, the derivative for y receives a contribution through one path: y → u → v → f. Multiply along this path: ∂f/∂y = 2y × x × 1 = 2xy.yu = y²du/dy = 2yxv = xu∂v/∂u = xw = x³f = v + w∂f/∂v = 1Chain rule∂f/∂y = 2y × x × 1 = 2xy

Scroll the graph sideways to see the chain-rule box.

Теперь, когда мы возьмем формулу производной 2xy2xy и заменим x=3x=3 и y=2y=2, мы получим 2(3)(2)=122(3)(2)=12, соответствующую частной производной, которую мы вычислили ранее. Мы можем вычислить эту формулу для любой входной пары, не вычисляя предварительно промежуточные значения функции.

В x=3x=3 и y=2y=2 небольшое изменение hh в yy приводит к изменению примерно 4h4h в uu. Масштабы умножения изменяются на 33, что дает изменение vv примерно на 12h12h. Добавление неизмененного ww оставляет это изменение нетронутым.

Несколько путей: сложение вкладов в производную

Мы проследили, как yy влияет на вывод по одному пути. Давайте теперь найдем частную производную f(x,y)=xy2+x3f(x,y)=xy^2+x^3 относительно xx, оставив yy фиксированным. Используем те же промежуточные результаты:

u=y2,v=xu,w=x3,f=v+wu=y^2,\qquad v=xu,\qquad w=x^3,\qquad f=v+w

Значение u=y2u=y^2 остается постоянным, но v=xuv=xu и w=x3w=x^3 изменяются с помощью xx. Это дает нам два пути от xx к конечному выводу:

x⟶v=xu⟶f=v+wandx⟶w=x3⟶f=v+wx\longrightarrow v=xu\longrightarrow f=v+w \qquad\text{and}\qquad x\longrightarrow w=x^3\longrightarrow f=v+w

Мы уже рассчитали ∂f/∂x=y2+3x2\partial f/\partial x=y^2+3x^2, дифференцируя xy2+x3xy^2+x^3 по xx, сохраняя константу yy. Цепное правило дает один и тот же ответ, используя локальные производные по обоим путям:

ОперацияЛокальная производная
Умножьте xx на фиксированное значение uu, чтобы получить vv∂v/∂x=u=y2\partial v/\partial x=u=y^2
Куб xx для получения wwdw/dx=3x2dw/dx=3x^2
Добавьте vv и ww, чтобы получить ff, учитывая эффект vv∂f/∂v=1\partial f/\partial v=1
Добавьте vv и ww, чтобы получить ff, учитывая эффект ww∂f/∂w=1\partial f/\partial w=1

Каждая локальная производная сложения сохраняет свой другой вход фиксированным. Оба входа изменяются при изменении xx, поэтому нам нужны оба входа.

Теперь мы применяем цепное правило многих переменных: умножаем локальные производные вдоль каждого пути, затем добавляем два вклада, чтобы получить производную конечного результата по отношению к xx:

∂f∂x=∂f∂v⋅∂v∂x+∂f∂w⋅dwdx=1⋅y2+1⋅3x2=y2+3x2\frac{\partial f}{\partial x} =\frac{\partial f}{\partial v}\cdot\frac{\partial v}{\partial x} +\frac{\partial f}{\partial w}\cdot\frac{dw}{dx} =1\cdot y^2+1\cdot3x^2 =y^2+3x^2

На диаграмме ниже показаны два пути от xx к конечному выводу: x→v→fx\to v\to f и x→w→fx\to w\to f. Оба отслеживают эффект изменения xx, при этом yy остается постоянным. Узлы yy и u=y2u=y^2 серые, поскольку эта ветвь остается фиксированной; он по-прежнему предоставляет значение uu, используемое при умножении. Оранжевый путь вносит вклад 1⋅y21\cdot y^2, а фиолетовый путь — 1⋅3x21\cdot3x^2. В полях активных операций показаны их локальные производные. В поле выше производные умножаются по каждому пути, а затем складывают вклады, чтобы получить y2+3x2y^2+3x^2.

The chain rule through two paths from x to fHolding y constant, the derivative for x receives contributions through two paths: x → v → f and x → w → f. Multiply along each path, then add: ∂f/∂x = 1 × y² + 1 × 3x² = y² + 3x².yu = y²xv = xu∂v/∂x = u = y²w = x³dw/dx = 3x²f = v + w∂f/∂v = 1∂f/∂w = 1Chain ruleMultiply along paths, then add(∂f/∂v) × (∂v/∂x)+(∂f/∂w) × (dw/dx)1 × y²+1 × 3x²∂f/∂x = y² + 3x²

Scroll the graph sideways to follow the paths.

Теперь, когда мы возьмем формулу производной y2+3x2y^2+3x^2 и заменим x=3x=3 и y=2y=2, мы получим 22+3(32)=4+27=312^2+3(3^2)=4+27=31. Это соответствует частной производной, которую мы вычислили ранее, без предварительного вычисления промежуточных значений функции.

Вычисление производных: символьное и автоматическое дифференцирование

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

Производная квадрата из одного лишь умножения

Предположим, наш движок поддерживает умножение, но не имеет операции возведения в степень. Мы всё равно можем вычислить s=x2s=x^2 как s=x⋅xs=x\cdot x. Для умножения s=abs=ab локальные производные равны:

∂s∂a=b,∂s∂b=a.\frac{\partial s}{\partial a}=b,\qquad \frac{\partial s}{\partial b}=a.

Обе входные позиции здесь ссылаются на одну переменную: a=xa=x и b=xb=x. Изменение xx влияет на результат через обе позиции, поэтому мы складываем их вклады:

dsdx=∂s∂adadx+∂s∂bdbdx=x⋅1+x⋅1=2x.\frac{ds}{dx} =\frac{\partial s}{\partial a}\frac{da}{dx} +\frac{\partial s}{\partial b}\frac{db}{dx} =x\cdot1+x\cdot1 =2x.

Производная квадрата следует из умножения и цепного правила; отдельное правило для квадрата нам не понадобилось. В обратном проходе начинаем с градиента ss, равного 11. Каждая входная позиция добавляет значение другого операнда к градиенту xx. При x=3x=3 два вклада дают 3+3=63+3=6. Хотя узел xx всего один, необходимо учесть оба его использования.

Та же идея работает для куба. Запишем t=x⋅xt=x\cdot x, а затем c=t⋅xc=t\cdot x. Соединяя производные, получаем:

dcdx=xdtdx+tdxdx=x(x+x)+x2=3x2.\frac{dc}{dx} =x\frac{dt}{dx}+t\frac{dx}{dx} =x(x+x)+x^2 =3x^2.

Таким образом, движок может дифференцировать степени с положительными целыми показателями, записанные как последовательные умножения, без реализации правила производной степенной функции. Другим функциям нужны подходящие дополнительные базовые операции: например, одним умножением нельзя выразить sin⁡(x)\sin(x) или произвольные дробные степени. Общий принцип — задать правила дифференцирования базовых операций, а затем соединять эти правила для дифференцирования построенных из них функций.

Одни и те же правила могут строить выражения или вычислять числа

В примере с квадратом мы сохранили xx как символ, построили x⋅1+x⋅1x\cdot1+x\cdot1 и упростили выражение до 2x2x. Такое построение выражений для производных называется символьным дифференцированием. Программа может автоматически строить эти выражения, используя те же локальные правила и зависимости.

Micrograd применяет эти правила к числовым значениям. При x=3x=3 исходное умножение даёт s=9s=9, а его обратное правило накапливает 3+3=63+3=6 в градиенте xx. Это автоматическое дифференцирование: вычисление математически эквивалентно подстановке x=3x=3 в 2x2x, хотя сама символьная формула не строится. Обратное распространение использует автоматическое дифференцирование в обратном режиме для вычисления градиентов потерь, необходимых для обучения.

Оба метода соединяют одни и те же правила дифференциального исчисления. В этом сравнении меняется то, строим ли мы выражения для производных или выполняем вычисления производных над числами:

МетодС чем работаетЧто даёт
Символьное дифференцированиеСимвольные выражения, включая именованные промежуточные выраженияФормулы производных, которые можно вычислить для выбранных входных значений
Автоматическое дифференцированиеОперации программы и их значения для выбранных входовЧисловые значения производных, полученные соединением локальных производных

От формул производных к исполняемым правилам

В более раннем однопутном символьном графе мы написали du/dy=2ydu/dy=2y рядом с операцией u=y2u=y^2. Эта формула также говорит нам, как вычислить производную с помощью арифметики: возьмите входное значение и умножьте его на 22. В точке y=2y=2 результат — 44. Это числовая локальная производная.: локальная производная, вычисляемая при определенных входных значениях.

Autodiff не выводит правила производных базовых операций из самих значений и не открывает их символьно во время выполнения. Разработчик движка задаёт правило для каждой базовой операции, превращая известную формулу производной в код. Если выбрать возведение в квадрат как базовую операцию, 2y2y становится 2 * input_value. Формула объясняет правило математически, а код выполняет его над числами. Для входа 2 получаем 4. Если же реализовать квадрат как y * y, два вклада от умножения дадут тот же результат: 2 + 2 = 4.

Движок использует граф и цепное правило для дифференцирования комбинаций этих операций. Каждое локальное правило реализуется один раз и повторно используется при каждом появлении соответствующей операции:

Работа программыИзвестное локальное производное правилоЧисленный расчет
u = y ** 2du/dy=2ydu/dy=2y2 * y.data
v = x * u∂v/∂x=u\partial v/\partial x=u, ∂v/∂u=x\partial v/\partial u=xu.data, x.data
w = x ** 3dw/dx=3x2dw/dx=3x^23 * x.data ** 2
f = v + w∂f/∂v=1\partial f/\partial v=1, ∂f/∂w=1\partial f/\partial w=11, 1

Здесь .data содержит числовое значение узла. Производная умножения по uu получается путем чтения значения другого операнда, xx. Механизму не обязательно знать, как был рассчитан этот операнд, чтобы применить это локальное правило.

Механизм также записывает, какие входные данные дали каждый результат, сохраняя зависимости, показанные на нашем графике. Обычная арифметика вычисляет значения; autodiff добавляет правила отслеживания зависимостей и производные. Он также может отслеживать операции, выполняемые внутри циклов и условных ветвей, без необходимости переписывать весь расчет в виде одной формулы.

Для графа в нашем примере мы выбираем отдельную базовую операцию возведения в степень, сохраняя каждый квадрат и куб как одну операцию. Их можно реализовать как y.pow(2) и x.pow(3). Это выбор реализации: последовательное умножение тоже подходит для этих степеней. Метод pow(exponent) применяет общее правило производной степенной функции:

ddy(yn)=nyn−1\frac{d}{dy}(y^n)=ny^{n-1}

Для используемых здесь положительных целочисленных показателей его расчет выглядит следующим образом:

pow(exponent) {
  return new Value(
    this.data ** exponent,                         // function value
    [[this, exponent * this.data ** (exponent - 1)]] // input and local derivative
  );
}

Здесь this — входной узел. Операция вычисляет выходное значение и записывает соединение с этим входом вместе с его числовой локальной производной. Обратный проход будет использовать сохраненную производную для распространения вклада градиента на входные данные.

Вызов y.pow(2) передаёт показатель степени 22, поэтому локальная производная равна 2y2−1=2y2y^{2-1}=2y. Вызов x.pow(3) даёт 3x3−1=3x23x^{3-1}=3x^2. Движок получает показатель из аргумента операции, а программист задаёт общее правило дифференцирования. Движок не открывает это правило во время выполнения. Специализированный метод square() может использовать то же вычисление с показателем, зафиксированным на 22.

Граф предоставляет связи; реализованные правила предоставляют значения локальных производных. Затем цепное правило сообщает движку, как их объединить.

Теперь мы можем вернуться к таблице из символического примера, снова следуя только y→u→vy\to u\to v с фиксированным xx для расчета ∂v/∂y\partial v/\partial y. Autodiff выполняет те же операции с выбранными входными данными, поэтому он также вычисляет их числовые выходные значения. Ему по-прежнему нужны локальные производные, но он оценивает уже реализованные правила для каждой операции, а не выводит новые формулы во время выполнения:

ШагВ x=3x=3, y=2y=2, с фиксированным xx
Используйте те же операцииu=y2u=y^2, затем v=xuv=xu
Рассчитать значения операцииu=22=4u=2^2=4, затем v=3⋅4=12v=3\cdot4=12
Оценка локальных производных правилdu/dy=2y=4du/dy=2y=4, ∂v/∂u=x=3\partial v/\partial u=x=3
Объедините числовые производные, используя цепное правило∂v/∂y=(∂v/∂u)(du/dy)=3⋅4=12\partial v/\partial y=(\partial v/\partial u)(du/dy)=3\cdot4=12

Продолжение последнего добавления дает ∂f/∂y=1⋅12=12\partial f/\partial y=1\cdot12=12. В этой таблице для пояснения выделен эффект одного входа. Наш символический расчет объединил 1⋅x⋅2y1\cdot x\cdot2y в формулу 2xy2xy. Autodiff оценивает отдельные правила на текущих входах и вместо этого объединяет 1⋅3⋅41\cdot3\cdot4. Он использует известные правила производной в виде числовых инструкций без построения символьной формулы производной для всей функции.

Виджет ниже использует тот же график, что и пример с одним путем. Переключение между Символьное дифференцирование и Автоматическое дифференцирование для сравнения объединения выражений производных с объединением их числовых значений в x=3x=3 и y=2y=2.

One path from y through u and v to fHolding x constant, the derivative for y receives a contribution through one path: y → u → v → f. Multiply along this path: ∂f/∂y = 2y × x × 1 = 2xy.yu = y²du/dy = 2yxv = xu∂v/∂u = xw = x³f = v + w∂f/∂v = 1Chain rule∂f/∂y = 2y × x × 1 = 2xy

Scroll the graph sideways to see the chain-rule box.

В символическом режиме сплошные стрелки показывают, как формируются функции: каждый промежуточный результат становится входными данными для другой операции. В режиме автоматического дифференцирования те же стрелки описывают прямой поток числовых значений: при возведении в квадрат получается u=4u=4, а при умножении с x=3x=3 получается v=12v=12. Зависимости остаются прежними; теперь мы оцениваем операции на определенных входах. Этот виджет сравнения не анимирует этот поток. Пошаговое руководство ниже покажет эти прямые значения и обратное распространение вкладов производных.

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

Виджет сравнения отображает оцененные производные; в более позднем пошаговом руководстве также будут отображены прямые значения. Выход квадрата u=y2u=y^2 и его локальная производная du/dy=2ydu/dy=2y равны 44 в y=2y=2, но это разные величины: в y=3y=3 они будут 99 и 66.

Вычисление формулы комбинированной производной дает тот же результат, что и сначала вычисление локальных производных, а затем их объединение с использованием цепного правила. В (3,2)(3,2) вычисление 2xy2xy дает 1212, как и умножение 4×3×14\times3\times1. Операции и их зависимости остаются прежними; что меняется, так это то, комбинируем ли мы производные выражения или числовые значения.

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

Прямой и обратный режимы

Виджет показывает, какие локальные производные мы объединяем, но не показывает порядок их распространения. Его пунктирные связи собирают множители в произведение. Прямой режим и обратный режим autodiff могут использовать один и тот же граф вычислений, и оба оценивают функцию от входных данных до выходных. Они различаются тем, как распространяются производные: прямой режим переносит их от входов к выходам, а обратный режим переносит производные вклады от выхода обратно к входам.

Начиная с одного выбранного входа, прямой режим может рассчитать, как все выходы реагируют на этот вход за один проход производной. Начиная с одного выбранного выхода, обратный режим может рассчитать, как этот выход реагирует на все входы за один обратный проход. Мы сосредоточимся на обратном режиме — подходе, используемом при обратном распространении ошибки. Полный обратный проход вычисляет как ∂f/∂x\partial f/\partial x, так и ∂f/∂y\partial f/\partial y, накапливая вклады от обоих путей в xx.

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

  • Прямой проход оценивает функцию. В этом пошаговом руководстве каждая операция вычисляет свое значение и сохраняет входные соединения и числовые локальные производные.
  • Обратный проход начинается с производной выходных данных относительно самого себя, ∂f/∂f=1\partial f/\partial f=1, и использует сохраненные локальные производные для распространения числовых вкладов назад с использованием цепного правила. Вклады в один и тот же вход накапливаются.

В таблице ниже показано, что наш прямой проход рассчитывает и сохраняет в x=3x=3 и y=2y=2. Тип операции определяет, какое производное правило выполняется; сохраненные ребра соединяют свои входные данные с оцененными локальными производными:

РасчетВыходное значение в (x,y)=(3,2)(x,y)=(3,2)Сохраненные входные соединения и локальные производные
u=y2u=y^244Ввод yy, с du/dy=4du/dy=4
v=xuv=xu1212Вход xx, с ∂v/∂x=4\partial v/\partial x=4; вход uu, с ∂v/∂u=3\partial v/\partial u=3
w=x3w=x^32727Ввод xx, с dw/dx=27dw/dx=27
f=v+wf=v+w3939Вход vv, с ∂f/∂v=1\partial f/\partial v=1; вход ww, с ∂f/∂w=1\partial f/\partial w=1

Выходные значения и локальные производные играют разные роли. Например, при умножении используется u=4u=4 для вычисления выходного значения v=12v=12, и это же значение операнда предоставляет его локальную производную по отношению к xx. Обратный метод объединяет сохраненные локальные производные с накопленной чувствительностью к конечному результату.

Модель — это программа, поэтому autodiff имеет доступ к операциям, которые произвели ее выходные данные; конечная таблица наблюдений ввода-вывода сама по себе не может предоставить эту информацию или определить точные производные. Для фиксированного обучающего пакета расчет прогноза и потерь модели имеет множество параметров и одно значение потерь. В обратном режиме повторно используются промежуточные результаты для вычисления всех производных параметров за один обратный проход, а не повторяется весь расчет для каждого параметра.

Мы установили, что записывает движок и как он использует локальные производные правила. Далее мы проследим тот же расчет в интерактивном прямом и обратном проходе, наблюдая, как записываются значения и накапливаются производные вклады.

Автоматическое дифференцирование в обратном режиме шаг за шагом

Давайте теперь проследим полный расчет автодифференциации в обратном режиме, используя ту же функцию f(x,y)=xy2+x3f(x,y)=xy^2+x^3 в (x,y)=(3,2)(x,y)=(3,2). Мы оценим четыре операции из таблицы выше, а затем проследим их производные назад, чтобы получить градиент (31,12)(31,12):

x = Value(3)
y = Value(2)

u = y ** 2
v = x * u
w = x ** 3
f = v + w

f.backward()

Прямой проход дает u=4u=4, v=12v=12, w=27w=27 и конечный результат f=39f=39. Каждый результат сохраняет связи с входными данными, которые его создали, а также информацию, необходимую для локального правила производной. Обратный проход вычисляет, как конечный результат реагирует на каждый узел; он не меняет эти прямые значения.

Прямые значения также называются primals. Производные, накопленные во время обратного прохода, называются сопряженными: каждая из них измеряет, как конечный результат изменяется по отношению к конкретному входному или промежуточному значению. В нашем виджете они обозначены value и grad соответственно. Сопряженный — это производная конечного результата, отличная от локальной производной операции.

Перед обратным проходом каждый .grad начинается с нуля. Мы запускаем f.grad = 1, потому что ∂f/∂f=1\partial f/\partial f=1.

.grad каждого скалярного узла хранит производную конечного результата относительно этого узла. Вместе x.grad и y.grad образуют вектор градиента (31,12)(31,12). В пошаговом руководстве сохраняются числовые локальные производные для соединений и используются их в обратном направлении: parent.grad += node.grad * local_derivative.

Предыдущий виджет сравнения следовал только по пути от yy для вычисления ∂f/∂y\partial f/\partial y. Виджет ниже обрабатывает весь график за один обратный проход, вычисляя производные как по xx, так и по yy. Эти шаги выполняются последовательно в рамках одного прохода. В каждом поле отдельно показано выходное значение, числовые локальные производные и накопленный градиент.

Входные значения фиксируются как x=3x=3 и y=2y=2, что эквивалентно созданию x = Value(3) и y = Value(2). Выберите Вычислить прямой проход, чтобы увидеть, как четыре операции создают выходные данные. Затем выберите Задать градиент выхода и выполните шаги назад, выполнив сложение, кубирование, умножение и возведение в квадрат. Каждый шаг назад выделяет операцию и ее входные данные, затем соединяет выражение правила символической цепочки с его числовой заменой и соответствующим обновлением .grad под графиком. Посмотрите, как два вклада накапливаются в точке xx.

The same operations, forward and backwardf(x, y) = xy² + x³
Computation graph of f = xy² + x³Start with x = 3 and y = 2, then evaluate u = y², v = xu, w = x³, and f = v + w. Local derivatives describe each operation. Each grad describes the final output’s derivative with respect to that node.yvalue 2grad —u = y²value ——grad —xvalue 3grad —v = xuvalue ———grad —w = x³value ——grad —f = v + wvalue ———grad —Chain rule∂f/∂x = —∂f/∂y = —
Stage 0 of 6. Start with x = 3 and y = 2, then evaluate u = y², v = xu, w = x³, and f = v + w.

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

Шаг назадОбновление градиента
Семенаf.grad=1\mathtt{f.grad}=1
Дополнениеv.grad=f.grad⋅1=1\mathtt{v.grad}=\mathtt{f.grad}\cdot1=1, w.grad=f.grad⋅1=1\mathtt{w.grad}=\mathtt{f.grad}\cdot1=1
Кубx.grad=w.grad⋅3x2=1⋅27=27\mathtt{x.grad}=\mathtt{w.grad}\cdot3x^2=1\cdot27=27
Умножение, вклад в xxДобавьте v.grad⋅u=1⋅4=4\mathtt{v.grad}\cdot u=1\cdot4=4, получив x.grad=27+4=31\mathtt{x.grad}=27+4=31
Умножение, вклад в uuu.grad=v.grad⋅x=1⋅3=3\mathtt{u.grad}=\mathtt{v.grad}\cdot x=1\cdot3=3
Площадьy.grad=u.grad⋅2y=3⋅4=12\mathtt{y.grad}=\mathtt{u.grad}\cdot2y=3\cdot4=12

Два пути от xx вносят вклад отдельно, тогда как yy получает свой вклад через uu и vv:

∂f∂x=1×27⏟through w+1×4⏟through v=31,∂f∂y=1×3×4=12\frac{\partial f}{\partial x}=\underbrace{1\times27}_{\text{through }w}+\underbrace{1\times4}_{\text{through }v}=31, \qquad \frac{\partial f}{\partial y}=1\times3\times4=12

Этот порядок обрабатывает ветвь куба перед ветвью умножения. Другие допустимые обратные заказы дают тот же результат при условии, что узел получает все последующие вклады до того, как он передаст свою накопленную производную на свои входы.

Виджет показывает полный обратный проход. Давайте теперь подключим к коду шаги умножения v=xuv=xu и квадрата u=y2u=y^2. Обратный шаг умножения умножает две разные производные:

∂f∂v⏟stored in v.grad×∂v∂x⏟u.data, the other operand’s value=1×4\underbrace{\frac{\partial f}{\partial v}}_{\text{stored in v.grad}} \times \underbrace{\frac{\partial v}{\partial x}}_{\text{u.data, the other operand's value}} =1\times4

Первый фактор сообщает нам, как конечный результат ff реагирует на vv. Второй рассказывает нам, как vv реагирует на xx, удерживая uu фиксированным. Их умножение — это цепное правило: оно дает вклад этого пути в производную по xx.

Пошаговое руководство сохраняет числовые локальные производные во время прямого прохода. Другая реализация может сохранять значения операндов и оценивать те же локальные правила в обратном направлении. В этой форме метод умножения __mul__(self, other) создает результирующий узел с именем out, и его обратное правило можно записать как:

self.grad += out.grad * other.data  # contribution to the left operand
other.grad += out.grad * self.data  # contribution to the right operand

Для v = x * u out — это vv, self — это xx, а other — это uu. Записанные с этими именами, за обновлениями умножения следует обновление квадрата:

# Multiplication: v = x * u
x.grad += v.grad * u.data       # add 1 × 4 to the existing 27 → 31
u.grad += v.grad * x.data       # add 1 × 3 → 3

# Square: u = y ** 2
y.grad += u.grad * (2 * y.data) # add 3 × 4 → 12

Прямой проход предоставил u=4u=4, x=3x=3 и y=2y=2. Умножение использует значение другого операнда для каждой локальной производной. Квадрат использует 2y=42y=4 и умножает его на накопленное u.grad = 3.

КодЗначение
out.gradПроизводная окончательного результата по отношению к результату этой операции
other.dataЗначение другого операнда, равное локальной производной при умножении
+=Добавьте вклад этого пути в накопленную производную ввода

other.data хранит значение, а не производную. Правило умножения говорит, что это значение равно нужной локальной производной. Для сложения локальная производная равна 11, поэтому соответствующее обновление — self.grad += out.grad * 1. Для степени вычисляем правило производной степенной функции при заданном входе: локальная производная куба — 3x23x^2, а квадрата — 2y2y.

В символьном виджете мы объединили локальные производные выражения по пути от yy:

∂f∂y=∂f∂v⋅∂v∂u⋅dudy=1⋅x⋅2y=2xy\frac{\partial f}{\partial y} =\frac{\partial f}{\partial v}\cdot\frac{\partial v}{\partial u}\cdot\frac{du}{dy} =1\cdot x\cdot2y =2xy

Код следует тем же связям, но объединяет цифры. Дополнение дает v.grad = 1; умножение дает u.grad = 1 × 3 = 3; квадрат дает y.grad = 3 × 4 = 12. Таким образом, u.grad уже содержит первые два фактора символического выражения. Autodiff получает ту же производную без построения комбинированной формулы 2xy2xy.

Эти шаги вычисляют производные в точке (3,2)(3,2); они не обновляют входные данные, чтобы минимизировать функцию. Класс Value автоматизирует обратные шаги за один вызов f.backward().

От вычислений к движку

Производная измеряет местную чувствительность. Частные производные изолируют эффект каждого входного сигнала, а градиент собирает эти измерения в вектор. Обратное распространение ошибки вычисляет их, действуя в обратном направлении через записанные операции: умножая производные вдоль каждого пути и добавляя вклады в местах пересечения путей.

Чтобы увидеть, как эти идеи реализованы на Python, следуйте пошаговому руководству Андрея Карпати по Micrograd. Локальные правила производных, накопление градиента и обратный обход, которые мы рассмотрели здесь, обеспечивают основу для понимания его реализации.

Для более глубокого математического представления изучите это выступление об автоматическом дифференцировании и линейных отображениях. Он связывает исчисление с линейной алгеброй, показывая, как производные действуют как линейные отображения, как их представляют матрицы Якоби и как их объединяет цепное правило — математические основы автоматического дифференцирования.

Просмотр изображения

Масштаб: жест щипка или + / −. Перемещение: перетаскивание или стрелки. Esc — закрыть.