Русский
Русский
English
Статистика
Реклама

Adam

Перевод Объясняем на пальцах принцип действия оптимизаторов для нейронных сетей основные алгоритмы, и зачем они нужны

16.04.2021 18:11:03 | Автор: admin

Оптимизаторы важный компонент архитектуры нейронных сетей. Они играют важную роль в процессе тренировки нейронных сетей, помогая им делать всё более точные прогнозы. Специально к старту нового потока расширенного курса помашинному и глубокому обучению, делимся с вами простым описанием основных методик, используемых оптимизаторами градиентного спуска, такими как SGD, Momentum, RMSProp, Adam и др.


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

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

Большинство популярных библиотек глубокого обучения, например PyTorch и Keras, имеют множество встроенных оптимизаторов, базирующихся на использовании алгоритма градиентного спуска, например SGD, Adadelta, Adagrad, RMSProp, Adam и пр.

Но почему алгоритмов оптимизации так много? Как выбрать из них правильный?

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

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

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

Кривая потерь

Начнём с того, что рассмотрим на 3D-изображении, как работает стандартный алгоритм градиентного спуска.

Кривая потерь для алгоритма градиентного спускаКривая потерь для алгоритма градиентного спуска

На рисунке показана сеть с двумя весовыми параметрами:

  • На горизонтальной плоскости размещаются две оси для весов w1 и w2, соответственно.

  • На вертикальной оси откладываются значения потерь для каждого сочетания весов.

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

Синяя линия соответствует траектории алгоритма градиентного спуска при оптимизации:

  • Работа алгоритма начинается с выбора некоторых случайных значений обоих весов и вычисления значения потери.

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

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

Вычисление градиента

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

Обновление параметра градиентного спускаОбновление параметра градиентного спуска

Градиент измеряет уклон и рассчитывается как значение изменения в вертикальном направлении (dL), поделённое на значение изменения горизонтальном направлении (dW). Другими словами, для крутых уклонов значение градиента большое, для пологих маленькое.

Вычисление градиентаВычисление градиента

Практическое применение алгоритма градиентного спуска

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

  • Во-первых, на приведённом выше рисунке кривая потерь идёт идеально плавно. На самом деле горбов и впадин на ней гораздо больше, чем плавных мест.

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

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

  • Ландшафт круто уходит вниз в одном направлении, но, чтобы попасть в самую нижнюю точку, нужно двигаться в направлении с меньшими изменениями высоты?

  • Весь ландшафт представляется алгоритму плоским во всех направлениях?

  • Алгоритм попадает в глубокую канаву? Как ему оттуда выбраться?

Давайте рассмотрим несколько примеров кривых, перемещение по которым может создавать трудности.

Трудности при оптимизации градиентного спуска

Локальные минимумы

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

Локальный минимум и глобальный минимумЛокальный минимум и глобальный минимум

Седловые точки

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

Седловая точкаСедловая точка

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

Алгоритм градиентного спуска при этом ошибочно полагает, что минимум им найден.

Овраги

Ещё одна головная боль алгоритма градиентного спуска пересечение оврагов. Овраг это протяжённая узкая долина, имеющая крутой уклон в одном направлении (т.е. по сторонам долины) и плавный уклон в другом (т.е. вдоль долины). Довольно часто овраг приводит к минимуму. Поскольку навигация по такой форме кривой затруднена, такую кривую часто называют патологическим искривлением (Pathological Curvature).

ОврагиОвраги

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

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

Первое улучшение алгоритма градиентного спуска стохастический градиентный спуск (SGD)

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

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

Случайность выбора хороша тем, что она помогает глубже исследовать ландшафт потерь.

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

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

Второе усовершенствование алгоритма градиентного спуска накопление импульса (Momentum)

Динамическая корректировка количества обновлений

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

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

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

  • Скорректировать градиент.

  • Скорректировать значение скорости обучения.

SGD с функцией накопления импульса и обычный SGD

Первое действие, то есть корректировка градиента, выполняется с помощью функции накопления импульса.

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

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

  1. Сразу встаёт вопрос как далеко можно углубляться в прошлое? Чем глубже мы погрузимся в прошлое, тем меньше вероятность воздействия аномалий на конечный результат.

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

