выполнить градиентный спуск. Каждый вес в сети обновляется с помощью обычной инструкции градиентного спуска:
w[i, j] = w[i, j] – e * dc_dw[ij]
Подведем итоги: цель данного упражнения состояла в том, чтобы вычислить градиенты функции стоимости относительно входных данных слоя, учитывая градиенты стоимости относительно выходных данных слоя. Таким образом, мы вычисляем все градиенты путем их обратного распространения через все предыдущие слои. Последним шагом является использование этих градиентов стоимости в сравнении с выходными (или входными) слоями для расчета градиентов в зависимости от параметров (весов линейных слоев).
Польза от нескольких слоев
Принцип обучения остается прежним: он заключается в настройке параметров сети таким образом, чтобы система допускала минимум возможных ошибок. Сквозное обучение многослойных сетей – это так называемое глубокое обучение, или обучение преобразованию входных данных в осмысленные представления, как это делал экстрактор признаков в улучшенном перцептроне. В действительности, последовательные слои – это обученная версия экстрактора признаков. Решающим преимуществом многослойных сетей является то, что они автоматически учатся правильно представлять сигнал.
Объяснение с производными[55]
Вышеупомянутая разработка была скорее интуитивной, основанной на очень небольшом математическом опыте. Однако существует и другая разработка, для некоторых более понятная, которая использует такие понятия, как производные, частные производные, векторы и матрицы.
Правило дифференцирования сложных функций гласит, что производная от (f(z)) относительно x, обозначенная (c(f(z))ʼ, равна cʼ(f(z))) * fʼ(z). Это правило дифференцирования сложных функций и является основой обратного распространения ошибки.
Пример
Если изменить z на незначительную величину dz, выход f(z) изменится на величину fʼ(z) * dz. Это – простое следствие определения производной, которая является пределом при приближении dz к нулю отношения fʼ(z) = [f(z+dz) – f(z)]/dz. Умножив обе стороны на dz, получаем:
f(z + dz) – f(z) = fʼ(z) * dz
Отсюда следует, что когда выход функции f изменяется, как fʼ(z) * dz, выход изменится, как cʼ(f(z)) * fʼ(z) * dz. Отношение между изменением выхода c(f(z)) и изменением входа (т. е. dz) равняется cʼ(f(z)) * fʼ(z). Это и есть производная от c(f(z)):
c(f(z))ʼ = cʼ(f(z)) * fʼ(z)
По своей сути, производная – это отношение двух бесконечно малых величин, в данном случае – это будет бесконечно малое изменение выхода, деленное на бесконечно малое изменение входа. Если функция зависит от более чем одной переменной, отношение изменений выхода и изменений конкретной переменной[56] называется частной производной. Мы уже видели такие примеры в предыдущей главе.
Ситуация усложняется, когда функция не только зависит от нескольких переменных, но и имеет несколько выходных параметров.
Например, линейный слой – это функция с несколькими входными переменными и несколькими выходами. Каждый выход s[i] представляет собой взвешенную сумму входов z[j] с использованием формулы
Помочь все это вычислить может небольшая программа на Python:
def lineaire(z, w, s, UP):
for i in range(len(s)):
s[i] = 0
for j in UP[i]:
s[i] = s[i] + w[i, j] * z[j]
return s
Веса слоев можно представить как массив чисел с двумя нижними индексами: индекс строки i и индекс столбца j. Сам этот массив можно рассматривать как вектор, в котором каждый элемент сам является вектором:
[w[0, 0], w[0, 1], w[0, 2], …],
[w[1, 0], w[1, 1], w[1, 2], …],
………………]
Данный массив чисел представляет собой матрицу.
Линейная функция lineaire(), вставленная выше в начало программы, вычисляет произведение матрицы w на вектор z, то есть вычисляет вектор s, размер которого равен количеству строк w, а каждый элемент которого является скалярным произведением соответствующих значений w и z.
В PyTorch существует эффективная функция для решения этой задачи.
Для обратного распространения градиента через слой с несколькими входами и выходами, например, линейный слой, необходимо применить некоторую форму правила вывода сложной функции, которая учитывает частные производные каждого выхода по отношению к каждому входу.
Теперь давайте объясним, как вычислять градиенты в многослойной архитектуре, используя это правило вывода сложных функций.
Представьте себе двухслойную сеть, подобную той, что изображена слева на рис. 5.7. Каждый ее слой является параметризованной функцией. Первый ее слой f(x, wf) принимает входные данные x и параметр wf и выдает свои выходные данные zf. Второй слой g(zf, wg) принимает выходные данные первого слоя и параметр wg и создает выходные данные сети zg. Функция стоимости C(zg, y) измеряет разницу между выходом сети zg и желаемым выходом y.
Рис. 5.7. Многослойная сеть, представленная графом взаимосвязанных функциональных модулей
В современной формулировке глубокого обучения многоуровневая сеть представляет собой граф взаимосвязанных модулей, связанных модулем стоимости. Модуль – это любая функция, которая может иметь несколько входов, несколько выходов и несколько параметров. Полный слой линейных нейронов – это «модуль».
Слева: сеть состоит из двух модулей: первый f(x, wf), выход которого равен zf. За этим модулем следует второй модуль g(zf, wg), выход которого равен zg. Выход zg сравнивается с желаемым выходом y с использованием модуля стоимости C(zg, y).
По центру: более общий пример сети, состоящей из слоев пронумерованных модулей. Выход модуля номер k, обозначенный вектором z[k], образуется путем применения функции fk по отношению к выходу предыдущего модуля, т. е. вектора z[k] и его вектора параметров w[k]:
z[k + 1] = fk(z[k], w[k])
Зная градиент стоимости относительно вектора z[k + 1], обозначенного g[k +1], можно вычислить градиент стоимости относительно вектора z[k], обозначенного g[k] (по условию, g[k] представляет собой вектор строки, а не как вектор столбца) по следующей формуле:
g[k] = g[k + 1] * dfk_dzk
где dfk_dzk – матрица Якоби функции fk(z[k], w[k]) по отношению к z[k], то есть массив, каждый элемент которого указывает, насколько нарушается конкретный выход fk, когда нарушается конкретный вход. Таблица содержит термин для каждой пары входов и выходов. Матрица Якоби изображена справа.
Предположим, что нам известен градиент C(zg, y) относительно zg, который мы обозначим как dC(zg, y)/dzg[57]. Поскольку C(zg, y) = C(g(zf, wg), y), то по правилу дифференцирования сложных функций можно записать:
dC(zg, y)/dzf = dC(zg, y)/dzg * dgz/dzf
но zg – это не что иное, как выход g(fz, wg). Следовательно, мы можем преобразовать это выражение так:
dC(zg, y)/dzf = dC(zg, y)/dzg * dg(zf, wg)/dzf
Выражение слева – это вектор. Справа от знака равенства стоит произведение двух производных. Первая из них