Градиентный спуск и обратное распространение ошибки
- О чём эта тема
- В конспекте 1 веса для XOR подбирались вручную. Здесь рассматривается способ находить веса автоматически: минимизировать функцию ошибки методом градиентного спуска. Обратное распространение ошибки вводится как способ эффективно вычислить нужные для этого производные в сети из нескольких слоёв. В конце — первая рабочая реализация обучаемого нейрона.
- Аннотация
- Конспект посвящён тому, как нейрон подбирает веса автоматически. Сначала вводится функция ошибки и объясняется, почему для градиентных методов квадрат отклонения удобнее модуля. Далее напоминается определение производной как предела и вводится градиент в пространстве нескольких весов, после чего формулируется идея градиентного спуска, обсуждаются выбор скорости обучения и проблема локальных минимумов. Центральная часть — метод обратного распространения ошибки с разбором прямого и обратного прохода по сети. В завершение обучаемый нейрон реализуется на numpy, чтобы каждый шаг алгоритма был виден в коде в явном виде.
- Пререквизиты
- Конспект 1 (нейрон, веса, функция активации, сигмоида). Понятие производной из курса математического анализа — здесь оно вводится формально ещё раз, но базовая интуиция пригодится.
- Мотивация
- Веса для XOR в конспекте 1 были подобраны вручную — задача с двумя входами и известной структурой это ещё позволяет. Для сети с тысячами весов и незнакомой заранее зависимостью подбор руками невозможен. Нужен автоматический способ находить веса, минимизирующие ошибку сети на обучающих данных — это и называется обучением нейросети.
1. Что значит «обучиться»?
Обучиться — значит по обучающей выборке подобрать веса \(w\), при которых предсказания сети \(\hat y\) как можно ближе к истинным значениям \(y\). Чтобы формально сравнивать варианты весов, нужна числовая мера несовпадения \(\hat y\) и \(y\) — функция ошибки. Возможных мер несколько; две употребительные — среднее абсолютное отклонение (MAE) и среднеквадратичное отклонение (MSE):
Обе меры равны нулю при точном совпадении и растут с ростом расхождения, но для дальнейших целей между ними есть решающая разница: \((y-\hat y)^2\) дифференцируема по \(\hat y\) при любом значении, а \(|y-\hat y|\) не имеет производной в точке \(y=\hat y\) (излом графика). Поскольку метод обучения, рассматриваемый ниже, использует производную функции ошибки, из двух мер выбирают квадратичную. Для одного обучающего примера с выходом одного нейрона ошибку по традиции берут в виде
Множитель \(\tfrac12\) на расположение минимума не влияет — он введён исключительно для того, чтобы при дифференцировании (2.2) двойка из показателя степени сократилась с \(\tfrac12\); минимум \(E\) при этом остаётся ровно там же, где и минимум \((y-\hat y)^2\).
2. Производная и градиент
Задачу «подобрать веса, минимизирующие \(E\)» уже решали методом перебора (конспект 1, метод наименьших квадратов) и аналитически через нормальное уравнение — но оба способа годятся только для линейной зависимости и квадратичной ошибки в явном матричном виде. Для нейрона с произвольной функцией активации аналитического решения в общем случае нет, и перебор всех значений веса — с реальной точностью и не одним весом, а тысячами — невозможен физически. Нужен направленный способ изменять веса так, чтобы \(E\) гарантированно уменьшалась. Для этого используется производная.
Производная функции \(C\) в точке \(w_0\) — предел отношения приращения функции к приращению аргумента при стремлении последнего к нулю:
Отношение под знаком предела — это угловой коэффициент секущей, проходящей через точки \((w_0, C(w_0))\) и \((w_0+\Delta x, C(w_0+\Delta x))\). Производная — это то значение, к которому угловой коэффициент секущей стремится, когда вторая точка приближается к первой, то есть угловой коэффициент касательной в точке \(w_0\).
Синяя линия — секущая через точки \(w_0\) и \(w_0+\Delta x\) на кривой \(C(w)=0{,}5w^2\), \(w_0=-2\). Уменьшайте \(\Delta x\) и наблюдайте, как угловой коэффициент секущей приближается к истинной производной \(C'(w_0)\).
2.1. Градиент: производная по нескольким весам
Ошибка \(E\) зависит не от одного веса, а от всех весов сети сразу: \(E=E(w_1,\ldots,w_n)\). Для каждого веса в отдельности можно вычислить частную производную \(\partial E/\partial w_i\) — производную по \(w_i\) при остальных весах, зафиксированных как константы. Вектор всех частных производных называется градиентом:
Градиент указывает направление, в котором \(E\) растёт быстрее всего в окрестности точки \(w\). Соответственно, направление наискорейшего убывания \(E\) — противоположное, \(-\nabla E(w)\).
3. Идея алгоритма обучения: движение против градиента
Раз \(-\nabla E(w)\) указывает направление быстрейшего убывания ошибки, естественный способ подбирать веса — сделать маленький шаг именно в этом направлении, затем пересчитать градиент в новой точке и повторить:
Коэффициент \(\alpha>0\) — скорость обучения (англ. learning rate): она задаёт длину шага. Для одного веса при однозначной связи знака производной и направления изменения правило (2.5) можно проверить прямым рассуждением. Пусть \(E\) как функция одного веса \(w\) имеет вид ямы с минимумом где-то посередине. Рассмотрим точку 1 с некоторым значением \(w_1\) и точку 2 с меньшей ошибкой, \(E_2<E_1\), правее неё; чтобы попасть из точки 1 в точку 2, нужно увеличить \(w\) (\(\Delta w=w_2-w_1>0\)), а производная \(E'(w_1)\) при этом отрицательна (функция убывает слева направо). Рассмотрим и точку 3, из которой в точку 4 с меньшей ошибкой нужно, наоборот, уменьшить \(w\) (\(\Delta w=w_4-w_3<0\)), а производная \(E'(w_3)\) там положительна. В обоих случаях знак \(\Delta w\) противоположен знаку производной — это в точности и есть правило (2.5) при \(n=1\): \(\Delta w = -\alpha E'(w)\).
3.1. Скорость обучения и локальные минимумы
Слишком большая \(\alpha\) заставляет шаг перепрыгивать через минимум и раскачиваться вокруг него или вовсе расходиться; слишком маленькая — делает сходимость крайне медленной. Есть и более фундаментальная проблема: если \(E(w)\) имеет несколько локальных минимумов, градиентный спуск останавливается в том из них, что ближе к стартовой точке, — и он не обязан быть глобальным минимумом.
Шесть шариков расставлены вдоль случайно сгенерированной кривой \(E(w)\) с несколькими локальными минимумами. Запустите спуск — каждый шарик независимо катится против градиента (2.5) в своей точке. Кнопка «новая кривая» генерирует новый рельеф и новую расстановку шариков: одна и та же \(\alpha\) на разных стартовых точках может привести в разные минимумы, и не все из них — глобальный.
На практике с проблемой локальных минимумов борются не перебором всех стартовых точек, а другими средствами (случайной инициализацией весов, модификациями градиентного спуска с «инерцией» и т. п.); подробное рассмотрение этих средств выходит за рамки данного конспекта.
4. Метод обратного распространения ошибки
Правило (2.5) требует градиент \(\nabla E\) — частную производную \(E\) по каждому весу сети. Для одного нейрона с одним слоем весов это несложно (см. §4.1), но в сети с несколькими слоями вес влияет на \(E\) не напрямую, а через цепочку промежуточных нейронов. Метод обратного распространения ошибки — это не отдельный от градиентного спуска алгоритм, а способ эффективно вычислить все нужные частные производные в такой сети с помощью цепного правила дифференцирования сложной функции, переиспользуя уже вычисленные значения, а не пересчитывая их заново для каждого веса.
Метод состоит из двух этапов:
- прямой проход — входной сигнал проходит по сети от входа к выходу как обычно, и по полученному выходу \(\hat y\) вычисляется ошибка \(E\) по (2.2);
- обратный проход — ошибка распространяется от выхода к входу: для каждого слоя, начиная с последнего, вычисляется частная производная \(E\) по весам этого слоя через уже найденные производные следующего слоя (по цепному правилу), и веса корректируются по правилу (2.5).
4.1. Цепное правило для одного нейрона
Покажем, откуда берётся частная производная \(\partial E/\partial w_i\) для отдельного нейрона с выходом \(\hat y = f(\mathrm{net})\), \(\mathrm{net}=\sum_i x_i w_i\) (для краткости смещение опущено). Ошибка \(E\) зависит от \(w_i\) через две промежуточные величины — \(\hat y\) и \(\mathrm{net}\), — и по цепному правилу:
(первый множитель — производная (2.2) по \(\hat y\); третий — производная \(\mathrm{net}\) по \(w_i\), равная просто \(x_i\)). Для сигмоиды производная выражается через саму функцию:
Подставив (2.6)–(2.7) в правило обновления (2.5) при \(\alpha=1\), получаем конкретную формулу, которая используется в реализации следующего параграфа:
Для сети с несколькими слоями та же процедура повторяется слой за слоем справа налево: частная производная \(E\) по весам скрытого слоя выражается через уже вычисленные производные следующего (более близкого к выходу) слоя — отсюда и название «обратное распространение».
5. Реализация: свой первый обучаемый нейрон
Соберём формулы (2.6)–(2.8) в код на numpy. Обучающая выборка — четыре примера с тремя
входами; целевое значение в каждом примере совпадает со значением первого входа
(\(y=x_1\)), так что после обучения веса должны показать: первый вход важен, два остальных — нет.
from numpy import exp, array, random, dot # обучающая выборка: 4 примера, по 3 входа в каждом training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]]) # целевой выход — совпадает со значением первого входа training_set_outputs = array([[0, 1, 1, 0]]).T # инициализация весов случайными числами из [-1, 1] random.seed(1) synaptic_weights = 2 * random.random((3, 1)) - 1 for iteration in range(100000): # прямой проход: net = X * w, затем сигмоида (1.7) output = 1 / (1 + exp(-dot(training_set_inputs, synaptic_weights))) # обратный проход: дельта-правило (2.8) в матричном виде, # суммирование по всем 4 примерам выполняется через X^T * (...) synaptic_weights += dot( training_set_inputs.T, (training_set_outputs - output) * output * (1 - output) ) print(synaptic_weights)
Число проходов по всей обучающей выборке (здесь — 100000) принято называть числом эпох.
Строка внутри цикла, вычисляющая output, — это формула (1.5)+(1.7) (прямой проход);
строка, изменяющая synaptic_weights, — это в точности формула (2.8), записанная для всех
четырёх обучающих примеров сразу: training_set_inputs.T при умножении на столбец из
четырёх значений \((y-\hat y)\hat y(1-\hat y)\) суммирует вклад каждого примера в \(\Delta w_i\) — то
же самое, что \(\sum_{\text{примеры}} (y-\hat y)\hat y(1-\hat y)\,x_i\) в (2.8) для каждого веса
\(w_i\) по отдельности.
random.seed(1), получают при каждом запуске разные начальные веса и,
соответственно, слегка разные (хотя и близкие) итоговые веса. Для воспроизводимости результатов
seed фиксируют; это не влияет на то, чему в итоге обучится сеть, только на конкретные числа.
Контрольные вопросы
-
Квадратичная функция дифференцируема при любом значении отклонения, а модуль не имеет производной в нуле — градиентному методу нужна производная функции ошибки везде, где он движется.
-
Предел отношения приращения функции к приращению аргумента при стремлении приращения аргумента к нулю — предел углового коэффициента секущей, равный угловому коэффициенту касательной.
-
Вектор частных производных функции по каждому из весов. Указывает направление наискорейшего возрастания функции; противоположное направление — наискорейшего убывания.
-
Чтобы веса двигались в сторону убывания ошибки. Со знаком «плюс» шаг шёл бы в сторону роста ошибки, то есть обучение ухудшало бы сеть.
-
Слишком большая — шаг перепрыгивает через минимум, возможны колебания или расхождение. Слишком маленькая — сходимость к минимуму становится крайне медленной.
-
Это не отдельный метод оптимизации, а способ эффективно вычислить градиент ошибки по всем весам многослойной сети с помощью цепного правила, переиспользуя производные, уже вычисленные для более поздних слоёв. Сам шаг обновления весов остаётся обычным градиентным спуском.