Функция накопления импульса использует при работе экспоненциальное скользящее среднее, а не текущее значение градиента.

Переходы через овраги с помощью функции накопления импульса

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

Функция накопления импульса помогает преодолевать оврагиФункция накопления импульса помогает преодолевать овраги

С помощью функции накопления импульса можно сгладить зигзагообразные скачки алгоритма SGD.

  • Для первого параметра с крутым уклоном большое значение градиента приведёт к резкому повороту от одной стороны оврага к другой. Однако на следующем шаге такое перемещение будет скомпенсировано резким поворотом в обратном направлении.

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

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

  • SGD с накоплением импульса

  • Ускоренный градиент Нестерова

Третье усовершенствование алгоритма градиентного спуска изменение скорости обучения (на базе значения градиента)

Как уже было сказано выше, вторым способом изменения количества обновлений параметров является изменение скорости обучения.

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

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

Мы можем использовать это обстоятельство, чтобы адаптировать скорость обучения к каждому параметру. Чтобы выбрать скорость обучения для данного параметра, мы можем обратиться к прошлым градиентам (для каждого параметра отдельно).

Данная функциональность реализована в нескольких алгоритмах оптимизации, использующих разные, но похожие методы, например Adagrad, Adadelta, RMS Prop.

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

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

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

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

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

Четвёртое усовершенствование алгоритма градиентного спуска изменение скорости обучения (на базе тренировочной выборки)

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

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

Заключение

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

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Перевод Реализуем и сравниваем оптимизаторы моделей в глубоком обучении

27.10.2020 20:10:40 | Автор: admin

Реализуем и сравниваем 4 популярных оптимизатора обучения нейронных сетей: оптимизатор импульса, среднеквадратичное распространение, мини-пакетный градиентный спуск и адаптивную оценку момента. Репозиторий, много кода на Python и его вывод, визуализации и формулы всё это под катом.



Введение


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

Что такое оптимизатор?


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

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

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

Некоторые часто используемые термины:


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

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



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

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

  • Гиперпараметры

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

  • Скорость обучения

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

Популярные оптимизаторы



Ниже приведены некоторые из самых популярных оптимизаторов:

  1. Стохастический градиентный спуск (SGD).
  2. Оптимизатор импульса (Momentum).
  3. Среднеквадратичное распространение (RMSProp).
  4. Адаптивная оценка момента (Adam).

Рассмотрим каждый из них в деталях.

1. Стохастический градиентный спуск (особенно мини-пакетный)


Мы используем один пример за раз при обучении модели (в чистом SGD) и обновления параметра. Но мы должны использовать еще один для цикла. Это займет много времени. Поэтому используем мини-пакетный SGD.

Мини-пакетный градиентный спуск стремится сбалансировать устойчивость стохастического градиентного спуска и эффективность пакетного градиентного спуска. Это наиболее распространенная реализация градиентного спуска, используемая в области глубокого обучения. В мини-пакетном SGD при обучении модели мы берем группу примеров (например, 32, 64 примера и т. д.). Такой подход работает лучше, потому что требуется единственный цикл для мини-пакетов, а не для каждого примера. Мини-пакеты выбираются случайным образом для каждой итерации, но почему? Когда мини-пакеты выбираются случайным образом, то при застревании в локальных минимумах некоторые шумные шаги могут привести к выходу из этих минимумов. Зачем нам этот оптимизатор?

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

Как генерировать случайные мини-пакеты?

