Градиентный спуск - это алгоритм итеративного обучения и рабочая лошадка нейронных сетей. Благодаря множеству настраиваемых примеров для PyTorch или Keras создание нейронных сетей для печенья может стать тривиальным упражнением. Однако, когда дела идут наперекосяк, понимание основ может сэкономить часы утомительной отладки.

В этом посте мы собираемся углубиться в математические формулировки градиентного спуска, чтобы лучше понять его. Некоторые примеры просты и решаются вручную. Кроме того, запись помогает улучшить память, так что не стесняйтесь брать лист бумаги и калькулятор!

Метрика ошибки

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

  • y = основная правда
  • ŷ (y шляпа) = предсказание
  • j = единицы вывода сети
  • μ ( греческая буква мю ) = все точки данных

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

Далее у нас есть μ точек данных . Во второй сумме для каждой точки данных, где мы вычислили внутреннюю сумму квадратов расстояний, мы суммируем их все.

Напомним, что прогноз ŷ NN определяется ее весами:

  • w = вес
  • x = особенность
  • i = количество функций

Таким образом, подключив уравнение ŷ к уравнению SSE, мы видим, что ошибка зависит от весов:

Поэтому наша цель - определить веса, которые минимизируют ошибку. Последнее иногда называют функцией стоимости, обозначаемой J (w), идея заключается в том, что мы накладываем штрафной член на неправильные прогнозы.

Правило цепи

Теория

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

В случае единственного блока вывода, где j = 1, мы можем взять производную ошибки E относительно веса:

Пусть ŷ будет результатом единственной функции активации f, которая принимает входные данные h, так что h принимает входные данные и умножает их на их веса:

Замена в производной ошибки:

Обратите внимание на частную производную члена суммирования, если мы запишем ее для случайного веса wi:

Частная производная суммы по i равна xi, все остальные равны нулю. Мы можем подставить обратно в частную производную от E:

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

Чтобы упростить наши обозначения, мы определяем член ошибки δ как ошибку, умноженную на производную функции активации в h:

Это упрощает шаг веса до:

Запишем это в более общих обозначениях:

Пример с одним выходом

Теперь, когда у нас есть представление о том, как действовать, мы можем применить его к одному модулю вывода. Мы используем сигмовидную функцию, обозначенную S, как функцию активации. Напомним, что общее обозначение функции активации было f (h):

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

Реализация на python:

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

С помощью приведенных выше формул мы можем рассчитать изменение веса следующим образом:

Общая реализация градиентного спуска

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

  • m = количество записей в данных

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

  1. Установите шаг веса на ноль: Δwi = 0
  2. Для каждой записи в данных обучения:
  • Сделайте прямой проход по сети и рассчитайте выход ŷ
  • Рассчитайте член ошибки δ для единицы вывода.
  • Обновите шаг веса: Δwi = Δwi + δxi

3. Обновите веса: wi = wi + η Δwi / m.

4. Повторите для эпох

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

Мы следуем методу, предписанному Stanford CS231n, и инициализируем их случайным образом, чтобы все они имели разные начальные значения и расходились, нарушая симметрию. Поэтому мы инициализируем веса из нормального распределения с центром в 0 и масштабируем его как 1 / sqrt (n), где n - количество входных единиц.

Для набора данных, который имеет n_recrods и n_features, мы можем реализовать это на Python следующим образом:

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

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

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

Рассмотрим ошибку δ, относящуюся к единице вывода k и скрытой единице j скрытого слоя h. Мы можем записать эту ошибку следующим образом:

  • o = выходной слой
  • h = скрытый слой
  • j = скрытая единица h
  • k = единица вывода

Шаг градиентного спуска такой же, как определено ранее, но с дополнительными индексами, чтобы определить, где происходит операция:

  • wij = веса между входным слоем и скрытым слоем
  • xi = входные единицы измерения

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

  • δ output = ошибка вывода
  • V in = входные данные для слоя, такие как активация скрытого слоя для модуля вывода.

Расчет руки обратного распространения

Если вы еще не взяли ручку и бумагу, сейчас хорошее время😉.

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

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

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

Для этого примера предположим, что наш целевой результат равен y = 1.

Начнем с прямого прохода, идущего от входа к скрытому модулю:

Затем активируется выход скрытого блока:

Для выходной заметки мы объединяем умножение веса с активацией:

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

Следовательно, термин ошибки для выходного устройства:

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

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

Скорость обучения, умноженная на ошибку блока вывода, умноженная на значение активации скрытого блока, равна шагу веса скрытого для вывода:

Следовательно, текущий вес W = 0,1, соединяющий скрытый с выходным слоем, будет обновлен на эту величину.

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

После обновления весов мы можем повторять описанные выше шаги до тех пор, пока наш прогноз не станет почти равным истине.

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

Пример NumPy

Мы можем сделать то же самое в NumPy, используя немного более сложную сеть: три входных узла, 2 скрытых узла, один выходной слой.

Заключение

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

Есть еще много ресурсов для изучения, например Stanford CS231n. Я также очень рекомендую Grokking Deep Learning Эндрю Траска, хороший ресурс с интерпретируемыми примерами.