Обратное распространение ошибки

Обратное распространение ошибки

Содержание: - Введение - Простые выражения, интерпретирующие градиент - Составные выражения, цепное правило, обратное распространение - Интуитивное понимание обратного распространения - Модульность: Пример сигмовидной активации - Бэкпроп на практике: Поэтапное вычисление - Закономерности в обратном потоке - Градиенты для векторизованных операций - Краткая сводка

Введение

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

Постановка задачи. Основная задача, рассматриваемая в этом разделе, заключается в следующем: нам дана некоторая функция f(x),где x является вектором входных данных, и мы заинтересованы в вычислении градиента f в x (т.е. ∇f(x)).

Мотивация. Напомним, что основная причина, по которой мы интересуемся этой проблемой, заключается в том, что в конкретном случае нейронных сетей f будет соответствовать функции потерь ( L ) и входные данные x будет состоять из обучающих данных и весовых коэффициентов нейронной сети. Например, в качестве функции потерь может использоваться функция потерь SVM, а в качестве входных данных — обучающие данные \((x_i,y_i), i=1 \ldots N\), а также веса и предубеждения W,b. Обратите внимание, что (как это обычно бывает в машинном обучении) мы рассматриваем обучающие данные как заданные и фиксированные, а весовые коэффициенты — как переменные, которыми мы можем управлять. Следовательно, даже если мы можем легко использовать обратное распространение ошибки для вычисления градиента по входным примерам \(x_i\). На практике мы обычно вычисляем градиент только для параметров (например, W,b), чтобы мы могли использовать его для обновления параметров. Однако, как мы увидим позже, градиент по \(x_i\), например, может быть полезен для визуализации и интерпретации того, что может делать нейронная сеть.

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

Простые выражения и интерпретация градиента

Давайте начнём с простого, чтобы разработать обозначения и соглашения для более сложных выражений. Рассмотрим простую функцию умножения двух чисел f(x,y)=xy. Чтобы вычислить частную производную для любого из входных параметров, достаточно воспользоваться простым математическим расчётом:

$$ f(x,y) = x y \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = y \hspace{0.5in} \frac{\partial f}{\partial y} = x $$

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

$$ \frac{df(x)}{dx} = \lim_{h\ \to 0} \frac{f(x + h) - f(x)}{h} $$

Технически примечательно, что знак деления в левой части, в отличие от знака деления в правой части, не является делением. Вместо этого эта запись указывает на то, что оператор \( \frac{d}{dx} \) применяется к функции f и возвращает другую функцию (производную). Можно представить, что приведённое выше выражение означает, что h очень мало, а значит функция хорошо аппроксимируется прямой линией, а производная — это её наклон. Другими словами, производная от каждой переменной показывает чувствительность всего выражения к её значению. Например, если x=4,y=−3 тогда f(x,y)=−12 и производная от \(x\) \(\frac{\partial f}{\partial x} = -3\)3. Это говорит нам о том, что если мы увеличим значение этой переменной на небольшую величину, то всё выражение уменьшится (из-за отрицательного знака) в три раза. Это можно увидеть, если переставить слагаемые в приведённом выше уравнении ( \( f(x + h) = f(x) + h \frac{df(x)}{dx} \) ). Аналогично, поскольку \(\frac{\partial f}{\partial y} = 4\), мы ожидаем, что увеличение стоимости y на какую-то очень небольшую сумму h также увеличит результат функции (из-за положительного знака) и 4h.

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

Как уже упоминалось, градиент ∇f является вектором частных производных, поэтому мы имеем, что \(\nabla f = [\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}] = [y, x]\). Несмотря на то, что градиент технически является вектором, для простоты мы часто используем такие термины, как «градиент по x», вместо технически корректного выражения «частная производная по x».

Мы также можем вывести производные для операции сложения:

$$ f(x,y) = x + y \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = 1 \hspace{0.5in} \frac{\partial f}{\partial y} = 1 $$