def RandomMiniBatches(X, Y, MiniBatchSize):    m = X.shape[0]      miniBatches = []        permutation = list(np.random.permutation(m))    shuffled_X = X[permutation, :]    shuffled_Y = Y[permutation, :].reshape((m,1))   #sure for uptpur shape    num_minibatches = m // MiniBatchSize     for k in range(0, num_minibatches):        miniBatch_X = shuffled_X[k * MiniBatchSize:(k + 1) * MiniBatchSize,:]        miniBatch_Y = shuffled_Y[k * MiniBatchSize:(k + 1) * MiniBatchSize,:]        miniBatch = (miniBatch_X, miniBatch_Y)        miniBatches.append(miniBatch)        #handeling last batch    if m % MiniBatchSize != 0:        # end = m - MiniBatchSize * m // MiniBatchSize        miniBatch_X = shuffled_X[num_minibatches * MiniBatchSize:, :]        miniBatch_Y = shuffled_Y[num_minibatches * MiniBatchSize:, :]        miniBatch = (miniBatch_X, miniBatch_Y)        miniBatches.append(miniBatch)        return miniBatches 

Каким будет формат модели?

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

def model(X,Y,learning_rate,num_iter,hidden_size,keep_prob,optimizer):    L = len(hidden_size)    params = initilization(X.shape[1], hidden_size)    for i in range(1,num_iter):        MiniBatches = RandomMiniBatches(X, Y, 64)   # GET RAMDOMLY MINIBATCHES        p , q = MiniBatches[2]        for MiniBatch in MiniBatches:               #LOOP FOR MINIBATCHES            (MiniBatch_X, MiniBatch_Y) = MiniBatch            cache, A = model_forward(MiniBatch_X, params, L,keep_prob)             #FORWARD PROPOGATIONS            cost = cost_f(A, MiniBatch_Y)                                          #COST FUNCTION            grad = backward(MiniBatch_X, MiniBatch_Y, params, cache, L,keep_prob)  #BACKWARD PROPAGATION             params = update_params(params, grad, beta=0.9,learning_rate=learning_rate)    return params

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



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

2. Оптимизатор импульса


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



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


Нормально значение около 0,9

Видно, что мы создали два параметра vdW, и vdb из параметров обратного распространения. Рассмотрим значение = 0.9, тогда уравнения приобретает вид:

vdw= 0.9 * vdw + 0.1 * dwvdb = 0.9 * vdb + 0.1 * db

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

def update_params_with_momentum(params, grads, v, beta, learning_rate):        # grads has the dw and db parameters from backprop    # params  has the W and b parameters which we have to update     for l in range(len(params) // 2 ):        # HERE WE COMPUTING THE VELOCITIES         v["dW" + str(l + 1)] = beta * v["dW" + str(l + 1)] + (1 - beta) * grads['dW' + str(l + 1)]        v["db" + str(l + 1)] = beta * v["db" + str(l + 1)] + (1 - beta) * grads['db' + str(l + 1)]                #updating parameters W and b        params["W" + str(l + 1)] = params["W" + str(l + 1)] - learning_rate * v["dW" + str(l + 1)]        params["b" + str(l + 1)] = params["b" + str(l + 1)] - learning_rate * v["db" + str(l + 1)]    return params

Репозиторий находится здесь

3. Среднеквадратичное распространение


Среднеквадратичное распространение корня (RMSprop) это экспоненциально затухающее среднее значение. Существенным свойством RMSprop является то, что вы не ограничены только суммой прошлых градиентов, но вы более ограничены градиентами последних временных шагов. RMSprop вносит свой вклад в экспоненциально затухающее среднее значение прошлых квадратичных градиентов. В RMSProp мы пытаемся уменьшить вертикальное движение, используя среднее значение, потому что они суммируются приблизительно до 0, принимая среднее значение. RMSprop предоставляет среднее значение для обновления.


Источник



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

