Независимо от того, являетесь ли вы новичком в машинном обучении или экспертом, вам было бы трудно понять концепцию обратного распространения в нейронных сетях. Если вы новичок, первый взгляд на сложные этапы, связанные вместе в алгоритме обратного распространения, был бы определенно устрашающим. В то время как некоторые из нас потратили бы время на анализ алгоритма и получение интуиции, большинство из нас абстрагировало бы процесс обучения. Распространенный и очень разумный вопрос, который может возникнуть у людей, которые работали с фреймворками глубокого обучения, такими как Pytorch от Facebook и Tensorflow от Google:

«Почему мы должны тратить время на понимание необходимости обратного распространения информации, если одна строка loss.backward() в Pytorch или tape.gradient(loss_value, model.variables) в TensorFlow может творить чудеса?»

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

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

Краткое введение в нейронные сети:

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

Звучит сложно? Позвольте мне сломать это. Каждая нейронная сеть имеет входной слой, серию скрытых слоев и выходной слой. Давайте рассмотрим четыре категории изображений: «кошка», «собака», «лягушка» и «лошадь». Значения в входном слое представляют собой значения пикселей данного изображения, которое мы хотим разделить на четыре категории. Маленькие кружки в каждом слое называются нейронами. Значения выходного слоя представляют собой оценку для каждой категории. Мы относим изображение к категории, получившей наивысший балл. Например, если нейрон «лягушка» в выходном слое получает наивысшее значение по сравнению с другими нейронами слоя, мы говорим, что изображение - «лягушка». Другие промежуточные слои называются скрытыми слоями.

Каждое соединение между двумя слоями имеет набор параметров, называемых весами. Эти веса не являются случайными числами, матрица весов между различными слоями может быть визуализирована как шаблон или функция, которую мы ищем на изображении для ее классификации. Значения следующего слоя вычисляются путем применения функции, называемой функция активации, к значениям предыдущего слоя и весам между двумя слоями. Обычно используемые функции активации - сигмоидальная, tanh, выпрямленная линейная единица (также называемая ReLU), утечка ReLU и Maxout. Разница между выходными данными, которые мы прогнозируем с использованием сети, и фактическим классом изображения определяет потерю сети. Чем больше изображений мы классифицируем правильно, тем меньше потери. Есть несколько способов вычисления потерь нейронной сети. Один наивный подход - найти среднеквадратичную ошибку, то есть среднее квадратов разницы между прогнозируемыми и фактическими значениями. Другие методы, которые часто используются для вычисления потерь, - это softmax (кросс-энтропия) и поддержка векторной машины (шарнир). Итак, цель задачи нейронной сети состоит в том, чтобы узнать лучший набор весов, чтобы дать нам желаемые оценки в выходном слое. Другими словами, чтобы минимизировать функцию потерь сети.

Что такое обратное распространение?

Обучение нейронной сети обычно происходит в два этапа.

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

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

Краткий обзор производных:

Рассмотрим следующую функцию.

Достаточно просто найти частную производную по любому из входов. Например, если

Частная производная по переменной x (∂f / ∂x) и переменной y (∂f / ∂y) равна 3 и -2 соответственно. Это дает нам понимание, что увеличение переменной x на величину ε увеличит выходную функцию на 3ε. Аналогично, увеличение переменной y на величину ε уменьшило бы выходную функцию на 2ε. Таким образом, производная функции по каждой переменной говорит нам о чувствительности функции по отношению к этой переменной.

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

Как сделать обратное распространение?

В оставшейся части статьи мы будем использовать следующий пример.

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

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

Мы отметили выходы на графике, а также вычислили локальные градиенты узлов. Обратное распространение - это «локальный» процесс, и его можно рассматривать как рекурсивное применение цепного правила. Теперь нам нужна чувствительность нашей функции вывода (потерь) по отношению к входным переменным a, b и c графа (т.е. ∂f / ∂a, ∂f / ∂b и ∂f / ∂c). Мы начинаем с выходной переменной и находим производную вывода графика относительно каждой переменной по правилу рекурсивной цепочки. Производная выходной переменной по самой себе равна единице.

Возьмем один узел на графике и получим четкую картину.

Мы вычислили выходные данные и локальные градиенты каждого узла перед тем, как начать обратное распространение. Граф, который не знал о существовании других узлов, когда мы вычисляли выходные данные и локальные градиенты, взаимодействует с другими узлами и изучает производную своего выходного значения на конечном выходе графа (∂f / ∂x). Таким образом, мы находим производную выхода графика по переменной ( ∂f / ∂a ) путем умножения ее локального градиента ( ∂x / ∂a) с восходящим градиентом, который мы получаем из выходного значения узла. ( ∂f / ∂x) . В этом суть обратного распространения ошибки. Если мы посмотрим на переменную b, мы можем использовать правило многомерной цепочки, чтобы найти производную выхода f по переменной b.

Мы также можем интерпретировать это правило многомерной цепочки, сказав: «градиенты добавляют на ветвях». Таким образом, мы нашли чувствительность переменных a, b и c к выходному сигналу f путем вычисления производных методом обратного распространения ошибки.

На этом этапе у вас могут возникнуть следующие вопросы.

«Зачем нужен этот обходной метод поиска градиентов путем обратного распространения ошибки, в то время как мы можем вычислить те же градиенты, дифференцируя их простым, прямым способом?»

«О, обратное распространение - это не что иное, как цепное правило! Что в этом особенного? "

Почему обратное распространение?

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

Стратегия 1: прямая дифференциация

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

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

Прямое дифференцирование определяет, как один из входов влияет на каждый узел в графе.

Стратегия 2: обратная дифференциация:

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

Если вы правильно заметили, выполнив обратное дифференцирование (или обратное распространение), мы вычислили производную f (наша функция вывода или потерь) по каждому узлу на графике. Да, вы правильно поняли, что касается каждого узла на графике!

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

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

Почему трехкратное ускорение?

Мы нашли только производную от f относительно b при прямом дифференцировании.

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

Итак, это все?

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

Эй, обратное распространение мощное!

Сверточные нейронные сети (CNN) - это класс глубоких нейронных сетей (глубоких, подразумевающих большое количество скрытых слоев), которые в основном используются для визуального распознавания - классификации изображений. ImageNet, крупнейшая визуальная база данных, состоит из более 14 миллионов изображений, относящихся к 20 тысячам категорий. Это общепринятая практика оценивать производительность CNN путем обучения и тестирования в базе данных ImageNet из-за большого количества помеченных изображений, которые у нее есть. Текущая стандартная архитектура CNN, которая лучше всего работает в ImageNet, - это ResNet-152, которая имеет 152 уровня и количество параметров, близких к миллиарду! Выполняя обратное распространение, мы можем получить градиенты функции потерь по всем входам и весам сети. Подумайте о огромном ускорении в миллиарды раз, когда мы выбираем обратное распространение вместо прямой дифференциации. Звучит потрясающе, не правда ли?

Надеюсь, вы поняли настоящую интуицию, стоящую за обратным распространением, и почему это предпочтительнее. Ваше здоровье! :)

Использованная литература:

  1. Http://cs231n.github.io/optimization-2/
  2. Https://www.youtube.com/watch?v=d14TUNcbn1k
  3. Http://colah.github.io/posts/2015-08-Backprop/
  4. Https://medium.com/@karpathy/yes-you-should-understand-backprop-e2f06eab496b

Больше чтения:

  1. Http://neuralnetworksanddeeplearning.com/chap2.html
  2. Http://cs231n.stanford.edu/handouts/derivatives.pdf

П.С. Для построения сетевых диаграмм использовал LaTeX и www.draw.io.