то есть производная по обоим x,y является единым, независимо от того, какими значения x,y являются. Это имеет смысл, поскольку увеличение x или y увеличило бы выпуск продукции f. И скорость этого увеличения будет зависеть от фактических значений x,y (в отличие от случая с умножением выше). Последняя функция, которую мы будем часто использовать в этом классе, — это операция max:

$$ f(x,y) = \max(x, y) \hspace{0.5in} \rightarrow \hspace{0.5in} \frac{\partial f}{\partial x} = \mathbb{1}(x >= y) \hspace{0.5in} \frac{\partial f}{\partial y} = \mathbb{1}(y >= x) $$

То есть (суб)градиент равен 1 для большего входного значения и 0 для другого входного значения. Интуитивно понятно, что если входные значения x=4,y=2тогда максимальное значение равно 4, и функция не чувствительна к настройке y. То есть, если бы мы увеличили его на крошечную величину h функция бы продолжила выводить 4, и поэтому градиент равен нулю: эффекта нет. Конечно, если бы мы изменили y на большую величину (например, больше 2), то значение f изменилось бы, но производные ничего не говорят нам о влиянии таких больших изменений на входные данные функции. Они информативны только для крошечных, бесконечно малых изменений входных данных, как показано на \(\lim_{h \rightarrow 0}\) в его определении.

Составные выражения с правилом цепочки

Теперь давайте рассмотрим более сложные выражения, включающие несколько составных функций, например f(x,y,z)=(x+y)z. Это выражение по-прежнему достаточно простое, чтобы дифференцировать его напрямую, но мы подойдём к нему с особой стороны, которая поможет понять принцип обратного распространения ошибки. В частности, обратите внимание, что это выражение можно разбить на два: q=x+y и f=qz. Более того, мы знаем, как вычислить производные обоих выражений по отдельности, как показано в предыдущем разделе. f это просто умножение q и z, так что \(\frac{\partial f}{\partial q} = z, \frac{\partial f}{\partial z} = q\), и q является добавлением x и y, итак \( \frac{\partial q}{\partial x} = 1, \frac{\partial q}{\partial y} = 1 \). Однако нам необязательно знать градиент для промежуточного значения q - ценность \(\frac{\partial f}{\partial q}\) нивелируется. Вместо этого нас, в конечном счете, интересует градиент f в отношении его вклада x,y,z. Правило цепочки говорит нам о том, что правильный способ «объединить» эти выражения градиента в цепочку — это умножение. Например, \(\frac{\partial f}{\partial x} = \frac{\partial f}{\partial q} \frac{\partial q}{\partial x} \). На практике это просто умножение двух чисел, обозначающих два градиента. Давайте рассмотрим это на примере:

# set some inputs
x = -2; y = 5; z = -4

# perform the forward pass
q = x + y # q becomes 3
f = q * z # f becomes -12

# perform the backward pass (backpropagation) in reverse order:
# first backprop through f = q * z
dfdz = q # df/dz = q, so gradient on z becomes 3
dfdq = z # df/dq = z, so gradient on q becomes -4
dqdx = 1.0
dqdy = 1.0
# now backprop through q = x + y
dfdx = dfdq * dqdx  # The multiplication here is the chain rule!
dfdy = dfdq * dqdy  

У нас остаётся градиент в переменных [dfdx,dfdy,dfdz], который показывает чувствительность переменных x,y,z к f!. Это самый простой пример обратного распространения ошибки. Далее мы будем использовать более лаконичную запись, в которой отсутствует префикс df! Например, мы будем просто писать dq вместо dfdq и всегда предполагать, что градиент вычисляется для конечного результата.

Это вычисление также можно хорошо визуализировать с помощью принципиальной схемы:


-2-4x5-4y-43z3-4q+-121f*

Реальная "схема" слева показывает визуальное представление вычислений. Прямой проход вычисляет значения от входных данных до выходных (показано зелёным цветом).Обратный проход, затем выполняет обратное распространение ошибки, которое начинается с конца и рекурсивно применяет правило цепочки для вычисления градиентов (показано красным цветом) вплоть до входных данных схемы. Градиенты можно представить как текущие в обратном направлении по схеме.


Интуитивное понимание обратного распространения

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

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

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