def initilization_RMS(params):    s = {}    for i in range(len(params)//2 ):        s["dW" + str(i)] = np.zeros(params["W" + str(i)].shape)        s["db" + str(i)] = np.zeros(params["b" + str(i)].shape)    return sdef update_params_with_RMS(params, grads,s, beta, learning_rate):        # grads has the dw and db parameters from backprop    # params  has the W and b parameters which we have to update     for l in range(len(params) // 2 ):        # HERE WE COMPUTING THE VELOCITIES         s["dW" + str(l)]= beta * s["dW" + str(l)] + (1 - beta) * np.square(grads['dW' + str(l)])        s["db" + str(l)] = beta * s["db" + str(l)] + (1 - beta) * np.square(grads['db' + str(l)])                #updating parameters W and b        params["W" + str(l)] = params["W" + str(l)] - learning_rate * grads['dW' + str(l)] / (np.sqrt( s["dW" + str(l)] )+ pow(10,-4))        params["b" + str(l)] = params["b" + str(l)] - learning_rate * grads['db' + str(l)] / (np.sqrt( s["db" + str(l)]) + pow(10,-4))    return params

4. Оптимизатор Adam


Adam один из самых эффективных алгоритмов оптимизации в обучении нейронных сетей. Он сочетает в себе идеи RMSProp и оптимизатора импульса. Вместо того чтобы адаптировать скорость обучения параметров на основе среднего первого момента (среднего значения), как в RMSProp, Adam также использует среднее значение вторых моментов градиентов. В частности, алгоритм вычисляет экспоненциальное скользящее среднее градиента и квадратичный градиент, а параметры beta1 и beta2 управляют скоростью затухания этих скользящих средних. Каким образом?

def initilization_Adam(params):    s = {}    v = {}    for i in range(len(params)//2 ):        v["dW" + str(i)] = np.zeros(params["W" + str(i)].shape)        v["db" + str(i)] = np.zeros(params["b" + str(i)].shape)        s["dW" + str(i)] = np.zeros(params["W" + str(i)].shape)        s["db" + str(i)] = np.zeros(params["b" + str(i)].shape)    return v, s    def update_params_with_Adam(params, grads,v, s, beta1,beta2, learning_rate,t):    epsilon = pow(10,-8)    v_corrected = {}                             s_corrected = {}     # grads has the dw and db parameters from backprop    # params  has the W and b parameters which we have to update     for l in range(len(params) // 2 ):        # HERE WE COMPUTING THE VELOCITIES         v["dW" + str(l)] = beta1 * v["dW" + str(l)] + (1 - beta1) * grads['dW' + str(l)]        v["db" + str(l)] = beta1 * v["db" + str(l)] + (1 - beta1) * grads['db' + str(l)]        v_corrected["dW" + str(l)] = v["dW" + str(l)] / (1 - np.power(beta1, t))        v_corrected["db" + str(l)] = v["db" + str(l)] / (1 - np.power(beta1, t))        s["dW" + str(l)] = beta2 * s["dW" + str(l)] + (1 - beta2) * np.power(grads['dW' + str(l)], 2)        s["db" + str(l)] = beta2 * s["db" + str(l)] + (1 - beta2) * np.power(grads['db' + str(l)], 2)        s_corrected["dW" + str(l)] = s["dW" + str(l)] / (1 - np.power(beta2, t))        s_corrected["db" + str(l)] = s["db" + str(l)] / (1 - np.power(beta2, t))        params["W" + str(l)] = params["W" + str(l)] - learning_rate * v_corrected["dW" + str(l)] / np.sqrt(s_corrected["dW" + str(l)] + epsilon)        params["b" + str(l)] = params["b" + str(l)] - learning_rate * v_corrected["db" + str(l)] / np.sqrt(s_corrected["db" + str(l)] + epsilon)    return params

Гиперпараметры

  • значение 1(beta1) почти 0,9
  • 2 (бета2) почти 0.999
  • предотвращение деления на ноль ( 10^-8) (не слишком сильно влияет на обучение)

Зачем этот оптимизатор?

Его преимущества:

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

Давайте построим модель и посмотрим, как гиперпараметры ускоряют обучение


Давайте проведем практическую демонстрацию того, как ускорить обучение. В этой статье мы не будем объяснять другие вещи (инициализацию, отсев, forward_prop, back_prop, градиентный спуск и т. д.). Необходимые дл обучения Функции уже встроены в NumPy. Если вы хотите взглянуть на это, вот ссылка!

Давайте начнем!


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

1. Инициализация:

Мы инициализируем параметры с помощью функции инициализации, которая принимает входные данные, такие как features_size (в нашем случае 12288) и скрытый массив размеров (мы использовали [100,1]) и данный вывод как параметры инициализации. Существует другой метод инициализации. Я призываю прочитать эту статью.

def initilization(input_size,layer_size):    params = {}    np.random.seed(0)     params['W' + str(0)] = np.random.randn(layer_size[0], input_size) * np.sqrt(2 / input_size)    params['b' + str(0)] = np.zeros((layer_size[0], 1))    for l in range(1,len(layer_size)):        params['W' + str(l)] = np.random.randn(layer_size[l],layer_size[l-1]) * np.sqrt(2/layer_size[l])        params['b' + str(l)] = np.zeros((layer_size[l],1))    return params

2. Прямое распространение:

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

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

Мы вычисляем значение активации для каждого слоя с помощью функции forward_activation.

#activations-----------------------------------------------def forward_activation(A_prev, w, b, activation):    z = np.dot(A_prev, w.T) + b.T    if activation == 'relu':        A = np.maximum(0, z)    elif activation == 'sigmoid':        A = 1/(1+np.exp(-z))    else:        A = np.tanh(z)    return A#________model forward ____________________________________________________________________________________________________________def model_forward(X,params, L,keep_prob):    cache = {}    A =X    for l in range(L-1):        w = params['W' + str(l)]        b = params['b' + str(l)]        A = forward_activation(A, w, b, 'relu')        if l%2 == 0:            cache['D' + str(l)] = np.random.randn(A.shape[0],A.shape[1]) < keep_prob            A = A * cache['D' + str(l)] / keep_prob        cache['A' + str(l)] = A    w = params['W' + str(L-1)]    b = params['b' + str(L-1)]    A = forward_activation(A, w, b, 'sigmoid')    cache['A' + str(L-1)] = A    return cache, A

3. Обратное распространение:

Здесь мы пишем функцию обратного распространения. Она вернет grad (наклон). Мы используем grad при обновлении параметров, (если вы об этом не знаете). Рекомендую прочитать эту статью.

def backward(X, Y, params, cach,L,keep_prob):    grad ={}    m = Y.shape[0]    cach['A' + str(-1)] = X    grad['dz' + str(L-1)] = cach['A' + str(L-1)] - Y    cach['D' + str(- 1)] = 0    for l in reversed(range(L)):        grad['dW' + str(l)] = (1 / m) * np.dot(grad['dz' + str(l)].T, cach['A' + str(l-1)])        grad['db' + str(l)] = 1 / m * np.sum(grad['dz' + str(l)].T, axis=1, keepdims=True)        if l%2 != 0:            grad['dz' + str(l-1)] = ((np.dot(grad['dz' + str(l)], params['W' + str(l)]) * cach['D' + str(l-1)] / keep_prob) *                                 np.int64(cach['A' + str(l-1)] > 0))        else :            grad['dz' + str(l - 1)] = (np.dot(grad['dz' + str(l)], params['W' + str(l)]) *                                       np.int64(cach['A' + str(l - 1)] > 0))    return grad

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

def model(X,Y,learning_rate,num_iter,hidden_size,keep_prob,optimizer):    L = len(hidden_size)    params = initilization(X.shape[1], hidden_size)    costs = []    itr  = []    if optimizer == 'momentum':        v = initilization_moment(params)    elif optimizer == 'rmsprop':        s = initilization_RMS(params)    elif optimizer == 'adam' :        v,s = initilization_Adam(params)    for i in range(1,num_iter):        MiniBatches = RandomMiniBatches(X, Y, 32)   # GET RAMDOMLY MINIBATCHES        p , q = MiniBatches[2]        for MiniBatch in MiniBatches:               #LOOP FOR MINIBATCHES            (MiniBatch_X, MiniBatch_Y) = MiniBatch            cache, A = model_forward(MiniBatch_X, params, L,keep_prob)     #FORWARD PROPOGATIONS            cost = cost_f(A, MiniBatch_Y)                                  #COST FUNCTION            grad = backward(MiniBatch_X, MiniBatch_Y, params, cache, L,keep_prob) #BACKWARD PROPAGATION             if optimizer == 'momentum':                params = update_params_with_momentum(params, grad, v, beta=0.9,learning_rate=learning_rate)            elif optimizer == 'rmsprop':               params = update_params_with_RMS(params, grad, s, beta=0.9,learning_rate=learning_rate)            elif optimizer == 'adam' :                params = update_params_with_Adam(params, grad,v, s, beta1=0.9,beta2=0.999,  learning_rate=learning_rate,t=i)                                         #UPDATE PARAMETERS            elif optimizer == "minibatch":                params = update_params(params, grad,learning_rate=learning_rate)                            if i%5 == 0:            costs.append(cost)            itr.append(i)            if i % 100 == 0 :                print('cost of iteration______{}______{}'.format(i,cost))    return params,costs,itr

Обучение с мини-пакетами

params, cost_sgd,itr = model(X_train, Y_train, learning_rate = 0.01,               num_iter=500, hidden_size=[100, 1],keep_prob=1,optimizer='minibatch')Y_train_pre = predict(X_train, params, 2)print('train_accuracy------------', accuracy_score(Y_train_pre, Y_train))

Вывод при подходе с мини-пакетами:

cost of iteration______100______0.35302967575683797 cost of iteration______200______0.472914548745098 cost of iteration______300______0.4884728238471557 cost of iteration______400______0.21551100063345618 train_accuracy------------ 0.8494208494208494

Обучение с оптимизатором импульса

params,cost_momentum, itr = model(X_train, Y_train, learning_rate = 0.01,               num_iter=500, hidden_size=[100, 1],keep_prob=1,optimizer='momentum')Y_train_pre = predict(X_train, params, 2)print('train_accuracy------------', accuracy_score(Y_train_pre, Y_train))

Вывод оптимизатора импульса:

cost of iteration______100______0.36278494129038086 cost of iteration______200______0.4681552335189021 cost of iteration______300______0.382226159384529 cost of iteration______400______0.18219310793752702 train_accuracy------------ 0.8725868725868726

Тренировка с RMSprop

params,cost_rms,itr = model(X_train, Y_train, learning_rate = 0.01,               num_iter=500, hidden_size=[100, 1],keep_prob=1,optimizer='rmsprop')Y_train_pre = predict(X_train, params, 2)print('train_accuracy------------', accuracy_score(Y_train_pre, Y_train))

Вывод RMSprop:

cost of iteration______100______0.2983858963793841 cost of iteration______200______0.004245700579927428 cost of iteration______300______0.2629426607580565 cost of iteration______400______0.31944824707807556 train_accuracy------------ 0.9613899613899614

Тренировка с Adam

params,cost_adam, itr = model(X_train, Y_train, learning_rate = 0.01,               num_iter=500, hidden_size=[100, 1],keep_prob=1,optimizer='adam')Y_train_pre = predict(X_train, params, 2)print('train_accuracy------------', accuracy_score(Y_train_pre, Y_train))

Вывод Adam:

cost of iteration______100______0.3266223660473619 cost of iteration______200______0.08214547683157716 cost of iteration______300______0.0025645257286439583 cost of iteration______400______0.058015188756586206 train_accuracy------------ 0.9845559845559846

Вы видели разницу в точности между ними? Мы использовали те же параметры инициализации, ту же скорость обучения и то же количество итераций; отличается только оптимизатор, но посмотрите на результат!

Mini-batch accuracy : 0.8494208494208494momemtum accuracy  : 0.8725868725868726Rms accuracy    : 0.9613899613899614adam accuracy    : 0.9845559845559846

Графическая визуализация модели


Вы можете обратиться в репозиторий, если у вас есть сомнения по поводу кода.

Резюме



источник
Как мы уже видели, оптимизатор Adam дает хорошую точность по сравнению с другими оптимизаторами. На рисунке выше видно, как модель учится на итерациях. Momentum дает скорость SGD, а RMSProp дает экспоненциальное среднее значение веса для обновленных параметров. Мы использовали меньше данных в приведенной выше модели, но мы увидим больше преимуществ оптимизаторов при работе с большими наборами данных и многими итерациями. Мы обсудили основную идею оптимизаторов, и я надеюсь, что это даст вам некоторую мотивацию узнать больше об оптимизаторах и использовать их!

Ресурсы


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

image




Рекомендуемые статьи


Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru