Как быстрее обучить нейронную сеть с помощью оптимизаторов?

Тайны нейронных сетей, часть IV

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

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

Оптимизация алгоритмов машинного обучения

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

Ловушки по пути

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

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

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

Градиентный спуск

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

Где α - гиперпараметр, называемый скоростью обучения, который переводится в длину шага, который мы будем выполнять на каждой итерации. Его выбор выражает - в определенной степени - компромисс между скоростью обучения и точностью получаемого результата. Выбор слишком малого шага приводит к утомительным вычислениям и необходимости выполнять гораздо больше итераций. С другой стороны, выбор слишком большого значения может помешать нам найти минимум. Такая ситуация представлена ​​на рисунке 2 - мы можем видеть, как в последующих итерациях мы подпрыгиваем, не имея возможности стабилизироваться. Между тем, модель, в которой был определен соответствующий шаг, практически сразу нашлась как минимум.

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

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

Мини-пакетный градиентный спуск

Давайте сначала попробуем разобраться с последней из проблем, упомянутых в предыдущей главе - неэффективностью. Хотя векторизация позволяет нам ускорить вычисления, обрабатывая множество обучающих примеров одновременно, когда в наборе данных есть миллионы записей, весь процесс все равно займет много времени для завершения .. Давайте попробуем использовать довольно тривиальное решение - разделить весь набор данных на более мелкие партии и использовать их для обучения в последующих итерациях. Наша новая стратегия визуализирована на анимации выше. Представьте, что контуры, нарисованные слева, символизируют функцию затрат, которую мы хотим оптимизировать. Мы видим, что из-за гораздо меньшего объема данных, которые нам нужно обработать, наш новый алгоритм принимает решения намного быстрее. Также обратим внимание на контраст в движении сравниваемых моделей. Градиентный спуск требует редких, относительно больших шагов с небольшим шумом. С другой стороны, градиентный спуск по размеру пакета требует большего количества шагов, но из-за возможного разнообразия анализируемых наборов данных у нас гораздо больше шума. Возможно даже, что на одной из итераций мы будем двигаться в направлении, противоположном намеченному. Однако в среднем мы приближаемся к минимуму.

Я знаю, о чем вы думаете…. Как выбрать размер партии? Как это часто бывает в глубоком обучении, ответ не является окончательным и зависит от конкретного случая. Если размер нашей партии равен всему набору данных, то мы, по сути, имеем дело с обычным градиентным спуском. С другой стороны, если размер равен 1, то на каждой итерации мы используем только один пример из нашего набора данных, как следствие теряем преимущества векторизации. Этот подход иногда оправдан, и примером стратегии, в которой он используется, является стохастический градиентный спуск. Это делается путем выбора случайных записей набора данных и использования их в качестве обучающего набора в последующих итерациях. Однако, если мы решаем использовать мини-пакет, мы обычно выбираем промежуточное значение - обычно выбираемое из диапазона от 64 до 512 примеров.

Экспоненциально взвешенные средние

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

EWA, по сути, представляет собой усреднение многих предыдущих значений, чтобы стать независимым от местных колебаний и сосредоточиться на общей тенденции. Его значение рассчитывается с использованием рекурсивной формулы, показанной выше, где β - параметр, используемый для управления диапазоном значений, которые необходимо усреднить. Можно сказать, что в последующих итерациях мы учитывали примеры 1 / (1 - β). Для больших значений β график получается более гладким, потому что мы усредняем много записей. С другой стороны, наш график движется все больше и больше вправо, потому что, когда мы усредняем за большой период времени, EWA медленнее адаптируется к новому тренду. Это можно увидеть на Рисунке 5, где мы проиллюстрировали фактическую стоимость акций на момент закрытия, а также на 4 графиках, показывающих значение экспоненциально взвешенных средних значений, рассчитанных для различных бета-параметров.

Градиентный спуск с импульсом

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