Давайте разберёмся, как это работает, на примере. Сложение получило на вход [-2, 5] и выдало на выходе 3. Поскольку сложение вычисляет операцию сложения, его локальный градиент для обоих входных значений равен +1. Остальная часть схемы вычислила итоговое значение, равное -12. Во время обратного прохода, при котором правило цепочки рекурсивно применяется в обратном направлении, сложение (которое является входом для умножения) узнаёт, что градиент его выходного значения равен -4. Если мы представим, что схема «хочет» вывести более высокое значение (что может помочь с интуитивным пониманием), то мы можем представить, что схема «хочет», чтобы выходное значение логического элемента «и» было ниже (из-за отрицательного знака) и с силой 4. Чтобы продолжить рекурсию и вычислить градиент, логический элемент «и» берёт этот градиент и умножает его на все локальные градиенты для своих входов (делая градиент для x и y равным 1 * -4 = -4). Обратите внимание, что это даёт желаемый эффект: если x, y уменьшатся (в соответствии с их отрицательным градиентом), то выходное значение сумматора уменьшится, что, в свою очередь, приведёт к увеличению выходного значения умножителя.

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

Модульность: Пример сигмовидной активации

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

$$ f(w,x) = \frac{1}{1+e^{-(w_0x_0 + w_1x_1 + w_2)}} $$

как мы увидим позже на занятии, это выражение описывает двумерный нейрон (с входными данными x и весами w), который использует функцию сигмоидальной активации. Но пока давайте рассматривать это очень просто как функцию, которая преобразует входные данные w,x в одно число. Функция состоит из нескольких логических элементов. Помимо тех, что описаны выше (сложение, умножение, максимальное значение), есть ещё четыре:

$$ f(x) = \frac{1}{x} \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = -1/x^2 \\ f_c(x) = c + x \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = 1 \\ f(x) = e^x \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = e^x \\ f_a(x) = ax \hspace{1in} \rightarrow \hspace{1in} \frac{df}{dx} = a $$

Где функции \(f_c, f_a\) преобразуйте входные данные в константу, равную c и масштабируйте входные данные на константу, равную a, соответственно. Технически это частные случаи сложения и умножения, но мы вводим их как (новые) унарные операции, поскольку нам не нужны градиенты для констант c,a. Тогда полная схема выглядит следующим образом:

2.00-0.20w0-1.000.39x0-3.00-0.39w1-2.00-0.59x1-3.000.20w2-2.000.20*6.000.20*4.000.20+1.000.20+-1.00-0.20*-10.37-0.53exp1.37-0.53+10.731.001/x
Пример схемы для двумерного нейрона с сигмоидальной функцией активации. Входные данные — [x0, x1], а (обучаемые) весовые коэффициенты нейрона — [w0, w1, w2]. Как мы увидим позже, нейрон вычисляет скалярное произведение входных данных, а затем его активация мягко сжимается сигмоидальной функцией до диапазона от 0 до 1.

В приведённом выше примере мы видим длинную цепочку вызовов функций, которые работают с результатом скалярного произведения w,x. Функция, которую реализуют эти операции, называется сигмоидальной функцией σ(x). Оказывается, производная сигмоидальной функции по входным данным упрощается, если выполнить дифференцирование (после забавной сложной части, где мы добавляем и вычитаем 1 в числителе):

$$ \sigma(x) = \frac{1}{1+e^{-x}} \\ \rightarrow \hspace{0.3in} \frac{d\sigma(x)}{dx} = \frac{e^{-x}}{(1+e^{-x})^2} = \left( \frac{1 + e^{-x} - 1}{1 + e^{-x}} \right) \left( \frac{1}{1+e^{-x}} \right) = \left( 1 - \sigma(x) \right) \sigma(x) $$

