Машинное обучение, Математика

По одному LEGO за раз: математическое объяснение того, как нейронные сети обучаются с помощью реализации с нуля

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

Шаги по запуску кода:

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

Нейронные сети как композиция из частей

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

  • Вход X передает в нейронную сеть необработанные данные, которые хранятся в матрице, в которой наблюдения представляют собой строки, а измерения - столбцы.
  • Вес W1 сопоставляет введенный X с первым скрытым слоем h1. Веса W1 тогда работают как линейное ядро
  • Сигмоидальная функция предотвращает выпадение чисел в скрытом слое за пределы допустимого диапазона, масштабируя их до 0–1. Результат - массив нейронных активаций h1 = сигмовидная (WX)

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

Зачем мне это читать?

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

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

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

Конкретный пример: изучение функции XOR

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

Чтобы проиллюстрировать эту важную концепцию, обратите внимание ниже, как прямая линия не может разделять 0 и 1, выходы функции XOR. Реальные проблемы также разделимы нелинейно.

Топология сети проста:

  • Входной X - двумерный вектор.
  • Веса W1 - это матрица 2x3 со случайно инициализированными значениями.
  • Скрытый слой h1 состоит из трех нейронов. Каждый нейрон получает в качестве входных данных взвешенную сумму наблюдений, это внутренний продукт, выделенный зеленым на рисунке ниже: z1 = [x1, x2] [w1, w2]
  • Веса W2 представляет собой матрицу 3x2 со случайно инициализированными значениями и
  • Выходной слой h2 состоит из двух нейронов, поскольку функция XOR возвращает либо 0 (y1 = [0,1]), либо 1 (y2 = [1,0]).

Более наглядно:

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

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

Инициализация сети

Давайте инициализируем веса сети случайными числами.

Шаг вперед:

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

Вот как это бывает:

  • Линейно отобразите входные данные X, используя веса W1 в качестве ядра:

  • Масштабируйте эту взвешенную сумму z1 с помощью сигмовидной функции, чтобы получить значения первого скрытого слоя h1. Обратите внимание, что исходный 2D-вектор теперь отображается в 3D-пространстве.

  • Аналогичный процесс происходит и для второго слоя h2. Давайте сначала вычислим взвешенную сумму z2 первого скрытого слоя, который теперь является входными данными.

  • А затем вычислите их функцию активации сигмовидной кишки. Этот вектор [0,37166596 0,45414264] представляет логарифмическую вероятность или предсказанный вектор, вычисленный сетью с заданным входом X.

Расчет общей потери

Также известная как «прогнозируемый фактический минус», цель функции потерь состоит в количественной оценке расстояния между прогнозируемым вектором h2 и фактической меткой, предоставленной людьми y.

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

Шаг назад:

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

Этот шаг выполняется в обратном порядке, чем шаг вперед. Сначала вычисляется частная производная функции потерь по весам выходного слоя (dLoss / dW2), а затем скрытого слоя (dLoss / dW1). Давайте подробно объясним каждую из них.

dLoss / dW2:

Цепное правило гласит, что мы можем разложить вычисление градиентов нейронной сети на дифференцируемые части:

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

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

Подстановка значений в эти частные производные позволяет нам вычислять градиенты относительно весов W2 следующим образом.

Результатом является матрица dLoss / dW2 3x2, которая обновит исходные значения W2 в направлении, минимизирующем функцию потерь.

dLoss / dW1:

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

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

Например, частные производные dLoss / dh2 и dh2 / dz2 уже были вычислены как зависимость для изучения весов выходного слоя dLoss / dW2 в предыдущем разделе.

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

Наконец, мы присваиваем весам новые значения и завершаем шаг обучения в сети.

Реализация

Давайте переведем приведенные выше математические уравнения в код, используя только Numpy в качестве нашего механизма линейной алгебры. Нейронные сети обучаются в цикле, в котором каждая итерация представляет сети уже откалиброванные входные данные. В этом небольшом примере давайте просто рассмотрим весь набор данных на каждой итерации. Вычисления шага вперед, потери и шага назад приводят к хорошему обобщению, поскольку мы обновляем обучаемые параметры ( матрицы w1 и w2 в коде) с соответствующими им градиентами (матрицы dL_dw1 и dL_dw2) в каждом цикле. Код хранится в этом репозитории: https://github.com/omar-florez/scratch_mlp

Давай запустим это!

См. Ниже некоторые нейронные сети, обученные приближенно выполнять функцию XOR за несколько итераций.

Левый график: точность. Центральный график: выученная граница принятия решения. Правый график: функция потерь.

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

Наличие 50 нейронов в скрытом слое значительно увеличивает способность модели определять более сложные границы принятия решений. Это может не только дать более точные результаты, но также и разнести градиенты, что является серьезной проблемой при обучении нейронных сетей. Это происходит, когда очень большие градиенты умножают веса во время обратного распространения ошибки и, таким образом, генерируют большие обновленные веса. Это причина того, почему значение потерь внезапно увеличивается на последних этапах обучения (этап ›90). Компонент регуляризации функции потерь вычисляет квадратные значения уже очень больших весов (сумма (W²) / 2N).

Этой проблемы можно избежать, снизив скорость обучения, как показано ниже. Или путем реализации политики, снижающей скорость обучения с течением времени. Или путем усиления регуляризации, возможно, L1 вместо L2. Взрывающиеся и исчезающие градиенты - интересные явления, и мы посвятим их анализу позже.