Как обычно, мы используем обратное распространение, чтобы вычислить значения dW и db для каждого уровня нашей сети. Однако на этот раз вместо прямого использования вычисленного градиента для обновления значений наших параметров NN мы сначала вычисляем промежуточные значения VdW и Vdb. Эти значения фактически являются EWA производных функции стоимости по отдельным параметрам. Наконец, мы будем использовать VdW и Vdb в градиентном спуске. Весь процесс можно описать следующими уравнениями. Также стоит отметить, что реализация этого метода требует хранения значений EWA между итерациями. Вы можете увидеть подробности в одной из записных книжек в моем репозитории.

Давайте теперь попробуем развить определенную интуицию о влиянии EWA на поведение нашей модели. Еще раз представим, что контуры, нарисованные ниже, символизируют функцию затрат, которую мы оптимизируем. Визуализация выше показывает сравнение стандартного градиентного спуска и GD с импульсом. Мы видим, что форма графика функции стоимости требует очень медленного пути оптимизации. Как и в примере с ценами на фондовом рынке, использование экспоненциально взвешенных средних позволяет нам сосредоточиться на ведущей тенденции, а не на шуме. Компонент, указывающий на минимум, усиливается, а компонент, ответственный за колебания, медленно устраняется. Более того, если мы получим градиенты, указывающие в аналогичном направлении в последующих обновлениях, скорость обучения увеличится. Это приводит к более быстрому схождению и уменьшению колебаний. Однако у этого метода есть недостаток - по мере приближения к минимуму значение импульса увеличивается и может стать настолько большим, что алгоритм не сможет остановиться в нужном месте.

RMSProp

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

Используя пример с рисунка 6 и приведенные выше уравнения, давайте рассмотрим логику, лежащую в основе этой стратегии. Согласно названию, на каждой итерации мы поэлементно вычисляем квадраты производных функции стоимости по соответствующим параметрам. Кроме того, мы используем EWA для усреднения значений, полученных в последних итерациях. Наконец, прежде чем мы обновим значения параметров нашей сети, соответствующие компоненты градиента делятся на квадратный корень из суммы квадратов. Это означает, что скорость обучения снижается быстрее для параметров с большим градиентом и медленнее для параметров с небольшим градиентом. Таким образом, наш алгоритм уменьшает колебания и предотвращает преобладание шума над сигналом. Чтобы избежать деления на ноль (числовая стабильность), мы также добавляем к знаменателю очень маленькое значение, отмеченное в указанных формулах как.

Я должен признать, что во время написания этой статьи у меня было два вау-момента - моменты, когда я был действительно шокирован качественным скачком между анализируемыми мной оптимизаторами. Первый был, когда я увидел разницу во времени обучения между стандартным градиентным спуском и мини-пакетом. Второй теперь, когда я сравнил RMSprop со всем, что мы видели до сих пор. Однако у этого метода есть свои недостатки. Поскольку знаменатель приведенных выше уравнений увеличивается на каждой итерации, скорость обучения становится меньше. Как следствие, это может привести к полной остановке нашей модели.

Адам

И последнее, но не менее важное: позвольте мне рассказать вам об адаптивной оценке момента. Это алгоритм, который, как и RMSProp, хорошо работает в широком спектре приложений. Он использует преимущества самых больших преимуществ RMSProp и объединяет их с идеями, известными из оптимизации импульса. В результате получилась стратегия, позволяющая быстро и эффективно оптимизировать. На приведенном выше рисунке показано, насколько хорошо обсуждаемые оптимизаторы справляются с оптимизацией сложной части функции. Сразу видно, что у Адама в этом деле все хорошо.

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

Заключение

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

Поздравляю, если вам удалось попасть сюда. Это было, конечно, не самое легкое чтение. Если вам понравилась эта статья, подпишитесь на меня в Twitter и Medium и посмотрите другие проекты, над которыми я работаю, на GitHub и Kaggle. Эта статья является четвертой частью серии Тайны нейронных сетей, если у вас еще не была возможность, прочтите другие статьи. А если у вас есть интересные идеи для следующих постов, пишите в комментариях. Оставайтесь любопытными!