Как мы видим, градиент упрощается и становится на удивление простым. Например, сигмоидальное выражение получает на вход 1,0 и вычисляет на выходе 0,73 во время прямого прохода. Приведённый выше вывод показывает, что локальный градиент будет равен (1 — 0,73) * 0,73 ~= 0,2, как и в случае с предыдущей схемой (см. изображение выше), за исключением того, что в этом случае это будет сделано с помощью одного простого и эффективного выражения (и с меньшим количеством численных проблем). Таким образом, в любом реальном практическом применении было бы очень полезно объединить эти операции в один элемент управления. Давайте рассмотрим обратное распространение ошибки для этого нейрона в коде:

w = [2,-3,-3] # assume some random weights and data
x = [-1, -2]

# forward pass
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid function

# backward pass through the neuron (backpropagation)
ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation
dx = [w[0] * ddot, w[1] * ddot] # backprop into x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w
# we're done! we have the gradients on the inputs to the circuit

Совет по реализации: поэтапное обратное распространение ошибки. Как показано в приведенном выше коде, на практике всегда полезно разбивать прямой проход на этапы, которые легко поддаются обратному распространению ошибки. Например, здесь мы создали промежуточную переменную dot, которая содержит результат скалярного произведения w и x. Затем во время обратного прохода мы последовательно вычисляем (в обратном порядке) соответствующие переменные (например, ddot и, в конечном итоге, dw, dx), которые содержат градиенты этих переменных.

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

Бэкпроп на практике: Поэтапное вычисление

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

$$ f(x,y) = \frac{x + \sigma(y)}{\sigma(x) + (x+y)^2} $$

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

x = 3 # example values
y = -4

# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # sigmoid in numerator   #(1)
num = x + sigy # numerator                               #(2)
sigx = 1.0 / (1 + math.exp(-x)) # sigmoid in denominator #(3)
xpy = x + y                                              #(4)
xpysqr = xpy**2                                          #(5)
den = sigx + xpysqr # denominator                        #(6)
invden = 1.0 / den                                       #(7)
f = num * invden # done!                                 #(8)

Фух, к концу выражения мы вычислили прямой проход. Обратите внимание, что мы структурировали код таким образом, что он содержит несколько промежуточных переменных, каждая из которых представляет собой простое выражение, для которого мы уже знаем локальные градиенты. Поэтому вычислить обратный путь легко: мы пойдём в обратном направлении, и для каждой переменной на пути прямого прохода (sigy, num, sigx, xpy, xpysqr, den, invden) у нас будет та же переменная, но начинающаяся с d, которая будет содержать градиент выходного сигнала схемы по отношению к этой переменной. Кроме того, обратите внимание, что каждый элемент в нашем обратном распространении ошибки будет включать вычисление локального градиента этого выражения и объединение его с градиентом этого выражения путём умножения. Для каждой строки мы также указываем, к какой части прямого прохода она относится:

# backprop f = num * invden
dnum = invden # gradient on numerator                             #(8)
dinvden = num                                                     #(8)
# backprop invden = 1.0 / den 
dden = (-1.0 / (den**2)) * dinvden                                #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden                                                #(6)
dxpysqr = (1) * dden                                              #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr                                        #(5)
# backprop xpy = x + y
dx = (1) * dxpy                                                   #(4)
dy = (1) * dxpy                                                   #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below  #(3)
# backprop num = x + sigy
dx += (1) * dnum                                                  #(2)
dsigy = (1) * dnum                                                #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy                                 #(1)
# done! phew

Обратите внимание на несколько вещей:

Кэшируйте переменные прямого пути. Для вычисления обратного пути очень полезно иметь некоторые переменные, которые использовались при прямом пути. На практике вы хотите структурировать свой код таким образом, чтобы кэшировать эти переменные и чтобы они были доступны во время обратного распространения. Если это слишком сложно, можно (но нерационально) пересчитать их.

Градиенты суммируются в точках разветвления. В прямом выражении переменные x, y встречаются несколько раз, поэтому при обратном распространении ошибки мы должны быть внимательны и использовать += вместо = для накопления градиента по этим переменным (иначе мы перезапишем его). Это соответствует правилу дифференцирования сложной функции в математическом анализе, которое гласит, что если переменная разветвляется на разные части схемы, то градиенты, которые возвращаются к ней, суммируются.

Закономерности в обратном потоке

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


3.00-8.00x-4.006.00y2.002.00z-1.000.00w-12.002.00*2.002.00max-10.002.00+-20.001.00*2
Пример схемы, демонстрирующей интуитивное понимание операций, которые выполняет обратное распространение ошибки во время обратного прохода для вычисления градиентов по входным данным. Операция суммирования равномерно распределяет градиенты по всем своим входам. Операция максимального значения направляет градиент на вход с наибольшим значением. Операция умножения принимает входные значения, меняет их местами и умножает на градиент.

Рассматривая приведенную выше диаграмму в качестве примера, мы можем видеть, что:

Сложение всегда берёт градиент на выходе и распределяет его поровну между всеми входами, независимо от того, какими были их значения во время прямого прохода. Это следует из того, что локальный градиент для операции сложения равен +1,0, поэтому градиенты на всех входах будут в точности равны градиентам на выходе, потому что они будут умножены на x1,0 (и останутся неизменными). В приведённом выше примере обратите внимание, что сумматор направил градиент 2,00 на оба входа, разделив его поровну и оставив без изменений.

Макс-сглаживатель направляет градиент. В отличие от сумматора, который распределяет градиент без изменений по всем своим входам, макс-сглаживатель распределяет градиент (без изменений) только по одному из своих входов (по входу, который имел наибольшее значение во время прямого прохода). Это связано с тем, что локальный градиент для макс-сглаживателя равен 1,0 для наибольшего значения и 0,0 для всех остальных значений. В приведённом выше примере функция max направила градиент 2,00 на переменную z, которая имела более высокое значение, чем w, а градиент w остался равным нулю.

Умножитель немного сложнее в интерпретации. Его локальные градиенты — это входные значения (кроме переключаемых), которые умножаются на градиент выходного значения в соответствии с правилом цепочки. В приведённом выше примере градиент x равен -8,00, что составляет -4,00 x 2,00.

Неинтуитивные эффекты и их последствия. Обратите внимание, что если один из входов умножителя очень мал, а другой очень велик, то умножитель сделает что-то немного неинтуитивное: он присвоит относительно большой градиент малому входу и крошечный градиент большому входу. Обратите внимание, что в линейных классификаторах, где веса умножаются на скалярное произведение, \(w^Tx_i\) (умноженные) на входные данные, это означает, что масштаб данных влияет на величину градиента весовых коэффициентов. Например, если вы умножите все примеры входных данных \(x_i\). Если во время предварительной обработки умножить на 1000, то градиент по весам будет в 1000 раз больше, и вам придётся уменьшить скорость обучения на этот коэффициент, чтобы компенсировать разницу. Вот почему предварительная обработка так важна, иногда даже в мелочах! Интуитивное понимание того, как распределяются градиенты, может помочь вам отладить некоторые из этих случаев.

Градиенты для векторизованных операций

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

Градиент при умножении матриц. Возможно, самая сложная операция — это умножение матриц (которое обобщает все операции умножения матриц на векторы и векторов на векторы):

# forward pass
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)

# now suppose we had the gradient on D from above in the circuit
dD = np.random.randn(*D.shape) # same shape as D
dW = dD.dot(X.T) #.T gives the transpose of the matrix
dX = W.T.dot(dD)

Совет: используйте анализ измерений! Обратите внимание, что вам не нужно запоминать выражения для dW и dX, поскольку их легко повторно вывести на основе измерений. Например, мы знаем, что градиент весов dW должен быть того же размера, что и W после его вычисления, и что он должен зависеть от матричного умножения X и dD (как в случае, когда оба X,W являются одиночными числами, а не матрицами). Всегда есть только один способ достичь этого, чтобы размеры соответствовали друг другу. Например, X имеет размер [10 x 3], а dD — размер [5 x 3], поэтому, если мы хотим, чтобы dW и W имели форму [5 x 10], то единственный способ добиться этого — использовать dD.dot(X.T), как показано выше.

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

Эрик Леарнед-Миллер также написал более подробный документ о вычислении матричных/векторных производных, который может оказаться вам полезным. Найдите его здесь.

Краткая сводка

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

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

Ссылки